@schukai/monster 4.40.1 → 4.42.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.
@@ -14,56 +14,56 @@
14
14
 
15
15
  import { Datasource } from "./datasource.mjs";
16
16
  import {
17
- assembleMethodSymbol,
18
- CustomElement,
19
- registerCustomElement,
20
- getSlottedElements,
21
- updaterTransformerMethodsSymbol,
17
+ assembleMethodSymbol,
18
+ CustomElement,
19
+ registerCustomElement,
20
+ getSlottedElements,
21
+ updaterTransformerMethodsSymbol,
22
22
  } from "../../dom/customelement.mjs";
23
23
  import {
24
- findTargetElementFromEvent,
25
- fireCustomEvent,
24
+ findTargetElementFromEvent,
25
+ fireCustomEvent,
26
26
  } from "../../dom/events.mjs";
27
27
  import { clone } from "../../util/clone.mjs";
28
28
  import {
29
- isString,
30
- isFunction,
31
- isInstance,
32
- isObject,
33
- isArray,
29
+ isString,
30
+ isFunction,
31
+ isInstance,
32
+ isObject,
33
+ isArray,
34
34
  } from "../../types/is.mjs";
35
35
  import {
36
- validateArray,
37
- validateInteger,
38
- validateObject,
36
+ validateArray,
37
+ validateInteger,
38
+ validateObject,
39
39
  } from "../../types/validate.mjs";
40
40
  import { Observer } from "../../types/observer.mjs";
41
41
  import {
42
- ATTRIBUTE_DATATABLE_HEAD,
43
- ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
44
- ATTRIBUTE_DATASOURCE_SELECTOR,
45
- ATTRIBUTE_DATATABLE_ALIGN,
46
- ATTRIBUTE_DATATABLE_SORTABLE,
47
- ATTRIBUTE_DATATABLE_MODE,
48
- ATTRIBUTE_DATATABLE_INDEX,
49
- ATTRIBUTE_DATATABLE_MODE_HIDDEN,
50
- ATTRIBUTE_DATATABLE_FEATURES,
51
- ATTRIBUTE_DATATABLE_MODE_VISIBLE,
52
- ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
53
- ATTRIBUTE_DATATABLE_MODE_FIXED,
42
+ ATTRIBUTE_DATATABLE_HEAD,
43
+ ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
44
+ ATTRIBUTE_DATASOURCE_SELECTOR,
45
+ ATTRIBUTE_DATATABLE_ALIGN,
46
+ ATTRIBUTE_DATATABLE_SORTABLE,
47
+ ATTRIBUTE_DATATABLE_MODE,
48
+ ATTRIBUTE_DATATABLE_INDEX,
49
+ ATTRIBUTE_DATATABLE_MODE_HIDDEN,
50
+ ATTRIBUTE_DATATABLE_FEATURES,
51
+ ATTRIBUTE_DATATABLE_MODE_VISIBLE,
52
+ ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
53
+ ATTRIBUTE_DATATABLE_MODE_FIXED,
54
54
  } from "./constants.mjs";
55
55
  import { instanceSymbol } from "../../constants.mjs";
56
56
  import {
57
- Header,
58
- createOrderStatement,
59
- DIRECTION_ASC,
60
- DIRECTION_DESC,
61
- DIRECTION_NONE,
57
+ Header,
58
+ createOrderStatement,
59
+ DIRECTION_ASC,
60
+ DIRECTION_DESC,
61
+ DIRECTION_NONE,
62
62
  } from "./datatable/header.mjs";
63
63
  import { DatatableStyleSheet } from "./stylesheet/datatable.mjs";
64
64
  import {
65
- handleDataSourceChanges,
66
- datasourceLinkedElementSymbol,
65
+ handleDataSourceChanges,
66
+ datasourceLinkedElementSymbol,
67
67
  } from "./util.mjs";
68
68
  import "./columnbar.mjs";
69
69
  import "./filter-button.mjs";
@@ -151,437 +151,470 @@ const resizeObserverSymbol = Symbol("resizeObserver");
151
151
  * @fires monster-datatable-selection-changed
152
152
  **/
153
153
  class DataTable extends CustomElement {
154
- /**
155
- * This method is called by the `instanceof` operator.
156
- * @return {symbol}
157
- */
158
- static get [instanceSymbol]() {
159
- return Symbol.for("@schukai/monster/components/datatable@@instance");
160
- }
161
-
162
- /**
163
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
164
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
165
- *
166
- * The individual configuration values can be found in the table.
167
- *
168
- * @property {Object} templates Template definitions
169
- * @property {string} templates.main Main template
170
- * @property {Object} datasource Datasource configuration
171
- * @property {string} datasource.selector Selector for the datasource
172
- * @property {string} datasource.orderDelimiter Order delimiter
173
- * @property {Object} mapping Mapping configuration
174
- * @property {string} mapping.data Data mapping
175
- * @property {Array} data Data
176
- * @property {Array} headers Headers
177
- * @property {Object} responsive Responsive configuration
178
- * @property {number} responsive.breakpoint Breakpoint for responsive mode
179
- * @property {Object} labels Labels
180
- * @property {string} labels.theListContainsNoEntries Label for empty state
181
- * @property {Object} classes Classes
182
- * @property {string} classes.container Container class
183
- * @property {Object} features Features
184
- * @property {boolean} features.settings Settings feature
185
- * @property {boolean} features.footer Footer feature
186
- * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
187
- * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
188
- * @property {boolean} features.copyAll Copy all feature
189
- * @property {boolean} features.help Help feature
190
- * @property {Object} templateMapping Template mapping
191
- * @property {string} templateMapping.row-key Row key
192
- * @property {string} templateMapping.filter-id Filter id
193
- **/
194
- get defaults() {
195
- return Object.assign(
196
- {},
197
- super.defaults,
198
- {
199
- templates: {
200
- main: getTemplate(),
201
- emptyState: getEmptyTemplate(),
202
- },
203
-
204
- datasource: {
205
- selector: null,
206
- orderDelimiter: ",", // look at initOptionsFromArguments()
207
- },
208
-
209
- mapping: {
210
- data: "dataset",
211
- },
212
-
213
- data: [],
214
- headers: [],
215
-
216
- responsive: {
217
- breakpoint: 900,
218
- },
219
-
220
- labels: getTranslations(),
221
-
222
- classes: {
223
- control: "monster-theme-control-container-1",
224
- container: "",
225
- row: "monster-theme-control-row-1",
226
- },
227
-
228
- features: {
229
- settings: true,
230
- footer: true,
231
- autoInit: true,
232
- doubleClickCopyToClipboard: true,
233
- copyAll: true,
234
- help: true,
235
- },
236
-
237
- copy: {
238
- delimiter: ";",
239
- quoteOpen: '"',
240
- quoteClose: '"',
241
- rowBreak: "\n",
242
- },
243
-
244
- templateMapping: {
245
- "row-key": null,
246
- "filter-id": null,
247
- },
248
- },
249
- initOptionsFromArguments.call(this),
250
- );
251
- }
252
-
253
- /**
254
- *
255
- * @param {string} selector
256
- * @return {NodeList}
257
- */
258
- getGridElements(selector) {
259
- return this[gridElementSymbol].querySelectorAll(selector);
260
- }
261
-
262
- /**
263
- *
264
- * @return {string}
265
- */
266
- static getTag() {
267
- return "monster-datatable";
268
- }
269
-
270
- /**
271
- * @return {void}
272
- */
273
- disconnectedCallback() {
274
- super.disconnectedCallback();
275
- if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
276
- this[resizeObserverSymbol].disconnect();
277
- }
278
- }
279
-
280
- /**
281
- * @return {void}
282
- */
283
- connectedCallback() {
284
- const self = this;
285
- super.connectedCallback();
286
-
287
- this[resizeObserverSymbol] = new ResizeObserver((entries) => {
288
- updateGrid.call(self);
289
- });
290
-
291
- requestAnimationFrame(() => {
292
- let parent = this.parentNode;
293
- while (!(parent instanceof HTMLElement) && parent !== null) {
294
- parent = parent.parentNode;
295
- }
296
-
297
- if (parent instanceof HTMLElement) {
298
- this[resizeObserverSymbol].observe(parent);
299
- }
300
- });
301
- }
302
-
303
- /**
304
- * Get the row number of the selected rows as an array
305
- *
306
- * @returns {number[]}
307
- */
308
- getSelectedRows() {
309
- const rows = this.getGridElements(`[data-monster-role="select-row"]`);
310
- const selectedRows = [];
311
- rows.forEach((row) => {
312
- if (row.checked) {
313
- const key = row.parentNode.getAttribute(
314
- "data-monster-insert-reference",
315
- );
316
- const index = key.split("-").pop();
317
- selectedRows.push(parseInt(index, 10));
318
- }
319
- });
320
-
321
- return selectedRows;
322
- }
323
-
324
- /**
325
- * @return void
326
- */
327
- [assembleMethodSymbol]() {
328
- const rawKey = this.getOption("templateMapping.row-key");
329
-
330
- if (rawKey === null) {
331
- if (this.id !== null && this.id !== "") {
332
- const rawKey = this.getOption("templateMapping.row-key");
333
- if (rawKey === null) {
334
- this.setOption("templateMapping.row-key", this.id + "-row");
335
- }
336
- } else {
337
- this.setOption("templateMapping.row-key", "row");
338
- }
339
- }
340
-
341
- if (this.id !== null && this.id !== "") {
342
- this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
343
- } else {
344
- this.setOption("templateMapping.filter-id", "filter");
345
- }
346
-
347
- super[assembleMethodSymbol]();
348
-
349
- initControlReferences.call(this);
350
- initEventHandler.call(this);
351
-
352
- getSlottedElements
353
- .call(this, "[data-monster-role=row-action-button]", "bar")
354
- .forEach((i, e) => {
355
- if (e instanceof HTMLElement) {
356
- e.style.visibility = "hidden";
357
- e.style.width = "max-content";
358
-
359
- const pN = e.parentNode;
360
- if (pN instanceof HTMLElement) {
361
- pN.style.flexGrow = "10";
362
- pN.style.display = "flex";
363
- pN.style.justifyContent = "flex-start";
364
- }
365
- }
366
- });
367
-
368
- getHostConfig
369
- .call(this, getColumnVisibilityConfigKey)
370
- .then((config) => {
371
- const headerOrderMap = new Map();
372
-
373
- getHostConfig
374
- .call(this, getStoredOrderConfigKey)
375
- .then((orderConfig) => {
376
- if (isArray(orderConfig) || orderConfig.length > 0) {
377
- for (let i = 0; i < orderConfig.length; i++) {
378
- const item = orderConfig[i];
379
- const parts = item.split(" ");
380
- const field = parts[0];
381
- const direction = parts[1] || DIRECTION_ASC;
382
- headerOrderMap.set(field, direction);
383
- }
384
- }
385
- })
386
- .then(() => {
387
- try {
388
- initGridAndStructs.call(this, config, headerOrderMap);
389
- } catch (error) {
390
- addErrorAttribute(this, error);
391
- }
392
-
393
- updateColumnBar.call(this);
394
- })
395
- .catch((error) => {
396
- addErrorAttribute(this, error);
397
- });
398
- })
399
- .catch((error) => {
400
- addErrorAttribute(this, error);
401
- })
402
- .finally(() => {
403
- const selector = this.getOption("datasource.selector");
404
-
405
- if (isString(selector)) {
406
- const element = findElementWithSelectorUpwards(this, selector);
407
- if (element === null) {
408
- throw new Error("the selector must match exactly one element");
409
- }
410
-
411
- if (!isInstance(element, Datasource)) {
412
- throw new TypeError("the element must be a datasource");
413
- }
414
-
415
- this[datasourceLinkedElementSymbol] = element;
416
-
417
- queueMicrotask(() => {
418
- handleDataSourceChanges.call(this);
419
- if (element && "datasource" in element) {
420
- element.datasource.attachObserver(
421
- new Observer(handleDataSourceChanges.bind(this)),
422
- );
423
- }
424
- });
425
- }
426
- });
427
- }
428
-
429
- /**
430
- * @return {CSSStyleSheet[]}
431
- */
432
- static getCSSStyleSheet() {
433
- return [DatatableStyleSheet];
434
- }
435
-
436
- /**
437
- * Copy a row from the datatable
438
- *
439
- * @param {number|string} fromIndex
440
- * @param {number|string} toIndex
441
- * @return {DataTable}
442
- * @fires monster-datatable-row-copied
443
- */
444
- copyRow(fromIndex, toIndex) {
445
- const datasource = this[datasourceLinkedElementSymbol];
446
- if (!datasource) {
447
- return this;
448
- }
449
- let d = datasource.data;
450
- let c = clone(d);
451
-
452
- let rows = c;
453
- const mapping = this.getOption("mapping.data");
454
-
455
- if (mapping) {
456
- rows = c?.[mapping];
457
- }
458
-
459
- if (rows === undefined || rows === null) {
460
- rows = [];
461
- }
462
-
463
- if (toIndex === undefined) {
464
- toIndex = rows.length;
465
- }
466
-
467
- if (isString(fromIndex)) {
468
- fromIndex = parseInt(fromIndex);
469
- }
470
- if (isString(toIndex)) {
471
- toIndex = parseInt(toIndex);
472
- }
473
-
474
- if (toIndex < 0 || toIndex > rows.length) {
475
- throw new RangeError("index out of bounds");
476
- }
477
-
478
- validateArray(rows);
479
- validateInteger(fromIndex);
480
- validateInteger(toIndex);
481
-
482
- if (fromIndex < 0 || fromIndex >= rows.length) {
483
- throw new RangeError("index out of bounds");
484
- }
485
-
486
- rows.splice(toIndex, 0, clone(rows[fromIndex]));
487
- datasource.data = c;
488
-
489
- fireCustomEvent(this, "monster-datatable-row-copied", {
490
- index: toIndex,
491
- });
492
-
493
- return this;
494
- }
495
-
496
- /**
497
- * Remove a row from the datatable
498
- *
499
- * @param {number|string} index
500
- * @return {DataTable}
501
- * @fires monster-datatable-row-removed
502
- */
503
- removeRow(index) {
504
- const datasource = this[datasourceLinkedElementSymbol];
505
- if (!datasource) {
506
- return this;
507
- }
508
- let d = datasource.data;
509
- let c = clone(d);
510
-
511
- let rows = c;
512
- const mapping = this.getOption("mapping.data");
513
-
514
- if (mapping) {
515
- rows = c?.[mapping];
516
- }
517
-
518
- if (rows === undefined || rows === null) {
519
- rows = [];
520
- }
521
-
522
- if (isString(index)) {
523
- index = parseInt(index);
524
- }
525
-
526
- validateArray(rows);
527
- validateInteger(index);
528
-
529
- if (index < 0 || index >= rows.length) {
530
- throw new RangeError("index out of bounds");
531
- }
532
- if (mapping) {
533
- rows = c?.[mapping];
534
- }
535
-
536
- rows.splice(index, 1);
537
- datasource.data = c;
538
-
539
- fireCustomEvent(this, "monster-datatable-row-removed", {
540
- index: index,
541
- });
542
-
543
- return this;
544
- }
545
-
546
- /**
547
- * Add a row to the datatable
548
- *
549
- * @param {Object} data
550
- * @return {DataTable}
551
- *
552
- * @fires monster-datatable-row-added
553
- **/
554
- addRow(data) {
555
- const datasource = this[datasourceLinkedElementSymbol];
556
- if (!datasource) {
557
- return this;
558
- }
559
- let d = datasource.data;
560
- let c = clone(d);
561
-
562
- let rows = c;
563
-
564
- const mapping = this.getOption("mapping.data");
565
- if (mapping) {
566
- rows = c?.[mapping];
567
- }
568
-
569
- if (rows === undefined || rows === null) {
570
- rows = [];
571
- }
572
-
573
- validateArray(rows);
574
- validateObject(data);
575
-
576
- rows.push(data);
577
- datasource.data = c;
578
-
579
- fireCustomEvent(this, "monster-datatable-row-added", {
580
- index: rows.length - 1,
581
- });
582
-
583
- return this;
584
- }
154
+ /**
155
+ * This method is called by the `instanceof` operator.
156
+ * @return {symbol}
157
+ */
158
+ static get [instanceSymbol]() {
159
+ return Symbol.for("@schukai/monster/components/datatable@@instance");
160
+ }
161
+
162
+ /**
163
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
164
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
165
+ *
166
+ * The individual configuration values can be found in the table.
167
+ *
168
+ * @property {Object} templates Template definitions
169
+ * @property {string} templates.main Main template
170
+ * @property {Object} datasource Datasource configuration
171
+ * @property {string} datasource.selector Selector for the datasource
172
+ * @property {string} datasource.orderDelimiter Order delimiter
173
+ * @property {Object} mapping Mapping configuration
174
+ * @property {string} mapping.data Data mapping
175
+ * @property {Array} data Data
176
+ * @property {Array} headers Headers
177
+ * @property {Object} responsive Responsive configuration
178
+ * @property {number} responsive.breakpoint Breakpoint for responsive mode
179
+ * @property {Object} labels Labels
180
+ * @property {string} labels.theListContainsNoEntries Label for empty state
181
+ * @property {Object} classes Classes
182
+ * @property {string} classes.container Container class
183
+ * @property {Object} features Features
184
+ * @property {boolean} features.settings Settings feature
185
+ * @property {boolean} features.footer Footer feature
186
+ * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
187
+ * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
188
+ * @property {boolean} features.copyAll Copy all feature
189
+ * @property {boolean} features.help Help feature
190
+ * @property {boolean} features.resizeable Columns are resizable
191
+ * @property {Object} copy Copy configuration
192
+ * @property {string} copy.delimiter Delimiter character
193
+ * @property {string} copy.quoteOpen Quote open character
194
+ * @property {string} copy.quoteClose Quote close character
195
+ * @property {string} copy.rowBreak Row break character
196
+ * @property {Object} templateMapping Template mapping
197
+ * @property {string} templateMapping.row-key Row key
198
+ * @property {string} templateMapping.filter-id Filter id
199
+ **/
200
+ get defaults() {
201
+ return Object.assign(
202
+ {},
203
+ super.defaults,
204
+ {
205
+ templates: {
206
+ main: getTemplate(),
207
+ emptyState: getEmptyTemplate(),
208
+ },
209
+
210
+ datasource: {
211
+ selector: null,
212
+ orderDelimiter: ",", // look at initOptionsFromArguments()
213
+ },
214
+
215
+ mapping: {
216
+ data: "dataset",
217
+ },
218
+
219
+ data: [],
220
+ headers: [],
221
+
222
+ responsive: {
223
+ breakpoint: 900,
224
+ },
225
+
226
+ labels: getTranslations(),
227
+
228
+ classes: {
229
+ control: "monster-theme-control-container-1",
230
+ container: "",
231
+ row: "monster-theme-control-row-1",
232
+ },
233
+
234
+ features: {
235
+ settings: true,
236
+ footer: true,
237
+ autoInit: true,
238
+ doubleClickCopyToClipboard: true,
239
+ copyAll: true,
240
+ help: true,
241
+ resizable: true,
242
+ },
243
+
244
+ copy: {
245
+ delimiter: ";",
246
+ quoteOpen: '"',
247
+ quoteClose: '"',
248
+ rowBreak: "\n",
249
+ },
250
+
251
+ templateMapping: {
252
+ "row-key": null,
253
+ "filter-id": null,
254
+ },
255
+ },
256
+ initOptionsFromArguments.call(this),
257
+ );
258
+ }
259
+
260
+ /**
261
+ *
262
+ * @param {string} selector
263
+ * @return {NodeList}
264
+ */
265
+ getGridElements(selector) {
266
+ return this[gridElementSymbol].querySelectorAll(selector);
267
+ }
268
+
269
+ /**
270
+ *
271
+ * @return {string}
272
+ */
273
+ static getTag() {
274
+ return "monster-datatable";
275
+ }
276
+
277
+ /**
278
+ * @return {void}
279
+ */
280
+ disconnectedCallback() {
281
+ super.disconnectedCallback();
282
+ if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
283
+ this[resizeObserverSymbol].disconnect();
284
+ }
285
+ }
286
+
287
+ /**
288
+ * @return {void}
289
+ */
290
+ connectedCallback() {
291
+ const self = this;
292
+ super.connectedCallback();
293
+
294
+ this[resizeObserverSymbol] = new ResizeObserver((entries) => {
295
+ updateGrid.call(self);
296
+ });
297
+
298
+ requestAnimationFrame(() => {
299
+ let parent = this.parentNode;
300
+ while (!(parent instanceof HTMLElement) && parent !== null) {
301
+ parent = parent.parentNode;
302
+ }
303
+
304
+ if (parent instanceof HTMLElement) {
305
+ this[resizeObserverSymbol].observe(parent);
306
+ }
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Get the row number of the selected rows as an array
312
+ *
313
+ * @returns {number[]}
314
+ */
315
+ getSelectedRows() {
316
+ const rows = this.getGridElements(`[data-monster-role="select-row"]`);
317
+ const selectedRows = [];
318
+ rows.forEach((row) => {
319
+ if (row.checked) {
320
+ const key = row.parentNode.getAttribute(
321
+ "data-monster-insert-reference",
322
+ );
323
+ const index = key.split("-").pop();
324
+ selectedRows.push(parseInt(index, 10));
325
+ }
326
+ });
327
+
328
+ return selectedRows;
329
+ }
330
+
331
+ /**
332
+ * @return void
333
+ */
334
+ [assembleMethodSymbol]() {
335
+ const self = this;
336
+ const rawKey = this.getOption("templateMapping.row-key");
337
+
338
+ if (rawKey === null) {
339
+ if (this.id !== null && this.id !== "") {
340
+ const rawKey = this.getOption("templateMapping.row-key");
341
+ if (rawKey === null) {
342
+ this.setOption("templateMapping.row-key", this.id + "-row");
343
+ }
344
+ } else {
345
+ this.setOption("templateMapping.row-key", "row");
346
+ }
347
+ }
348
+
349
+ if (this.id !== null && this.id !== "") {
350
+ this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
351
+ } else {
352
+ this.setOption("templateMapping.filter-id", "filter");
353
+ }
354
+
355
+ super[assembleMethodSymbol]();
356
+
357
+ initControlReferences.call(this);
358
+ initEventHandler.call(this);
359
+
360
+ getSlottedElements
361
+ .call(this, "[data-monster-role=row-action-button]", "bar")
362
+ .forEach((i, e) => {
363
+ if (e instanceof HTMLElement) {
364
+ e.style.visibility = "hidden";
365
+ e.style.width = "max-content";
366
+
367
+ const pN = e.parentNode;
368
+ if (pN instanceof HTMLElement) {
369
+ pN.style.flexGrow = "10";
370
+ pN.style.display = "flex";
371
+ pN.style.justifyContent = "flex-start";
372
+ }
373
+ }
374
+ });
375
+
376
+ getHostConfig
377
+ .call(this, getColumnVisibilityConfigKey)
378
+ .then((config) => {
379
+ const headerOrderMap = new Map();
380
+
381
+ getHostConfig
382
+ .call(this, getStoredOrderConfigKey)
383
+ .then((orderConfig) => {
384
+ if (isArray(orderConfig) || orderConfig.length > 0) {
385
+ for (let i = 0; i < orderConfig.length; i++) {
386
+ const item = orderConfig[i];
387
+ const parts = item.split(" ");
388
+ const field = parts[0];
389
+ const direction = parts[1] || DIRECTION_ASC;
390
+ headerOrderMap.set(field, direction);
391
+ }
392
+ }
393
+ })
394
+ .then(() => {
395
+ try {
396
+ initGridAndStructs.call(this, config, headerOrderMap);
397
+ } catch (error) {
398
+ addErrorAttribute(this, error);
399
+ }
400
+
401
+ updateColumnBar.call(this);
402
+ })
403
+ .catch((error) => {
404
+ addErrorAttribute(this, error);
405
+ });
406
+ })
407
+ .catch((error) => {
408
+ addErrorAttribute(this, error);
409
+ })
410
+ .finally(() => {
411
+ const selector = this.getOption("datasource.selector");
412
+
413
+ if (isString(selector)) {
414
+ const element = findElementWithSelectorUpwards(this, selector);
415
+ if (element === null) {
416
+ throw new Error("the selector must match exactly one element");
417
+ }
418
+
419
+ if (!isInstance(element, Datasource)) {
420
+ throw new TypeError("the element must be a datasource");
421
+ }
422
+
423
+ this[datasourceLinkedElementSymbol] = element;
424
+
425
+ queueMicrotask(() => {
426
+ handleDataSourceChanges.call(this);
427
+ if (element && "datasource" in element) {
428
+ element.datasource.attachObserver(
429
+ new Observer(handleDataSourceChanges.bind(this)),
430
+ );
431
+ }
432
+ });
433
+ }
434
+ });
435
+
436
+ this.attachObserver(
437
+ new Observer((a) => {
438
+ const selectAll = this[gridHeadersElementSymbol].querySelector(
439
+ `[data-monster-role="select-all"]`,
440
+ );
441
+ if (selectAll instanceof HTMLInputElement) {
442
+ selectAll.checked = false;
443
+ }
444
+
445
+ getSlottedElements
446
+ .call(this, "[data-monster-role=row-action-button]", "bar")
447
+ .forEach((i, e) => {
448
+ const selected = self.getSelectedRows();
449
+ if (e instanceof HTMLElement) {
450
+ e.style.visibility = "hidden";
451
+ }
452
+ });
453
+
454
+ const rows = this.getGridElements(`[data-monster-role="select-row"]`);
455
+ rows.forEach((row) => {
456
+ row.checked = false;
457
+ });
458
+ }),
459
+ );
460
+ }
461
+
462
+ /**
463
+ * @return {CSSStyleSheet[]}
464
+ */
465
+ static getCSSStyleSheet() {
466
+ return [DatatableStyleSheet];
467
+ }
468
+
469
+ /**
470
+ * Copy a row from the datatable
471
+ *
472
+ * @param {number|string} fromIndex
473
+ * @param {number|string} toIndex
474
+ * @return {DataTable}
475
+ * @fires monster-datatable-row-copied
476
+ */
477
+ copyRow(fromIndex, toIndex) {
478
+ const datasource = this[datasourceLinkedElementSymbol];
479
+ if (!datasource) {
480
+ return this;
481
+ }
482
+ let d = datasource.data;
483
+ let c = clone(d);
484
+
485
+ let rows = c;
486
+ const mapping = this.getOption("mapping.data");
487
+
488
+ if (mapping) {
489
+ rows = c?.[mapping];
490
+ }
491
+
492
+ if (rows === undefined || rows === null) {
493
+ rows = [];
494
+ }
495
+
496
+ if (toIndex === undefined) {
497
+ toIndex = rows.length;
498
+ }
499
+
500
+ if (isString(fromIndex)) {
501
+ fromIndex = parseInt(fromIndex);
502
+ }
503
+ if (isString(toIndex)) {
504
+ toIndex = parseInt(toIndex);
505
+ }
506
+
507
+ if (toIndex < 0 || toIndex > rows.length) {
508
+ throw new RangeError("index out of bounds");
509
+ }
510
+
511
+ validateArray(rows);
512
+ validateInteger(fromIndex);
513
+ validateInteger(toIndex);
514
+
515
+ if (fromIndex < 0 || fromIndex >= rows.length) {
516
+ throw new RangeError("index out of bounds");
517
+ }
518
+
519
+ rows.splice(toIndex, 0, clone(rows[fromIndex]));
520
+ datasource.data = c;
521
+
522
+ fireCustomEvent(this, "monster-datatable-row-copied", {
523
+ index: toIndex,
524
+ });
525
+
526
+ return this;
527
+ }
528
+
529
+ /**
530
+ * Remove a row from the datatable
531
+ *
532
+ * @param {number|string} index
533
+ * @return {DataTable}
534
+ * @fires monster-datatable-row-removed
535
+ */
536
+ removeRow(index) {
537
+ const datasource = this[datasourceLinkedElementSymbol];
538
+ if (!datasource) {
539
+ return this;
540
+ }
541
+ let d = datasource.data;
542
+ let c = clone(d);
543
+
544
+ let rows = c;
545
+ const mapping = this.getOption("mapping.data");
546
+
547
+ if (mapping) {
548
+ rows = c?.[mapping];
549
+ }
550
+
551
+ if (rows === undefined || rows === null) {
552
+ rows = [];
553
+ }
554
+
555
+ if (isString(index)) {
556
+ index = parseInt(index);
557
+ }
558
+
559
+ validateArray(rows);
560
+ validateInteger(index);
561
+
562
+ if (index < 0 || index >= rows.length) {
563
+ throw new RangeError("index out of bounds");
564
+ }
565
+ if (mapping) {
566
+ rows = c?.[mapping];
567
+ }
568
+
569
+ rows.splice(index, 1);
570
+ datasource.data = c;
571
+
572
+ fireCustomEvent(this, "monster-datatable-row-removed", {
573
+ index: index,
574
+ });
575
+
576
+ return this;
577
+ }
578
+
579
+ /**
580
+ * Add a row to the datatable
581
+ *
582
+ * @param {Object} data
583
+ * @return {DataTable}
584
+ *
585
+ * @fires monster-datatable-row-added
586
+ **/
587
+ addRow(data) {
588
+ const datasource = this[datasourceLinkedElementSymbol];
589
+ if (!datasource) {
590
+ return this;
591
+ }
592
+ let d = datasource.data;
593
+ let c = clone(d);
594
+
595
+ let rows = c;
596
+
597
+ const mapping = this.getOption("mapping.data");
598
+ if (mapping) {
599
+ rows = c?.[mapping];
600
+ }
601
+
602
+ if (rows === undefined || rows === null) {
603
+ rows = [];
604
+ }
605
+
606
+ validateArray(rows);
607
+ validateObject(data);
608
+
609
+ rows.push(data);
610
+ datasource.data = c;
611
+
612
+ fireCustomEvent(this, "monster-datatable-row-added", {
613
+ index: rows.length - 1,
614
+ });
615
+
616
+ return this;
617
+ }
585
618
  }
586
619
 
587
620
  /**
@@ -589,7 +622,7 @@ class DataTable extends CustomElement {
589
622
  * @return {string}
590
623
  */
591
624
  function getColumnVisibilityConfigKey() {
592
- return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
625
+ return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
593
626
  }
594
627
 
595
628
  /**
@@ -597,7 +630,7 @@ function getColumnVisibilityConfigKey() {
597
630
  * @return {string}
598
631
  */
599
632
  function getFilterConfigKey() {
600
- return generateUniqueConfigKey("datatable", this?.id, "filter");
633
+ return generateUniqueConfigKey("datatable", this?.id, "filter");
601
634
  }
602
635
 
603
636
  /**
@@ -605,554 +638,625 @@ function getFilterConfigKey() {
605
638
  * @return {Promise}
606
639
  */
607
640
  function getHostConfig(callback) {
608
- const host = findElementWithSelectorUpwards(this, "monster-host");
609
-
610
- if (!host) {
611
- addErrorAttribute(this, "no host found");
612
- return Promise.resolve({});
613
- }
614
-
615
- if (!this.id) {
616
- addErrorAttribute(this, "no id found; id is required for config");
617
- return Promise.resolve({});
618
- }
619
-
620
- if (!host || !isFunction(host?.getConfig)) {
621
- throw new TypeError("the host must be a monster-host");
622
- }
623
-
624
- const configKey = callback.call(this);
625
- return host.hasConfig(configKey).then((hasConfig) => {
626
- if (hasConfig) {
627
- return host.getConfig(configKey);
628
- } else {
629
- return {};
630
- }
631
- });
641
+ const host = findElementWithSelectorUpwards(this, "monster-host");
642
+
643
+ if (!host) {
644
+ addErrorAttribute(this, "no host found");
645
+ return Promise.resolve({});
646
+ }
647
+
648
+ if (!this.id) {
649
+ addErrorAttribute(this, "no id found; id is required for config");
650
+ return Promise.resolve({});
651
+ }
652
+
653
+ if (!host || !isFunction(host?.getConfig)) {
654
+ throw new TypeError("the host must be a monster-host");
655
+ }
656
+
657
+ const configKey = callback.call(this);
658
+ return host.hasConfig(configKey).then((hasConfig) => {
659
+ if (hasConfig) {
660
+ return host.getConfig(configKey);
661
+ } else {
662
+ return {};
663
+ }
664
+ });
632
665
  }
633
666
 
634
667
  /**
635
668
  * @private
636
669
  */
637
670
  function updateColumnBar() {
638
- if (!this[columnBarElementSymbol]) {
639
- return;
640
- }
641
-
642
- const columns = [];
643
- for (const header of this.getOption("headers")) {
644
- const mode = header.getInternal("mode");
645
-
646
- if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
647
- continue;
648
- }
649
-
650
- columns.push({
651
- visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
652
- name: header.label,
653
- index: header.index,
654
- });
655
- }
656
-
657
- this[columnBarElementSymbol].setOption("columns", columns);
671
+ if (!this[columnBarElementSymbol]) {
672
+ return;
673
+ }
674
+
675
+ const columns = [];
676
+ for (const header of this.getOption("headers")) {
677
+ const mode = header.getInternal("mode");
678
+
679
+ if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
680
+ continue;
681
+ }
682
+
683
+ columns.push({
684
+ visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
685
+ name: header.label,
686
+ index: header.index,
687
+ });
688
+ }
689
+
690
+ this[columnBarElementSymbol].setOption("columns", columns);
658
691
  }
659
692
 
660
693
  /**
661
694
  * @private
662
695
  */
663
696
  function updateHeaderFromColumnBar() {
664
- if (!this[columnBarElementSymbol]) {
665
- return;
666
- }
697
+ if (!this[columnBarElementSymbol]) {
698
+ return;
699
+ }
667
700
 
668
- const options = this[columnBarElementSymbol].getOption("columns");
669
- if (!isArray(options)) return;
701
+ const options = this[columnBarElementSymbol].getOption("columns");
702
+ if (!isArray(options)) return;
670
703
 
671
- const invisibleMap = {};
704
+ const invisibleMap = {};
672
705
 
673
- for (let i = 0; i < options.length; i++) {
674
- const option = options[i];
675
- invisibleMap[option.index] = option.visible;
676
- }
706
+ for (let i = 0; i < options.length; i++) {
707
+ const option = options[i];
708
+ invisibleMap[option.index] = option.visible;
709
+ }
677
710
 
678
- for (const header of this.getOption("headers")) {
679
- const mode = header.getInternal("mode");
711
+ for (const header of this.getOption("headers")) {
712
+ const mode = header.getInternal("mode");
680
713
 
681
- if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
682
- continue;
683
- }
714
+ if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
715
+ continue;
716
+ }
684
717
 
685
- if (invisibleMap[header.index] === false) {
686
- header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
687
- } else {
688
- header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
689
- }
690
- }
718
+ if (invisibleMap[header.index] === false) {
719
+ header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
720
+ } else {
721
+ header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
722
+ }
723
+ }
691
724
  }
692
725
 
693
726
  /**
694
727
  * @private
695
728
  */
696
729
  function updateConfigColumnBar() {
697
- if (!this[columnBarElementSymbol]) {
698
- return;
699
- }
700
-
701
- const options = this[columnBarElementSymbol].getOption("columns");
702
- if (!isArray(options)) return;
703
-
704
- const map = {};
705
- for (let i = 0; i < options.length; i++) {
706
- const option = options[i];
707
- map[option.name] = option.visible;
708
- }
709
-
710
- const host = findElementWithSelectorUpwards(this, "monster-host");
711
- if (!(host && this.id)) {
712
- return;
713
- }
714
- const configKey = getColumnVisibilityConfigKey.call(this);
715
-
716
- try {
717
- host.setConfig(configKey, map);
718
- } catch (error) {
719
- addErrorAttribute(this, error);
720
- }
730
+ if (!this[columnBarElementSymbol]) {
731
+ return;
732
+ }
733
+
734
+ const options = this[columnBarElementSymbol].getOption("columns");
735
+ if (!isArray(options)) return;
736
+
737
+ const map = {};
738
+ for (let i = 0; i < options.length; i++) {
739
+ const option = options[i];
740
+ map[option.name] = option.visible;
741
+ }
742
+
743
+ const host = findElementWithSelectorUpwards(this, "monster-host");
744
+ if (!(host && this.id)) {
745
+ return;
746
+ }
747
+ const configKey = getColumnVisibilityConfigKey.call(this);
748
+
749
+ try {
750
+ host.setConfig(configKey, map);
751
+ } catch (error) {
752
+ addErrorAttribute(this, error);
753
+ }
721
754
  }
722
755
 
723
756
  /**
724
757
  * @private
725
758
  */
726
759
  function initEventHandler() {
727
- const self = this;
728
-
729
- const quoteOpenChar = this.getOption("copy.quoteOpen");
730
- const quoteCloseChar = this.getOption("copy.quoteClose");
731
- const delimiterChar = this.getOption("copy.delimiter");
732
- const rowBreak = this.getOption("copy.rowBreak");
733
-
734
- self[columnBarElementSymbol].attachObserver(
735
- new Observer((e) => {
736
- updateHeaderFromColumnBar.call(self);
737
- updateGrid.call(self);
738
- updateConfigColumnBar.call(self);
739
- }),
740
- );
741
-
742
- self[gridHeadersElementSymbol].addEventListener("click", function (event) {
743
- let element = null;
744
- const datasource = self[datasourceLinkedElementSymbol];
745
- if (!datasource) {
746
- return;
747
- }
748
-
749
- element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
750
- if (element instanceof HTMLElement) {
751
- const index = element.parentNode?.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
752
- const headers = self.getOption("headers");
753
-
754
- event.preventDefault();
755
-
756
- headers[index].changeDirection();
757
-
758
- queueMicrotask(function () {
759
- /** hotfix, normally this should be done via the updater, no idea why this is not possible. */
760
- element.setAttribute(
761
- ATTRIBUTE_DATATABLE_SORTABLE,
762
- `${headers[index].field} ${headers[index].direction}`,
763
- );
764
-
765
- storeOrderStatement.call(self, true);
766
- });
767
- }
768
- });
769
-
770
- const eventHandlerDoubleClickCopyToClipboard = (event) => {
771
- const element = findTargetElementFromEvent(event, "data-monster-head");
772
- if (element) {
773
- let text = "";
774
-
775
- if (event.shiftKey) {
776
- const index = element.getAttribute("data-monster-insert-reference");
777
- if (index) {
778
- const cols = self.getGridElements(
779
- `[data-monster-insert-reference="${index}"]`,
780
- );
781
-
782
- const colTexts = [];
783
- for (let i = 0; i < cols.length; i++) {
784
- const col = cols[i];
785
-
786
- if (
787
- col.querySelector("monster-button-bar") ||
788
- col.querySelector("monster-button")
789
- ) {
790
- continue;
791
- }
792
-
793
- if (col.textContent) {
794
- colTexts.push(
795
- quoteOpenChar + col.textContent.trim() + quoteCloseChar,
796
- );
797
- }
798
- }
799
-
800
- text = colTexts.join(delimiterChar);
801
- }
802
- } else {
803
- if (
804
- element.querySelector("monster-button-bar") ||
805
- element.querySelector("monster-button")
806
- ) {
807
- return;
808
- }
809
-
810
- text = element.textContent.trim();
811
- }
812
-
813
- if (getWindow().navigator.clipboard && text) {
814
- getWindow()
815
- .navigator.clipboard.writeText(text)
816
- .then(
817
- () => {},
818
- (err) => {},
819
- );
820
- }
821
- }
822
- };
823
-
824
- if (self.getOption("features.doubleClickCopyToClipboard")) {
825
- self[gridElementSymbol].addEventListener(
826
- "dblclick",
827
- eventHandlerDoubleClickCopyToClipboard,
828
- );
829
- }
830
-
831
- if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
832
- this[copyAllElementSymbol].addEventListener("click", (event) => {
833
- event.preventDefault();
834
-
835
- const table = [];
836
- let currentRow = [];
837
- let currentIndex = null;
838
-
839
- const cols = self.getGridElements(`[data-monster-insert-reference]`);
840
- const rowIndexes = new Map();
841
- cols.forEach((col) => {
842
- const index = col.getAttribute("data-monster-insert-reference");
843
- rowIndexes.set(index, true);
844
- });
845
-
846
- rowIndexes.forEach((value, key) => {
847
- const cols = self.getGridElements(
848
- `[data-monster-insert-reference="${key}"]`,
849
- );
850
-
851
- for (let i = 0; i < cols.length; i++) {
852
- const col = cols[i];
853
-
854
- if (
855
- col.querySelector("monster-button-bar") ||
856
- col.querySelector("monster-button")
857
- ) {
858
- continue;
859
- }
860
-
861
- if (col.textContent) {
862
- currentRow.push(
863
- quoteOpenChar + col.textContent.trim() + quoteCloseChar,
864
- );
865
- }
866
- }
867
-
868
- if (currentRow.length > 0) {
869
- table.push(currentRow);
870
- }
871
- currentRow = [];
872
- });
873
-
874
- if (table.length > 0) {
875
- const text = table.map((row) => row.join(delimiterChar)).join(rowBreak);
876
- if (getWindow().navigator.clipboard && text) {
877
- getWindow()
878
- .navigator.clipboard.writeText(text)
879
- .then(
880
- () => {},
881
- (err) => {},
882
- );
883
- }
884
- }
885
- });
886
- }
887
-
888
- const selectRowCallback = (event) => {
889
- const element = findTargetElementFromEvent(
890
- event,
891
- "data-monster-role",
892
- "select-row",
893
- );
894
-
895
- if (!(element instanceof HTMLInputElement)) {
896
- return;
897
- }
898
-
899
- const parentNode = element.parentNode;
900
- if (!(parentNode instanceof HTMLDivElement)) {
901
- return;
902
- }
903
-
904
- const key = element.parentNode.getAttribute(
905
- "data-monster-insert-reference",
906
- );
907
-
908
- const row = self.getGridElements(
909
- `[data-monster-insert-reference="${key}"]`,
910
- );
911
-
912
- const index = key.split("-").pop();
913
-
914
- if (element.checked) {
915
- row.forEach((col) => {
916
- col.classList.add("selected");
917
- });
918
-
919
- fireCustomEvent(self, "monster-datatable-row-selected", {
920
- index: index,
921
- });
922
- } else {
923
- row.forEach((col) => {
924
- col.classList.remove("selected");
925
- });
926
-
927
- fireCustomEvent(self, "monster-datatable-row-deselected", {
928
- index: index,
929
- });
930
- }
931
-
932
- fireCustomEvent(this, "monster-datatable-selection-changed", {});
933
-
934
- const rows = self.getGridElements(`[data-monster-role="select-row"]`);
935
- const allSelected = Array.from(rows).every((row) => row.checked);
936
-
937
- const selectAll = this[gridHeadersElementSymbol].querySelector(
938
- `[data-monster-role="select-all"]`,
939
- );
940
-
941
- getSlottedElements
942
- .call(this, "[data-monster-role=row-action-button]", "bar")
943
- .forEach((i, e) => {
944
- const selected = self.getSelectedRows();
945
- const mode = selected.length === 0 ? "hidden" : "visible";
946
- if (e instanceof HTMLElement) {
947
- e.style.visibility = mode;
948
- }
949
- });
950
-
951
- if (selectAll) {
952
- selectAll.checked = allSelected;
953
- }
954
- };
955
-
956
- this[gridElementSymbol].addEventListener("click", selectRowCallback);
957
- this[gridElementSymbol].addEventListener("touch", selectRowCallback);
958
-
959
- const selectAllCallback = (event) => {
960
- const element = findTargetElementFromEvent(
961
- event,
962
- "data-monster-role",
963
- "select-all",
964
- );
965
- if (element) {
966
- const mode = element.checked;
967
-
968
- const rows = this.getGridElements(`[data-monster-role="select-row"]`);
969
- rows.forEach((row) => {
970
- row.checked = mode;
971
- });
972
-
973
- if (mode) {
974
- fireCustomEvent(this, "monster-datatable-all-rows-selected", {});
975
- } else {
976
- fireCustomEvent(this, "monster-datatable-all-rows-deselected", {});
977
- }
978
-
979
- getSlottedElements
980
- .call(this, "[data-monster-role=row-action-button]", "bar")
981
- .forEach((i, e) => {
982
- if (e instanceof HTMLElement) {
983
- e.style.visibility = mode ? "visible" : "hidden";
984
- }
985
- });
986
-
987
- fireCustomEvent(this, "monster-datatable-selection-changed", {});
988
- }
989
- };
990
-
991
- this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback);
992
- this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback);
760
+ const self = this;
761
+
762
+ // --- Column resizing state ---
763
+ let isResizing = false;
764
+ let resizingColumnIndex = -1;
765
+ let startX = 0;
766
+ let startWidth = 0;
767
+
768
+ // --- Pointer-based resize handlers (robust für Maus/Touch/Stylus) ---
769
+ const onPointerDown = (event) => {
770
+ // Wichtig: Helper mit (attrName, value) aufrufen – nicht mit CSS-Selector
771
+ const resizeHandle = findTargetElementFromEvent(
772
+ event,
773
+ "data-monster-role",
774
+ "resize-handle",
775
+ );
776
+ if (!resizeHandle) return;
777
+
778
+ event.preventDefault();
779
+ isResizing = true;
780
+
781
+ const headerCell = resizeHandle.closest("[data-monster-index]");
782
+ resizingColumnIndex = parseInt(
783
+ headerCell.getAttribute("data-monster-index"),
784
+ 10,
785
+ );
786
+ startX = event.clientX;
787
+ startWidth = headerCell.offsetWidth;
788
+
789
+ // Capture, damit Events beim schnellen Ziehen nicht “verloren gehen”
790
+ event.target.setPointerCapture?.(event.pointerId);
791
+
792
+ getWindow().addEventListener("pointermove", onPointerMove);
793
+ getWindow().addEventListener("pointerup", onPointerUp);
794
+
795
+ // Globales Cursor-Styling während des Resizings
796
+ self.shadowRoot.host.classList.add("is-resizing");
797
+ };
798
+
799
+ const onPointerMove = (event) => {
800
+ if (!isResizing) return;
801
+
802
+ const deltaX = event.clientX - startX;
803
+ let newWidth = startWidth + deltaX;
804
+
805
+ const headers = self.getOption("headers");
806
+ const resizingHeader = headers[resizingColumnIndex];
807
+
808
+ const minWidth = 50; // Fallback auf 50px
809
+
810
+ const visibleHeaders = headers.filter(
811
+ (h) => h.getInternal("mode") !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
812
+ );
813
+ const numOtherVisibleColumns =
814
+ visibleHeaders.length > 1 ? visibleHeaders.length - 1 : 0;
815
+
816
+ const reservedSpaceForOthers = numOtherVisibleColumns * 50;
817
+ const tableContainerWidth = self[gridElementSymbol].clientWidth;
818
+ const maxWidth = tableContainerWidth - reservedSpaceForOthers;
819
+
820
+ newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
821
+
822
+ resizingHeader.setInternal("grid", `${newWidth}px`);
823
+
824
+ updateGrid.call(self);
825
+ };
826
+
827
+ const onPointerUp = () => {
828
+ if (!isResizing) return;
829
+
830
+ isResizing = false;
831
+ getWindow().removeEventListener("pointermove", onPointerMove);
832
+ getWindow().removeEventListener("pointerup", onPointerUp);
833
+
834
+ self.shadowRoot.host.classList.remove("is-resizing");
835
+ };
836
+
837
+ // Pointer statt Maus: resilienter
838
+ this[gridHeadersElementSymbol].addEventListener("pointerdown", onPointerDown);
839
+
840
+ // --- Copy-Konfiguration ---
841
+ const quoteOpenChar = this.getOption("copy.quoteOpen");
842
+ const quoteCloseChar = this.getOption("copy.quoteClose");
843
+ const delimiterChar = this.getOption("copy.delimiter");
844
+ const rowBreak = this.getOption("copy.rowBreak");
845
+
846
+ // --- Column-Bar -> Header Sichtbarkeit & Persistenz ---
847
+ if (self[columnBarElementSymbol]) {
848
+ self[columnBarElementSymbol].attachObserver(
849
+ new Observer(() => {
850
+ updateHeaderFromColumnBar.call(self);
851
+ updateGrid.call(self);
852
+ updateConfigColumnBar.call(self);
853
+ }),
854
+ );
855
+ }
856
+
857
+ // --- Sortierung (Klick auf Header-Link) ---
858
+ self[gridHeadersElementSymbol].addEventListener("click", function (event) {
859
+ const datasource = self[datasourceLinkedElementSymbol];
860
+ if (!datasource) return;
861
+
862
+ const element = findTargetElementFromEvent(event, "data-monster-sortable");
863
+ if (element instanceof HTMLElement) {
864
+ const index = element.parentNode?.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
865
+ const headers = self.getOption("headers");
866
+
867
+ event.preventDefault();
868
+
869
+ headers[index].changeDirection();
870
+
871
+ queueMicrotask(function () {
872
+ element.setAttribute(
873
+ ATTRIBUTE_DATATABLE_SORTABLE,
874
+ `${headers[index].field} ${headers[index].direction}`,
875
+ );
876
+
877
+ storeOrderStatement.call(self, true);
878
+ });
879
+ }
880
+ });
881
+
882
+ // --- Double-Click Copy (Spalte oder ganze Zeile mit Shift) ---
883
+ const eventHandlerDoubleClickCopyToClipboard = (event) => {
884
+ const headCell = findTargetElementFromEvent(event, "data-monster-head");
885
+ if (!headCell) return;
886
+
887
+ let text = "";
888
+
889
+ if (event.shiftKey) {
890
+ const index = headCell.getAttribute("data-monster-insert-reference");
891
+ if (index) {
892
+ const cols = self.getGridElements(
893
+ `[data-monster-insert-reference="${index}"]`,
894
+ );
895
+
896
+ const colTexts = [];
897
+ for (let i = 0; i < cols.length; i++) {
898
+ const col = cols[i];
899
+
900
+ if (
901
+ col.querySelector("monster-button-bar") ||
902
+ col.querySelector("monster-button")
903
+ ) {
904
+ continue;
905
+ }
906
+
907
+ if (col.textContent) {
908
+ colTexts.push(
909
+ quoteOpenChar + col.textContent.trim() + quoteCloseChar,
910
+ );
911
+ }
912
+ }
913
+
914
+ text = colTexts.join(delimiterChar);
915
+ }
916
+ } else {
917
+ if (
918
+ headCell.querySelector("monster-button-bar") ||
919
+ headCell.querySelector("monster-button")
920
+ ) {
921
+ return;
922
+ }
923
+ text = headCell.textContent.trim();
924
+ }
925
+
926
+ if (getWindow().navigator.clipboard && text) {
927
+ getWindow()
928
+ .navigator.clipboard.writeText(text)
929
+ .then(
930
+ () => {},
931
+ () => {},
932
+ );
933
+ }
934
+ };
935
+
936
+ if (self.getOption("features.doubleClickCopyToClipboard")) {
937
+ self[gridElementSymbol].addEventListener(
938
+ "dblclick",
939
+ eventHandlerDoubleClickCopyToClipboard,
940
+ );
941
+ }
942
+
943
+ // --- Copy All ---
944
+ if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
945
+ this[copyAllElementSymbol].addEventListener("click", (event) => {
946
+ event.preventDefault();
947
+
948
+ const table = [];
949
+ let currentRow = [];
950
+
951
+ const cols = self.getGridElements(`[data-monster-insert-reference]`);
952
+ const rowIndexes = new Map();
953
+ cols.forEach((col) => {
954
+ const index = col.getAttribute("data-monster-insert-reference");
955
+ rowIndexes.set(index, true);
956
+ });
957
+
958
+ rowIndexes.forEach((_, key) => {
959
+ const rowCols = self.getGridElements(
960
+ `[data-monster-insert-reference="${key}"]`,
961
+ );
962
+
963
+ for (let i = 0; i < rowCols.length; i++) {
964
+ const col = rowCols[i];
965
+
966
+ if (
967
+ col.querySelector("monster-button-bar") ||
968
+ col.querySelector("monster-button")
969
+ ) {
970
+ continue;
971
+ }
972
+
973
+ if (col.textContent) {
974
+ currentRow.push(
975
+ quoteOpenChar + col.textContent.trim() + quoteCloseChar,
976
+ );
977
+ }
978
+ }
979
+
980
+ if (currentRow.length > 0) {
981
+ table.push(currentRow);
982
+ }
983
+ currentRow = [];
984
+ });
985
+
986
+ if (table.length > 0) {
987
+ const text = table.map((row) => row.join(delimiterChar)).join(rowBreak);
988
+ if (getWindow().navigator.clipboard && text) {
989
+ getWindow()
990
+ .navigator.clipboard.writeText(text)
991
+ .then(
992
+ () => {},
993
+ () => {},
994
+ );
995
+ }
996
+ }
997
+ });
998
+ }
999
+
1000
+ // --- Row Selection (einzeln) ---
1001
+ const selectRowCallback = (event) => {
1002
+ const checkbox = findTargetElementFromEvent(
1003
+ event,
1004
+ "data-monster-role",
1005
+ "select-row",
1006
+ );
1007
+
1008
+ if (!(checkbox instanceof HTMLInputElement)) return;
1009
+
1010
+ const parentNode = checkbox.parentNode;
1011
+ if (!(parentNode instanceof HTMLDivElement)) return;
1012
+
1013
+ const key = parentNode.getAttribute("data-monster-insert-reference");
1014
+ const row = self.getGridElements(
1015
+ `[data-monster-insert-reference="${key}"]`,
1016
+ );
1017
+ const index = key.split("-").pop();
1018
+
1019
+ if (checkbox.checked) {
1020
+ row.forEach((col) => col.classList.add("selected"));
1021
+ fireCustomEvent(self, "monster-datatable-row-selected", { index });
1022
+ } else {
1023
+ row.forEach((col) => col.classList.remove("selected"));
1024
+ fireCustomEvent(self, "monster-datatable-row-deselected", { index });
1025
+ }
1026
+
1027
+ fireCustomEvent(this, "monster-datatable-selection-changed", {});
1028
+
1029
+ const rows = self.getGridElements(`[data-monster-role="select-row"]`);
1030
+ const allSelected = Array.from(rows).every((r) => r.checked);
1031
+
1032
+ const selectAll = this[gridHeadersElementSymbol].querySelector(
1033
+ `[data-monster-role="select-all"]`,
1034
+ );
1035
+
1036
+ // Row-Action-Buttons je nach Selection sichtbar/unsichtbar
1037
+ getSlottedElements
1038
+ .call(this, "[data-monster-role=row-action-button]", "bar")
1039
+ .forEach((i, e) => {
1040
+ const selected = self.getSelectedRows();
1041
+ const mode = selected.length === 0 ? "hidden" : "visible";
1042
+ if (e instanceof HTMLElement) e.style.visibility = mode;
1043
+ });
1044
+
1045
+ if (selectAll) selectAll.checked = allSelected;
1046
+ };
1047
+
1048
+ this[gridElementSymbol].addEventListener("click", selectRowCallback);
1049
+ this[gridElementSymbol].addEventListener("touch", selectRowCallback);
1050
+
1051
+ // --- Row Selection (alle) ---
1052
+ const selectAllCallback = (event) => {
1053
+ const toggle = findTargetElementFromEvent(
1054
+ event,
1055
+ "data-monster-role",
1056
+ "select-all",
1057
+ );
1058
+ if (!toggle) return;
1059
+
1060
+ const mode = toggle.checked;
1061
+ const rows = this.getGridElements(`[data-monster-role="select-row"]`);
1062
+ rows.forEach((row) => {
1063
+ row.checked = mode;
1064
+ });
1065
+
1066
+ if (mode) {
1067
+ fireCustomEvent(this, "monster-datatable-all-rows-selected", {});
1068
+ } else {
1069
+ fireCustomEvent(this, "monster-datatable-all-rows-deselected", {});
1070
+ }
1071
+
1072
+ getSlottedElements
1073
+ .call(this, "[data-monster-role=row-action-button]", "bar")
1074
+ .forEach((i, e) => {
1075
+ if (e instanceof HTMLElement)
1076
+ e.style.visibility = mode ? "visible" : "hidden";
1077
+ });
1078
+
1079
+ fireCustomEvent(this, "monster-datatable-selection-changed", {});
1080
+ };
1081
+
1082
+ this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback);
1083
+ this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback);
993
1084
  }
994
1085
 
995
1086
  /**
996
1087
  * @private
997
1088
  */
998
1089
  function initGridAndStructs(hostConfig, headerOrderMap) {
999
- const rowID = this.getOption("templateMapping.row-key");
1000
-
1001
- if (!this[gridElementSymbol]) {
1002
- addErrorAttribute(this, "no grid element found");
1003
- return;
1004
- }
1005
-
1006
- let template;
1007
- getSlottedElements.call(this).forEach((e) => {
1008
- if (e instanceof HTMLTemplateElement && e.id === rowID) {
1009
- template = e;
1010
- }
1011
- });
1012
-
1013
- if (!template) {
1014
- addErrorAttribute(this, "no template found, please add a template");
1015
- return;
1016
- }
1017
-
1018
- const rowCount = template.content.children.length;
1019
-
1020
- const headers = [];
1021
-
1022
- for (let i = 0; i < rowCount; i++) {
1023
- let hClass = "";
1024
- const row = template.content.children[i];
1025
-
1026
- let mode = "";
1027
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
1028
- mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
1029
- }
1030
-
1031
- let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
1032
- if (!grid || grid === "" || grid === "auto") {
1033
- grid = "minmax(0, 1fr)";
1034
- }
1035
-
1036
- let label = "";
1037
- let labelKey = "";
1038
-
1039
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
1040
- label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
1041
- labelKey = label;
1042
-
1043
- try {
1044
- if (label.startsWith("i18n:")) {
1045
- label = label.substring(5, label.length);
1046
- label = getDocumentTranslations().getText(label, label);
1047
- }
1048
- } catch (e) {
1049
- label = "i18n error " + label;
1050
- }
1051
- }
1052
-
1053
- if (!label) {
1054
- label = i + 1 + "";
1055
- mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
1056
- labelKey = label;
1057
- }
1058
-
1059
- if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
1060
- if (hostConfig[label] === false) {
1061
- mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
1062
- } else {
1063
- mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
1064
- }
1065
- }
1066
-
1067
- let align = "";
1068
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
1069
- align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
1070
- }
1071
-
1072
- switch (align) {
1073
- case "center":
1074
- hClass = "flex-center";
1075
- break;
1076
- case "end":
1077
- hClass = "flex-end";
1078
- break;
1079
- case "start":
1080
- hClass = "flex-start";
1081
- break;
1082
- default:
1083
- hClass = "flex-start";
1084
- }
1085
-
1086
- let field = "";
1087
- let direction = DIRECTION_NONE;
1088
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
1089
- field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
1090
- const parts = field.split(" ").map((item) => item.trim());
1091
- field = parts[0];
1092
-
1093
- if (headerOrderMap.has(field)) {
1094
- direction = headerOrderMap.get(field);
1095
- } else if (
1096
- parts.length === 2 &&
1097
- [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
1098
- ) {
1099
- direction = parts[1];
1100
- }
1101
- }
1102
-
1103
- if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
1104
- hClass += " hidden";
1105
- }
1106
-
1107
- const features = [];
1108
-
1109
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) {
1110
- const fl = row.getAttribute(ATTRIBUTE_DATATABLE_FEATURES).split(" ");
1111
-
1112
- fl.forEach((feature) => {
1113
- features.push(feature.trim());
1114
-
1115
- if (feature === "select") {
1116
- label = "<input type='checkbox' data-monster-role='select-all' />";
1117
-
1118
- while (row.firstChild) {
1119
- row.removeChild(row.firstChild);
1120
- }
1121
-
1122
- const checkbox = document.createElement("input");
1123
- checkbox.type = "checkbox";
1124
- checkbox.setAttribute("data-monster-role", "select-row");
1125
- row.appendChild(checkbox);
1126
- }
1127
- });
1128
- }
1129
-
1130
- let orderTemplate = "${field} ${direction}";
1131
- if (row.hasAttribute("data-monster-order-template")) {
1132
- orderTemplate = row.getAttribute("data-monster-order-template");
1133
- }
1134
-
1135
- const header = new Header();
1136
- header.setInternals({
1137
- field: field,
1138
- label: label,
1139
- classes: hClass,
1140
- index: i,
1141
- mode: mode,
1142
- grid: grid,
1143
- labelKey: labelKey + "-" + i,
1144
- direction: direction,
1145
- features: features,
1146
- orderTemplate: orderTemplate,
1147
- });
1148
-
1149
- headers.push(header);
1150
- }
1151
-
1152
- this.setOption("headers", headers);
1153
- queueMicrotask(() => {
1154
- storeOrderStatement.call(this, this.getOption("features.autoInit"));
1155
- });
1090
+ const rowID = this.getOption("templateMapping.row-key");
1091
+
1092
+ if (!this[gridElementSymbol]) {
1093
+ addErrorAttribute(this, "no grid element found");
1094
+ return;
1095
+ }
1096
+
1097
+ let template;
1098
+ getSlottedElements.call(this).forEach((e) => {
1099
+ if (e instanceof HTMLTemplateElement && e.id === rowID) {
1100
+ template = e;
1101
+ }
1102
+ });
1103
+
1104
+ if (!template) {
1105
+ addErrorAttribute(this, "no template found, please add a template");
1106
+ return;
1107
+ }
1108
+
1109
+ const rowCount = template.content.children.length;
1110
+
1111
+ const headers = [];
1112
+
1113
+ for (let i = 0; i < rowCount; i++) {
1114
+ let hClass = "";
1115
+ const row = template.content.children[i];
1116
+
1117
+ let mode = "";
1118
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
1119
+ mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
1120
+ }
1121
+
1122
+ let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
1123
+ if (!grid || grid === "" || grid === "auto") {
1124
+ grid = "minmax(0, 1fr)";
1125
+ }
1126
+
1127
+ let label = "";
1128
+ let labelKey = "";
1129
+
1130
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
1131
+ label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
1132
+ labelKey = label;
1133
+
1134
+ try {
1135
+ if (label.startsWith("i18n:")) {
1136
+ label = label.substring(5, label.length);
1137
+ label = getDocumentTranslations().getText(label, label);
1138
+ }
1139
+ } catch (e) {
1140
+ label = "i18n error " + label;
1141
+ }
1142
+ }
1143
+
1144
+ if (!label) {
1145
+ label = i + 1 + "";
1146
+ mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
1147
+ labelKey = label;
1148
+ }
1149
+
1150
+ if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
1151
+ if (hostConfig[label] === false) {
1152
+ mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
1153
+ } else {
1154
+ mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
1155
+ }
1156
+ }
1157
+
1158
+ let align = "";
1159
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
1160
+ align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
1161
+ }
1162
+
1163
+ switch (align) {
1164
+ case "center":
1165
+ hClass = "flex-center";
1166
+ break;
1167
+ case "end":
1168
+ hClass = "flex-end";
1169
+ break;
1170
+ case "start":
1171
+ hClass = "flex-start";
1172
+ break;
1173
+ default:
1174
+ hClass = "flex-start";
1175
+ }
1176
+
1177
+ let field = "";
1178
+ let direction = DIRECTION_NONE;
1179
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
1180
+ field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
1181
+ const parts = field.split(" ").map((item) => item.trim());
1182
+ field = parts[0];
1183
+
1184
+ if (headerOrderMap.has(field)) {
1185
+ direction = headerOrderMap.get(field);
1186
+ } else if (
1187
+ parts.length === 2 &&
1188
+ [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
1189
+ ) {
1190
+ direction = parts[1];
1191
+ }
1192
+ }
1193
+
1194
+ if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
1195
+ hClass += " hidden";
1196
+ }
1197
+
1198
+ const features = [];
1199
+
1200
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) {
1201
+ const fl = row.getAttribute(ATTRIBUTE_DATATABLE_FEATURES).split(" ");
1202
+
1203
+ fl.forEach((feature) => {
1204
+ features.push(feature.trim());
1205
+
1206
+ if (feature === "select") {
1207
+ label = "<input type='checkbox' data-monster-role='select-all' />";
1208
+
1209
+ while (row.firstChild) {
1210
+ row.removeChild(row.firstChild);
1211
+ }
1212
+
1213
+ const checkbox = document.createElement("input");
1214
+ checkbox.type = "checkbox";
1215
+ checkbox.setAttribute("data-monster-role", "select-row");
1216
+ row.appendChild(checkbox);
1217
+ }
1218
+ });
1219
+ }
1220
+
1221
+ let orderTemplate = "${field} ${direction}";
1222
+ if (row.hasAttribute("data-monster-order-template")) {
1223
+ orderTemplate = row.getAttribute("data-monster-order-template");
1224
+ }
1225
+
1226
+ const isResizable = this.getOption("features.resizable");
1227
+ const header = new Header();
1228
+ header.setInternals({
1229
+ field: field,
1230
+ label: label,
1231
+ classes: hClass,
1232
+ index: i,
1233
+ mode: mode,
1234
+ grid: grid,
1235
+ labelKey: labelKey + "-" + i,
1236
+ direction: direction,
1237
+ features: features,
1238
+ orderTemplate: orderTemplate,
1239
+ resizable: isResizable,
1240
+ });
1241
+
1242
+ headers.push(header);
1243
+ }
1244
+
1245
+ this.setOption("headers", headers);
1246
+ requestAnimationFrame(() => {
1247
+ storeOrderStatement.call(this, this.getOption("features.autoInit"));
1248
+
1249
+ const headerElements = this[gridHeadersElementSymbol].querySelectorAll(
1250
+ "[data-monster-index]",
1251
+ );
1252
+ headerElements.forEach((cell) => {
1253
+ const index = parseInt(cell.getAttribute("data-monster-index"), 10);
1254
+ if (headers[index]) {
1255
+ console.log("store initial width for header", index, cell.offsetWidth);
1256
+ headers[index].setInternal("initialWidth", cell.offsetWidth);
1257
+ }
1258
+ });
1259
+ });
1156
1260
  }
1157
1261
 
1158
1262
  /**
@@ -1160,101 +1264,101 @@ function initGridAndStructs(hostConfig, headerOrderMap) {
1160
1264
  * @returns {object}
1161
1265
  */
1162
1266
  function getTranslations() {
1163
- const locale = getLocaleOfDocument();
1164
- switch (locale.language) {
1165
- case "de":
1166
- return {
1167
- theListContainsNoEntries: "Die Liste enthält keine Einträge",
1168
- copyAll: "Alles kopieren",
1169
- helpText:
1170
- "<p>Sie können die Werte aus einzelnen Zeilen<br>" +
1171
- "in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" +
1172
- "<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" +
1173
- "Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>",
1174
- };
1175
- case "fr":
1176
- return {
1177
- theListContainsNoEntries: "La liste ne contient aucune entrée",
1178
- copyAll: "Copier tout",
1179
- helpText:
1180
- "<p>Vous pouvez copier les valeurs des rangées individuelles<br>" +
1181
- "dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" +
1182
- "<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" +
1183
- "Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>",
1184
- };
1185
- case "sp":
1186
- return {
1187
- theListContainsNoEntries: "La lista no contiene entradas",
1188
- copyAll: "Copiar todo",
1189
- helpText:
1190
- "<p>Puedes copiar los valores de filas individuales<br>" +
1191
- "al portapapeles haciendo doble clic en la columna correspondiente.</p>" +
1192
- "<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" +
1193
- "Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>",
1194
- };
1195
- case "it":
1196
- return {
1197
- theListContainsNoEntries: "L'elenco non contiene voci",
1198
- copyAll: "Copia tutto",
1199
- helpText:
1200
- "<p>Puoi copiare i valori dalle singole righe<br>" +
1201
- "negli appunti facendo doppio clic sulla colonna relativa.</p>" +
1202
- "<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" +
1203
- "Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>",
1204
- };
1205
- case "pl":
1206
- return {
1207
- theListContainsNoEntries: "Lista nie zawiera wpisów",
1208
- copyAll: "Kopiuj wszystko",
1209
- helpText:
1210
- "<p>Możesz skopiować wartości z poszczególnych wierszy<br>" +
1211
- "do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" +
1212
- "<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" +
1213
- "Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>",
1214
- };
1215
- case "no":
1216
- return {
1217
- theListContainsNoEntries: "Listen inneholder ingen oppføringer",
1218
- copyAll: "Kopier alt",
1219
- helpText:
1220
- "<p>Du kan kopiere verdier fra enkeltrader<br>" +
1221
- "til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" +
1222
- "<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" +
1223
- "Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>",
1224
- };
1225
- case "dk":
1226
- return {
1227
- theListContainsNoEntries: "Listen indeholder ingen poster",
1228
- copyAll: "Kopiér alt",
1229
- helpText:
1230
- "<p>Du kan kopiere værdier fra enkelte rækker<br>" +
1231
- "til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" +
1232
- "<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" +
1233
- "Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>",
1234
- };
1235
- case "sw":
1236
- return {
1237
- theListContainsNoEntries: "Listan innehåller inga poster",
1238
- copyAll: "Kopiera allt",
1239
- helpText:
1240
- "<p>Du kan kopiera värden från enskilda rader<br>" +
1241
- "till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" +
1242
- "<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" +
1243
- "Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>",
1244
- };
1245
-
1246
- case "en":
1247
- default:
1248
- return {
1249
- theListContainsNoEntries: "The list contains no entries",
1250
- copyAll: "Copy all",
1251
- helpText:
1252
- "<p>You can copy the values from individual rows<br>" +
1253
- "to the clipboard by double-clicking on the relevant column.</p>" +
1254
- "<p>To copy an entire row, hold down the Shift key while clicking.<br>" +
1255
- "If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>",
1256
- };
1257
- }
1267
+ const locale = getLocaleOfDocument();
1268
+ switch (locale.language) {
1269
+ case "de":
1270
+ return {
1271
+ theListContainsNoEntries: "Die Liste enthält keine Einträge",
1272
+ copyAll: "Alles kopieren",
1273
+ helpText:
1274
+ "<p>Sie können die Werte aus einzelnen Zeilen<br>" +
1275
+ "in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" +
1276
+ "<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" +
1277
+ "Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>",
1278
+ };
1279
+ case "fr":
1280
+ return {
1281
+ theListContainsNoEntries: "La liste ne contient aucune entrée",
1282
+ copyAll: "Copier tout",
1283
+ helpText:
1284
+ "<p>Vous pouvez copier les valeurs des rangées individuelles<br>" +
1285
+ "dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" +
1286
+ "<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" +
1287
+ "Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>",
1288
+ };
1289
+ case "sp":
1290
+ return {
1291
+ theListContainsNoEntries: "La lista no contiene entradas",
1292
+ copyAll: "Copiar todo",
1293
+ helpText:
1294
+ "<p>Puedes copiar los valores de filas individuales<br>" +
1295
+ "al portapapeles haciendo doble clic en la columna correspondiente.</p>" +
1296
+ "<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" +
1297
+ "Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>",
1298
+ };
1299
+ case "it":
1300
+ return {
1301
+ theListContainsNoEntries: "L'elenco non contiene voci",
1302
+ copyAll: "Copia tutto",
1303
+ helpText:
1304
+ "<p>Puoi copiare i valori dalle singole righe<br>" +
1305
+ "negli appunti facendo doppio clic sulla colonna relativa.</p>" +
1306
+ "<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" +
1307
+ "Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>",
1308
+ };
1309
+ case "pl":
1310
+ return {
1311
+ theListContainsNoEntries: "Lista nie zawiera wpisów",
1312
+ copyAll: "Kopiuj wszystko",
1313
+ helpText:
1314
+ "<p>Możesz skopiować wartości z poszczególnych wierszy<br>" +
1315
+ "do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" +
1316
+ "<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" +
1317
+ "Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>",
1318
+ };
1319
+ case "no":
1320
+ return {
1321
+ theListContainsNoEntries: "Listen inneholder ingen oppføringer",
1322
+ copyAll: "Kopier alt",
1323
+ helpText:
1324
+ "<p>Du kan kopiere verdier fra enkeltrader<br>" +
1325
+ "til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" +
1326
+ "<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" +
1327
+ "Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>",
1328
+ };
1329
+ case "dk":
1330
+ return {
1331
+ theListContainsNoEntries: "Listen indeholder ingen poster",
1332
+ copyAll: "Kopiér alt",
1333
+ helpText:
1334
+ "<p>Du kan kopiere værdier fra enkelte rækker<br>" +
1335
+ "til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" +
1336
+ "<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" +
1337
+ "Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>",
1338
+ };
1339
+ case "sw":
1340
+ return {
1341
+ theListContainsNoEntries: "Listan innehåller inga poster",
1342
+ copyAll: "Kopiera allt",
1343
+ helpText:
1344
+ "<p>Du kan kopiera värden från enskilda rader<br>" +
1345
+ "till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" +
1346
+ "<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" +
1347
+ "Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>",
1348
+ };
1349
+
1350
+ case "en":
1351
+ default:
1352
+ return {
1353
+ theListContainsNoEntries: "The list contains no entries",
1354
+ copyAll: "Copy all",
1355
+ helpText:
1356
+ "<p>You can copy the values from individual rows<br>" +
1357
+ "to the clipboard by double-clicking on the relevant column.</p>" +
1358
+ "<p>To copy an entire row, hold down the Shift key while clicking.<br>" +
1359
+ "If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>",
1360
+ };
1361
+ }
1258
1362
  }
1259
1363
 
1260
1364
  /**
@@ -1262,83 +1366,83 @@ function getTranslations() {
1262
1366
  * @return {string}
1263
1367
  */
1264
1368
  export function getStoredOrderConfigKey() {
1265
- return generateUniqueConfigKey("datatable", this?.id, "stored-order");
1369
+ return generateUniqueConfigKey("datatable", this?.id, "stored-order");
1266
1370
  }
1267
1371
 
1268
1372
  /**
1269
1373
  * @private
1270
1374
  */
1271
1375
  function storeOrderStatement(doFetch) {
1272
- const headers = this.getOption("headers");
1273
- const delimiter = this.getOption("datasource.orderDelimiter");
1274
- const statement = createOrderStatement(headers, delimiter);
1275
- setDataSource.call(this, { order: statement }, doFetch);
1376
+ const headers = this.getOption("headers");
1377
+ const delimiter = this.getOption("datasource.orderDelimiter");
1378
+ const statement = createOrderStatement(headers, delimiter);
1379
+ setDataSource.call(this, { order: statement }, doFetch);
1276
1380
 
1277
- const host = findElementWithSelectorUpwards(this, "monster-host");
1278
- if (!(host && this.id)) {
1279
- return;
1280
- }
1381
+ const host = findElementWithSelectorUpwards(this, "monster-host");
1382
+ if (!(host && this.id)) {
1383
+ return;
1384
+ }
1281
1385
 
1282
- const configKey = getStoredOrderConfigKey.call(this);
1386
+ const configKey = getStoredOrderConfigKey.call(this);
1283
1387
 
1284
- // statement explode with , and remove all empty
1285
- const list = statement.split(",").filter((item) => item.trim() !== "");
1286
- if (list.length === 0) {
1287
- return;
1288
- }
1388
+ // statement explode with , and remove all empty
1389
+ const list = statement.split(",").filter((item) => item.trim() !== "");
1390
+ if (list.length === 0) {
1391
+ return;
1392
+ }
1289
1393
 
1290
- host.setConfig(configKey, list);
1394
+ host.setConfig(configKey, list);
1291
1395
  }
1292
1396
 
1293
1397
  /**
1294
1398
  * @private
1295
1399
  */
1296
1400
  function updateGrid() {
1297
- if (!this[gridElementSymbol]) {
1298
- throw new Error("no grid element is defined");
1299
- }
1300
-
1301
- let gridTemplateColumns = "";
1302
-
1303
- const headers = this.getOption("headers");
1304
-
1305
- let styles = "";
1306
-
1307
- for (let i = 0; i < headers.length; i++) {
1308
- const header = headers[i];
1309
-
1310
- if (
1311
- header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN &&
1312
- header.mode !== ATTRIBUTE_DATATABLE_MODE_FIXED
1313
- ) {
1314
- styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
1315
- styles += `[data-monster-role=datatable] > div:nth-child(${headers.length}n+${i + 1}) { display: none; }\n`;
1316
- } else {
1317
- gridTemplateColumns += `${header.grid} `;
1318
- }
1319
- }
1320
-
1321
- const sheet = new CSSStyleSheet();
1322
- if (styles !== "") sheet.replaceSync(styles);
1323
- this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
1324
-
1325
- const bodyWidth = this.parentNode.clientWidth;
1326
-
1327
- const breakpoint = this.getOption("responsive.breakpoint");
1328
- this[dataControlElementSymbol].classList.toggle(
1329
- "small",
1330
- bodyWidth <= breakpoint,
1331
- );
1332
-
1333
- if (bodyWidth > breakpoint) {
1334
- this[gridElementSymbol].style.gridTemplateColumns =
1335
- `${gridTemplateColumns}`;
1336
- this[gridHeadersElementSymbol].style.gridTemplateColumns =
1337
- `${gridTemplateColumns}`;
1338
- } else {
1339
- this[gridElementSymbol].style.gridTemplateColumns = "auto";
1340
- this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
1341
- }
1401
+ if (!this[gridElementSymbol]) {
1402
+ throw new Error("no grid element is defined");
1403
+ }
1404
+
1405
+ let gridTemplateColumns = "";
1406
+
1407
+ const headers = this.getOption("headers");
1408
+
1409
+ let styles = "";
1410
+
1411
+ for (let i = 0; i < headers.length; i++) {
1412
+ const header = headers[i];
1413
+
1414
+ if (
1415
+ header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN &&
1416
+ header.mode !== ATTRIBUTE_DATATABLE_MODE_FIXED
1417
+ ) {
1418
+ styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
1419
+ styles += `[data-monster-role=datatable] > div:nth-child(${headers.length}n+${i + 1}) { display: none; }\n`;
1420
+ } else {
1421
+ gridTemplateColumns += `${header.grid} `;
1422
+ }
1423
+ }
1424
+
1425
+ const sheet = new CSSStyleSheet();
1426
+ if (styles !== "") sheet.replaceSync(styles);
1427
+ this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
1428
+
1429
+ const bodyWidth = this.parentNode.clientWidth;
1430
+
1431
+ const breakpoint = this.getOption("responsive.breakpoint");
1432
+ this[dataControlElementSymbol].classList.toggle(
1433
+ "small",
1434
+ bodyWidth <= breakpoint,
1435
+ );
1436
+
1437
+ if (bodyWidth > breakpoint) {
1438
+ this[gridElementSymbol].style.gridTemplateColumns =
1439
+ `${gridTemplateColumns}`;
1440
+ this[gridHeadersElementSymbol].style.gridTemplateColumns =
1441
+ `${gridTemplateColumns}`;
1442
+ } else {
1443
+ this[gridElementSymbol].style.gridTemplateColumns = "auto";
1444
+ this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
1445
+ }
1342
1446
  }
1343
1447
 
1344
1448
  /**
@@ -1347,19 +1451,19 @@ function updateGrid() {
1347
1451
  * @param {bool} doFetch
1348
1452
  */
1349
1453
  function setDataSource({ order }, doFetch) {
1350
- const datasource = this[datasourceLinkedElementSymbol];
1454
+ const datasource = this[datasourceLinkedElementSymbol];
1351
1455
 
1352
- if (!datasource) {
1353
- return;
1354
- }
1456
+ if (!datasource) {
1457
+ return;
1458
+ }
1355
1459
 
1356
- if (isFunction(datasource?.setParameters)) {
1357
- datasource.setParameters({ order });
1358
- }
1460
+ if (isFunction(datasource?.setParameters)) {
1461
+ datasource.setParameters({ order });
1462
+ }
1359
1463
 
1360
- if (doFetch !== false && isFunction(datasource?.fetch)) {
1361
- datasource.fetch();
1362
- }
1464
+ if (doFetch !== false && isFunction(datasource?.fetch)) {
1465
+ datasource.fetch();
1466
+ }
1363
1467
  }
1364
1468
 
1365
1469
  /**
@@ -1367,30 +1471,30 @@ function setDataSource({ order }, doFetch) {
1367
1471
  * @return {DataTable}
1368
1472
  */
1369
1473
  function initControlReferences() {
1370
- if (!this.shadowRoot) {
1371
- throw new Error("no shadow-root is defined");
1372
- }
1474
+ if (!this.shadowRoot) {
1475
+ throw new Error("no shadow-root is defined");
1476
+ }
1373
1477
 
1374
- this[dataControlElementSymbol] = this.shadowRoot.querySelector(
1375
- "[data-monster-role=control]",
1376
- );
1478
+ this[dataControlElementSymbol] = this.shadowRoot.querySelector(
1479
+ "[data-monster-role=control]",
1480
+ );
1377
1481
 
1378
- this[gridElementSymbol] = this.shadowRoot.querySelector(
1379
- "[data-monster-role=datatable]",
1380
- );
1482
+ this[gridElementSymbol] = this.shadowRoot.querySelector(
1483
+ "[data-monster-role=datatable]",
1484
+ );
1381
1485
 
1382
- this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
1383
- "[data-monster-role=datatable-headers]",
1384
- );
1486
+ this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
1487
+ "[data-monster-role=datatable-headers]",
1488
+ );
1385
1489
 
1386
- this[columnBarElementSymbol] =
1387
- this.shadowRoot.querySelector("monster-column-bar");
1490
+ this[columnBarElementSymbol] =
1491
+ this.shadowRoot.querySelector("monster-column-bar");
1388
1492
 
1389
- this[copyAllElementSymbol] = this.shadowRoot.querySelector(
1390
- "[data-monster-role=copy-all]",
1391
- );
1493
+ this[copyAllElementSymbol] = this.shadowRoot.querySelector(
1494
+ "[data-monster-role=copy-all]",
1495
+ );
1392
1496
 
1393
- return this;
1497
+ return this;
1394
1498
  }
1395
1499
 
1396
1500
  /**
@@ -1400,25 +1504,25 @@ function initControlReferences() {
1400
1504
  * @throws {Error} the datasource could not be initialized
1401
1505
  */
1402
1506
  function initOptionsFromArguments() {
1403
- const options = { datasource: {} };
1404
- const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
1507
+ const options = { datasource: {} };
1508
+ const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
1405
1509
 
1406
- if (selector) {
1407
- options.datasource = { selector: selector };
1408
- }
1510
+ if (selector) {
1511
+ options.datasource = { selector: selector };
1512
+ }
1409
1513
 
1410
- options.datasource.orderDelimiter = ","; // workaround for the missing orderDelimiter
1514
+ options.datasource.orderDelimiter = ","; // workaround for the missing orderDelimiter
1411
1515
 
1412
- const breakpoint = this.getAttribute(
1413
- ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
1414
- );
1516
+ const breakpoint = this.getAttribute(
1517
+ ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
1518
+ );
1415
1519
 
1416
- if (breakpoint) {
1417
- options.responsive = {};
1418
- options.responsive.breakpoint = parseInt(breakpoint);
1419
- }
1520
+ if (breakpoint) {
1521
+ options.responsive = {};
1522
+ options.responsive.breakpoint = parseInt(breakpoint);
1523
+ }
1420
1524
 
1421
- return options;
1525
+ return options;
1422
1526
  }
1423
1527
 
1424
1528
  /**
@@ -1426,7 +1530,7 @@ function initOptionsFromArguments() {
1426
1530
  * @return {string}
1427
1531
  */
1428
1532
  function getEmptyTemplate() {
1429
- return `<monster-state data-monster-role="empty-without-action">
1533
+ return `<monster-state data-monster-role="empty-without-action">
1430
1534
  <div part="visual">
1431
1535
  <svg width="4rem" height="4rem" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1432
1536
  <path d="m21.5 22h-19c-1.378 0-2.5-1.121-2.5-2.5v-7c0-.07.015-.141.044-.205l3.969-8.82c.404-.896 1.299-1.475 2.28-1.475h11.414c.981 0 1.876.579 2.28 1.475l3.969 8.82c.029.064.044.135.044.205v7c0 1.379-1.122 2.5-2.5 2.5zm-20.5-9.393v6.893c0 .827.673 1.5 1.5 1.5h19c.827 0 1.5-.673 1.5-1.5v-6.893l-3.925-8.723c-.242-.536-.779-.884-1.368-.884h-11.414c-.589 0-1.126.348-1.368.885z"/>
@@ -1444,8 +1548,8 @@ function getEmptyTemplate() {
1444
1548
  * @return {string}
1445
1549
  */
1446
1550
  function getTemplate() {
1447
- // language=HTML
1448
- return `
1551
+ // language=HTML
1552
+ return `
1449
1553
  <div data-monster-role="control" part="control" data-monster-attributes="class path:classes.control">
1450
1554
  <template id="headers-row">
1451
1555
  <div data-monster-attributes="class path:headers-row.classes,