@schukai/monster 3.86.5 → 3.87.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,75 +12,75 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { Datasource } from "./datasource.mjs";
15
+ import {Datasource} from "./datasource.mjs";
16
16
  import {
17
- assembleMethodSymbol,
18
- CustomElement,
19
- registerCustomElement,
20
- getSlottedElements,
17
+ assembleMethodSymbol,
18
+ CustomElement,
19
+ registerCustomElement,
20
+ getSlottedElements,
21
21
  } from "../../dom/customelement.mjs";
22
22
  import {
23
- findTargetElementFromEvent,
24
- fireCustomEvent,
23
+ findTargetElementFromEvent,
24
+ fireCustomEvent,
25
25
  } from "../../dom/events.mjs";
26
- import { clone } from "../../util/clone.mjs";
26
+ import {clone} from "../../util/clone.mjs";
27
27
  import {
28
- isString,
29
- isFunction,
30
- isInstance,
31
- isObject,
32
- isArray,
28
+ isString,
29
+ isFunction,
30
+ isInstance,
31
+ isObject,
32
+ isArray,
33
33
  } from "../../types/is.mjs";
34
34
  import {
35
- validateArray,
36
- validateInteger,
37
- validateObject,
35
+ validateArray,
36
+ validateInteger,
37
+ validateObject,
38
38
  } from "../../types/validate.mjs";
39
- import { Observer } from "../../types/observer.mjs";
39
+ import {Observer} from "../../types/observer.mjs";
40
40
  import {
41
- ATTRIBUTE_DATATABLE_HEAD,
42
- ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
43
- ATTRIBUTE_DATASOURCE_SELECTOR,
44
- ATTRIBUTE_DATATABLE_ALIGN,
45
- ATTRIBUTE_DATATABLE_SORTABLE,
46
- ATTRIBUTE_DATATABLE_MODE,
47
- ATTRIBUTE_DATATABLE_INDEX,
48
- ATTRIBUTE_DATATABLE_MODE_HIDDEN,
49
- ATTRIBUTE_DATATABLE_MODE_VISIBLE,
50
- ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
51
- ATTRIBUTE_DATATABLE_MODE_FIXED,
41
+ ATTRIBUTE_DATATABLE_HEAD,
42
+ ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
43
+ ATTRIBUTE_DATASOURCE_SELECTOR,
44
+ ATTRIBUTE_DATATABLE_ALIGN,
45
+ ATTRIBUTE_DATATABLE_SORTABLE,
46
+ ATTRIBUTE_DATATABLE_MODE,
47
+ ATTRIBUTE_DATATABLE_INDEX,
48
+ ATTRIBUTE_DATATABLE_MODE_HIDDEN,
49
+ ATTRIBUTE_DATATABLE_MODE_VISIBLE,
50
+ ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
51
+ ATTRIBUTE_DATATABLE_MODE_FIXED,
52
52
  } from "./constants.mjs";
53
- import { instanceSymbol } from "../../constants.mjs";
53
+ import {instanceSymbol} from "../../constants.mjs";
54
54
  import {
55
- Header,
56
- createOrderStatement,
57
- DIRECTION_ASC,
58
- DIRECTION_DESC,
59
- DIRECTION_NONE,
55
+ Header,
56
+ createOrderStatement,
57
+ DIRECTION_ASC,
58
+ DIRECTION_DESC,
59
+ DIRECTION_NONE,
60
60
  } from "./datatable/header.mjs";
61
- import { DatatableStyleSheet } from "./stylesheet/datatable.mjs";
61
+ import {DatatableStyleSheet} from "./stylesheet/datatable.mjs";
62
62
  import {
63
- handleDataSourceChanges,
64
- datasourceLinkedElementSymbol,
63
+ handleDataSourceChanges,
64
+ datasourceLinkedElementSymbol,
65
65
  } from "./util.mjs";
66
66
  import "./columnbar.mjs";
67
67
  import "./filter-button.mjs";
68
68
  import {
69
- findElementWithSelectorUpwards,
70
- getDocument,
71
- getWindow,
69
+ findElementWithSelectorUpwards,
70
+ getDocument,
71
+ getWindow,
72
72
  } from "../../dom/util.mjs";
73
- import { addAttributeToken } from "../../dom/attributes.mjs";
74
- import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
75
- import { getDocumentTranslations } from "../../i18n/translations.mjs";
73
+ import {addAttributeToken} from "../../dom/attributes.mjs";
74
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../dom/constants.mjs";
75
+ import {getDocumentTranslations} from "../../i18n/translations.mjs";
76
76
  import "../state/state.mjs";
77
77
  import "../host/collapse.mjs";
78
- import { generateUniqueConfigKey } from "../host/util.mjs";
78
+ import {generateUniqueConfigKey} from "../host/util.mjs";
79
79
 
80
80
  import "./datasource/dom.mjs";
81
81
  import "./datasource/rest.mjs";
82
82
 
83
- export { DataTable };
83
+ export {DataTable};
84
84
 
85
85
  /**
86
86
  * @private
@@ -106,6 +106,12 @@ const gridHeadersElementSymbol = Symbol("gridHeadersElement");
106
106
  */
107
107
  const columnBarElementSymbol = Symbol("columnBarElement");
108
108
 
109
+ /**
110
+ * @private
111
+ * @type {symbol}
112
+ */
113
+ const copyAllElementSymbol = Symbol("copyAllElement");
114
+
109
115
  /**
110
116
  * @private
111
117
  * @type {symbol}
@@ -142,387 +148,398 @@ const resizeObserverSymbol = Symbol("resizeObserver");
142
148
  * @fires monster-datatable-row-added
143
149
  **/
144
150
  class DataTable extends CustomElement {
145
- /**
146
- * This method is called by the `instanceof` operator.
147
- * @return {symbol}
148
- */
149
- static get [instanceSymbol]() {
150
- return Symbol.for("@schukai/monster/components/datatable@@instance");
151
- }
152
-
153
- /**
154
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
155
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
156
- *
157
- * The individual configuration values can be found in the table.
158
- *
159
- * @property {Object} templates Template definitions
160
- * @property {string} templates.main Main template
161
- * @property {Object} datasource Datasource configuration
162
- * @property {string} datasource.selector Selector for the datasource
163
- * @property {Object} mapping Mapping configuration
164
- * @property {string} mapping.data Data mapping
165
- * @property {Array} data Data
166
- * @property {Array} headers Headers
167
- * @property {Object} responsive Responsive configuration
168
- * @property {number} responsive.breakpoint Breakpoint for responsive mode
169
- * @property {Object} labels Labels
170
- * @property {string} labels.theListContainsNoEntries Label for empty state
171
- * @property {Object} classes Classes
172
- * @property {string} classes.container Container class
173
- * @property {Object} features Features
174
- * @property {boolean} features.settings Settings feature
175
- * @property {boolean} features.footer Footer feature
176
- * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
177
- * @property {Object} templateMapping Template mapping
178
- * @property {string} templateMapping.row-key Row key
179
- * @property {string} templateMapping.filter-id Filter id
180
- **/
181
- get defaults() {
182
- return Object.assign(
183
- {},
184
- super.defaults,
185
- {
186
- templates: {
187
- main: getTemplate(),
188
- emptyState: getEmptyTemplate(),
189
- },
190
-
191
- datasource: {
192
- selector: null,
193
- },
194
-
195
- mapping: {
196
- data: "dataset",
197
- },
198
-
199
- data: [],
200
- headers: [],
201
-
202
- responsive: {
203
- breakpoint: 900,
204
- },
205
-
206
- labels: {
207
- theListContainsNoEntries: "The list contains no entries",
208
- },
209
-
210
- classes: {
211
- control: "monster-theme-control-container-1",
212
- container: "",
213
- row: "monster-theme-control-row-1",
214
- },
215
-
216
- features: {
217
- settings: true,
218
- footer: true,
219
- autoInit: true,
220
- },
221
-
222
- templateMapping: {
223
- "row-key": null,
224
- "filter-id": null,
225
- },
226
- },
227
- initOptionsFromArguments.call(this),
228
- );
229
- }
230
-
231
- /**
232
- *
233
- * @param {string} selector
234
- * @return {NodeListOf<*>}
235
- */
236
- getGridElements(selector) {
237
- return this[gridElementSymbol].querySelectorAll(selector);
238
- }
239
-
240
- /**
241
- *
242
- * @return {string}
243
- */
244
- static getTag() {
245
- return "monster-datatable";
246
- }
247
-
248
- /**
249
- * @return {void}
250
- */
251
- disconnectedCallback() {
252
- super.disconnectedCallback();
253
- if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
254
- this[resizeObserverSymbol].disconnect();
255
- }
256
- }
257
-
258
- /**
259
- * @return {void}
260
- */
261
- connectedCallback() {
262
- const self = this;
263
- super.connectedCallback();
264
-
265
- this[resizeObserverSymbol] = new ResizeObserver((entries) => {
266
- updateGrid.call(self);
267
- });
268
-
269
- this[resizeObserverSymbol].observe(this.parentNode);
270
- }
271
-
272
- /**
273
- * @return void
274
- */
275
- [assembleMethodSymbol]() {
276
- const rawKey = this.getOption("templateMapping.row-key");
277
-
278
- if (rawKey === null) {
279
- if (this.id !== null && this.id !== "") {
280
- const rawKey = this.getOption("templateMapping.row-key");
281
- if (rawKey === null) {
282
- this.setOption("templateMapping.row-key", this.id + "-row");
283
- }
284
- } else {
285
- this.setOption("templateMapping.row-key", "row");
286
- }
287
- }
288
-
289
- if (this.id !== null && this.id !== "") {
290
- this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
291
- } else {
292
- this.setOption("templateMapping.filter-id", "filter");
293
- }
294
-
295
- super[assembleMethodSymbol]();
296
-
297
- initControlReferences.call(this);
298
- initEventHandler.call(this);
299
-
300
- const selector = this.getOption("datasource.selector");
301
-
302
- if (isString(selector)) {
303
- const element = findElementWithSelectorUpwards(this, selector);
304
- if (element === null) {
305
- throw new Error("the selector must match exactly one element");
306
- }
307
-
308
- if (!isInstance(element, Datasource)) {
309
- throw new TypeError("the element must be a datasource");
310
- }
311
-
312
- this[datasourceLinkedElementSymbol] = element;
313
-
314
- queueMicrotask(() => {
315
- handleDataSourceChanges.call(this);
316
- element.datasource.attachObserver(
317
- new Observer(handleDataSourceChanges.bind(this)),
318
- );
319
- });
320
- }
321
-
322
- getHostConfig
323
- .call(this, getColumnVisibilityConfigKey)
324
- .then((config) => {
325
- const headerOrderMap = new Map();
326
-
327
- getHostConfig
328
- .call(this, getStoredOrderConfigKey)
329
- .then((orderConfig) => {
330
- if (isArray(orderConfig) || orderConfig.length > 0) {
331
- for (let i = 0; i < orderConfig.length; i++) {
332
- const item = orderConfig[i];
333
- const parts = item.split(" ");
334
- const field = parts[0];
335
- const direction = parts[1] || DIRECTION_ASC;
336
- headerOrderMap.set(field, direction);
337
- }
338
- }
339
- })
340
- .then(() => {
341
- try {
342
- initGridAndStructs.call(this, config, headerOrderMap);
343
- } catch (error) {
344
- addAttributeToken(
345
- this,
346
- ATTRIBUTE_ERRORMESSAGE,
347
- error?.message || error.toString(),
348
- );
349
- }
350
-
351
- updateColumnBar.call(this);
352
- })
353
- .catch((error) => {
354
- addAttributeToken(
355
- this,
356
- ATTRIBUTE_ERRORMESSAGE,
357
- error?.message || error.toString(),
358
- );
359
- });
360
- })
361
- .catch((error) => {
362
- addAttributeToken(
363
- this,
364
- ATTRIBUTE_ERRORMESSAGE,
365
- error?.message || error.toString(),
366
- );
367
- });
368
- }
369
-
370
- /**
371
- * @return {CSSStyleSheet[]}
372
- */
373
- static getCSSStyleSheet() {
374
- return [DatatableStyleSheet];
375
- }
376
-
377
- /**
378
- * Copy a row from the datatable
379
- *
380
- * @param {number|string} fromIndex
381
- * @param {number|string} toIndex
382
- * @return {DataTable}
383
- * @fires monster-datatable-row-copied
384
- */
385
- copyRow(fromIndex, toIndex) {
386
- const datasource = this[datasourceLinkedElementSymbol];
387
- if (!datasource) {
388
- return this;
389
- }
390
- let d = datasource.data;
391
- let c = clone(d);
392
-
393
- let rows = c;
394
- const mapping = this.getOption("mapping.data");
395
-
396
- if (mapping) {
397
- rows = c?.[mapping];
398
- }
399
-
400
- if (rows === undefined || rows === null) {
401
- rows = [];
402
- }
403
-
404
- if (toIndex === undefined) {
405
- toIndex = rows.length;
406
- }
407
-
408
- if (isString(fromIndex)) {
409
- fromIndex = parseInt(fromIndex);
410
- }
411
- if (isString(toIndex)) {
412
- toIndex = parseInt(toIndex);
413
- }
414
-
415
- if (toIndex < 0 || toIndex > rows.length) {
416
- throw new RangeError("index out of bounds");
417
- }
418
-
419
- validateArray(rows);
420
- validateInteger(fromIndex);
421
- validateInteger(toIndex);
422
-
423
- if (fromIndex < 0 || fromIndex >= rows.length) {
424
- throw new RangeError("index out of bounds");
425
- }
426
-
427
- rows.splice(toIndex, 0, clone(rows[fromIndex]));
428
- datasource.data = c;
429
-
430
- fireCustomEvent(this, "monster-datatable-row-copied", {
431
- index: toIndex,
432
- });
433
-
434
- return this;
435
- }
436
-
437
- /**
438
- * Remove a row from the datatable
439
- *
440
- * @param {number|string} index
441
- * @return {DataTable}
442
- * @fires monster-datatable-row-removed
443
- */
444
- removeRow(index) {
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 (isString(index)) {
464
- index = parseInt(index);
465
- }
466
-
467
- validateArray(rows);
468
- validateInteger(index);
469
-
470
- if (index < 0 || index >= rows.length) {
471
- throw new RangeError("index out of bounds");
472
- }
473
- if (mapping) {
474
- rows = c?.[mapping];
475
- }
476
-
477
- rows.splice(index, 1);
478
- datasource.data = c;
479
-
480
- fireCustomEvent(this, "monster-datatable-row-removed", {
481
- index: index,
482
- });
483
-
484
- return this;
485
- }
486
-
487
- /**
488
- * Add a row to the datatable
489
- *
490
- * @param {Object} data
491
- * @return {DataTable}
492
- *
493
- * @fires monster-datatable-row-added
494
- **/
495
- addRow(data) {
496
- const datasource = this[datasourceLinkedElementSymbol];
497
- if (!datasource) {
498
- return this;
499
- }
500
- let d = datasource.data;
501
- let c = clone(d);
502
-
503
- let rows = c;
504
-
505
- const mapping = this.getOption("mapping.data");
506
- if (mapping) {
507
- rows = c?.[mapping];
508
- }
509
-
510
- if (rows === undefined || rows === null) {
511
- rows = [];
512
- }
513
-
514
- validateArray(rows);
515
- validateObject(data);
516
-
517
- rows.push(data);
518
- datasource.data = c;
519
-
520
- fireCustomEvent(this, "monster-datatable-row-added", {
521
- index: rows.length - 1,
522
- });
523
-
524
- return this;
525
- }
151
+ /**
152
+ * This method is called by the `instanceof` operator.
153
+ * @return {symbol}
154
+ */
155
+ static get [instanceSymbol]() {
156
+ return Symbol.for("@schukai/monster/components/datatable@@instance");
157
+ }
158
+
159
+ /**
160
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
161
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
162
+ *
163
+ * The individual configuration values can be found in the table.
164
+ *
165
+ * @property {Object} templates Template definitions
166
+ * @property {string} templates.main Main template
167
+ * @property {Object} datasource Datasource configuration
168
+ * @property {string} datasource.selector Selector for the datasource
169
+ * @property {Object} mapping Mapping configuration
170
+ * @property {string} mapping.data Data mapping
171
+ * @property {Array} data Data
172
+ * @property {Array} headers Headers
173
+ * @property {Object} responsive Responsive configuration
174
+ * @property {number} responsive.breakpoint Breakpoint for responsive mode
175
+ * @property {Object} labels Labels
176
+ * @property {string} labels.theListContainsNoEntries Label for empty state
177
+ * @property {Object} classes Classes
178
+ * @property {string} classes.container Container class
179
+ * @property {Object} features Features
180
+ * @property {boolean} features.settings Settings feature
181
+ * @property {boolean} features.footer Footer feature
182
+ * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
183
+ * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
184
+ * @property {Object} templateMapping Template mapping
185
+ * @property {string} templateMapping.row-key Row key
186
+ * @property {string} templateMapping.filter-id Filter id
187
+ **/
188
+ get defaults() {
189
+ return Object.assign(
190
+ {},
191
+ super.defaults,
192
+ {
193
+ templates: {
194
+ main: getTemplate(),
195
+ emptyState: getEmptyTemplate(),
196
+ },
197
+
198
+ datasource: {
199
+ selector: null,
200
+ },
201
+
202
+ mapping: {
203
+ data: "dataset",
204
+ },
205
+
206
+ data: [],
207
+ headers: [],
208
+
209
+ responsive: {
210
+ breakpoint: 900,
211
+ },
212
+
213
+ labels: {
214
+ theListContainsNoEntries: "The list contains no entries",
215
+ copyAll: "Copy all",
216
+ },
217
+
218
+ classes: {
219
+ control: "monster-theme-control-container-1",
220
+ container: "",
221
+ row: "monster-theme-control-row-1",
222
+ },
223
+
224
+ features: {
225
+ settings: true,
226
+ footer: true,
227
+ autoInit: true,
228
+ doubleClickCopyToClipboard: true,
229
+ copyAll: true,
230
+ },
231
+
232
+ copy: {
233
+ delimiter: ";",
234
+ quoteOpen: '"',
235
+ quoteClose: '"',
236
+ rowBreak: "\n",
237
+ },
238
+
239
+ templateMapping: {
240
+ "row-key": null,
241
+ "filter-id": null,
242
+ },
243
+ },
244
+ initOptionsFromArguments.call(this),
245
+ );
246
+ }
247
+
248
+ /**
249
+ *
250
+ * @param {string} selector
251
+ * @return {NodeListOf<*>}
252
+ */
253
+ getGridElements(selector) {
254
+ return this[gridElementSymbol].querySelectorAll(selector);
255
+ }
256
+
257
+ /**
258
+ *
259
+ * @return {string}
260
+ */
261
+ static getTag() {
262
+ return "monster-datatable";
263
+ }
264
+
265
+ /**
266
+ * @return {void}
267
+ */
268
+ disconnectedCallback() {
269
+ super.disconnectedCallback();
270
+ if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
271
+ this[resizeObserverSymbol].disconnect();
272
+ }
273
+ }
274
+
275
+ /**
276
+ * @return {void}
277
+ */
278
+ connectedCallback() {
279
+ const self = this;
280
+ super.connectedCallback();
281
+
282
+ this[resizeObserverSymbol] = new ResizeObserver((entries) => {
283
+ updateGrid.call(self);
284
+ });
285
+
286
+ this[resizeObserverSymbol].observe(this.parentNode);
287
+ }
288
+
289
+ /**
290
+ * @return void
291
+ */
292
+ [assembleMethodSymbol]() {
293
+ const rawKey = this.getOption("templateMapping.row-key");
294
+
295
+ if (rawKey === null) {
296
+ if (this.id !== null && this.id !== "") {
297
+ const rawKey = this.getOption("templateMapping.row-key");
298
+ if (rawKey === null) {
299
+ this.setOption("templateMapping.row-key", this.id + "-row");
300
+ }
301
+ } else {
302
+ this.setOption("templateMapping.row-key", "row");
303
+ }
304
+ }
305
+
306
+ if (this.id !== null && this.id !== "") {
307
+ this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
308
+ } else {
309
+ this.setOption("templateMapping.filter-id", "filter");
310
+ }
311
+
312
+ super[assembleMethodSymbol]();
313
+
314
+ initControlReferences.call(this);
315
+ initEventHandler.call(this);
316
+
317
+ const selector = this.getOption("datasource.selector");
318
+
319
+ if (isString(selector)) {
320
+ const element = findElementWithSelectorUpwards(this, selector);
321
+ if (element === null) {
322
+ throw new Error("the selector must match exactly one element");
323
+ }
324
+
325
+ if (!isInstance(element, Datasource)) {
326
+ throw new TypeError("the element must be a datasource");
327
+ }
328
+
329
+ this[datasourceLinkedElementSymbol] = element;
330
+
331
+ queueMicrotask(() => {
332
+ handleDataSourceChanges.call(this);
333
+ element.datasource.attachObserver(
334
+ new Observer(handleDataSourceChanges.bind(this)),
335
+ );
336
+ });
337
+ }
338
+
339
+ getHostConfig
340
+ .call(this, getColumnVisibilityConfigKey)
341
+ .then((config) => {
342
+ const headerOrderMap = new Map();
343
+
344
+ getHostConfig
345
+ .call(this, getStoredOrderConfigKey)
346
+ .then((orderConfig) => {
347
+ if (isArray(orderConfig) || orderConfig.length > 0) {
348
+ for (let i = 0; i < orderConfig.length; i++) {
349
+ const item = orderConfig[i];
350
+ const parts = item.split(" ");
351
+ const field = parts[0];
352
+ const direction = parts[1] || DIRECTION_ASC;
353
+ headerOrderMap.set(field, direction);
354
+ }
355
+ }
356
+ })
357
+ .then(() => {
358
+ try {
359
+ initGridAndStructs.call(this, config, headerOrderMap);
360
+ } catch (error) {
361
+ addAttributeToken(
362
+ this,
363
+ ATTRIBUTE_ERRORMESSAGE,
364
+ error?.message || error.toString(),
365
+ );
366
+ }
367
+
368
+ updateColumnBar.call(this);
369
+ })
370
+ .catch((error) => {
371
+ addAttributeToken(
372
+ this,
373
+ ATTRIBUTE_ERRORMESSAGE,
374
+ error?.message || error.toString(),
375
+ );
376
+ });
377
+ })
378
+ .catch((error) => {
379
+ addAttributeToken(
380
+ this,
381
+ ATTRIBUTE_ERRORMESSAGE,
382
+ error?.message || error.toString(),
383
+ );
384
+ });
385
+ }
386
+
387
+ /**
388
+ * @return {CSSStyleSheet[]}
389
+ */
390
+ static getCSSStyleSheet() {
391
+ return [DatatableStyleSheet];
392
+ }
393
+
394
+ /**
395
+ * Copy a row from the datatable
396
+ *
397
+ * @param {number|string} fromIndex
398
+ * @param {number|string} toIndex
399
+ * @return {DataTable}
400
+ * @fires monster-datatable-row-copied
401
+ */
402
+ copyRow(fromIndex, toIndex) {
403
+ const datasource = this[datasourceLinkedElementSymbol];
404
+ if (!datasource) {
405
+ return this;
406
+ }
407
+ let d = datasource.data;
408
+ let c = clone(d);
409
+
410
+ let rows = c;
411
+ const mapping = this.getOption("mapping.data");
412
+
413
+ if (mapping) {
414
+ rows = c?.[mapping];
415
+ }
416
+
417
+ if (rows === undefined || rows === null) {
418
+ rows = [];
419
+ }
420
+
421
+ if (toIndex === undefined) {
422
+ toIndex = rows.length;
423
+ }
424
+
425
+ if (isString(fromIndex)) {
426
+ fromIndex = parseInt(fromIndex);
427
+ }
428
+ if (isString(toIndex)) {
429
+ toIndex = parseInt(toIndex);
430
+ }
431
+
432
+ if (toIndex < 0 || toIndex > rows.length) {
433
+ throw new RangeError("index out of bounds");
434
+ }
435
+
436
+ validateArray(rows);
437
+ validateInteger(fromIndex);
438
+ validateInteger(toIndex);
439
+
440
+ if (fromIndex < 0 || fromIndex >= rows.length) {
441
+ throw new RangeError("index out of bounds");
442
+ }
443
+
444
+ rows.splice(toIndex, 0, clone(rows[fromIndex]));
445
+ datasource.data = c;
446
+
447
+ fireCustomEvent(this, "monster-datatable-row-copied", {
448
+ index: toIndex,
449
+ });
450
+
451
+ return this;
452
+ }
453
+
454
+ /**
455
+ * Remove a row from the datatable
456
+ *
457
+ * @param {number|string} index
458
+ * @return {DataTable}
459
+ * @fires monster-datatable-row-removed
460
+ */
461
+ removeRow(index) {
462
+ const datasource = this[datasourceLinkedElementSymbol];
463
+ if (!datasource) {
464
+ return this;
465
+ }
466
+ let d = datasource.data;
467
+ let c = clone(d);
468
+
469
+ let rows = c;
470
+ const mapping = this.getOption("mapping.data");
471
+
472
+ if (mapping) {
473
+ rows = c?.[mapping];
474
+ }
475
+
476
+ if (rows === undefined || rows === null) {
477
+ rows = [];
478
+ }
479
+
480
+ if (isString(index)) {
481
+ index = parseInt(index);
482
+ }
483
+
484
+ validateArray(rows);
485
+ validateInteger(index);
486
+
487
+ if (index < 0 || index >= rows.length) {
488
+ throw new RangeError("index out of bounds");
489
+ }
490
+ if (mapping) {
491
+ rows = c?.[mapping];
492
+ }
493
+
494
+ rows.splice(index, 1);
495
+ datasource.data = c;
496
+
497
+ fireCustomEvent(this, "monster-datatable-row-removed", {
498
+ index: index,
499
+ });
500
+
501
+ return this;
502
+ }
503
+
504
+ /**
505
+ * Add a row to the datatable
506
+ *
507
+ * @param {Object} data
508
+ * @return {DataTable}
509
+ *
510
+ * @fires monster-datatable-row-added
511
+ **/
512
+ addRow(data) {
513
+ const datasource = this[datasourceLinkedElementSymbol];
514
+ if (!datasource) {
515
+ return this;
516
+ }
517
+ let d = datasource.data;
518
+ let c = clone(d);
519
+
520
+ let rows = c;
521
+
522
+ const mapping = this.getOption("mapping.data");
523
+ if (mapping) {
524
+ rows = c?.[mapping];
525
+ }
526
+
527
+ if (rows === undefined || rows === null) {
528
+ rows = [];
529
+ }
530
+
531
+ validateArray(rows);
532
+ validateObject(data);
533
+
534
+ rows.push(data);
535
+ datasource.data = c;
536
+
537
+ fireCustomEvent(this, "monster-datatable-row-added", {
538
+ index: rows.length - 1,
539
+ });
540
+
541
+ return this;
542
+ }
526
543
  }
527
544
 
528
545
  /**
@@ -530,7 +547,7 @@ class DataTable extends CustomElement {
530
547
  * @return {string}
531
548
  */
532
549
  function getColumnVisibilityConfigKey() {
533
- return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
550
+ return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
534
551
  }
535
552
 
536
553
  /**
@@ -538,7 +555,7 @@ function getColumnVisibilityConfigKey() {
538
555
  * @return {string}
539
556
  */
540
557
  function getFilterConfigKey() {
541
- return generateUniqueConfigKey("datatable", this?.id, "filter");
558
+ return generateUniqueConfigKey("datatable", this?.id, "filter");
542
559
  }
543
560
 
544
561
  /**
@@ -546,287 +563,403 @@ function getFilterConfigKey() {
546
563
  * @return {Promise}
547
564
  */
548
565
  function getHostConfig(callback) {
549
- const host = findElementWithSelectorUpwards(this, "monster-host");
550
-
551
- if (!(host && this.id)) {
552
- return Promise.resolve({});
553
- }
554
-
555
- if (!host || !isFunction(host?.getConfig)) {
556
- throw new TypeError("the host must be a monster-host");
557
- }
558
-
559
- const configKey = callback.call(this);
560
- return host.hasConfig(configKey).then((hasConfig) => {
561
- if (hasConfig) {
562
- return host.getConfig(configKey);
563
- } else {
564
- return {};
565
- }
566
- });
566
+ const host = findElementWithSelectorUpwards(this, "monster-host");
567
+
568
+ if (!(host && this.id)) {
569
+ return Promise.resolve({});
570
+ }
571
+
572
+ if (!host || !isFunction(host?.getConfig)) {
573
+ throw new TypeError("the host must be a monster-host");
574
+ }
575
+
576
+ const configKey = callback.call(this);
577
+ return host.hasConfig(configKey).then((hasConfig) => {
578
+ if (hasConfig) {
579
+ return host.getConfig(configKey);
580
+ } else {
581
+ return {};
582
+ }
583
+ });
567
584
  }
568
585
 
569
586
  /**
570
587
  * @private
571
588
  */
572
589
  function updateColumnBar() {
573
- if (!this[columnBarElementSymbol]) {
574
- return;
575
- }
576
-
577
- const columns = [];
578
- for (const header of this.getOption("headers")) {
579
- const mode = header.getInternal("mode");
580
-
581
- if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
582
- continue;
583
- }
584
-
585
- columns.push({
586
- visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
587
- name: header.label,
588
- index: header.index,
589
- });
590
- }
591
-
592
- this[columnBarElementSymbol].setOption("columns", columns);
590
+ if (!this[columnBarElementSymbol]) {
591
+ return;
592
+ }
593
+
594
+ const columns = [];
595
+ for (const header of this.getOption("headers")) {
596
+ const mode = header.getInternal("mode");
597
+
598
+ if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
599
+ continue;
600
+ }
601
+
602
+ columns.push({
603
+ visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
604
+ name: header.label,
605
+ index: header.index,
606
+ });
607
+ }
608
+
609
+ this[columnBarElementSymbol].setOption("columns", columns);
593
610
  }
594
611
 
595
612
  /**
596
613
  * @private
597
614
  */
598
615
  function updateHeaderFromColumnBar() {
599
- if (!this[columnBarElementSymbol]) {
600
- return;
601
- }
616
+ if (!this[columnBarElementSymbol]) {
617
+ return;
618
+ }
602
619
 
603
- const options = this[columnBarElementSymbol].getOption("columns");
604
- if (!isArray(options)) return;
620
+ const options = this[columnBarElementSymbol].getOption("columns");
621
+ if (!isArray(options)) return;
605
622
 
606
- const invisibleMap = {};
623
+ const invisibleMap = {};
607
624
 
608
- for (let i = 0; i < options.length; i++) {
609
- const option = options[i];
610
- invisibleMap[option.index] = option.visible;
611
- }
625
+ for (let i = 0; i < options.length; i++) {
626
+ const option = options[i];
627
+ invisibleMap[option.index] = option.visible;
628
+ }
612
629
 
613
- for (const header of this.getOption("headers")) {
614
- const mode = header.getInternal("mode");
630
+ for (const header of this.getOption("headers")) {
631
+ const mode = header.getInternal("mode");
615
632
 
616
- if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
617
- continue;
618
- }
633
+ if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
634
+ continue;
635
+ }
619
636
 
620
- if (invisibleMap[header.index] === false) {
621
- header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
622
- } else {
623
- header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
624
- }
625
- }
637
+ if (invisibleMap[header.index] === false) {
638
+ header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
639
+ } else {
640
+ header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
641
+ }
642
+ }
626
643
  }
627
644
 
628
645
  /**
629
646
  * @private
630
647
  */
631
648
  function updateConfigColumnBar() {
632
- if (!this[columnBarElementSymbol]) {
633
- return;
634
- }
635
-
636
- const options = this[columnBarElementSymbol].getOption("columns");
637
- if (!isArray(options)) return;
638
-
639
- const map = {};
640
- for (let i = 0; i < options.length; i++) {
641
- const option = options[i];
642
- map[option.name] = option.visible;
643
- }
644
-
645
- const host = findElementWithSelectorUpwards(this, "monster-host");
646
- if (!(host && this.id)) {
647
- return;
648
- }
649
- const configKey = getColumnVisibilityConfigKey.call(this);
650
-
651
- try {
652
- host.setConfig(configKey, map);
653
- } catch (error) {
654
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
655
- }
649
+ if (!this[columnBarElementSymbol]) {
650
+ return;
651
+ }
652
+
653
+ const options = this[columnBarElementSymbol].getOption("columns");
654
+ if (!isArray(options)) return;
655
+
656
+ const map = {};
657
+ for (let i = 0; i < options.length; i++) {
658
+ const option = options[i];
659
+ map[option.name] = option.visible;
660
+ }
661
+
662
+ const host = findElementWithSelectorUpwards(this, "monster-host");
663
+ if (!(host && this.id)) {
664
+ return;
665
+ }
666
+ const configKey = getColumnVisibilityConfigKey.call(this);
667
+
668
+ try {
669
+ host.setConfig(configKey, map);
670
+ } catch (error) {
671
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
672
+ }
656
673
  }
657
674
 
658
675
  /**
659
676
  * @private
660
677
  */
661
678
  function initEventHandler() {
662
- const self = this;
663
-
664
- self[columnBarElementSymbol].attachObserver(
665
- new Observer((e) => {
666
- updateHeaderFromColumnBar.call(self);
667
- updateGrid.call(self);
668
- updateConfigColumnBar.call(self);
669
- }),
670
- );
671
-
672
- self[gridHeadersElementSymbol].addEventListener("click", function (event) {
673
- let element = null;
674
- const datasource = self[datasourceLinkedElementSymbol];
675
- if (!datasource) {
676
- return;
677
- }
678
-
679
- element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
680
- if (element) {
681
- const index = element.parentNode.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
682
- const headers = self.getOption("headers");
683
-
684
- event.preventDefault();
685
-
686
- headers[index].changeDirection();
687
-
688
- queueMicrotask(function () {
689
- /** hotfix, normally this should be done via the updater, no idea why this is not possible. */
690
- element.setAttribute(
691
- ATTRIBUTE_DATATABLE_SORTABLE,
692
- `${headers[index].field} ${headers[index].direction}`,
693
- );
694
-
695
- storeOrderStatement.call(self, true);
696
- });
697
- }
698
- });
679
+ const self = this;
680
+
681
+ const quoteOpenChar = this.getOption("copy.quoteOpen");
682
+ const quoteCloseChar = this.getOption("copy.quoteClose");
683
+ const delimiterChar = this.getOption("copy.delimiter");
684
+ const rowBreak = this.getOption("copy.rowBreak");
685
+
686
+ self[columnBarElementSymbol].attachObserver(
687
+ new Observer((e) => {
688
+ updateHeaderFromColumnBar.call(self);
689
+ updateGrid.call(self);
690
+ updateConfigColumnBar.call(self);
691
+ }),
692
+ );
693
+
694
+ self[gridHeadersElementSymbol].addEventListener("click", function (event) {
695
+ let element = null;
696
+ const datasource = self[datasourceLinkedElementSymbol];
697
+ if (!datasource) {
698
+ return;
699
+ }
700
+
701
+ element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
702
+ if (element) {
703
+ const index = element.parentNode.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
704
+ const headers = self.getOption("headers");
705
+
706
+ event.preventDefault();
707
+
708
+ headers[index].changeDirection();
709
+
710
+ queueMicrotask(function () {
711
+ /** hotfix, normally this should be done via the updater, no idea why this is not possible. */
712
+ element.setAttribute(
713
+ ATTRIBUTE_DATATABLE_SORTABLE,
714
+ `${headers[index].field} ${headers[index].direction}`,
715
+ );
716
+
717
+ storeOrderStatement.call(self, true);
718
+ });
719
+ }
720
+ });
721
+
722
+
723
+ const eventHandlerDoubleClickCopyToClipboard = (event) => {
724
+ const element = findTargetElementFromEvent(event, "data-monster-head");
725
+ if (element) {
726
+
727
+
728
+ let text = "";
729
+
730
+ if (event.shiftKey) {
731
+ const index = element.getAttribute("data-monster-insert-reference");
732
+ if (index) {
733
+ const cols = self.getGridElements(`[data-monster-insert-reference="${index}"]`);
734
+
735
+ const colTexts = []
736
+ for (let i = 0; i < cols.length; i++) {
737
+ const col = cols[i];
738
+
739
+ if (col.querySelector("monster-button-bar") || col.querySelector("monster-button")) {
740
+ continue;
741
+ }
742
+
743
+ if (col.textContent) {
744
+ colTexts.push(quoteOpenChar+col.textContent.trim()+quoteCloseChar);
745
+ }
746
+ }
747
+
748
+ text = colTexts.join(delimiterChar);
749
+
750
+ }
751
+
752
+
753
+ } else {
754
+ if (element.querySelector("monster-button-bar") || element.querySelector("monster-button")) {
755
+ return;
756
+ }
757
+
758
+ text = element.textContent.trim();
759
+
760
+ }
761
+
762
+ if (getWindow().navigator.clipboard && text) {
763
+ getWindow().navigator.clipboard.writeText(text).then(
764
+ () => {
765
+ },
766
+ (err) => {
767
+ }
768
+ );
769
+ }
770
+ }
771
+ }
772
+
773
+ if (self.getOption("features.doubleClickCopyToClipboard")) {
774
+ self[gridElementSymbol].addEventListener("dblclick", eventHandlerDoubleClickCopyToClipboard);
775
+ }
776
+
777
+ if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
778
+ this[copyAllElementSymbol].addEventListener("click", (event) => {
779
+ event.preventDefault();
780
+
781
+ const table = [];
782
+ let currentRow = [];
783
+ let currentIndex= null;
784
+
785
+ const cols = self.getGridElements(`[data-monster-insert-reference]`);
786
+ const rowIndexes = new Map();
787
+ cols.forEach((col) => {
788
+ const index = col.getAttribute("data-monster-insert-reference");
789
+ rowIndexes.set(index, true);
790
+ });
791
+
792
+ rowIndexes.forEach((value, key) => {
793
+ const cols = self.getGridElements(`[data-monster-insert-reference="${key}"]`);
794
+
795
+ for (let i = 0; i < cols.length; i++) {
796
+ const col = cols[i];
797
+
798
+ if (col.querySelector("monster-button-bar") || col.querySelector("monster-button")) {
799
+ continue;
800
+ }
801
+
802
+ if (col.textContent) {
803
+ currentRow.push(quoteOpenChar+col.textContent.trim()+quoteCloseChar);
804
+ }
805
+ }
806
+
807
+ if(currentRow.length > 0) {
808
+ table.push(currentRow);
809
+ }
810
+ currentRow = [];
811
+ });
812
+
813
+ if(table.length > 0) {
814
+ const text = table.map(row => row.join(delimiterChar)).join(rowBreak);
815
+ if (getWindow().navigator.clipboard && text) {
816
+ getWindow().navigator.clipboard.writeText(text).then(
817
+ () => {
818
+ },
819
+ (err) => {
820
+ }
821
+ );
822
+ }
823
+ }
824
+
825
+ });
826
+
827
+
828
+ }
829
+
830
+
831
+
699
832
  }
700
833
 
701
834
  /**
702
835
  * @private
703
836
  */
704
837
  function initGridAndStructs(hostConfig, headerOrderMap) {
705
- const rowID = this.getOption("templateMapping.row-key");
706
-
707
- if (!this[gridElementSymbol]) {
708
- throw new Error("no grid element is defined");
709
- }
710
-
711
- let template;
712
- getSlottedElements.call(this).forEach((e) => {
713
- if (e instanceof HTMLTemplateElement && e.id === rowID) {
714
- template = e;
715
- }
716
- });
717
-
718
- if (!template) {
719
- throw new Error("no template is defined");
720
- }
721
-
722
- const rowCount = template.content.children.length;
723
-
724
- const headers = [];
725
-
726
- for (let i = 0; i < rowCount; i++) {
727
- let hClass = "";
728
- const row = template.content.children[i];
729
-
730
- let mode = "";
731
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
732
- mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
733
- }
734
-
735
- let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
736
- if (!grid || grid === "" || grid === "auto") {
737
- grid = "minmax(0, 1fr)";
738
- }
739
-
740
- let label = "";
741
- let labelKey = "";
742
-
743
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
744
- label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
745
- labelKey = label;
746
-
747
- try {
748
- if (label.startsWith("i18n:")) {
749
- label = label.substring(5, label.length);
750
- label = getDocumentTranslations().getText(label, label);
751
- }
752
- } catch (e) {
753
- label = "i18n error " + label;
754
- }
755
- }
756
-
757
- if (!label) {
758
- label = i + 1 + "";
759
- mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
760
- labelKey = label;
761
- }
762
-
763
- if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
764
- if (hostConfig[label] === false) {
765
- mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
766
- } else {
767
- mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
768
- }
769
- }
770
-
771
- let align = "";
772
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
773
- align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
774
- }
775
-
776
- switch (align) {
777
- case "center":
778
- hClass = "flex-center";
779
- break;
780
- case "end":
781
- hClass = "flex-end";
782
- break;
783
- case "start":
784
- hClass = "flex-start";
785
- break;
786
- default:
787
- hClass = "flex-start";
788
- }
789
-
790
- let field = "";
791
- let direction = DIRECTION_NONE;
792
- if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
793
- field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
794
- const parts = field.split(" ").map((item) => item.trim());
795
- field = parts[0];
796
-
797
- if (headerOrderMap.has(field)) {
798
- direction = headerOrderMap.get(field);
799
- } else if (
800
- parts.length === 2 &&
801
- [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
802
- ) {
803
- direction = parts[1];
804
- }
805
- }
806
-
807
- if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
808
- hClass += " hidden";
809
- }
810
-
811
- const header = new Header();
812
- header.setInternals({
813
- field: field,
814
- label: label,
815
- classes: hClass,
816
- index: i,
817
- mode: mode,
818
- grid: grid,
819
- labelKey: labelKey,
820
- direction: direction,
821
- });
822
-
823
- headers.push(header);
824
- }
825
-
826
- this.setOption("headers", headers);
827
- queueMicrotask(() => {
828
- storeOrderStatement.call(this, this.getOption("features.autoInit"));
829
- });
838
+ const rowID = this.getOption("templateMapping.row-key");
839
+
840
+ if (!this[gridElementSymbol]) {
841
+ throw new Error("no grid element is defined");
842
+ }
843
+
844
+ let template;
845
+ getSlottedElements.call(this).forEach((e) => {
846
+ if (e instanceof HTMLTemplateElement && e.id === rowID) {
847
+ template = e;
848
+ }
849
+ });
850
+
851
+ if (!template) {
852
+ throw new Error("no template is defined");
853
+ }
854
+
855
+ const rowCount = template.content.children.length;
856
+
857
+ const headers = [];
858
+
859
+ for (let i = 0; i < rowCount; i++) {
860
+ let hClass = "";
861
+ const row = template.content.children[i];
862
+
863
+ let mode = "";
864
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
865
+ mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
866
+ }
867
+
868
+ let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
869
+ if (!grid || grid === "" || grid === "auto") {
870
+ grid = "minmax(0, 1fr)";
871
+ }
872
+
873
+ let label = "";
874
+ let labelKey = "";
875
+
876
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
877
+ label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
878
+ labelKey = label;
879
+
880
+ try {
881
+ if (label.startsWith("i18n:")) {
882
+ label = label.substring(5, label.length);
883
+ label = getDocumentTranslations().getText(label, label);
884
+ }
885
+ } catch (e) {
886
+ label = "i18n error " + label;
887
+ }
888
+ }
889
+
890
+ if (!label) {
891
+ label = i + 1 + "";
892
+ mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
893
+ labelKey = label;
894
+ }
895
+
896
+ if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
897
+ if (hostConfig[label] === false) {
898
+ mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
899
+ } else {
900
+ mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
901
+ }
902
+ }
903
+
904
+ let align = "";
905
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
906
+ align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
907
+ }
908
+
909
+ switch (align) {
910
+ case "center":
911
+ hClass = "flex-center";
912
+ break;
913
+ case "end":
914
+ hClass = "flex-end";
915
+ break;
916
+ case "start":
917
+ hClass = "flex-start";
918
+ break;
919
+ default:
920
+ hClass = "flex-start";
921
+ }
922
+
923
+ let field = "";
924
+ let direction = DIRECTION_NONE;
925
+ if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
926
+ field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
927
+ const parts = field.split(" ").map((item) => item.trim());
928
+ field = parts[0];
929
+
930
+ if (headerOrderMap.has(field)) {
931
+ direction = headerOrderMap.get(field);
932
+ } else if (
933
+ parts.length === 2 &&
934
+ [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
935
+ ) {
936
+ direction = parts[1];
937
+ }
938
+ }
939
+
940
+ if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
941
+ hClass += " hidden";
942
+ }
943
+
944
+ const header = new Header();
945
+ header.setInternals({
946
+ field: field,
947
+ label: label,
948
+ classes: hClass,
949
+ index: i,
950
+ mode: mode,
951
+ grid: grid,
952
+ labelKey: labelKey,
953
+ direction: direction,
954
+ });
955
+
956
+ headers.push(header);
957
+ }
958
+
959
+ this.setOption("headers", headers);
960
+ queueMicrotask(() => {
961
+ storeOrderStatement.call(this, this.getOption("features.autoInit"));
962
+ });
830
963
  }
831
964
 
832
965
  /**
@@ -834,79 +967,79 @@ function initGridAndStructs(hostConfig, headerOrderMap) {
834
967
  * @return {string}
835
968
  */
836
969
  export function getStoredOrderConfigKey() {
837
- return generateUniqueConfigKey("datatable", this?.id, "stored-order");
970
+ return generateUniqueConfigKey("datatable", this?.id, "stored-order");
838
971
  }
839
972
 
840
973
  /**
841
974
  * @private
842
975
  */
843
976
  function storeOrderStatement(doFetch) {
844
- const headers = this.getOption("headers");
845
- const statement = createOrderStatement(headers);
846
- setDataSource.call(this, { orderBy: statement }, doFetch);
977
+ const headers = this.getOption("headers");
978
+ const statement = createOrderStatement(headers);
979
+ setDataSource.call(this, {orderBy: statement}, doFetch);
847
980
 
848
- const host = findElementWithSelectorUpwards(this, "monster-host");
849
- if (!(host && this.id)) {
850
- return;
851
- }
981
+ const host = findElementWithSelectorUpwards(this, "monster-host");
982
+ if (!(host && this.id)) {
983
+ return;
984
+ }
852
985
 
853
- const configKey = getStoredOrderConfigKey.call(this);
986
+ const configKey = getStoredOrderConfigKey.call(this);
854
987
 
855
- // statement explode with , and remove all empty
856
- const list = statement.split(",").filter((item) => item.trim() !== "");
857
- if (list.length === 0) {
858
- return;
859
- }
988
+ // statement explode with , and remove all empty
989
+ const list = statement.split(",").filter((item) => item.trim() !== "");
990
+ if (list.length === 0) {
991
+ return;
992
+ }
860
993
 
861
- host.setConfig(configKey, list);
994
+ host.setConfig(configKey, list);
862
995
  }
863
996
 
864
997
  /**
865
998
  * @private
866
999
  */
867
1000
  function updateGrid() {
868
- if (!this[gridElementSymbol]) {
869
- throw new Error("no grid element is defined");
870
- }
871
-
872
- let gridTemplateColumns = "";
873
-
874
- const headers = this.getOption("headers");
875
-
876
- let styles = "";
877
-
878
- for (let i = 0; i < headers.length; i++) {
879
- const header = headers[i];
880
-
881
- if (header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
882
- styles += `[data-monster-role=datatable]>[data-monster-head="${header.labelKey}"] { display: none; }\n`;
883
- styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
884
- } else {
885
- gridTemplateColumns += `${header.grid} `;
886
- }
887
- }
888
-
889
- const sheet = new CSSStyleSheet();
890
- if (styles !== "") sheet.replaceSync(styles);
891
- this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
892
-
893
- const bodyWidth = this.parentNode.clientWidth;
894
-
895
- const breakpoint = this.getOption("responsive.breakpoint");
896
- this[dataControlElementSymbol].classList.toggle(
897
- "small",
898
- bodyWidth <= breakpoint,
899
- );
900
-
901
- if (bodyWidth > breakpoint) {
902
- this[gridElementSymbol].style.gridTemplateColumns =
903
- `${gridTemplateColumns}`;
904
- this[gridHeadersElementSymbol].style.gridTemplateColumns =
905
- `${gridTemplateColumns}`;
906
- } else {
907
- this[gridElementSymbol].style.gridTemplateColumns = "auto";
908
- this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
909
- }
1001
+ if (!this[gridElementSymbol]) {
1002
+ throw new Error("no grid element is defined");
1003
+ }
1004
+
1005
+ let gridTemplateColumns = "";
1006
+
1007
+ const headers = this.getOption("headers");
1008
+
1009
+ let styles = "";
1010
+
1011
+ for (let i = 0; i < headers.length; i++) {
1012
+ const header = headers[i];
1013
+
1014
+ if (header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
1015
+ styles += `[data-monster-role=datatable]>[data-monster-head="${header.labelKey}"] { display: none; }\n`;
1016
+ styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
1017
+ } else {
1018
+ gridTemplateColumns += `${header.grid} `;
1019
+ }
1020
+ }
1021
+
1022
+ const sheet = new CSSStyleSheet();
1023
+ if (styles !== "") sheet.replaceSync(styles);
1024
+ this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
1025
+
1026
+ const bodyWidth = this.parentNode.clientWidth;
1027
+
1028
+ const breakpoint = this.getOption("responsive.breakpoint");
1029
+ this[dataControlElementSymbol].classList.toggle(
1030
+ "small",
1031
+ bodyWidth <= breakpoint,
1032
+ );
1033
+
1034
+ if (bodyWidth > breakpoint) {
1035
+ this[gridElementSymbol].style.gridTemplateColumns =
1036
+ `${gridTemplateColumns}`;
1037
+ this[gridHeadersElementSymbol].style.gridTemplateColumns =
1038
+ `${gridTemplateColumns}`;
1039
+ } else {
1040
+ this[gridElementSymbol].style.gridTemplateColumns = "auto";
1041
+ this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
1042
+ }
910
1043
  }
911
1044
 
912
1045
  /**
@@ -914,20 +1047,20 @@ function updateGrid() {
914
1047
  * @param {Header[]} headers
915
1048
  * @param {bool} doFetch
916
1049
  */
917
- function setDataSource({ orderBy }, doFetch) {
918
- const datasource = this[datasourceLinkedElementSymbol];
1050
+ function setDataSource({orderBy}, doFetch) {
1051
+ const datasource = this[datasourceLinkedElementSymbol];
919
1052
 
920
- if (!datasource) {
921
- return;
922
- }
1053
+ if (!datasource) {
1054
+ return;
1055
+ }
923
1056
 
924
- if (isFunction(datasource?.setParameters)) {
925
- datasource.setParameters({ orderBy });
926
- }
1057
+ if (isFunction(datasource?.setParameters)) {
1058
+ datasource.setParameters({orderBy});
1059
+ }
927
1060
 
928
- if (doFetch !== false && isFunction(datasource?.fetch)) {
929
- datasource.fetch();
930
- }
1061
+ if (doFetch !== false && isFunction(datasource?.fetch)) {
1062
+ datasource.fetch();
1063
+ }
931
1064
  }
932
1065
 
933
1066
  /**
@@ -935,24 +1068,28 @@ function setDataSource({ orderBy }, doFetch) {
935
1068
  * @return {DataTable}
936
1069
  */
937
1070
  function initControlReferences() {
938
- if (!this.shadowRoot) {
939
- throw new Error("no shadow-root is defined");
940
- }
941
-
942
- this[dataControlElementSymbol] = this.shadowRoot.querySelector(
943
- "[data-monster-role=control]",
944
- );
945
-
946
- this[gridElementSymbol] = this.shadowRoot.querySelector(
947
- "[data-monster-role=datatable]",
948
- );
949
- this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
950
- "[data-monster-role=datatable-headers]",
951
- );
952
- this[columnBarElementSymbol] =
953
- this.shadowRoot.querySelector("monster-column-bar");
954
-
955
- return this;
1071
+ if (!this.shadowRoot) {
1072
+ throw new Error("no shadow-root is defined");
1073
+ }
1074
+
1075
+ this[dataControlElementSymbol] = this.shadowRoot.querySelector(
1076
+ "[data-monster-role=control]",
1077
+ );
1078
+
1079
+ this[gridElementSymbol] = this.shadowRoot.querySelector(
1080
+ "[data-monster-role=datatable]",
1081
+ );
1082
+ this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
1083
+ "[data-monster-role=datatable-headers]",
1084
+ );
1085
+ this[columnBarElementSymbol] =
1086
+ this.shadowRoot.querySelector("monster-column-bar");
1087
+
1088
+ this[copyAllElementSymbol] = this.shadowRoot.querySelector(
1089
+ "[data-monster-role=copy-all]",
1090
+ );
1091
+
1092
+ return this;
956
1093
  }
957
1094
 
958
1095
  /**
@@ -962,22 +1099,22 @@ function initControlReferences() {
962
1099
  * @throws {Error} the datasource could not be initialized
963
1100
  */
964
1101
  function initOptionsFromArguments() {
965
- const options = {};
966
- const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
967
-
968
- if (selector) {
969
- options.datasource = { selector: selector };
970
- }
971
-
972
- const breakpoint = this.getAttribute(
973
- ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
974
- );
975
- if (breakpoint) {
976
- options.responsive = {};
977
- options.responsive.breakpoint = parseInt(breakpoint);
978
- }
979
-
980
- return options;
1102
+ const options = {};
1103
+ const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
1104
+
1105
+ if (selector) {
1106
+ options.datasource = {selector: selector};
1107
+ }
1108
+
1109
+ const breakpoint = this.getAttribute(
1110
+ ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
1111
+ );
1112
+ if (breakpoint) {
1113
+ options.responsive = {};
1114
+ options.responsive.breakpoint = parseInt(breakpoint);
1115
+ }
1116
+
1117
+ return options;
981
1118
  }
982
1119
 
983
1120
  /**
@@ -985,7 +1122,7 @@ function initOptionsFromArguments() {
985
1122
  * @return {string}
986
1123
  */
987
1124
  function getEmptyTemplate() {
988
- return `<monster-state data-monster-role="empty-without-action">
1125
+ return `<monster-state data-monster-role="empty-without-action">
989
1126
  <div part="visual">
990
1127
  <svg width="4rem" height="4rem" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
991
1128
  <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"/>
@@ -1003,8 +1140,8 @@ function getEmptyTemplate() {
1003
1140
  * @return {string}
1004
1141
  */
1005
1142
  function getTemplate() {
1006
- // language=HTML
1007
- return `
1143
+ // language=HTML
1144
+ return `
1008
1145
  <div data-monster-role="control" part="control" data-monster-attributes="class path:classes.control">
1009
1146
  <template id="headers-row">
1010
1147
  <div data-monster-attributes="class path:headers-row.classes,
@@ -1018,6 +1155,7 @@ function getTemplate() {
1018
1155
  <slot name="filter"></slot>
1019
1156
  </div>
1020
1157
  <div class="bar">
1158
+ <a href="#" data-monster-attributes="class path:features.copyAll | ?::hidden" data-monster-role="copy-all" data-monster-replace="path:locale.copyAll">Copy all</a>
1021
1159
  <monster-column-bar
1022
1160
  data-monster-attributes="class path:features.settings | ?::hidden"></monster-column-bar>
1023
1161
  <slot name="bar"></slot>