@kubex/zinc 1.0.3 → 1.0.5

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.
Files changed (154) hide show
  1. package/.github/workflows/js_build_and_deploy.yaml +1 -1
  2. package/dist/custom-elements.json +25150 -0
  3. package/dist/vscode.html-custom-data.json +2874 -0
  4. package/dist/web-types.json +6611 -0
  5. package/dist/zn.d.ts +6664 -0
  6. package/dist/zn.min.css +1 -0
  7. package/dist/zn.min.js +4740 -0
  8. package/docs/_includes/default.njk +1 -0
  9. package/docs/assets/scripts/docs.js +4 -5
  10. package/docs/assets/scripts/search.js +0 -9
  11. package/docs/assets/styles/docs.css +3 -3
  12. package/docs/data/data-table.json +79 -0
  13. package/docs/data/products-table.json +36 -0
  14. package/docs/eleventy.config.cjs +3 -0
  15. package/docs/pages/components/data-select.md +10 -0
  16. package/docs/pages/components/data-table.md +84 -45
  17. package/docs/pages/components/dialog.md +1 -1
  18. package/docs/pages/components/editor.md +1 -3
  19. package/docs/pages/components/filter-wrapper.md +20 -0
  20. package/docs/pages/components/icon.md +2 -0
  21. package/docs/pages/components/item.md +2 -2
  22. package/docs/pages/components/menu-item.md +12 -3
  23. package/docs/pages/components/panel.md +17 -0
  24. package/docs/pages/components/progress-tile.md +17 -1
  25. package/docs/pages/components/settings-container.md +137 -0
  26. package/docs/pages/components/textarea.md +1 -1
  27. package/package.json +3 -1
  28. package/scss/_root.scss +26 -0
  29. package/scss/boot.scss +27 -1
  30. package/scss/mixins/index.scss +30 -1
  31. package/scss/shared/_base.scss +1 -1
  32. package/scss/shared/_button.scss +1 -1
  33. package/scss/shared/layout.scss +7 -0
  34. package/scss/themes/_dark.scss +5 -5
  35. package/scss/themes/_light.scss +6 -5
  36. package/scss/variables/index.scss +9 -0
  37. package/src/components/alert/alert.component.ts +1 -1
  38. package/src/components/alert/alert.scss +18 -4
  39. package/src/components/button/button.component.ts +6 -2
  40. package/src/components/button/button.scss +16 -1
  41. package/src/components/button-menu/button-menu.component.ts +23 -15
  42. package/src/components/button-menu/button-menu.scss +4 -4
  43. package/src/components/checkbox/checkbox.component.ts +5 -1
  44. package/src/components/checkbox/checkbox.scss +1 -1
  45. package/src/components/chip/chip.component.ts +7 -3
  46. package/src/components/chip/chip.scss +9 -1
  47. package/src/components/collapsible/collapsible.component.ts +48 -2
  48. package/src/components/confirm/confirm.component.ts +1 -2
  49. package/src/components/content-block/content-block.component.ts +25 -1
  50. package/src/components/content-block/content-block.scss +8 -0
  51. package/src/components/data-select/data-select.component.ts +1 -1
  52. package/src/components/data-table/data-table.component.ts +361 -422
  53. package/src/components/data-table/data-table.scss +6 -1
  54. package/src/components/datepicker/datepicker.scss +1 -1
  55. package/src/components/dialog/dialog.component.ts +52 -33
  56. package/src/components/dialog/dialog.scss +15 -1
  57. package/src/components/dropdown/dropdown.component.ts +1 -1
  58. package/src/components/editor/editor.component.ts +196 -158
  59. package/src/components/editor/editor.scss +25 -5
  60. package/src/components/editor/modules/ai/index.ts +281 -0
  61. package/src/components/editor/modules/ai/panel/ai-panel.component.ts +102 -0
  62. package/src/components/editor/modules/ai/panel/ai-panel.scss +83 -0
  63. package/src/components/editor/modules/ai/tooltip/ai-tooltip.component.ts +30 -0
  64. package/src/components/editor/modules/ai/tooltip/ai-tooltip.scss +28 -0
  65. package/src/components/editor/modules/{attachment-module.ts → attachment/attachment.ts} +7 -7
  66. package/src/components/editor/modules/context-menu/context-menu-component.ts +115 -0
  67. package/src/components/editor/modules/context-menu/context-menu.scss +49 -0
  68. package/src/components/editor/modules/context-menu/context-menu.ts +296 -0
  69. package/src/components/editor/modules/context-menu/quick-action/index.ts +12 -0
  70. package/src/components/editor/modules/context-menu/quick-action/quick-action.component.ts +18 -0
  71. package/src/components/editor/modules/date-picker/date-picker.ts +77 -0
  72. package/src/components/editor/modules/dialog/dialog.component.ts +115 -0
  73. package/src/components/editor/modules/dialog/dialog.scss +43 -0
  74. package/src/components/editor/modules/dialog/dialog.ts +46 -0
  75. package/src/components/editor/modules/{drag-drop-module.ts → drag-drop/drag-drop.ts} +6 -6
  76. package/src/components/editor/modules/emoji/emoji.ts +106 -0
  77. package/src/components/editor/modules/emoji/headless/headless-emoji.component.ts +122 -0
  78. package/src/components/editor/modules/emoji/headless/headless-emoji.scss +52 -0
  79. package/src/components/editor/modules/emoji/headless/headless-emoji.ts +223 -0
  80. package/src/components/editor/modules/{image-resize-module/image-resize-module.ts → image-resize/image-resize.ts} +5 -5
  81. package/src/components/editor/modules/{time-tracking-module.ts → time-tracking/time-tracking.ts} +5 -5
  82. package/src/components/editor/modules/toolbar/tool/index.ts +12 -0
  83. package/src/components/editor/modules/toolbar/tool/tool.component.ts +30 -0
  84. package/src/components/editor/modules/toolbar/tool/tool.scss +5 -0
  85. package/src/components/editor/modules/toolbar/toolbar.component.ts +738 -0
  86. package/src/components/editor/modules/toolbar/toolbar.scss +98 -0
  87. package/src/components/editor/modules/toolbar/toolbar.ts +328 -0
  88. package/src/components/expanding-action/expanding-action.component.ts +2 -2
  89. package/src/components/expanding-action/expanding-action.scss +2 -2
  90. package/src/components/filter-wrapper/filter-wrapper.component.ts +91 -0
  91. package/src/components/filter-wrapper/filter-wrapper.scss +11 -0
  92. package/src/components/filter-wrapper/filter-wrapper.test.ts +10 -0
  93. package/src/components/filter-wrapper/index.ts +12 -0
  94. package/src/components/header/header.component.ts +1 -1
  95. package/src/components/header/header.scss +8 -1
  96. package/src/components/hover-container/hover-container.scss +39 -36
  97. package/src/components/icon/icon.component.ts +11 -5
  98. package/src/components/icon/icon.scss +15 -2
  99. package/src/components/inline-edit/inline-edit.component.ts +28 -3
  100. package/src/components/inline-edit/inline-edit.scss +4 -1
  101. package/src/components/input/input.scss +1 -1
  102. package/src/components/item/item.component.ts +30 -19
  103. package/src/components/item/item.scss +56 -14
  104. package/src/components/linked-select/linked-select.component.ts +5 -1
  105. package/src/components/menu/menu.component.ts +1 -0
  106. package/src/components/menu/menu.scss +1 -1
  107. package/src/components/menu-item/menu-item.component.ts +22 -4
  108. package/src/components/menu-item/menu-item.scss +54 -7
  109. package/src/components/navbar/navbar.component.ts +7 -1
  110. package/src/components/navbar/navbar.scss +26 -16
  111. package/src/components/option/option.component.ts +4 -1
  112. package/src/components/option/option.scss +9 -0
  113. package/src/components/page-nav/page-nav.component.ts +2 -2
  114. package/src/components/page-nav/page-nav.scss +4 -4
  115. package/src/components/panel/panel.component.ts +20 -9
  116. package/src/components/panel/panel.scss +5 -1
  117. package/src/components/popup/popup.component.ts +16 -2
  118. package/src/components/progress-tile/progress-tile.component.ts +10 -4
  119. package/src/components/progress-tile/progress-tile.scss +7 -4
  120. package/src/components/query-builder/query-builder.component.ts +1 -1
  121. package/src/components/radio/radio.scss +1 -1
  122. package/src/components/scroll-container/scroll-container.component.ts +56 -9
  123. package/src/components/scroll-container/scroll-container.scss +37 -2
  124. package/src/components/select/select.component.ts +3 -0
  125. package/src/components/select/select.scss +1 -1
  126. package/src/components/settings-container/index.ts +12 -0
  127. package/src/components/settings-container/settings-container.component.ts +226 -0
  128. package/src/components/settings-container/settings-container.scss +92 -0
  129. package/src/components/settings-container/settings-container.test.ts +10 -0
  130. package/src/components/skeleton/skeleton.scss +5 -4
  131. package/src/components/sp/sp.component.ts +2 -0
  132. package/src/components/sp/sp.scss +2 -0
  133. package/src/components/split-button/split-button.component.ts +24 -18
  134. package/src/components/split-pane/split-pane.component.ts +5 -5
  135. package/src/components/stat/stat.component.ts +5 -1
  136. package/src/components/stat/stat.scss +24 -1
  137. package/src/components/style/style.component.ts +18 -0
  138. package/src/components/tabs/tabs.component.ts +12 -1
  139. package/src/components/textarea/textarea.component.ts +3 -1
  140. package/src/components/textarea/textarea.scss +21 -2
  141. package/src/components/vertical-stepper/vertical-stepper.scss +1 -1
  142. package/src/internal/form.ts +3 -1
  143. package/src/internal/slot.ts +29 -3
  144. package/src/utilities/on.ts +7 -8
  145. package/src/utilities/query.ts +5 -1
  146. package/src/wc.scss +7 -0
  147. package/src/zinc.ts +5 -0
  148. package/src/components/editor/modules/dialog-module/dialog-module.component.ts +0 -289
  149. package/src/components/editor/modules/dialog-module/dialog-module.scss +0 -115
  150. package/src/components/editor/modules/dialog-module/dialog-module.ts +0 -99
  151. package/src/components/editor/modules/events/zn-show-canned-response-dialog.ts +0 -15
  152. package/src/components/editor/modules/menu-module/menu-module.component.ts +0 -149
  153. package/src/components/editor/modules/menu-module/menu-module.scss +0 -17
  154. package/src/components/editor/modules/menu-module/menu-module.ts +0 -203
@@ -19,20 +19,46 @@ import ZnHoverContainer from "../hover-container";
19
19
  import ZnMenu from "../menu";
20
20
  import ZnMenuItem from "../menu-item";
21
21
  import ZnSkeleton from "../skeleton";
22
+
23
+ import type ZnDataSelect from "../data-select";
24
+ import type ZnInput from "../input";
25
+ import type ZnQueryBuilder from "../query-builder";
22
26
  import type ZnSelect from "../select";
23
27
 
24
28
  import styles from './data-table.scss';
25
29
 
26
30
  const DEFAULT_PAGE = 1;
27
31
  const DEFAULT_PER_PAGE = 10;
28
- const DEFAULT_TOTAL_PAGES = 10;
29
32
 
30
- interface TableData {
31
- page: number;
32
- per_page: number;
33
+ interface Cell {
34
+ text: string;
35
+ column: string;
36
+ color?: string;
37
+ style?: string;
38
+ iconSrc?: string;
39
+ iconColor?: string;
40
+ hoverContent?: string;
41
+ hoverPlacement?: string;
42
+ chipColor?: string;
43
+ gaid?: string;
44
+ sortValue?: string;
45
+ uri?: string;
46
+ target?: string;
47
+ }
48
+
49
+ interface Row {
50
+ id: string;
51
+ uri?: string;
52
+ target?: string;
53
+ actions?: ActionConfig[];
54
+ cells: Cell[];
55
+ }
56
+
57
+ interface Response {
58
+ rows: Row[];
59
+ perPage: number;
33
60
  total: number;
34
- total_pages: number;
35
- data: any[];
61
+ page: number;
36
62
  }
37
63
 
38
64
  export enum ActionSlots {
@@ -40,101 +66,48 @@ export enum ActionSlots {
40
66
  modify = 'modify-action',
41
67
  create = 'create-action',
42
68
  filter = 'filter',
69
+ filter_top = 'filter-top',
43
70
  sort = 'sort',
71
+ inputs = 'inputs'
44
72
  }
45
73
 
46
- interface RowData {
47
- [key: string]: string | number | object | undefined;
48
- }
49
-
50
- interface RenderDataValue {
51
- value: string | number;
52
- type: string;
53
- tag: string;
54
- url: string;
55
- target: string;
56
- gaid?: string;
57
- html?: string;
58
- color?: string;
59
- caption: CaptionConfig;
60
- buttons: ButtonConfig[];
61
- menu: MenuConfig[];
62
- icon: IconConfig;
63
- hover: HoverContainerConfig;
64
- }
65
-
66
- interface CaptionConfig {
67
- title: string;
68
- summary: string;
74
+ interface ActionConfig {
75
+ text: string;
69
76
  uri: string;
70
77
  target: string;
71
78
  gaid: string;
72
- icon: IconConfig;
73
- }
74
-
75
- interface IconConfig {
76
- src: string;
77
- size: number;
78
- color: string;
79
- }
80
-
81
- interface HoverContainerConfig {
82
- label: string;
83
- content: string;
84
- anchor: string;
85
- placement:
86
- | 'top'
87
- | 'top-start'
88
- | 'top-end'
89
- | 'bottom'
90
- | 'bottom-start'
91
- | 'bottom-end'
92
- | 'right'
93
- | 'right-start'
94
- | 'right-end'
95
- | 'left'
96
- | 'left-start'
97
- | 'left-end';
98
- icon: IconConfig;
99
- }
100
-
101
- interface ButtonConfig {
102
- id: string;
103
- href: string;
104
- gaid: string;
105
- uri: string;
106
- size: string;
107
- color: string;
79
+ confirmType: string;
80
+ confirmTitle: string;
81
+ confirmContent: string;
108
82
  icon: string;
109
- iconSize: string;
110
- tooltip: string;
111
- target: string;
112
- label: string;
113
- outline: boolean;
114
- confirm: ConfirmConfig;
83
+ type: string;
115
84
  }
116
85
 
117
- interface MenuConfig {
118
- id: string;
119
- href: string;
120
- gaid: string;
121
- target: string;
86
+ interface HeaderConfig {
87
+ key: string;
122
88
  label: string;
89
+ required?: boolean;
90
+ default?: boolean;
91
+ sortable?: boolean;
92
+ filterable?: boolean;
123
93
  }
124
94
 
125
- interface ConfirmConfig {
126
- type: string;
127
- trigger: string;
128
- fid: string;
129
- caption: string;
130
- content: string;
131
- action: string;
95
+ interface DataRequest {
96
+ page: number;
97
+ perPage: number;
98
+ sortColumn: string;
99
+ sortDirection: string;
100
+ filter: string;
132
101
  }
133
102
 
134
- interface HeaderConfig {
135
- title: string;
136
- position: string;
137
- }
103
+ type AllowedInputElement =
104
+ HTMLInputElement
105
+ | HTMLSelectElement
106
+ | HTMLTextAreaElement
107
+ | ZnInput
108
+ | ZnSelect
109
+ | ZnDataSelect
110
+ | ZnQueryBuilder
138
111
 
139
112
  /**
140
113
  * @summary Short summary of the component's intended use.
@@ -180,14 +153,11 @@ export default class ZnDataTable extends ZincElement {
180
153
  @property({attribute: 'data-uri'}) dataUri: string;
181
154
  @property({attribute: 'data', type: Object, reflect: true}) data: any;
182
155
  @property({attribute: 'sort-column'}) sortColumn: string;
183
- @property({attribute: 'sort-direction'}) sortDirection: string;
156
+ @property({attribute: 'sort-direction'}) sortDirection: string = "asc";
184
157
  @property({attribute: 'filter'}) filter: string = '';
185
-
186
158
  @property({attribute: 'wide-column'}) wideColumn: string;
187
159
  @property({attribute: 'key'}) key: string = 'id';
188
-
189
- @property({attribute: 'headers', type: Object})
190
- headers: Record<string, string | HeaderConfig> = {};
160
+ @property({attribute: 'headers', type: Object}) headers: Record<string, HeaderConfig> = {};
191
161
 
192
162
  // Hide header text keeping the content - e.g. Action buttons without a header
193
163
  @property({attribute: 'hide-headers', type: Object}) hiddenHeaders = '{}';
@@ -208,10 +178,19 @@ export default class ZnDataTable extends ZincElement {
208
178
 
209
179
  @property() caption: string;
210
180
 
181
+ @property({attribute: "empty-state-caption"}) emptyStateCaption: string;
182
+
183
+ @property({attribute: "empty-state-icon"}) emptyStateIcon: string = "data_alert";
184
+
211
185
  // Hide the checkbox column
212
186
  @property({attribute: 'hide-checkboxes', type: Boolean}) hideCheckboxes: boolean;
187
+
213
188
  @property() filters: [] = [];
214
189
 
190
+ @property() method: 'GET' | 'POST' = 'POST';
191
+
192
+ @property({attribute: "no-initial-load", type: Boolean}) noInitialLoad: boolean = false;
193
+
215
194
  // Data Table Properties
216
195
  private _initialLoad = true;
217
196
  private _lastTableContent: TemplateResult = html``;
@@ -223,7 +202,6 @@ export default class ZnDataTable extends ZincElement {
223
202
  private totalPages: number;
224
203
 
225
204
  private _rows: any[] = [];
226
- private _filteredRows: any[] = [];
227
205
 
228
206
  private numberOfRowsSelected: number = 0;
229
207
  private selectedRows: any[] = [];
@@ -237,94 +215,123 @@ export default class ZnDataTable extends ZincElement {
237
215
  ActionSlots.modify.valueOf(),
238
216
  ActionSlots.create.valueOf(),
239
217
  ActionSlots.filter.valueOf(),
240
- ActionSlots.sort.valueOf()
218
+ ActionSlots.sort.valueOf(),
219
+ ActionSlots.inputs.valueOf()
241
220
  );
242
221
 
243
222
  private _dataTask = new Task(this, {
244
- task: async ([dataUri], {signal}) => {
245
- let url = dataUri;
246
-
247
- const params = new URLSearchParams();
248
- params.append('page', this.page.toString());
249
- params.append('per_page', this.itemsPerPage.toString());
250
- if (this.sortColumn) {
251
- params.append('sort_column', this.sortColumn);
252
- }
253
- if (this.sortDirection) {
254
- params.append('sort_direction', this.sortDirection);
223
+ task: async ([dataUri, requestParams], {signal}) => {
224
+ if (dataUri === undefined || this.noInitialLoad && this._initialLoad) {
225
+ return {rows: [], page: 1, perPage: this.itemsPerPage, total: 0};
255
226
  }
256
- if (this.filter) {
257
- params.append('filter', this.filter);
258
- }
259
- url += '?' + params.toString();
260
-
261
- if (!this.dataUri) {
262
- return new Promise<TableData>((resolve) => {
263
- if (!this.data) throw new Error('No data provided, please provide data or dataUri');
264
- let localData = Array.isArray(this.data?.data) ? this.data.data as RowData[] : [];
265
-
266
- if (this.sortColumn) {
267
- localData = localData.sort((a, b) => {
268
- const aValue = a?.[this.sortColumn];
269
- const bValue = b?.[this.sortColumn];
270
- return this.sortData(aValue, bValue);
271
- });
272
- }
273
227
 
274
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
275
- const totalPages = Math.ceil(localData.length / (this.data.per_page as number ?? this.itemsPerPage));
276
- const start = (this.page - 1) * this.itemsPerPage;
277
- const end = start + this.itemsPerPage;
278
- const paginatedData = localData.slice(start, end);
279
-
280
- resolve({
281
- page: this.page,
282
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
283
- per_page: this.data.per_page as number ?? this.itemsPerPage,
284
- total: localData.length,
285
- total_pages: totalPages,
286
- data: paginatedData,
287
- } satisfies TableData);
228
+ const requestData: DataRequest = {
229
+ page: this.page,
230
+ perPage: this.itemsPerPage,
231
+ sortColumn: this.sortColumn,
232
+ sortDirection: this.sortDirection,
233
+ filter: this.filter,
234
+ };
235
+
236
+ // get all inputs that are in the inputs slot and add them to the
237
+ const inputs = this.hasSlotController.getSlots(ActionSlots.inputs.valueOf());
238
+ const params: Record<string, any> = {};
239
+ if (inputs) {
240
+ inputs.forEach((input) => {
241
+ const allowedInputs = ['zn-input', 'zn-select', 'zn-query-builder', 'zn-multiselect', 'zn-params-select', 'input', 'select', 'textarea'];
242
+ if (allowedInputs.includes(input.tagName.toLowerCase())) {
243
+ const value = (input as AllowedInputElement).value as string || input.getAttribute('value');
244
+ const name = (input as AllowedInputElement).name || input.getAttribute('name');
245
+ if (name) {
246
+ params[name] = value;
247
+ }
248
+ }
288
249
  });
250
+ Object.assign(requestData, params);
251
+ }
252
+
253
+
254
+ // Add any extra request params
255
+ if (requestParams && typeof requestParams === 'object') {
256
+ Object.assign(requestData, requestParams);
289
257
  }
290
258
 
291
259
  // This is also used for Rubix, so it may not work for your application.
292
- const response = await fetch(url, {
260
+ const response = await fetch(dataUri, {
261
+ method: this.method,
262
+ headers: {
263
+ 'x-kx-fetch-style': 'zn-data-table',
264
+ },
293
265
  signal,
294
266
  credentials: 'same-origin',
267
+ body: this.method === 'POST' ? JSON.stringify(requestData) : undefined
295
268
  });
296
269
 
297
270
  if (!response.ok) throw new Error(response.statusText);
298
271
  return response.json();
299
272
  },
300
- args: () => [this.dataUri]
273
+ args: () => [this.dataUri, this.requestParams]
301
274
  });
302
275
 
276
+ private rowHasActions: boolean = false;
277
+
278
+ requestParams: Record<string, any> = {};
279
+
280
+ builtinRequestParams: Record<string, any> = {};
281
+
282
+ refresh() {
283
+ // Allow manual refresh to trigger the first data load when no-initial-load is set
284
+ this._initialLoad = false;
285
+ this._dataTask.run().then(r => r);
286
+ }
287
+
303
288
  render() {
304
- const tableBody = this._dataTask.render({
305
- pending: () => {
306
- if (this._initialLoad) {
289
+ // If no-initial-load is set, do not invoke the Task on the first render
290
+ let tableBody: TemplateResult;
291
+ if (this.noInitialLoad && this._initialLoad) {
292
+ tableBody = html`
293
+ <slot name="empty-state"></slot>`;
294
+ } else {
295
+ tableBody = this._dataTask.render({
296
+ pending: () => {
297
+ if (this._initialLoad) {
298
+ return html`
299
+ <div>${this.loadingTable()}</div>`;
300
+ }
301
+ return html`
302
+ <div class="reduced-opacity">${this._lastTableContent}</div>`;
303
+ },
304
+ complete: (data) => {
305
+ this._initialLoad = false;
306
+ this._lastTableContent = html`
307
+ <div>${this.renderTable(data as Response)}</div>`;
308
+ return this._lastTableContent;
309
+ },
310
+ error: (error) => {
311
+ if (error instanceof Error) {
312
+ if (error.name === "SyntaxError") {
313
+ console.debug(error.message)
314
+ return html`
315
+ <zn-sp>
316
+ <zn-alert level="error">Unable to load data</zn-alert>
317
+ </zn-sp>`;
318
+ } else if (error.message === "Not Found") {
319
+ return this.emptyState();
320
+ }
321
+ }
307
322
  return html`
308
- <div>${this.loadingTable()}</div>`;
323
+ <div>${error}</div>`
309
324
  }
310
- return html`
311
- <div class="reduced-opacity">${this._lastTableContent}</div>`;
312
- },
313
- complete: (data) => {
314
- this._initialLoad = false;
315
- this._lastTableContent = html`
316
- <div>${this.renderTable(data as TableData)}</div>`;
317
- return this._lastTableContent;
318
- },
319
- error: (error) => html`
320
- <div>${error}</div>`
321
- }) as TemplateResult;
325
+ }) as TemplateResult;
326
+ }
322
327
 
323
328
  const hasActions = this.hasSlotController.test(ActionSlots.delete.valueOf())
324
329
  || this.hasSlotController.test(ActionSlots.modify.valueOf())
325
330
  || this.hasSlotController.test(ActionSlots.create.valueOf())
326
331
  || this.hasSlotController.test(ActionSlots.sort.valueOf())
327
- || this.hasSlotController.test(ActionSlots.filter.valueOf());
332
+ || this.hasSlotController.test(ActionSlots.filter.valueOf())
333
+ || this.hasSlotController.test(ActionSlots.filter_top.valueOf())
334
+ || this.hasSlotController.test(ActionSlots.inputs.valueOf());
328
335
 
329
336
  // Headers do not need to be re-rendered with new data
330
337
  return html`
@@ -362,28 +369,50 @@ export default class ZnDataTable extends ZincElement {
362
369
  }
363
370
  }
364
371
 
365
- renderTable(data: TableData) {
366
- this.itemsPerPage = data.per_page ?? DEFAULT_PER_PAGE;
367
- this.page = data.page ?? DEFAULT_PAGE;
368
- this.totalPages = data.total_pages ?? DEFAULT_TOTAL_PAGES;
372
+ emptyState() {
373
+ return html`
374
+ <div class="table--empty">
375
+ <zn-empty-state
376
+ caption="${this.emptyStateCaption ? this.emptyStateCaption : (this.caption ? "No " + this.caption.toLowerCase() + " found" : "No data found")}"
377
+ icon="${this.emptyStateIcon}">
378
+ </zn-empty-state>
379
+ </div>`;
380
+ }
369
381
 
370
- if (!data?.data || data.data.length === 0) {
371
- return html`
372
- <div class="table--empty">
373
- <zn-empty-state
374
- caption="${this.caption ? "No " + this.caption.toLowerCase() + " found" : "No data found"}"
375
- icon="data_alert">
376
- </zn-empty-state>
377
- </div>`;
382
+ renderTable(data: Response) {
383
+ this.itemsPerPage = Math.max(1, data.perPage ?? DEFAULT_PER_PAGE);
384
+ this.page = Math.max(1, data.page ?? DEFAULT_PAGE);
385
+ this.totalPages = Math.ceil(Math.max(1, data.total) / this.itemsPerPage);
386
+
387
+ if (!data?.rows || data.rows.length === 0) {
388
+ return this.emptyState();
378
389
  }
379
390
 
380
- const keys = Object.entries(this.headers).map(([key, _]) => key);
381
- const filteredKeys = keys.filter((key) => !Object.values(this.hiddenColumns).includes(key));
391
+ const filteredHeaders = Object.values(this.headers).filter((header) => {
392
+ return !Object.values(this.hiddenColumns).includes(header.key);
393
+ });
382
394
 
383
- this._filteredRows = this.getRows(filteredKeys, data);
384
- this._rows = this.getRows(keys, data);
395
+ this._rows = this.getRows(data);
385
396
 
386
397
  const hasSelectedRows = this.selectedRows.length > 0;
398
+ this.rowHasActions = this._rows.some((row: Row) => row.actions && row.actions.length > 0);
399
+
400
+ // To make sure the cells are in the same order as the headers, we need to map them cell.col to heading.key
401
+ this._rows.forEach((row: Row) => {
402
+ const orderedCells: Cell[] = [];
403
+ // const unorderedCells = row.cells.filter((c: Cell) => !filteredHeaders.some((h: HeaderConfig) => h.key === c.column));
404
+
405
+ filteredHeaders.forEach((header: HeaderConfig) => {
406
+ const cell = row.cells.find((c: Cell) => c.column === header.key);
407
+ if (cell) {
408
+ orderedCells.push(cell);
409
+ } else {
410
+ orderedCells.push({text: '', column: header.key});
411
+ }
412
+ });
413
+
414
+ row.cells = orderedCells;//.concat(unorderedCells);
415
+ });
387
416
 
388
417
  return html`
389
418
  <div style="overflow-x: auto">
@@ -399,17 +428,20 @@ export default class ZnDataTable extends ZincElement {
399
428
  <th>
400
429
  <div><input type="checkbox" @change="${this.selectAll}"></div>
401
430
  </th>`}
402
- ${filteredKeys.map((key: any) => this.renderCellHeader(key))}
431
+ ${filteredHeaders.map((header: HeaderConfig) => this.renderCellHeader(header))}
432
+ ${this.rowHasActions ? html`
433
+ <th></th>` : html``}
403
434
  </tr>
404
435
  </thead>
405
436
  <tbody>
406
- ${this._filteredRows.map((row: any) => html`
437
+ ${this._rows.map((row: Row) => html`
407
438
  <tr>
408
439
  ${this.hideCheckboxes ? html`` : html`
409
440
  <td class="${classMap({'hidden': !hasSelectedRows})}">
410
441
  <div><input type="checkbox" @change="${this.selectRow}"></div>
411
442
  </td>`}
412
- ${row.map((value: RenderDataValue, index: number) => this.renderCellBody(index, value))}
443
+ ${row.cells.map((value: Cell, index: number) => this.renderCellBody(index, value))}
444
+ ${this.rowHasActions ? this.renderActions(row) : null}
413
445
  </tr>`)}
414
446
  </tbody>
415
447
  </table>
@@ -421,6 +453,8 @@ export default class ZnDataTable extends ZincElement {
421
453
 
422
454
  getTableHeader() {
423
455
  return html`
456
+ <slot name="${ActionSlots.filter_top.valueOf()}"></slot>
457
+ <slot name="${ActionSlots.inputs.valueOf()}" style="display: none"></slot>
424
458
  <div class="table__header">
425
459
  <div class="table__header__actions">
426
460
  ${this.getActions()}
@@ -460,7 +494,7 @@ export default class ZnDataTable extends ZincElement {
460
494
  }
461
495
 
462
496
  getPagination() {
463
- if (this.hidePagination || (this.totalPages <= 1 && this._rows.length <= 10)) return null;
497
+ if (this.hidePagination || (this.totalPages <= 1 && this._rows.length <= this.itemsPerPage)) return null;
464
498
 
465
499
  const optionsRowsPerPage = [10, 20, 30, 40, 50];
466
500
  optionsRowsPerPage.filter((option) => option <= this._rows.length);
@@ -597,22 +631,6 @@ export default class ZnDataTable extends ZincElement {
597
631
  this.requestUpdate();
598
632
  }
599
633
 
600
-
601
- private updateActionKeys(slotName: string) {
602
- if (this.hasSlotController.test(slotName)) {
603
- // we need to look into the slot controller for the keys input
604
- const slots = this.hasSlotController.getSlots(slotName);
605
- if (slots) {
606
- slots.forEach((slot) => {
607
- const input = slot.querySelector('input[name="keys"]');
608
- if (input && input instanceof HTMLInputElement) {
609
- input.value = this.getSelectedKeys().join(',');
610
- }
611
- });
612
- }
613
- }
614
- }
615
-
616
634
  selectRow(e: PointerEvent) {
617
635
  if (!(e.target && (e.target instanceof Element))) {
618
636
  return;
@@ -670,181 +688,61 @@ export default class ZnDataTable extends ZincElement {
670
688
  };
671
689
  }
672
690
 
673
- renderData(data: RenderDataValue) {
691
+ renderCell(data: Cell) {
674
692
  if (data && typeof data === 'object') {
675
- let content: TemplateResult = html`${data['value']}`;
676
-
677
- if (data['html']) {
678
- return html`${unsafeHTML(data['html'])}`;
679
- }
680
-
681
- if (data['type'] === 'timestamp') {
682
- const timestamp = typeof data['value'] === 'string' ? parseInt(data['value']) : data['value'];
683
- content = html`${new Intl.DateTimeFormat('en-GB', {
684
- dateStyle: 'medium',
685
- timeStyle: 'short',
686
- hourCycle: 'h12',
687
- timeZone: 'UTC'
688
- }).format(new Date(timestamp < 10000000000 ? timestamp * 1000 : timestamp))}`;
689
- }
690
-
691
- if (data['tag'] === 'zn-chip') {
692
- const type = data['type'];
693
- content = html`
694
- <zn-chip type="${type}">${content}</zn-chip>`;
695
- }
693
+ let content: TemplateResult = html`${data.text}`;
696
694
 
697
- if (data['tag'] === 'code') {
695
+ if (data.style || data.color) {
698
696
  content = html`
699
- <code>${content}</code>`;
697
+ <zn-style ${ifDefined(data.style || nothing)}
698
+ color="${ifDefined(data.color || nothing)}">${content}
699
+ </zn-style>`;
700
700
  }
701
701
 
702
- if (data['url']) {
702
+ if (data.uri) {
703
703
  content = html`
704
- <a href="${data['url']}"
705
- data-target="${ifDefined(data['target'])}"
706
- gaid="${ifDefined(data['gaid'])}">${content}</a>`;
704
+ <a href="${data.uri}"
705
+ data-target="${ifDefined(data.target || nothing)}"
706
+ gaid="${ifDefined(data.gaid || nothing)}">${content}</a>`;
707
707
  }
708
708
 
709
- if (data['caption']) {
710
- let title = html`<span class="title">${data['caption'].title}</span>`;
711
- let captionIcon = html``;
712
-
713
- if (data['caption'].icon) {
714
- const icon = data['caption'].icon;
715
- const src = icon['src'] ?? icon;
716
- const size = icon['size'] ?? 16;
717
- const color = icon['color'] ?? '';
718
-
719
- captionIcon = html`
720
- <zn-icon src="${src}" size="${size}" color="${color}"></zn-icon>`;
721
- }
722
-
723
- if (data['caption'].uri) {
724
- title = html` <a data-target="${ifDefined(data['caption'].target || nothing)}"
725
- href="${data['caption'].uri}"
726
- gaid="${ifDefined(data['caption'].gaid)}"
727
- class="title">
728
- ${data['caption'].title}
729
- </a>`
730
- }
731
-
732
- content = html`
733
- <div class="${classMap({
734
- 'caption': true,
735
- 'caption--icon': data['caption'].icon !== undefined,
736
- })}">
737
- ${captionIcon}
738
- <div>
739
- ${title}
740
- <span class="summary">${data['caption'].summary}</span>
741
- </div>
742
- </div>`;
709
+ if (data.chipColor) {
710
+ return html`
711
+ <zn-chip type="${data.chipColor}">${content}</zn-chip>`;
743
712
  }
744
713
 
745
- if (data['hover']) {
746
- const placement = data['hover'].placement ?? 'top';
714
+ if (data.hoverContent) {
715
+ const placement = data.hoverPlacement ?? 'top';
747
716
 
748
- if (data['hover'].icon) {
749
- const icon = data['hover'].icon;
750
- const src = icon['src'] ?? icon;
751
- const size = icon['size'] ?? 16;
752
- const color = icon['color'] ?? '';
717
+ if (data.iconSrc) {
718
+ const src = data.iconSrc;
719
+ const color = data.iconColor ?? '';
753
720
 
754
721
  return html`
755
- ${data['hover'].label}
722
+ ${content}
756
723
  <zn-hover-container placement="${placement}"
757
724
  flip>
758
- <zn-icon src="${src}" size="${size}" color="${color}"></zn-icon>
725
+ <zn-icon src="${src}" color="${color}"></zn-icon>
759
726
  <div slot="content">
760
- ${unsafeHTML(data['hover'].content)}
727
+ ${unsafeHTML(data.hoverContent)}
761
728
  </div>
762
729
  </zn-hover-container>`;
763
730
  }
764
731
 
765
732
  return html`
766
- ${data['hover'].label}
767
733
  <zn-hover-container placement="${placement}"
768
734
  flip>
769
- <div slot="anchor">${data['hover'].anchor}</div>
770
- ${unsafeHTML(data['hover'].content)}
735
+ <div slot="anchor">${content}</div>
736
+ ${unsafeHTML(data.hoverContent)}
771
737
  </zn-hover-container>`;
772
738
  }
773
739
 
774
- if (data['menu']) {
775
- return html`
776
- <zn-dropdown>
777
- <zn-button slot="trigger" icon="more_horiz" icon-size="24" color="transparent" size="content"></zn-button>
778
- <zn-menu>
779
- ${data['menu'].map((item: MenuConfig) => {
780
- return html`
781
- <zn-menu-item id="${item.id}"
782
- href="${item.href}"
783
- gaid="${item.gaid}"
784
- data-target="${ifDefined(item.target)}">
785
- ${item.label}
786
- </zn-menu-item>`;
787
- })}
788
- </zn-menu>
789
- </zn-dropdown>`
790
- }
791
-
792
- if (data['buttons']) {
793
- content = html`
794
- ${Object.values(data['buttons']).map((button) => {
795
- if (button.confirm) {
796
- return html`
797
- <div>
798
- <zn-button
799
- id="${button.id}"
800
- size="${button.size}"
801
- color="${button.color}"
802
- icon="${button.icon}"
803
- icon-size="${button.iconSize}"
804
- tooltip="${button.tooltip}"
805
- outline="${ifDefined(button.outline)}">
806
- ${button.label || nothing}
807
- </zn-button>
808
- <zn-confirm
809
- fid="${button.confirm.fid}"
810
- trigger="${button.confirm.trigger}"
811
- type="${button.confirm.type}"
812
- caption="${button.confirm.caption}"
813
- content="${button.confirm.content}"
814
- action="${button.confirm.action}"></zn-confirm>
815
- </div>`;
816
- }
817
- return html`
818
- <zn-button
819
- id="${button.id}"
820
- href="${button.href}"
821
- gaid="${button.gaid}"
822
- size="${button.size}"
823
- color="${button.color}"
824
- icon="${button.icon}"
825
- icon-size="${button.iconSize}"
826
- tooltip="${button.tooltip}"
827
- data-uri="${button.uri}"
828
- data-target="${['modal', 'slide'].includes(button.target) ? button.target : nothing}"
829
- target="${!['modal', 'slide'].includes(button.target) ? button.target : nothing}"
830
- outline="${ifDefined(button.outline)}">
831
- ${button.label || nothing}
832
- </zn-button>`;
833
- })}`;
834
- }
740
+ if (data.iconSrc) {
741
+ const src = data.iconSrc;
742
+ const color = data.iconColor ?? '';
835
743
 
836
- if (data['icon']) {
837
- const icon = data['icon']?.['src'] ?? data['icon'];
838
- const size = data['icon']?.['size'] ?? 16;
839
- const color = data['icon']?.['color'] ?? '';
840
-
841
- content = html`
842
- <zn-icon src="${icon}" size="${size}" color="${color}"></zn-icon> ${content}`;
843
- }
844
-
845
- if (data['color']) {
846
- content = html`
847
- <zn-style color="${data['color']}">${content}</zn-style>`;
744
+ return html`
745
+ <zn-icon src="${src}" color="${color}"></zn-icon> ${content}`;
848
746
  }
849
747
 
850
748
  return content;
@@ -853,6 +751,21 @@ export default class ZnDataTable extends ZincElement {
853
751
  return data;
854
752
  }
855
753
 
754
+ private updateActionKeys(slotName: string) {
755
+ if (this.hasSlotController.test(slotName)) {
756
+ // we need to look into the slot controller for the keys input
757
+ const slots = this.hasSlotController.getSlots(slotName);
758
+ if (slots) {
759
+ slots.forEach((slot) => {
760
+ const input = slot.querySelector('input[name="keys"]');
761
+ if (input && input instanceof HTMLInputElement) {
762
+ input.value = this.getSelectedKeys().join(',');
763
+ }
764
+ });
765
+ }
766
+ }
767
+ }
768
+
856
769
  private getTableSortIcon(key: any) {
857
770
  if (this.sortColumn !== key) {
858
771
  return html`
@@ -874,68 +787,33 @@ export default class ZnDataTable extends ZincElement {
874
787
  </div>`;
875
788
  }
876
789
 
877
- private renderCellHeader(key: string) {
878
- const sortable = !Object.values(this.unsortableHeaders).includes(key) && !Object.values(this.hiddenHeaders).includes(key) && !this.unsortable;
879
- let headerKeys = Object.keys(this.headers);
880
- headerKeys = headerKeys.filter((k) => !Object.values(this.hiddenColumns).includes(k));
881
- const header: string | HeaderConfig = this.headers[key];
882
-
883
- if (this.hasHeaderPosition(header)) {
884
- return html`
885
- <th
886
- class="${classMap({
887
- 'table__head': true,
888
- 'table__head--wide': key === this.wideColumn,
889
- 'table__head--last': key === headerKeys[headerKeys.length - 1],
890
- 'table__head--hidden': Object.values(this.hiddenHeaders).includes(key),
891
- [`table__head--${header.position}`]: true
892
- })}"
893
- @click="${sortable ? this.updateSort(key) : undefined}">
894
- <div>
895
- ${header.title}
896
- ${sortable ? this.getTableSortIcon(key) : nothing}
897
- </div>
898
- </th>
899
- `;
900
- }
790
+ private renderCellHeader(header: HeaderConfig) {
791
+ const sortable = !Object.values(this.unsortableHeaders).includes(header.key) && !Object.values(this.hiddenHeaders).includes(header.key) && !this.unsortable;
792
+ const lastHeader = Object.keys(this.headers).filter((key) => !Object.values(this.hiddenColumns).includes(key)).slice(-1)[0];
793
+ const lastHeaderKey = lastHeader ? this.headers[lastHeader].key : '';
901
794
 
902
795
  return html`
903
796
  <th
904
797
  class="${classMap({
905
798
  'table__head': true,
906
- 'table__head--wide': key === this.wideColumn,
907
- 'table__head--last': key === headerKeys[headerKeys.length - 1],
908
- 'table__head--hidden': Object.values(this.hiddenHeaders).includes(key),
799
+ 'table__head--wide': header.key === this.wideColumn,
800
+ 'table__head--last': header.key === lastHeaderKey,
801
+ 'table__head--hidden': Object.values(this.hiddenHeaders).includes(header.key),
909
802
  })}"
910
- @click="${sortable ? this.updateSort(key) : undefined}">
803
+ @click="${sortable ? this.updateSort(header.key) : undefined}">
911
804
  <div>
912
- ${header}
913
- ${sortable ? this.getTableSortIcon(key) : nothing}
805
+ ${header.label}
806
+ ${sortable ? this.getTableSortIcon(header.key) : nothing}
914
807
  </div>
915
808
  </th>
916
809
  `;
917
810
  }
918
811
 
919
- private renderCellBody(index: number, value: RenderDataValue) {
812
+ private renderCellBody(index: number, value: Cell) {
920
813
  const headerKeys: string[] = Object.keys(this.headers).filter(
921
814
  (key) => !Object.values(this.hiddenColumns).includes(key)
922
815
  );
923
816
  const headerKey: string = headerKeys[index];
924
- const header = this.headers[headerKey];
925
-
926
- if (this.hasHeaderPosition(header)) {
927
- return html`
928
- <td
929
- @click="${this.selectRow}"
930
- class="${classMap({
931
- 'table__cell': true,
932
- 'table__cell--wide': headerKey === this.wideColumn,
933
- 'table__cell--last': headerKey === headerKeys[headerKeys.length - 1],
934
- [`table__cell--${header.position}`]: true
935
- })}">
936
- <div>${this.renderData(value)}</div>
937
- </td>`;
938
- }
939
817
 
940
818
  return html`
941
819
  <td
@@ -945,32 +823,49 @@ export default class ZnDataTable extends ZincElement {
945
823
  'table__cell--wide': headerKey === this.wideColumn,
946
824
  'table__cell--last': headerKey === headerKeys[headerKeys.length - 1],
947
825
  })}">
948
- <div>${this.renderData(value)}</div>
826
+ <div>${this.renderCell(value)}</div>
949
827
  </td>`;
950
828
  }
951
829
 
952
- private hasHeaderPosition(header: string | HeaderConfig) {
953
- return typeof header === 'object' && header !== null && 'position' in header && 'title' in header;
954
- }
830
+ private getRows(data: Response): Row[] {
831
+ const sourceRows = data.rows;
955
832
 
956
- private getRows(keys: string[], data: TableData) {
957
- let rows = Object.values(data.data).map((row: any) => {
958
- return keys.map((header: any) => row[header]);
959
- });
833
+ if (this.sortColumn) {
834
+ const headerKeys = Object.keys(this.headers);
835
+ const sortIndex = headerKeys.indexOf(this.sortColumn);
960
836
 
961
- if (this.dataUri && this.sortColumn) {
962
- // sort the rows by the key
963
- rows = rows.sort((a, b) => {
964
- return this.sortData(a[keys.indexOf(this.sortColumn)], b[keys.indexOf(this.sortColumn)]);
837
+ sourceRows.sort((rowA: Row, rowB: Row) => {
838
+ const getCellForSort = (row: Row): Cell => {
839
+ const byHeading = row.cells.find((cell: Cell) => cell.column === this.sortColumn);
840
+ if (byHeading !== undefined) return byHeading as unknown as Cell;
841
+ return row.cells[sortIndex];
842
+ };
843
+
844
+ const aVal = getCellForSort(rowA);
845
+ const bVal = getCellForSort(rowB);
846
+ return this.sortData(aVal, bVal);
965
847
  });
966
848
  }
967
849
 
968
- return rows;
850
+ return sourceRows;
969
851
  }
970
852
 
971
- private getSelectedKeys() {
853
+ private getSelectedKeys(): (string)[] {
972
854
  const headerKeys = Object.keys(this.headers);
973
- return this.selectedRows.map((row) => row[headerKeys.indexOf(this.key)]);
855
+ const keyIndex = headerKeys.indexOf(this.key);
856
+
857
+ return this.selectedRows
858
+ .map((row: Row) => {
859
+ if (this.key === 'id' || keyIndex === -1) {
860
+ return row.id;
861
+ }
862
+ const cell = row.cells?.[keyIndex] as Cell;
863
+ if (cell.sortValue) {
864
+ return cell.sortValue;
865
+ }
866
+ return cell.text as string;
867
+ })
868
+ .filter((v): v is string => v !== undefined && v !== null);
974
869
  }
975
870
 
976
871
  private updateKeys() {
@@ -986,36 +881,27 @@ export default class ZnDataTable extends ZincElement {
986
881
  this.updateActionKeys('delete-action');
987
882
  }
988
883
 
989
- private sortData(a: RenderDataValue | string | number | object | undefined, b: RenderDataValue | string | number | object | undefined) {
990
- if (a === undefined || b === undefined) {
991
- return 0;
884
+ private extractComparable(cell: Cell): string {
885
+ const sortValue = cell.sortValue;
886
+ if (sortValue) {
887
+ return sortValue;
992
888
  }
993
889
 
994
- if (typeof a === 'object' && 'caption' in a && 'title' in a.caption) {
995
- a = a.caption.title as string;
996
- } else if (typeof a === 'object' && a && 'value' in a) {
997
- a = a.value as string | number;
998
- }
890
+ return cell.text;
891
+ }
999
892
 
1000
- if (typeof b === 'object' && 'caption' in b && 'title' in b.caption) {
1001
- b = b.caption.title as string;
1002
- } else if (typeof b === 'object' && b && 'value' in b) {
1003
- b = b.value as string | number;
1004
- }
893
+ private sortData(a: Cell, b: Cell) {
894
+ const aCompare = this.extractComparable(a);
895
+ const bCompare = this.extractComparable(b);
1005
896
 
1006
- if (typeof a === 'string') {
1007
- a = a.toLowerCase();
1008
- }
1009
-
1010
- if (typeof b === 'string') {
1011
- b = b.toLowerCase();
1012
- }
897
+ const aNormal = aCompare.toLowerCase();
898
+ const bNormal = bCompare.toLowerCase();
1013
899
 
1014
900
  if (this.sortDirection === 'asc') {
1015
- return a > b ? 1 : a < b ? -1 : 0;
901
+ return aNormal > bNormal ? 1 : aNormal < bNormal ? -1 : 0;
1016
902
  }
1017
903
 
1018
- return a < b ? 1 : a > b ? -1 : 0;
904
+ return aNormal < bNormal ? 1 : aNormal > bNormal ? -1 : 0;
1019
905
  }
1020
906
 
1021
907
  private loadingTable() {
@@ -1090,4 +976,57 @@ export default class ZnDataTable extends ZincElement {
1090
976
  </table>
1091
977
  </div>`;
1092
978
  }
979
+
980
+ private renderActions(row: Row) {
981
+ if ((row.actions === null || row.actions === undefined) && this.rowHasActions) {
982
+ // return an empty cell to keep the table structure
983
+ return html`
984
+ <td></td>`;
985
+ }
986
+
987
+ if (!row.actions || row.actions.length === 0) {
988
+ return html``;
989
+ }
990
+
991
+ return html`
992
+ <td class="table__cell table__cell--actions">
993
+ <zn-dropdown placement="bottom-end">
994
+ <zn-button slot="trigger"
995
+ icon="more_vert"
996
+ size="small"
997
+ color="transparent"
998
+ icon-size="24"
999
+ aria-label="Row actions">
1000
+ </zn-button>
1001
+ <zn-menu>
1002
+ ${row.actions?.map((action: ActionConfig) => {
1003
+ if (action.confirmContent) {
1004
+ const triggerId = 'confirm-action-' + Math.random().toString(36).substring(2, 15);
1005
+ return html`
1006
+ <zn-confirm trigger="${triggerId}"
1007
+ type="${ifDefined(action.confirmType)}"
1008
+ caption="${action.confirmTitle}"
1009
+ content="${action.confirmContent}"
1010
+ action="${action.uri}"></zn-confirm>
1011
+ <zn-menu-item id="${triggerId}">
1012
+ ${(action.icon) ? html`
1013
+ <zn-icon src="${action.icon}" size="20" slot="prefix"></zn-icon>` : html``}
1014
+ ${action.text}
1015
+ </zn-menu-item>`;
1016
+ } else {
1017
+ return html`
1018
+ <zn-menu-item value="${action.text}"
1019
+ href="${action.uri}"
1020
+ gaid="${ifDefined(action.gaid || nothing)}"
1021
+ data-target="${ifDefined(action.target || nothing)}">
1022
+ ${(action.icon) ? html`
1023
+ <zn-icon src="${action.icon}" size="20" slot="prefix"></zn-icon>` : html``}
1024
+ ${action.text}
1025
+ </zn-menu-item>`;
1026
+ }
1027
+ })}
1028
+ </zn-menu>
1029
+ </zn-dropdown>
1030
+ </td>`;
1031
+ }
1093
1032
  }