(function (root, factory) { "use strict"; const api = Object.freeze(factory()); if (typeof module === "object" && module.exports) module.exports = api; if (root) root.MbnLegacyBatchSelectionWorkflow = api; })(typeof globalThis === "object" ? globalThis : this, function () { "use strict"; const MAX_ITEMS = 2048; const identifierPattern = /^[A-Za-z0-9_-]{1,128}$/; function requireItemCount(value) { const count = Number(value); if (!Number.isInteger(count) || count < 0 || count > MAX_ITEMS) { throw new RangeError(`The selectable item count must be from 0 through ${MAX_ITEMS}.`); } return count; } function requireIndex(value, count, name) { const index = Number(value); if (!Number.isInteger(index) || index < 0 || index >= count) { throw new RangeError(`${name} is outside the selectable item range.`); } return index; } function normalizeSelectedIndices(values, count) { if (!Array.isArray(values)) throw new TypeError("Selected indices must be an array."); const unique = new Set(); for (const value of values) unique.add(requireIndex(value, count, "A selected index")); return [...unique].sort((left, right) => left - right); } function normalizeAnchor(value, count) { if (value === undefined || value === null || value === -1) return -1; return requireIndex(value, count, "The selection anchor"); } function createSelection(itemCount, selectedIndices = [], anchorIndex = -1) { const count = requireItemCount(itemCount); const selected = normalizeSelectedIndices(selectedIndices, count); const anchor = normalizeAnchor(anchorIndex, count); return Object.freeze({ itemCount: count, selectedIndices: Object.freeze(selected), anchorIndex: anchor }); } function inclusiveRange(left, right) { const from = Math.min(left, right); const to = Math.max(left, right); return Array.from({ length: to - from + 1 }, (_, offset) => from + offset); } // Mirrors MainForm.listView1_MouseDown: plain click replaces, Ctrl toggles, // Shift replaces with an anchor range, and Ctrl+Shift unions that range. // The clicked row becomes the next anchor after every successful gesture. function applyPointerSelection(selection, targetIndex, modifiers = {}) { if (!selection || typeof selection !== "object" || Array.isArray(selection)) { throw new TypeError("A legacy selection snapshot is required."); } const current = createSelection( selection.itemCount, selection.selectedIndices, selection.anchorIndex); const target = requireIndex(targetIndex, current.itemCount, "The target index"); if (!modifiers || typeof modifiers !== "object" || Array.isArray(modifiers)) { throw new TypeError("Selection modifiers must be an object."); } const control = modifiers.ctrlKey === true; const shift = modifiers.shiftKey === true; const anchor = current.anchorIndex >= 0 ? current.anchorIndex : target; let selected; if (control && shift) { selected = [...new Set([...current.selectedIndices, ...inclusiveRange(anchor, target)])] .sort((left, right) => left - right); } else if (control) { const values = new Set(current.selectedIndices); if (values.has(target)) values.delete(target); else values.add(target); selected = [...values].sort((left, right) => left - right); } else if (shift) { selected = inclusiveRange(anchor, target); } else { selected = [target]; } return createSelection(current.itemCount, selected, target); } function canActivateSingle(selection, targetIndex) { if (!selection || typeof selection !== "object" || Array.isArray(selection)) return false; try { const current = createSelection( selection.itemCount, selection.selectedIndices, selection.anchorIndex); const target = requireIndex(targetIndex, current.itemCount, "The target index"); return current.selectedIndices.length === 1 && current.selectedIndices[0] === target; } catch { return false; } } // Materialize the complete batch before the caller mutates its playlist. A // validation error therefore cannot leave an operator-visible partial batch. function materializeAtomicBatch(items, materialize) { if (!Array.isArray(items) || items.length < 1 || items.length > MAX_ITEMS) { throw new RangeError(`A batch must contain from 1 through ${MAX_ITEMS} items.`); } if (typeof materialize !== "function") { throw new TypeError("A batch materializer is required."); } const entries = []; const ids = new Set(); for (let index = 0; index < items.length; index += 1) { const entry = materialize(items[index], index); if (!entry || typeof entry !== "object" || Array.isArray(entry)) { throw new TypeError("Every materialized batch item must be a playlist entry object."); } const id = typeof entry.id === "string" ? entry.id.trim() : ""; if (!identifierPattern.test(id) || ids.has(id)) { throw new TypeError("Every materialized batch item must have a unique safe id."); } ids.add(id); entries.push(entry); } return Object.freeze(entries); } return Object.freeze({ MAX_ITEMS, createSelection, applyPointerSelection, canActivateSingle, materializeAtomicBatch }); });