@schukai/monster 3.115.4 → 3.116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,711 @@
1
+ /**
2
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact schukai GmbH.
11
+ */
12
+
13
+ import { instanceSymbol } from "../../constants.mjs";
14
+ import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
15
+ import { CustomElement, getSlottedElements } from "../../dom/customelement.mjs";
16
+ import {
17
+ assembleMethodSymbol,
18
+ registerCustomElement,
19
+ } from "../../dom/customelement.mjs";
20
+ import { findTargetElementFromEvent } from "../../dom/events.mjs";
21
+ import { BoardStyleSheet } from "./stylesheet/board.mjs";
22
+ import { Observer } from "../../types/observer.mjs";
23
+
24
+ export { Board };
25
+
26
+ /**
27
+ * @private
28
+ * @type {symbol}
29
+ */
30
+ export const boardElementSymbol = Symbol("boardElement");
31
+
32
+ /**
33
+ * @private
34
+ * @type {symbol}
35
+ */
36
+ export const gridElementSymbol = Symbol("gridElement");
37
+
38
+ /**
39
+ * @private
40
+ * @type {symbol}
41
+ */
42
+ export const parkingElementSymbol = Symbol("parkingElement");
43
+
44
+ /**
45
+ * A Board
46
+ *
47
+ * @fragments /fragments/components/layout/board/
48
+ *
49
+ * @example /examples/components/layout/board-simple
50
+ *
51
+ * @since 3.116.0
52
+ * @copyright schukai GmbH
53
+ * @summary A beautiful Board that can make your life easier and also looks good. You can use it to create a board, a dashboard, a kanban board, or whatever you want. It is a grid layout with drag and drop support.
54
+ */
55
+ class Board extends CustomElement {
56
+ /**
57
+ * This method is called by the `instanceof` operator.
58
+ * @returns {symbol}
59
+ */
60
+ static get [instanceSymbol]() {
61
+ return Symbol.for("@schukai/monster/components/layout/board@@instance");
62
+ }
63
+
64
+ /**
65
+ *
66
+ * @return {Components.Layout.Board
67
+ */
68
+ [assembleMethodSymbol]() {
69
+ super[assembleMethodSymbol]();
70
+ initControlReferences.call(this);
71
+ initEventHandler.call(this);
72
+ assignDraggableToAllSlottedElements.call(this);
73
+ return this;
74
+ }
75
+
76
+ /**
77
+ * To set the options via the HTML Tag, the attribute `data-monster-options` must be used.
78
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
79
+ *
80
+ * The individual configuration values can be found in the table.
81
+ *
82
+ * @property {Object} templates Template definitions
83
+ * @property {string} templates.main Main template
84
+ * @property {Object} dimensions Dimensions of the board
85
+ * @property {number} dimensions.0.rows Number of rows for the first breakpoint
86
+ * @property {number} dimensions.0.columns Number of columns for the first breakpoint
87
+ * @property {number} dimensions.600.rows Number of rows for the second breakpoint
88
+ * @property {number} dimensions.600.columns Number of columns for the second breakpoint
89
+ * @property {number} dimensions.1200.rows Number of rows for the third breakpoint
90
+ * @property {number} dimensions.1200.columns Number of columns for the third breakpoint
91
+ * @property {number} dimensions.1800.rows Number of rows for the fourth breakpoint
92
+ * @property {number} dimensions.1800.columns Number of columns for the fourth breakpoint
93
+ * @property {string} fillMode Fill mode for the board ("top", "bottom", "left", "right", "none")
94
+ */
95
+ get defaults() {
96
+ return Object.assign({}, super.defaults, {
97
+ templates: {
98
+ main: getTemplate(),
99
+ },
100
+
101
+ dimensions: {
102
+ 0: {
103
+ rows: 8,
104
+ columns: 1,
105
+ },
106
+ 600: {
107
+ rows: 4,
108
+ columns: 2,
109
+ },
110
+ 1200: {
111
+ rows: 4,
112
+ columns: 3,
113
+ },
114
+ 1800: {
115
+ rows: 8,
116
+ columns: 1,
117
+ },
118
+ },
119
+
120
+ fillMode: "none", // "top", "bottom", "left", "right", "none"
121
+ });
122
+ }
123
+
124
+ /**
125
+ * @return {string}
126
+ */
127
+ static getTag() {
128
+ return "monster-board";
129
+ }
130
+
131
+ /**
132
+ * @return {CSSStyleSheet[]}
133
+ */
134
+ static getCSSStyleSheet() {
135
+ return [BoardStyleSheet];
136
+ }
137
+ }
138
+
139
+ function assignDraggableToAllSlottedElements() {
140
+ const elements = getSlottedElements.call(this, "");
141
+ for (const element of elements) {
142
+ console.log(element);
143
+
144
+ if (element instanceof HTMLElement) {
145
+ element.setAttribute("draggable", "true");
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * @private
152
+ * @return {initEventHandler}
153
+ */
154
+ function initEventHandler() {
155
+ const self = this;
156
+ const element = this[gridElementSymbol];
157
+
158
+ var dragInfo = {
159
+ element: undefined,
160
+ };
161
+
162
+ this.attachObserver(
163
+ new Observer(() => {
164
+ initGrid.call(self);
165
+ }),
166
+ );
167
+
168
+ setTimeout(() => {
169
+ initGrid.call(self);
170
+ });
171
+
172
+ element.addEventListener("drop", function (event) {
173
+ event.preventDefault();
174
+
175
+ const gridInfo = getGridInfo(element);
176
+ const dropCell = getDropTargetCell(element, event, gridInfo);
177
+ const mode = self.getOption("fillMode") || "none";
178
+
179
+ const occupant = getElementAtCell.call(self, element, dropCell);
180
+ if (occupant && occupant !== dragInfo.element) {
181
+ shiftElement.call(self, occupant, element);
182
+ }
183
+
184
+ const targetCell = findEmptyCellInMode.call(
185
+ self,
186
+ element,
187
+ dropCell,
188
+ gridInfo,
189
+ dragInfo.element,
190
+ mode,
191
+ );
192
+ moveElementToCell(dragInfo.element, targetCell);
193
+
194
+ if (dragInfo.originalCell) {
195
+ if (
196
+ (mode === "top" || mode === "bottom") &&
197
+ dragInfo.originalCell.col !== targetCell.col
198
+ ) {
199
+ // Ursprüngliche Spalte neu ordnen
200
+ rebalanceColumn.call(
201
+ self,
202
+ element,
203
+ dragInfo.originalCell.col,
204
+ gridInfo,
205
+ mode,
206
+ );
207
+ } else if (
208
+ (mode === "left" || mode === "right") &&
209
+ dragInfo.originalCell.row !== targetCell.row
210
+ ) {
211
+ // Ursprüngliche Zeile neu ordnen
212
+ rebalanceRow.call(
213
+ self,
214
+ element,
215
+ dragInfo.originalCell.row,
216
+ gridInfo,
217
+ mode,
218
+ );
219
+ }
220
+ }
221
+
222
+ // Aufräumen: Markierung entfernen usw.
223
+ dragInfo.element.classList.remove("dragging");
224
+ dragInfo.element.style.opacity = "1";
225
+ dragInfo.element = undefined;
226
+ dragInfo.originalCell = undefined;
227
+ });
228
+
229
+ let clickedHandle = null;
230
+ const markElementHandle = (event) => {
231
+ clickedHandle = findTargetElementFromEvent(
232
+ event,
233
+ "data-monster-role",
234
+ "handle",
235
+ );
236
+ if (!clickedHandle) {
237
+ clickedHandle = null;
238
+ console.log("no handle");
239
+ } else {
240
+ console.log("handle");
241
+ }
242
+ };
243
+
244
+ element.addEventListener("mousedown", markElementHandle);
245
+ element.addEventListener("touchstart", markElementHandle);
246
+
247
+ element.addEventListener("dragstart", (event) => {
248
+ const target = event.target;
249
+
250
+ const h = target.querySelector("[data-monster-role='handle']");
251
+ console.log(h);
252
+ if (h instanceof HTMLElement) {
253
+ if (!clickedHandle) {
254
+ event.preventDefault();
255
+ return;
256
+ }
257
+ }
258
+
259
+ event.dataTransfer.setData("text/plain", event.target.id);
260
+ dragInfo.element = event.target;
261
+ event.target.style.opacity = "0.1";
262
+ event.target.classList.add("dragging");
263
+
264
+ const computedStyle = window.getComputedStyle(event.target);
265
+ const originalCol =
266
+ parseInt(computedStyle.getPropertyValue("grid-column-start"), 10) - 1;
267
+ const originalRow =
268
+ parseInt(computedStyle.getPropertyValue("grid-row-start"), 10) - 1;
269
+ dragInfo.originalCell = { row: originalRow, col: originalCol };
270
+ });
271
+
272
+ element.addEventListener("dragend", (event) => {
273
+ event.target.classList.remove("dragging");
274
+ event.target.style.opacity = "1";
275
+ dragInfo.element = undefined;
276
+ });
277
+
278
+ //let currentDropOverCell = null;
279
+ element.addEventListener("dragover", function (event) {
280
+ event.preventDefault();
281
+ const gridInfo = getGridInfo(this);
282
+ const cell = getDropTargetCell(this, event, gridInfo);
283
+
284
+ const occupant = getElementAtCell.call(self, this, cell);
285
+ if (occupant && occupant !== dragInfo.dragable) {
286
+ shiftElement.call(self, occupant, this);
287
+ }
288
+ });
289
+
290
+ return this;
291
+ }
292
+
293
+ /**
294
+ * Ordnet in der angegebenen Zeile alle Elemente neu, sodass Lücken geschlossen werden.
295
+ *
296
+ * @param {HTMLElement} gridContainer - Das Grid-Element.
297
+ * @param {number} row - Der 0-basierte Index der Zeile.
298
+ * @param {Object} gridInfo - Informationen zur Grid-Struktur (z.B. Anzahl der Spalten).
299
+ * @param {string} mode - "left" oder "right".
300
+ */
301
+ function rebalanceRow(gridContainer, row, gridInfo, mode) {
302
+ const children = Array.from(getSlottedElements.call(this, "div"));
303
+ let items = [];
304
+
305
+ children.forEach((elem) => {
306
+ if (elem.classList.contains("dragging")) return;
307
+ const computedStyle = window.getComputedStyle(elem);
308
+ const rowStart =
309
+ parseInt(computedStyle.getPropertyValue("grid-row-start"), 10) - 1;
310
+ if (rowStart === row) {
311
+ const colStart =
312
+ parseInt(computedStyle.getPropertyValue("grid-column-start"), 10) - 1;
313
+ items.push({ element: elem, col: colStart });
314
+ }
315
+ });
316
+
317
+ if (mode === "left") {
318
+ items.sort((a, b) => a.col - b.col);
319
+ items.forEach((item, index) => {
320
+ item.element.style.gridColumn = `${index + 1} / span 1`;
321
+ });
322
+ } else if (mode === "right") {
323
+ items.sort((a, b) => b.col - a.col);
324
+ const totalCols = gridInfo.columns.length;
325
+ items.forEach((item, index) => {
326
+ item.element.style.gridColumn = `${totalCols - index} / span 1`;
327
+ });
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Ordnet in der angegebenen Spalte alle Elemente neu, sodass Lücken geschlossen werden.
333
+ *
334
+ * @param {HTMLElement} gridContainer - Das Grid-Element.
335
+ * @param {number} column - Der 0-basierte Index der Spalte.
336
+ * @param {Object} gridInfo - Informationen zur Grid-Struktur (z.B. Anzahl der Zeilen).
337
+ * @param {string} mode - "top" oder "bottom".
338
+ */
339
+ function rebalanceColumn(gridContainer, column, gridInfo, mode) {
340
+ const children = Array.from(getSlottedElements.call(this, "div"));
341
+ let items = [];
342
+
343
+ children.forEach((elem) => {
344
+ if (elem.classList.contains("dragging")) return;
345
+ const computedStyle = window.getComputedStyle(elem);
346
+ const colStart =
347
+ parseInt(computedStyle.getPropertyValue("grid-column-start"), 10) - 1;
348
+ if (colStart === column) {
349
+ const rowStart =
350
+ parseInt(computedStyle.getPropertyValue("grid-row-start"), 10) - 1;
351
+ items.push({ element: elem, row: rowStart });
352
+ }
353
+ });
354
+
355
+ if (mode === "top") {
356
+ items.sort((a, b) => a.row - b.row);
357
+ items.forEach((item, index) => {
358
+ item.element.style.gridRow = `${index + 1} / span 1`;
359
+ });
360
+ } else if (mode === "bottom") {
361
+ items.sort((a, b) => b.row - a.row);
362
+ const totalRows = gridInfo.rows.length;
363
+ items.forEach((item, index) => {
364
+ item.element.style.gridRow = `${totalRows - index} / span 1`;
365
+ });
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Finds an empty cell in the grid based on the specified mode.
371
+ *
372
+ * @param {HTMLElement} gridContainer - The container element of the grid.
373
+ * @param {Object} cell - The current cell position with row and column properties.
374
+ * @param {Object} gridInfo - Information about the grid including rows and columns arrays.
375
+ * @param {HTMLElement} ignoreElement - An element to be ignored during the search for an empty cell.
376
+ * @param {string} mode - The search mode, determining how to find the empty cell.
377
+ * Possible values: "top", "bottom", "left", "right", "none".
378
+ * @return {Object} The position of the empty cell with 'row' and 'col' properties.
379
+ */
380
+ function findEmptyCellInMode(
381
+ gridContainer,
382
+ cell,
383
+ gridInfo,
384
+ ignoreElement,
385
+ mode,
386
+ ) {
387
+ switch (mode) {
388
+ case "top":
389
+ // Suche in der Spalte von oben nach unten
390
+ for (let row = 0; row < gridInfo.rows.length; row++) {
391
+ const target = { row, col: cell.col };
392
+ const occupant = getElementAtCell.call(this, gridContainer, target);
393
+ if (!occupant || occupant === ignoreElement) {
394
+ return target;
395
+ }
396
+ }
397
+ return { row: 0, col: cell.col };
398
+
399
+ case "bottom":
400
+ for (let row = gridInfo.rows.length - 1; row >= 0; row--) {
401
+ const target = { row, col: cell.col };
402
+ const occupant = getElementAtCell.call(this, gridContainer, target);
403
+ if (!occupant || occupant === ignoreElement) {
404
+ return target;
405
+ }
406
+ }
407
+ return { row: gridInfo.rows.length - 1, col: cell.col };
408
+
409
+ case "left":
410
+ for (let col = 0; col < gridInfo.columns.length; col++) {
411
+ const target = { row: cell.row, col: col };
412
+ const occupant = getElementAtCell.call(this, gridContainer, target);
413
+ if (!occupant || occupant === ignoreElement) {
414
+ return target;
415
+ }
416
+ }
417
+ return { row: cell.row, col: 0 };
418
+
419
+ case "right":
420
+ for (let col = gridInfo.columns.length - 1; col >= 0; col--) {
421
+ const target = { row: cell.row, col: col };
422
+ const occupant = getElementAtCell.call(this, gridContainer, target);
423
+ if (!occupant || occupant === ignoreElement) {
424
+ return target;
425
+ }
426
+ }
427
+ return { row: cell.row, col: gridInfo.columns.length - 1 };
428
+
429
+ case "none":
430
+ default:
431
+ return cell;
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Retrieves grid layout information from a grid container element, including the column and row sizes, and the gaps.
437
+ *
438
+ * @private
439
+ * @param {Element} gridContainer The DOM element representing the grid container.
440
+ * @return {Object} An object containing the grid's columns, rows, column gap, and row gap:
441
+ * - `columns`: An array of numbers representing the width of each column.
442
+ * - `rows`: An array of numbers representing the height of each row.
443
+ * - `columnGap`: A number representing the gap size between columns.
444
+ * - `rowGap`: A number representing the gap size between rows.
445
+ */
446
+ function getGridInfo(gridContainer) {
447
+ const style = window.getComputedStyle(gridContainer);
448
+
449
+ const columns = style
450
+ .getPropertyValue("grid-template-columns")
451
+ .split(/\s+/)
452
+ .map((val) => parseFloat(val));
453
+ const rows = style
454
+ .getPropertyValue("grid-template-rows")
455
+ .split(/\s+/)
456
+ .map((val) => parseFloat(val));
457
+
458
+ const columnGap = parseFloat(style.getPropertyValue("column-gap")) || 0;
459
+ const rowGap = parseFloat(style.getPropertyValue("row-gap")) || 0;
460
+
461
+ return { columns, rows, columnGap, rowGap };
462
+ }
463
+
464
+ /**
465
+ * Determines the drop target cell in a grid container based on the mouse event coordinates.
466
+ *
467
+ * @private
468
+ * @param {HTMLElement} gridContainer - The DOM element representing the grid container.
469
+ * @param {MouseEvent} event - The mouse event that contains the coordinates of the drop action.
470
+ * @param {Object} gridInfo - An object containing grid layout information.
471
+ * @param {number[]} gridInfo.columns - An array of column widths in the grid.
472
+ * @param {number[]} gridInfo.rows - An array of row heights in the grid.
473
+ * @param {number} gridInfo.columnGap - The gap size between columns in the grid.
474
+ * @param {number} gridInfo.rowGap - The gap size between rows in the grid.
475
+ * @return {Object} An object containing the row and column indices of the drop target cell.
476
+ * @return {number} return.row - The index of the row in which the drop occurred. Returns -1 if no valid row is found.
477
+ * @return {number} return.col - The index of the column in which the drop occurred. Returns -1 if no valid column is found.
478
+ */
479
+ function getDropTargetCell(gridContainer, event, gridInfo) {
480
+ const rect = gridContainer.getBoundingClientRect();
481
+ const x = event.clientX - rect.left; // relative X position
482
+ const y = event.clientY - rect.top; // relative Y position
483
+
484
+ let currentX = 0;
485
+ let colIndex = -1;
486
+ for (let i = 0; i < gridInfo.columns.length; i++) {
487
+ const colWidth = gridInfo.columns[i];
488
+ if (x >= currentX && x < currentX + colWidth) {
489
+ colIndex = i;
490
+ break;
491
+ }
492
+ currentX += colWidth + gridInfo.columnGap;
493
+ }
494
+
495
+ let currentY = 0;
496
+ let rowIndex = -1;
497
+ for (let i = 0; i < gridInfo.rows.length; i++) {
498
+ const rowHeight = gridInfo.rows[i];
499
+ if (y >= currentY && y < currentY + rowHeight) {
500
+ rowIndex = i;
501
+ break;
502
+ }
503
+ currentY += rowHeight + gridInfo.rowGap;
504
+ }
505
+
506
+ return { row: rowIndex, col: colIndex };
507
+ }
508
+
509
+ function initGrid() {
510
+ //const element = this[boardElementSymbol];
511
+
512
+ const dimensions = this.getOption("dimensions");
513
+
514
+ let stylesheet = "";
515
+ for (const [key, value] of Object.entries(dimensions)) {
516
+ stylesheet += `@container board (min-width: ${key}px) {
517
+ [data-monster-role="grid"] {
518
+ grid-template-columns: repeat(${value.columns}, 1fr);
519
+ grid-template-rows: repeat(${value.rows}, 1fr);
520
+ }
521
+ }
522
+ `;
523
+ }
524
+
525
+ const styleSheet = new CSSStyleSheet();
526
+ styleSheet.replaceSync(stylesheet);
527
+
528
+ this.shadowRoot.adoptedStyleSheets = [
529
+ ...Board.getCSSStyleSheet(),
530
+ styleSheet,
531
+ ];
532
+
533
+ return this;
534
+ }
535
+
536
+ /**
537
+ * @private
538
+ * @return {void}
539
+ */
540
+ function initControlReferences() {
541
+ this[boardElementSymbol] = this.shadowRoot.querySelector(
542
+ `[${ATTRIBUTE_ROLE}="control"]`,
543
+ );
544
+
545
+ this[gridElementSymbol] = this.shadowRoot.querySelector(
546
+ `[${ATTRIBUTE_ROLE}="grid"]`,
547
+ );
548
+
549
+ this[parkingElementSymbol] = this.shadowRoot.querySelector(
550
+ `[${ATTRIBUTE_ROLE}="parking"]`,
551
+ );
552
+ }
553
+
554
+ /**
555
+ * Retrieves the element located at the specified cell within a grid container.
556
+ *
557
+ * @private
558
+ * @param {HTMLElement} gridContainer - The container element representing the CSS grid.
559
+ * @param {{col: number, row: number}} cell - An object containing the zero-based column (col) and row (row) indices of the desired cell.
560
+ * @return {HTMLElement|null} The element at the specified cell, or null if no element exists at the given cell coordinates.
561
+ */
562
+ function getElementAtCell(gridContainer, cell) {
563
+ /** @var {Set<HTMLElement>} */
564
+ const children = getSlottedElements.call(this, "div").values();
565
+
566
+ for (const elem of children) {
567
+ // Überspringe das Element, wenn es gerade gezogen wird
568
+ if (elem.classList.contains("dragging")) {
569
+ continue;
570
+ }
571
+
572
+ // Fetch the computed styles for the current element
573
+ const computedStyle = window.getComputedStyle(elem);
574
+ let colValue = computedStyle.getPropertyValue("grid-column-start");
575
+ let rowValue = computedStyle.getPropertyValue("grid-row-start");
576
+
577
+ let gridColumn = computedStyle.getPropertyValue("grid-column"); // z. B. "1 / span 1"
578
+ let colX = parseInt(gridColumn.split("/")[0], 10) - 1;
579
+
580
+ if (colValue === "auto" || rowValue === "auto") {
581
+ continue;
582
+ }
583
+
584
+ const col = parseInt(colValue, 10) - 1; // 0-basierte Indizes
585
+ const row = parseInt(rowValue, 10) - 1;
586
+
587
+ if (col === cell.col && row === cell.row) {
588
+ return elem;
589
+ }
590
+ }
591
+ return null;
592
+ }
593
+
594
+ /**
595
+ * Adjusts the position of a given cell within a grid based on a default shifting algorithm.
596
+ * Tries to move the cell downwards first. If not possible, it attempts to move right,
597
+ * then left. If none of these movements are possible, it returns the original cell position.
598
+ *
599
+ * @param {Object} currentCell - The current position of the cell.
600
+ * @param {number} currentCell.row - The row index of the cell.
601
+ * @param {number} currentCell.col - The column index of the cell.
602
+ * @param {Object} gridInfo - Information about the grid structure.
603
+ * @param {Array} gridInfo.rows - The rows of the grid.
604
+ * @param {Array} gridInfo.columns - The columns of the grid.
605
+ * @return {Object} An object containing the new cell position with `row` and `col` properties.
606
+ */
607
+ function defaultShiftAlgorithm(currentCell, gridInfo) {
608
+ // try first to move down
609
+ let newRow = currentCell.row + 1;
610
+ if (newRow < gridInfo.rows.length) {
611
+ return { row: newRow, col: currentCell.col };
612
+ }
613
+ // if not possible, try to move right
614
+ let newCol = currentCell.col + 1;
615
+ if (newCol < gridInfo.columns.length) {
616
+ return { row: currentCell.row, col: newCol };
617
+ }
618
+ // if not possible, try to move left
619
+ newCol = currentCell.col - 1;
620
+ if (newCol >= 0) {
621
+ return { row: currentCell.row, col: newCol };
622
+ }
623
+ // finally, return the original cell position
624
+ return currentCell;
625
+ }
626
+
627
+ /**
628
+ * Shifts an element within a CSS grid container to a new position based on the specified algorithm.
629
+ *
630
+ * @param {HTMLElement} element - The element to be shifted within the grid container.
631
+ * @param {HTMLElement} gridContainer - The grid container that houses the element.
632
+ * @param {Function} [shiftAlgorithm=defaultShiftAlgorithm] - A function defining the shifting logic, which takes the current
633
+ * position and the grid information and returns the new position. Defaults to `defaultShiftAlgorithm` if not provided.
634
+ * @return {void} No return value.
635
+ */
636
+ function shiftElement(
637
+ element,
638
+ gridContainer,
639
+ shiftAlgorithm = defaultShiftAlgorithm,
640
+ ) {
641
+ if (element.classList.contains("dragging")) {
642
+ return;
643
+ }
644
+
645
+ const gridInfo = getGridInfo(gridContainer);
646
+
647
+ const currentCol = parseInt(element.style.gridColumn.split("/")[0], 10) - 1;
648
+ const currentRow = parseInt(element.style.gridRow.split("/")[0], 10) - 1;
649
+ const currentCell = { row: currentRow, col: currentCol };
650
+
651
+ if (element.dataset.originalPosition) {
652
+ const orig = JSON.parse(element.dataset.originalPosition);
653
+ const occupantAtOrig = getElementAtCell.call(this, gridContainer, orig);
654
+ if (
655
+ (!occupantAtOrig || occupantAtOrig === element) &&
656
+ (currentCell.row !== orig.row || currentCell.col !== orig.col)
657
+ ) {
658
+ element.style.gridColumn = `${orig.col + 1} / span 1`;
659
+ element.style.gridRow = `${orig.row + 1} / span 1`;
660
+ delete element.dataset.originalPosition;
661
+ return;
662
+ }
663
+ if (currentCell.row === orig.row && currentCell.col === orig.col) {
664
+ delete element.dataset.originalPosition;
665
+ }
666
+ } else {
667
+ element.dataset.originalPosition = JSON.stringify(currentCell);
668
+ }
669
+
670
+ const newCell = shiftAlgorithm(currentCell, gridInfo);
671
+ if (newCell.row === currentCell.row && newCell.col === currentCell.col) {
672
+ return;
673
+ }
674
+
675
+ const occupant = getElementAtCell.call(this, gridContainer, newCell);
676
+ if (occupant) {
677
+ occupant.style.gridColumn = `${currentCell.col + 1} / span 1`;
678
+ occupant.style.gridRow = `${currentCell.row + 1} / span 1`;
679
+ delete occupant.dataset.originalPosition;
680
+ }
681
+ element.style.gridColumn = `${newCell.col + 1} / span 1`;
682
+ element.style.gridRow = `${newCell.row + 1} / span 1`;
683
+ }
684
+
685
+ /**
686
+ * Moves an HTML element to a specific cell in a grid layout.
687
+ *
688
+ * @param {HTMLElement} element - The HTML element to be positioned within the grid.
689
+ * @param {{row: number, col: number}} cell - An object specifying the target cell's row and column indices.
690
+ * @return {void}
691
+ */
692
+ function moveElementToCell(element, cell) {
693
+ element.style.gridColumn = `${cell.col + 1} / span 1`;
694
+ element.style.gridRow = `${cell.row + 1} / span 1`;
695
+ }
696
+
697
+ /**
698
+ * @private
699
+ * @return {string}
700
+ */
701
+ function getTemplate() {
702
+ // language=HTML
703
+ return `
704
+ <div data-monster-role="parking"></div>
705
+ <div data-monster-role="control" part="control">
706
+ <div data-monster-role="grid" part="grid">
707
+ <slot></slot>
708
+ </div>`;
709
+ }
710
+
711
+ registerCustomElement(Board);