@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.
- package/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/source/components/datatable/stylesheet/dataset.mjs +1 -1
- package/source/components/datatable/stylesheet/datatable.mjs +1 -1
- package/source/components/host/overlay.mjs +4 -295
- package/source/components/layout/board.mjs +711 -0
- package/source/components/layout/overlay.mjs +320 -0
- package/source/components/layout/style/board.pcss +34 -0
- package/source/components/layout/style/tabs.pcss +2 -1
- package/source/components/layout/stylesheet/board.mjs +31 -0
- package/source/components/layout/stylesheet/tabs.mjs +1 -1
- package/source/components/style/skeleton.css +60 -20
- package/source/components/style/typography.css +2 -2
- package/source/components/stylesheet/skeleton.mjs +1 -1
- package/source/components/tree-menu/tree-menu.mjs +1 -1
- package/source/monster.mjs +3 -1
- package/source/types/version.mjs +1 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +40 -36
- /package/source/components/{host → layout}/style/overlay.pcss +0 -0
- /package/source/components/{host → layout}/stylesheet/overlay.mjs +0 -0
@@ -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);
|