Release v2.4.2
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,410 @@
|
||||
(function () {
|
||||
// Playlist table drag sorting is isolated here so the playlist editor script
|
||||
// can stay focused on slide picking, schedule editing, and row updates.
|
||||
|
||||
function getDragEventPoint(event) {
|
||||
var touch = event.touches && event.touches[0] ? event.touches[0] : event.changedTouches && event.changedTouches[0] ? event.changedTouches[0] : null;
|
||||
|
||||
return {
|
||||
x: touch ? touch.clientX : event.clientX,
|
||||
y: touch ? touch.clientY : event.clientY
|
||||
};
|
||||
}
|
||||
|
||||
function measureDraggedRowWidths(row) {
|
||||
var metrics = {
|
||||
rowRect: row.getBoundingClientRect(),
|
||||
cellRects: []
|
||||
};
|
||||
|
||||
Array.prototype.forEach.call(row.children, function (cell) {
|
||||
metrics.cellRects.push(cell.getBoundingClientRect());
|
||||
});
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
function lockDraggedRowWidths(row, metrics) {
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rowRect = metrics && metrics.rowRect ? metrics.rowRect : row.getBoundingClientRect();
|
||||
row.style.width = rowRect.width + 'px';
|
||||
row.style.height = rowRect.height + 'px';
|
||||
row.style.boxSizing = 'border-box';
|
||||
|
||||
Array.prototype.forEach.call(row.children, function (cell, index) {
|
||||
var cellRect = metrics && metrics.cellRects && metrics.cellRects[index] ? metrics.cellRects[index] : cell.getBoundingClientRect();
|
||||
cell.style.width = cellRect.width + 'px';
|
||||
cell.style.height = cellRect.height + 'px';
|
||||
cell.style.boxSizing = 'border-box';
|
||||
});
|
||||
}
|
||||
|
||||
function syncDraggedRowCellPadding(row, sourceRow) {
|
||||
if (!row || !sourceRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array.prototype.forEach.call(row.children, function (cell, index) {
|
||||
var sourceCell = sourceRow.children[index];
|
||||
if (!sourceCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceStyle = window.getComputedStyle(sourceCell);
|
||||
cell.style.paddingLeft = sourceStyle.paddingLeft;
|
||||
cell.style.paddingRight = sourceStyle.paddingRight;
|
||||
cell.style.paddingTop = sourceStyle.paddingTop;
|
||||
cell.style.paddingBottom = sourceStyle.paddingBottom;
|
||||
});
|
||||
}
|
||||
|
||||
function unlockDraggedRowWidths(row) {
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
row.style.width = '';
|
||||
row.style.height = '';
|
||||
row.style.boxSizing = '';
|
||||
|
||||
Array.prototype.forEach.call(row.children, function (cell) {
|
||||
cell.style.width = '';
|
||||
cell.style.height = '';
|
||||
cell.style.boxSizing = '';
|
||||
});
|
||||
}
|
||||
|
||||
function animateRowReorder(previousRects, excludedRows) {
|
||||
var excluded = Array.isArray(excludedRows) ? excludedRows : [];
|
||||
|
||||
if (!previousRects || typeof previousRects.forEach !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
previousRects.forEach(function (previousRect, row) {
|
||||
var nextRect;
|
||||
var deltaY;
|
||||
var previousTransition;
|
||||
|
||||
if (!row || excluded.indexOf(row) !== -1 || !row.parentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextRect = row.getBoundingClientRect();
|
||||
deltaY = previousRect.top - nextRect.top;
|
||||
|
||||
if (!deltaY) {
|
||||
return;
|
||||
}
|
||||
|
||||
previousTransition = row.style.transition;
|
||||
row.style.transition = 'none';
|
||||
row.style.transform = 'translate3d(0, ' + deltaY + 'px, 0)';
|
||||
row.offsetHeight;
|
||||
row.style.transition = 'transform 180ms ease';
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
if (!row.parentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
row.style.transform = '';
|
||||
|
||||
var cleanup = function () {
|
||||
row.style.transition = previousTransition;
|
||||
row.removeEventListener('transitionend', cleanup);
|
||||
row.removeEventListener('transitioncancel', cleanup);
|
||||
};
|
||||
|
||||
row.addEventListener('transitionend', cleanup, { once: true });
|
||||
row.addEventListener('transitioncancel', cleanup, { once: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initPlaylistTableDrag(options) {
|
||||
var tbody = options && options.tbody ? options.tbody : null;
|
||||
var onOrderChanged = options && typeof options.onOrderChanged === 'function' ? options.onOrderChanged : function () {};
|
||||
var rowDragState = null;
|
||||
|
||||
if (!tbody) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tbody.dataset && tbody.dataset.playlistTableDragBound === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tbody.dataset) {
|
||||
tbody.dataset.playlistTableDragBound = 'true';
|
||||
}
|
||||
|
||||
function getRows() {
|
||||
return Array.prototype.slice.call(tbody.querySelectorAll('tr[data-playlist-slide-row]:not([data-playlist-ghost-row])'));
|
||||
}
|
||||
|
||||
function getDragHandle(target) {
|
||||
return target && target.closest ? target.closest('[data-playlist-drag-handle]') : null;
|
||||
}
|
||||
|
||||
function prepareGhostRow(row) {
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
row.setAttribute('data-playlist-ghost-row', 'true');
|
||||
row.setAttribute('aria-hidden', 'true');
|
||||
row.classList.add('playlist-row-ghost');
|
||||
row.style.pointerEvents = 'none';
|
||||
|
||||
Array.prototype.forEach.call(row.querySelectorAll('[id]'), function (node) {
|
||||
node.removeAttribute('id');
|
||||
});
|
||||
|
||||
Array.prototype.forEach.call(row.querySelectorAll('input, select, textarea, button'), function (control) {
|
||||
control.disabled = true;
|
||||
control.setAttribute('tabindex', '-1');
|
||||
if (control.tagName === 'BUTTON') {
|
||||
control.setAttribute('type', 'button');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDragLayer(row, width) {
|
||||
var table = row.ownerDocument.createElement('table');
|
||||
var tbodyElement = row.ownerDocument.createElement('tbody');
|
||||
|
||||
table.className = 'playlist-row-drag-layer';
|
||||
table.style.position = 'fixed';
|
||||
table.style.top = '0';
|
||||
table.style.left = '0';
|
||||
table.style.margin = '0';
|
||||
table.style.borderCollapse = 'collapse';
|
||||
table.style.borderSpacing = '0';
|
||||
table.style.tableLayout = 'fixed';
|
||||
table.style.pointerEvents = 'none';
|
||||
table.style.zIndex = '1080';
|
||||
table.style.width = width + 'px';
|
||||
|
||||
tbodyElement.appendChild(row);
|
||||
table.appendChild(tbodyElement);
|
||||
row.ownerDocument.body.appendChild(table);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
function positionDragLayer(layer, metrics, point) {
|
||||
if (!layer || !metrics || !point) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.style.transform = 'translate3d(' + (point.x - metrics.offsetX) + 'px, ' + (point.y - metrics.offsetY) + 'px, 0)';
|
||||
}
|
||||
|
||||
function beginRowDrag() {
|
||||
if (!rowDragState || rowDragState.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
var row = rowDragState.row;
|
||||
var metrics = rowDragState.metrics;
|
||||
var placeholder = row.cloneNode(true);
|
||||
var rowRect = metrics.rowRect;
|
||||
|
||||
prepareGhostRow(placeholder);
|
||||
lockDraggedRowWidths(placeholder, metrics);
|
||||
syncDraggedRowCellPadding(placeholder, row);
|
||||
placeholder.classList.add('playlist-row-ghost');
|
||||
tbody.insertBefore(placeholder, row);
|
||||
|
||||
rowDragState.placeholder = placeholder;
|
||||
rowDragState.dragLayer = createDragLayer(row, metrics.rowRect.width);
|
||||
rowDragState.active = true;
|
||||
rowDragState.metrics.offsetX = rowDragState.startX - rowRect.left;
|
||||
rowDragState.metrics.offsetY = rowDragState.startY - rowRect.top;
|
||||
|
||||
lockDraggedRowWidths(row, metrics);
|
||||
syncDraggedRowCellPadding(row, placeholder);
|
||||
|
||||
row.classList.add('playlist-row-dragging');
|
||||
row.style.pointerEvents = 'none';
|
||||
row.setAttribute('aria-grabbed', 'true');
|
||||
|
||||
positionDragLayer(rowDragState.dragLayer, metrics, rowDragState.currentPoint || {
|
||||
x: rowDragState.startX,
|
||||
y: rowDragState.startY
|
||||
});
|
||||
}
|
||||
|
||||
function repositionGhostRow(clientY) {
|
||||
var rows = getRows();
|
||||
var insertBeforeRow = null;
|
||||
var previousRects = new Map();
|
||||
var moved = false;
|
||||
|
||||
rows.forEach(function (row) {
|
||||
if (row !== rowDragState.placeholder) {
|
||||
previousRects.set(row, row.getBoundingClientRect());
|
||||
}
|
||||
});
|
||||
|
||||
for (var index = 0; index < rows.length; index += 1) {
|
||||
var candidateRow = rows[index];
|
||||
var rect = candidateRow.getBoundingClientRect();
|
||||
|
||||
if (clientY < rect.top + rect.height / 2) {
|
||||
insertBeforeRow = candidateRow;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertBeforeRow) {
|
||||
if (rowDragState.placeholder !== insertBeforeRow && rowDragState.placeholder.nextSibling !== insertBeforeRow) {
|
||||
tbody.insertBefore(rowDragState.placeholder, insertBeforeRow);
|
||||
moved = true;
|
||||
}
|
||||
} else if (rowDragState.placeholder.parentNode !== tbody || rowDragState.placeholder.nextSibling) {
|
||||
tbody.appendChild(rowDragState.placeholder);
|
||||
moved = true;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
animateRowReorder(previousRects, [rowDragState.row, rowDragState.placeholder]);
|
||||
}
|
||||
}
|
||||
|
||||
function teardownRowDrag() {
|
||||
if (!rowDragState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rowDragState.supportsPointerEvents) {
|
||||
document.removeEventListener('pointermove', handleRowDragMove);
|
||||
document.removeEventListener('pointerup', handleRowDragEnd);
|
||||
document.removeEventListener('pointercancel', handleRowDragEnd);
|
||||
} else {
|
||||
document.removeEventListener('mousemove', handleRowDragMove);
|
||||
document.removeEventListener('mouseup', handleRowDragEnd);
|
||||
document.removeEventListener('touchmove', handleRowDragMove);
|
||||
document.removeEventListener('touchend', handleRowDragEnd);
|
||||
document.removeEventListener('touchcancel', handleRowDragEnd);
|
||||
}
|
||||
|
||||
if (rowDragState.active) {
|
||||
if (rowDragState.placeholder && rowDragState.placeholder.parentNode) {
|
||||
rowDragState.placeholder.parentNode.insertBefore(rowDragState.row, rowDragState.placeholder);
|
||||
rowDragState.placeholder.parentNode.removeChild(rowDragState.placeholder);
|
||||
} else if (rowDragState.row.parentNode !== tbody) {
|
||||
tbody.appendChild(rowDragState.row);
|
||||
}
|
||||
|
||||
if (rowDragState.dragLayer && rowDragState.dragLayer.parentNode) {
|
||||
rowDragState.dragLayer.parentNode.removeChild(rowDragState.dragLayer);
|
||||
}
|
||||
|
||||
rowDragState.row.classList.remove('playlist-row-dragging');
|
||||
rowDragState.row.style.pointerEvents = rowDragState.previousPointerEvents;
|
||||
rowDragState.row.removeAttribute('aria-grabbed');
|
||||
unlockDraggedRowWidths(rowDragState.row);
|
||||
onOrderChanged();
|
||||
}
|
||||
|
||||
rowDragState = null;
|
||||
}
|
||||
|
||||
function handleRowDragMove(event) {
|
||||
if (!rowDragState) {
|
||||
return;
|
||||
}
|
||||
|
||||
var point = getDragEventPoint(event);
|
||||
|
||||
if (!point) {
|
||||
return;
|
||||
}
|
||||
|
||||
rowDragState.currentPoint = point;
|
||||
|
||||
if (!rowDragState.active) {
|
||||
var threshold = typeof rowDragState.threshold === 'number' ? rowDragState.threshold : 3;
|
||||
|
||||
if (Math.abs(point.x - rowDragState.startX) < threshold && Math.abs(point.y - rowDragState.startY) < threshold) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRowDrag();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
positionDragLayer(rowDragState.dragLayer, rowDragState.metrics, point);
|
||||
repositionGhostRow(point.y);
|
||||
}
|
||||
|
||||
function handleRowDragEnd() {
|
||||
teardownRowDrag();
|
||||
}
|
||||
|
||||
function handleRowDragStart(event) {
|
||||
var handle = getDragHandle(event.target);
|
||||
var row = handle ? handle.closest('tr[data-playlist-slide-row]') : null;
|
||||
var point = getDragEventPoint(event);
|
||||
|
||||
if (!row || rowDragState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === 'mousedown' && event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!point) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
rowDragState = {
|
||||
row: row,
|
||||
metrics: measureDraggedRowWidths(row),
|
||||
startX: point.x,
|
||||
startY: point.y,
|
||||
currentPoint: point,
|
||||
threshold: 3,
|
||||
active: false,
|
||||
previousPointerEvents: row.style.pointerEvents || '',
|
||||
placeholder: null,
|
||||
dragLayer: null,
|
||||
supportsPointerEvents: typeof window !== 'undefined' && typeof window.PointerEvent === 'function'
|
||||
};
|
||||
|
||||
if (rowDragState.supportsPointerEvents) {
|
||||
document.addEventListener('pointermove', handleRowDragMove, { passive: false });
|
||||
document.addEventListener('pointerup', handleRowDragEnd);
|
||||
document.addEventListener('pointercancel', handleRowDragEnd);
|
||||
} else {
|
||||
document.addEventListener('mousemove', handleRowDragMove);
|
||||
document.addEventListener('mouseup', handleRowDragEnd);
|
||||
document.addEventListener('touchmove', handleRowDragMove, { passive: false });
|
||||
document.addEventListener('touchend', handleRowDragEnd);
|
||||
document.addEventListener('touchcancel', handleRowDragEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// The drag handle is intentionally isolated from the rest of the playlist
|
||||
// editor so it can be swapped or removed without touching schedule logic.
|
||||
if (typeof window !== 'undefined' && window.PointerEvent) {
|
||||
tbody.addEventListener('pointerdown', handleRowDragStart);
|
||||
} else {
|
||||
tbody.addEventListener('mousedown', handleRowDragStart);
|
||||
tbody.addEventListener('touchstart', handleRowDragStart, { passive: false });
|
||||
}
|
||||
}
|
||||
|
||||
window.initPlaylistTableDrag = initPlaylistTableDrag;
|
||||
}());
|
||||
Reference in New Issue
Block a user