1const sortableJsPath = "_content/BlazorSortable/Sortable.min.js"; 2const sortableJsVersion = "1.15.7"; 3let sortableJsLoadPromise = null; 4 5const stylesheetPath = "_content/BlazorSortable/blazor-sortable.css"; 6const highlightStylesheetPath = "_content/BlazorSortable/blazor-sortable-highlights.css"; 7const injectedStylesheets = new Set(); 8 9export async function initSortable(id, options, component, assetOptions) { 10 const el = document.getElementById(id); 11 if (!el) { 12 return; 13 } 14 15 if (assetOptions.autoLoadSortableJs) { 16 await ensureSortableJs(); 17 } else { 18 ensureSortableJsAvailable(); 19 } 20 21 if (assetOptions.autoLoadStylesheet) { 22 ensureStylesheet(stylesheetPath, assetOptions.version); 23 24 if (assetOptions.autoLoadHighlightStylesheet) { 25 ensureStylesheet(highlightStylesheetPath, assetOptions.version); 26 } 27 } 28 29 configureTransferCallbacks(options.group, component); 30 31 const sortable = new Sortable(el, buildSortableOptions(options, component)); 32 el.blazorSortableCleanup = configureTouchAwareMultiDragKey(el, sortable, options); 33} 34 35export function destroySortable(id) { 36 const el = document.getElementById(id); 37 const sortable = el ? Sortable.get(el) : null; 38 39 if (!sortable) { 40 return; 41 } 42 43 el.blazorSortableCleanup?.(); 44 delete el.blazorSortableCleanup; 45 46 sortable.destroy(); 47} 48 49function configureTransferCallbacks(group, component) { 50 if (group.pull === "function") { 51 group.pull = (to) => component.invokeMethod("OnPullJs", to.el.id); 52 } 53 54 if (group.put === "function") { 55 group.put = (_to, from) => component.invokeMethod("OnPutJs", from.el.id); 56 } 57} 58 59function configureTouchAwareMultiDragKey(el, sortable, options) { 60 if (!options.multiDragKey) { 61 return null; 62 } 63 64 const updateMultiDragKey = evt => { 65 const isTouchInput = evt.pointerType === "touch" || evt.type === "touchstart"; 66 sortable.option("multiDragKey", isTouchInput ? "" : options.multiDragKey); 67 }; 68 69 if (sortable.options.supportPointer) { 70 el.addEventListener("pointerdown", updateMultiDragKey, true); 71 return () => el.removeEventListener("pointerdown", updateMultiDragKey, true); 72 } 73 74 el.addEventListener("mousedown", updateMultiDragKey, true); 75 el.addEventListener("touchstart", updateMultiDragKey, true); 76 77 return () => { 78 el.removeEventListener("mousedown", updateMultiDragKey, true); 79 el.removeEventListener("touchstart", updateMultiDragKey, true); 80 }; 81} 82 83function buildSortableOptions(options, component) { 84 return { 85 ...options, 86 onStart: (evt) => { 87 component.invokeMethodAsync( 88 "OnStartJs", 89 evt.oldIndex, 90 getIndexes(evt.oldIndicies)); 91 }, 92 onEnd: (evt) => { 93 component.invokeMethodAsync("OnEndJs"); 94 95 resetCrossSortableMultiDragSelection(evt); 96 }, 97 onUpdate: (evt) => { 98 const isSwap = Boolean(evt.swapItem); 99 100 revertDomMove(evt, isSwap); 101 102 const newIndex = 103 evt.to.children.length === 1 && evt.newIndex === 1 104 ? 0 105 : evt.newIndex; 106 107 component.invokeMethodAsync( 108 "OnUpdateJs", 109 evt.oldIndex, 110 getIndexes(evt.oldIndicies), 111 newIndex, 112 getIndexes(evt.newIndicies), 113 isSwap); 114 }, 115 onAdd: (evt) => { 116 const isSwap = Boolean(evt.swapItem) && options.swap === true; 117 118 revertDomMove(evt, isSwap); 119 120 const isClone = isCloneMode(evt); 121 122 if (isClone) { 123 removeClone(evt); 124 } 125 126 component.invokeMethodAsync( 127 "OnAddJs", 128 evt.from.id, 129 evt.oldIndex, 130 getIndexes(evt.oldIndicies), 131 evt.newIndex, 132 getIndexes(evt.newIndicies), 133 isClone, 134 isSwap); 135 }, 136 onRemove: (evt) => { 137 revertDomMove(evt, false); 138 139 const isClone = isCloneMode(evt); 140 141 if (isClone) { 142 removeClone(evt); 143 } 144 145 component.invokeMethodAsync( 146 "OnRemoveJs", 147 evt.oldIndex, 148 getIndexes(evt.oldIndicies), 149 evt.to.id, 150 evt.newIndex, 151 getIndexes(evt.newIndicies), 152 isClone); 153 }, 154 onSelect: (evt) => { 155 keepSelectionWithinCurrentSortable(evt); 156 157 component.invokeMethodAsync( 158 "OnSelectJs", 159 getElementIndex(evt.item), 160 getElementIndexes(evt.items)); 161 }, 162 onDeselect: (evt) => { 163 component.invokeMethodAsync( 164 "OnDeselectJs", 165 getElementIndex(evt.item), 166 getElementIndexes(evt.items)); 167 }, 168 onSpill: (evt) => { 169 revertDomMove(evt, false); 170 171 const isClone = isCloneMode(evt); 172 173 if (isClone) { 174 removeClone(evt); 175 } 176 177 component.invokeMethodAsync( 178 "OnSpillJs", 179 getElementIndex(evt.item), 180 getElementIndexes(evt.items), 181 isClone); 182 } 183 }; 184} 185 186function revertDomMove(evt, isSwap) { 187 const pairs = getMovedElementPairs(evt); 188 189 for (const pair of pairs) { 190 pair.element.remove(); 191 } 192 193 if (isSwap) { 194 evt.swapItem.remove(); 195 } 196 197 for (const pair of pairs) { 198 const referenceItem = evt.from.children[pair.oldIndex] ?? null; 199 evt.from.insertBefore(pair.element, referenceItem); 200 } 201 202 if (isSwap) { 203 const swapReferenceItem = evt.to.children[evt.newIndex] ?? null; 204 evt.to.insertBefore(evt.swapItem, swapReferenceItem); 205 } 206} 207 208function getMovedElementPairs(evt) { 209 if (evt.oldIndicies?.length) { 210 return evt.oldIndicies.map(x => ({ 211 element: x.multiDragElement, 212 oldIndex: x.index 213 })); 214 } 215 216 return [{ 217 element: evt.item, 218 oldIndex: evt.oldIndex 219 }]; 220} 221 222function isCloneMode(evt) { 223 return evt.pullMode === "clone"; 224} 225 226function removeClone(evt) { 227 if (evt.clones?.length) { 228 for (const clone of evt.clones) { 229 clone.remove(); 230 } 231 232 return; 233 } 234 235 evt.clone?.remove(); 236} 237 238function getIndexes(indices) { 239 return indices?.map(x => x.index) ?? []; 240} 241 242function keepSelectionWithinCurrentSortable(evt) { 243 const currentItems = evt.items.filter(item => item.parentElement === evt.from); 244 245 if (currentItems.length === evt.items.length) { 246 return; 247 } 248 249 for (const item of evt.items) { 250 if (item.parentElement !== evt.from) { 251 Sortable.utils.deselect(item); 252 } 253 } 254 255 evt.items = currentItems; 256} 257 258function resetCrossSortableMultiDragSelection(evt) { 259 if (evt.from === evt.to || !evt.items?.length) { 260 return; 261 } 262 263 setTimeout(() => { 264 Sortable.get(evt.to)?.multiDrag?._deselectMultiDrag?.(); 265 }); 266} 267 268function getElementIndex(element) { 269 return Sortable.utils.index(element); 270} 271 272function getElementIndexes(elements) { 273 return elements?.map(getElementIndex) ?? []; 274} 275 276async function ensureSortableJs() { 277 if (isSortableJsAvailable()) { 278 return; 279 } 280 281 sortableJsLoadPromise ??= loadSortableJs(`${sortableJsPath}?v=${sortableJsVersion}`); 282 await sortableJsLoadPromise; 283 284 ensureSortableJsAvailable(); 285} 286 287function loadSortableJs(src) { 288 return new Promise((resolve, reject) => { 289 const sortableJs = document.createElement("script"); 290 sortableJs.src = src; 291 292 sortableJs.addEventListener("load", () => resolve(), { once: true }); 293 sortableJs.addEventListener("error", () => { 294 sortableJs.remove(); 295 sortableJsLoadPromise = null; 296 reject(new Error(`Failed to load SortableJS from '${src}'.`)); 297 }, { once: true }); 298 299 document.body.append(sortableJs); 300 }); 301} 302 303function ensureSortableJsAvailable() { 304 if (isSortableJsAvailable()) { 305 return; 306 } 307 308 throw new Error( 309 "SortableJS is not loaded. Add SortableJS before BlazorSortable initializes, or enable SortableOptions.AutoLoadSortableJs." 310 ); 311} 312 313function isSortableJsAvailable() { 314 return typeof Sortable === "function"; 315} 316 317function ensureStylesheet(path, version) { 318 if (injectedStylesheets.has(path)) { 319 return; 320 } 321 322 const headStylesheetLinks = getHeadStylesheetLinks(); 323 324 if (headStylesheetLinks.some(link => link.href.includes(path))) { 325 injectedStylesheets.add(path); 326 return; 327 } 328 329 const link = document.createElement("link"); 330 link.rel = "stylesheet"; 331 link.href = `${path}?v=${version}`; 332 333 insertHeadStylesheetLink(headStylesheetLinks, link); 334 injectedStylesheets.add(path); 335} 336 337function getHeadStylesheetLinks() { 338 return Array.from(document.head.querySelectorAll('link[rel="stylesheet"]')); 339} 340 341function insertHeadStylesheetLink(headStylesheetLinks, link) { 342 const firstHeadStylesheetLink = headStylesheetLinks[0]; 343 344 if (firstHeadStylesheetLink) { 345 firstHeadStylesheetLink.before(link); 346 } else { 347 document.head.append(link); 348 } 349}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.