@kubex/zinc 1.0.5 → 1.0.7

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 (29) hide show
  1. package/dist/custom-elements.json +302 -16
  2. package/dist/vscode.html-custom-data.json +13 -2
  3. package/dist/web-types.json +43 -5
  4. package/dist/zn.d.ts +81 -40
  5. package/dist/zn.min.js +333 -270
  6. package/package.json +1 -1
  7. package/src/components/bulk-actions/bulk-actions.component.ts +152 -84
  8. package/src/components/bulk-actions/bulk-actions.scss +2 -0
  9. package/src/components/chart/chart.component.ts +14 -12
  10. package/src/components/cols/cols.component.ts +7 -3
  11. package/src/components/cols/cols.scss +22 -14
  12. package/src/components/content-block/content-block.component.ts +164 -30
  13. package/src/components/data-select/data-select.component.ts +51 -10
  14. package/src/components/data-table/data-table.component.ts +58 -38
  15. package/src/components/data-table/data-table.scss +74 -7
  16. package/src/components/data-table-filter/data-table-filter.component.ts +1 -1
  17. package/src/components/editor/modules/attachment/attachment.ts +53 -41
  18. package/src/components/editor/modules/toolbar/toolbar.ts +42 -0
  19. package/src/components/empty-state/empty-state.component.ts +16 -8
  20. package/src/components/icon/icon.component.ts +2 -0
  21. package/src/components/icon/icon.scss +13 -0
  22. package/src/components/inline-edit/inline-edit.component.ts +24 -3
  23. package/src/components/inline-edit/inline-edit.scss +11 -1
  24. package/src/components/item/item.component.ts +11 -3
  25. package/src/components/item/item.scss +14 -19
  26. package/src/components/panel/panel.component.ts +2 -0
  27. package/src/components/panel/panel.scss +6 -0
  28. package/src/components/settings-container/settings-container.scss +11 -4
  29. package/src/components/style/style.component.ts +7 -1
@@ -1,10 +1,12 @@
1
1
  import {classMap} from "lit/directives/class-map.js";
2
+ import {deepQuerySelectorAll} from "../../utilities/query";
2
3
  import {HasSlotController} from "../../internal/slot";
3
- import {html, unsafeCSS} from 'lit';
4
+ import {html, nothing, unsafeCSS} from 'lit';
4
5
  import {property, queryAssignedNodes, queryAsync} from 'lit/decorators.js';
5
6
  import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
6
7
  import ZincElement from "../../internal/zinc-element";
7
8
  import type {PropertyValues} from 'lit';
9
+ import type ZnTile from "../tile";
8
10
 
9
11
  import styles from './content-block.scss';
10
12
 
@@ -28,6 +30,7 @@ export default class ContentBlock extends ZincElement {
28
30
 
29
31
  @property({type: Boolean, reflect: true}) outbound = false;
30
32
  @property({type: Boolean, reflect: true}) short = false;
33
+ @property({attribute: 'default-display', reflect: true}) defaultDisplay: 'text' | 'html' = 'text';
31
34
 
32
35
  @queryAssignedNodes({slot: 'html', flatten: true}) htmlNodes!: Node[];
33
36
 
@@ -37,6 +40,9 @@ export default class ContentBlock extends ZincElement {
37
40
 
38
41
  private _textRows: TextRow[] = [];
39
42
 
43
+ private _footerObserver?: MutationObserver;
44
+ private _replaceDebounce: number = 0;
45
+
40
46
  connectedCallback() {
41
47
  super.connectedCallback();
42
48
  this.iframe.then((iframe) => {
@@ -49,37 +55,34 @@ export default class ContentBlock extends ZincElement {
49
55
  convertedHtml = convertedHtml.replace(/>/g, '>');
50
56
  convertedHtml = convertedHtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
51
57
 
52
- const baseStyles = "<style>body,html{padding: 0; margin: 0; font-size: 13px; font-family: Arial;}img{max-width:100%; max-height: 500px;}</style>";
58
+ const baseStyles = "<style>body,html{background-color: #ffffff}img{max-width:100%; max-height: 500px;}</style>";
53
59
  iframe.srcdoc = baseStyles + convertedHtml;
54
60
  }
55
61
  }
56
62
 
57
63
  iframe.addEventListener("load", () => {
58
- // Save original styles
59
- const originalDisplay = iframe.style.display;
60
- const originalVisibility = iframe.style.visibility;
61
- const originalPosition = iframe.style.position;
62
-
63
- // Make iframe render but stay hidden
64
- iframe.style.display = 'block';
65
- iframe.style.visibility = 'hidden';
66
- iframe.style.position = 'absolute';
67
-
68
- setTimeout(() => {
69
- const iframeBody = iframe.contentDocument?.body;
70
- if (iframeBody) {
71
- const height = iframeBody.scrollHeight;
72
- iframe.style.height = `${height + 30}px`;
73
- }
74
- // Restore original styles
75
- iframe.style.display = originalDisplay;
76
- iframe.style.visibility = originalVisibility;
77
- iframe.style.position = originalPosition;
78
- }, 50);
64
+ setTimeout(() => this._resizeIframe(iframe), 50);
79
65
  });
80
66
  });
81
67
  }
82
68
 
69
+ disconnectedCallback() {
70
+ try {
71
+ if (this._footerObserver) {
72
+ this._footerObserver.disconnect();
73
+ this._footerObserver = undefined;
74
+ }
75
+ if (this._replaceDebounce) {
76
+ clearTimeout(this._replaceDebounce);
77
+ this._replaceDebounce = 0;
78
+ }
79
+ } finally {
80
+ // Ensure base class cleanup
81
+ // @ts-ignore - base may or may not implement disconnectedCallback
82
+ super.disconnectedCallback?.();
83
+ }
84
+ }
85
+
83
86
  private _collapseContent(e: Event) {
84
87
  const target = e.target as HTMLElement;
85
88
  const headerClick = target.classList.contains('content-block-header') ||
@@ -88,6 +91,15 @@ export default class ContentBlock extends ZincElement {
88
91
  if (headerClick && target.slot !== 'nav') {
89
92
  this.short = !this.short;
90
93
  this.requestUpdate();
94
+ this.updateComplete.then(() => {
95
+ if (!this.short) {
96
+ this.iframe.then((iframe) => {
97
+ if (iframe && !iframe.classList.contains('hidden')) {
98
+ this._resizeIframe(iframe);
99
+ }
100
+ });
101
+ }
102
+ });
91
103
  }
92
104
  }
93
105
 
@@ -107,10 +119,46 @@ export default class ContentBlock extends ZincElement {
107
119
  if (textContent) {
108
120
  iframe.classList.remove('hidden');
109
121
  textContent.classList.add('hidden');
122
+ setTimeout(() => this._resizeIframe(iframe), 0);
110
123
  }
111
124
  });
112
125
  }
113
126
 
127
+ private _resizeIframe(iframe?: HTMLIFrameElement) {
128
+ const resize = (frame: HTMLIFrameElement) => {
129
+ const doc = frame.contentDocument;
130
+ if (!doc) return;
131
+
132
+ // Allow the iframe to shrink as well as grow
133
+ frame.style.height = 'auto';
134
+
135
+ const body = doc.body;
136
+ const element = doc.documentElement;
137
+
138
+ const heights = [
139
+ body?.scrollHeight ?? 0,
140
+ element?.scrollHeight ?? 0,
141
+ body?.offsetHeight ?? 0,
142
+ element?.offsetHeight ?? 0
143
+ ];
144
+ const height = Math.max(...heights);
145
+
146
+ if (height > 0) {
147
+ frame.style.height = `${height}px`;
148
+ }
149
+ };
150
+
151
+ if (iframe) {
152
+ resize(iframe);
153
+ } else {
154
+ this.iframe.then((frame) => {
155
+ if (frame?.contentDocument) {
156
+ requestAnimationFrame(() => resize(frame));
157
+ }
158
+ });
159
+ }
160
+ }
161
+
114
162
  protected firstUpdated(_changedProperties: PropertyValues) {
115
163
  super.firstUpdated(_changedProperties);
116
164
 
@@ -130,12 +178,16 @@ export default class ContentBlock extends ZincElement {
130
178
  const hasTextSlot = this.hasSlotController.test('text');
131
179
  const hasHtmlSlot = this.hasSlotController.test('html');
132
180
  const showActions = hasTextSlot && hasHtmlSlot;
181
+ const initialShowHtml = hasHtmlSlot && (!hasTextSlot || this.defaultDisplay === 'html');
133
182
 
134
183
  return html`
135
- <zn-panel flush tabbed class="${classMap({
136
- 'content-block--outbound': this.outbound,
137
- 'content-block--short': this.short
138
- })}">
184
+ <zn-panel flush
185
+ tabbed
186
+ flush-footer="${hasFooter || nothing}"
187
+ class="${classMap({
188
+ 'content-block--outbound': this.outbound,
189
+ 'content-block--short': this.short
190
+ })}">
139
191
 
140
192
  <zn-header class="content-block-header"
141
193
  caption="${this.sender}"
@@ -167,10 +219,10 @@ export default class ContentBlock extends ZincElement {
167
219
  </zn-header>
168
220
 
169
221
  <zn-sp no-gap>
170
- <iframe class="hidden" title="Content block HTML preview"></iframe>
222
+ <iframe class="${classMap({'hidden': !initialShowHtml})}" title="Content block HTML preview"></iframe>
171
223
  <slot class="html-content" name="html" style="display: none;"></slot>
172
224
  <slot name="text" style="display: none;"></slot>
173
- <div class="text-content">
225
+ <div class="${classMap({'text-content': true, 'hidden': initialShowHtml})}">
174
226
  ${text.map((section) => html`
175
227
  ${section.type === 'reply' ? html`
176
228
  <div class="toggle-reply" @click="${this.showReply}">...</div>` : ''}
@@ -187,7 +239,7 @@ export default class ContentBlock extends ZincElement {
187
239
  </zn-sp>
188
240
 
189
241
  ${hasFooter ? html`
190
- <slot slot="footer" name="footer"></slot>` : ''}
242
+ <slot slot="footer" name="footer" @slotchange="${this._handleSlotChange}"></slot>` : ''}
191
243
  </zn-panel>
192
244
  `;
193
245
  }
@@ -199,6 +251,88 @@ export default class ContentBlock extends ZincElement {
199
251
  return trimmed.length > 32 ? trimmed.substring(0, 32) + '...' : trimmed;
200
252
  }
201
253
 
254
+ private _handleSlotChange(e: Event) {
255
+ const slot = e.target as HTMLSlotElement;
256
+ const assignedEls: Element[] = slot.assignedElements({flatten: true});
257
+ if (!assignedEls || assignedEls.length === 0) return;
258
+
259
+ // Reset and attach a new observer to watch for dynamic content inside the footer subtree
260
+ if (this._footerObserver) {
261
+ this._footerObserver.disconnect();
262
+ this._footerObserver = undefined;
263
+ }
264
+
265
+ const obs = new MutationObserver(() => {
266
+ this._debouncedReplace();
267
+ });
268
+ this._footerObserver = obs;
269
+
270
+ assignedEls.forEach(el => {
271
+ obs.observe(el, {
272
+ subtree: true,
273
+ childList: true,
274
+ attributes: true,
275
+ attributeFilter: ['href', 'download', 'caption']
276
+ });
277
+ });
278
+
279
+ // Run now and again shortly after to catch async rendering
280
+ this._replaceImagePlaceholders();
281
+ setTimeout(() => this._replaceImagePlaceholders(), 50);
282
+ }
283
+
284
+ private _debouncedReplace() {
285
+ if (this._replaceDebounce) {
286
+ clearTimeout(this._replaceDebounce);
287
+ }
288
+ this._replaceDebounce = window.setTimeout(() => this._replaceImagePlaceholders(), 50);
289
+ }
290
+
291
+ private _replaceImagePlaceholders() {
292
+ const textContainer = this.shadowRoot?.querySelector('.text-content') as HTMLDivElement | null;
293
+ if (!textContainer) {
294
+ // If the shadow content hasn't been rendered yet, try after the next update
295
+ this.updateComplete.then(() => this._replaceImagePlaceholders());
296
+ return;
297
+ }
298
+
299
+ const footer = this.querySelector(':scope > [slot="footer"]') as HTMLElement | null;
300
+ if (!footer) return;
301
+
302
+ const anchors = deepQuerySelectorAll('a[href]', footer, '') as HTMLAnchorElement[];
303
+ if (anchors.length === 0) return;
304
+
305
+ let textContent = textContainer.innerHTML;
306
+ const extensions = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'heic', 'tiff', 'tif', 'avif']);
307
+ anchors.forEach((a) => {
308
+ const candidates = new Set<string>();
309
+ const tile = a.querySelector('zn-tile') as ZnTile | null;
310
+
311
+ const caption = (tile?.caption ?? tile?.getAttribute?.('caption') ?? '').trim();
312
+ if (caption) candidates.add(caption);
313
+
314
+ const downloadAttr = (a.getAttribute('download') || '').trim();
315
+ if (downloadAttr) candidates.add(downloadAttr);
316
+
317
+ const url = a.getAttribute('href') || '';
318
+ candidates.forEach((rawName) => {
319
+ const name = (rawName || '').trim();
320
+ if (!name) return;
321
+
322
+ const base = name.includes('/') ? name.split('/').pop()! : name;
323
+ const lowerBase = base.toLowerCase();
324
+ const ext = lowerBase.includes('.') ? lowerBase.split('.').pop()! : '';
325
+ if (ext && !extensions.has(ext)) return;
326
+
327
+ const alt = base.replace(/"/g, '&quot;');
328
+ textContent = textContent.replace(`[image: ${base}]`, () => `<img src="${url}" alt="${alt}" style="max-height: 500px; max-width: 100%">`);
329
+ });
330
+ });
331
+
332
+ // Update the generated content directly without triggering a re-render
333
+ textContainer.innerHTML = textContent;
334
+ }
335
+
202
336
  protected getTextSections(): TextRow[] {
203
337
  const textContent = this.querySelector('[slot="text"]') as HTMLDivElement | null;
204
338
  const textRows: TextRow[] = [];
@@ -7,13 +7,12 @@ import {
7
7
  emptyDataProvider,
8
8
  type LocalDataProvider,
9
9
  } from "./providers/provider";
10
+ import {type CSSResultGroup, html, nothing, unsafeCSS, PropertyValues} from 'lit';
10
11
  import {FormControlController} from "../../internal/form";
11
- import {html, nothing, unsafeCSS} from 'lit';
12
12
  import {property, query} from 'lit/decorators.js';
13
13
  import {watch} from "../../internal/watch";
14
- import ZincElement from '../../internal/zinc-element';
15
- import type {CSSResultGroup} from 'lit';
16
14
  import type {ZincFormControl} from '../../internal/zinc-element';
15
+ import ZincElement from '../../internal/zinc-element';
17
16
  import type ZnSelect from "../select";
18
17
 
19
18
  import styles from './data-select.scss';
@@ -37,15 +36,22 @@ import styles from './data-select.scss';
37
36
  */
38
37
  export default class ZnDataSelect extends ZincElement implements ZincFormControl {
39
38
  static styles: CSSResultGroup = unsafeCSS(styles);
39
+
40
40
  @query('#select') select: ZnSelect;
41
41
  @query('#select__prefix') selectPrefix: HTMLElement;
42
+
42
43
  /** The name of the select. Used for form submission. */
43
44
  @property() name: string;
45
+
44
46
  /** The value of the select. Used for form submission. */
45
47
  @property() value: string;
48
+
46
49
  /** The provider of the select. */
47
50
  @property() provider: 'color' | 'currency' | 'country' | 'phone';
51
+
52
+ /** The position of the icon. */
48
53
  @property({attribute: 'icon-position'}) iconPosition: 'start' | 'end' | 'none' = 'none';
54
+
49
55
  /** An array of keys to use for filtering the options in the selected provider. */
50
56
  @property({
51
57
  attribute: 'filter',
@@ -54,27 +60,39 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
54
60
  toAttribute: (value: string[]) => value.join(',')
55
61
  }
56
62
  }) filter: string[];
63
+
57
64
  /** The selects size. */
58
65
  @property({reflect: true}) size: 'small' | 'medium' | 'large' = 'medium';
66
+
59
67
  /** Should we show the clear button */
60
68
  @property({type: Boolean}) clearable: boolean;
69
+
61
70
  /** Include an "All" option at the top. */
62
71
  @property({type: Boolean, attribute: 'allow-all'}) allowAll = false;
72
+
63
73
  /** The selects label. If you need to display HTML, use the `label` slot instead. */
64
74
  @property() label = '';
75
+
65
76
  /** Text that appears in a tooltip next to the label. If you need to display HTML in the tooltip, use the `label-tooltip` slot instead. */
66
77
  @property({attribute: 'label-tooltip'}) labelTooltip = '';
78
+
67
79
  /** Text that appears above the input, on the right, to add additional context. If you need to display HTML in this text, use the `context-note` slot instead. */
68
80
  @property({attribute: 'context-note'}) contextNote = '';
81
+
69
82
  /**
70
83
  * The preferred placement of the selects menu. Note that the actual placement may vary as needed to keep the listbox
71
84
  * inside the viewport.
72
85
  */
73
86
  @property({reflect: true}) placement: 'top' | 'bottom' = 'bottom';
87
+
74
88
  /** The selects help text. If you need to display HTML, use the `help-text` slot instead. */
75
89
  @property({attribute: 'help-text'}) helpText = '';
90
+
76
91
  /** The selects required attribute. */
77
92
  @property({type: Boolean, reflect: true}) required = false;
93
+
94
+ @property({attribute: "icon-only", type: Boolean, reflect: true}) iconOnly = false;
95
+
78
96
  protected readonly formControlController = new FormControlController(this);
79
97
 
80
98
  get validationMessage() {
@@ -85,6 +103,13 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
85
103
  return this.select.validity;
86
104
  }
87
105
 
106
+ constructor() {
107
+ super();
108
+ if (this.iconOnly && this.iconPosition === 'none') {
109
+ this.iconPosition = 'start';
110
+ }
111
+ }
112
+
88
113
  connectedCallback() {
89
114
  super.connectedCallback();
90
115
  window.addEventListener('keydown', this.closeOnTab);
@@ -95,6 +120,11 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
95
120
  window.removeEventListener('keydown', this.closeOnTab);
96
121
  }
97
122
 
123
+ protected firstUpdated(_changedProperties: PropertyValues) {
124
+ super.firstUpdated(_changedProperties);
125
+ this._updatePrefix();
126
+ }
127
+
98
128
  checkValidity(): boolean {
99
129
  return this.select.checkValidity();
100
130
  }
@@ -185,15 +215,15 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
185
215
  @zn-clear="${this.handleClear}"
186
216
  @blur=${this.blur}
187
217
  value="${this.value}"
188
- placeholder="Choose a ${localProvider.getName}"
218
+ placeholder="${this.getPlaceholder(localProvider)}"
189
219
  exportparts="combobox,expand-icon,form-control-help-text,form-control-input,display-input">
190
- ${this.iconPosition !== 'none' ? html`
220
+ ${(this.iconPosition !== 'none' || this.iconOnly) ? html`
191
221
  <div id="select__prefix" slot="prefix" class="select__prefix"></div>` : ''}
192
222
  ${data.map((item: DataProviderOption) => html`
193
223
  <zn-option class="select__option" value="${item.key}">
194
- ${this.iconPosition !== 'none' ? html`<span
224
+ ${(this.iconPosition !== 'none' || this.iconOnly) ? html`<span
195
225
  slot="${this.iconPosition === 'end' ? 'suffix' : 'prefix'}">${item.prefix}</span>` : ''}
196
- ${item.value}
226
+ ${this.iconOnly ? undefined : item.value}
197
227
  </zn-option>`)}
198
228
  </zn-select>`;
199
229
  }
@@ -201,19 +231,30 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
201
231
  private _updatePrefix() {
202
232
  // Set the prefix of the select to the selected values prefix
203
233
  const selectedOption = this.select.selectedOptions[0];
204
- if (selectedOption && (this.iconPosition !== 'none')) {
234
+ console.log('selectedOption', selectedOption);
235
+ if (selectedOption && (this.iconPosition !== 'none' || this.iconOnly) && this.selectPrefix) {
236
+ if (this.iconPosition === 'none' && this.iconOnly) {
237
+ this.iconPosition = 'start';
238
+ }
205
239
  const slot = this.iconPosition === 'start'
206
240
  ? selectedOption.querySelector('[slot="prefix"]')
207
241
  : selectedOption.querySelector('[slot="suffix"]');
208
-
209
242
  if (slot) {
210
243
  this.selectPrefix.innerHTML = '';
211
244
  this.selectPrefix.appendChild(slot.cloneNode(true));
212
245
  } else {
213
246
  this.selectPrefix.innerHTML = '';
214
247
  }
215
- } else {
248
+ } else if (this.selectPrefix) {
216
249
  this.selectPrefix.innerHTML = '';
217
250
  }
218
251
  }
252
+
253
+ private getPlaceholder(localProvider: LocalDataProvider<any>) {
254
+ if (this.value) {
255
+ return '';
256
+ }
257
+
258
+ return `Choose a ${localProvider.getName}`
259
+ }
219
260
  }
@@ -19,7 +19,7 @@ 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
-
22
+ import ZnStyle from "../style";
23
23
  import type ZnDataSelect from "../data-select";
24
24
  import type ZnInput from "../input";
25
25
  import type ZnQueryBuilder from "../query-builder";
@@ -148,6 +148,7 @@ export default class ZnDataTable extends ZincElement {
148
148
  'zn-button-group': ZnButtonGroup,
149
149
  'zn-confirm': ZnConfirm,
150
150
  'zn-skeleton': ZnSkeleton,
151
+ 'zn-style': ZnStyle,
151
152
  };
152
153
 
153
154
  @property({attribute: 'data-uri'}) dataUri: string;
@@ -183,7 +184,7 @@ export default class ZnDataTable extends ZincElement {
183
184
  @property({attribute: "empty-state-icon"}) emptyStateIcon: string = "data_alert";
184
185
 
185
186
  // Hide the checkbox column
186
- @property({attribute: 'hide-checkboxes', type: Boolean}) hideCheckboxes: boolean;
187
+ @property({attribute: 'hide-checkboxes', type: Boolean}) hideCheckboxes: boolean = true;
187
188
 
188
189
  @property() filters: [] = [];
189
190
 
@@ -277,8 +278,6 @@ export default class ZnDataTable extends ZincElement {
277
278
 
278
279
  requestParams: Record<string, any> = {};
279
280
 
280
- builtinRequestParams: Record<string, any> = {};
281
-
282
281
  refresh() {
283
282
  // Allow manual refresh to trigger the first data load when no-initial-load is set
284
283
  this._initialLoad = false;
@@ -330,12 +329,15 @@ export default class ZnDataTable extends ZincElement {
330
329
  || this.hasSlotController.test(ActionSlots.create.valueOf())
331
330
  || this.hasSlotController.test(ActionSlots.sort.valueOf())
332
331
  || this.hasSlotController.test(ActionSlots.filter.valueOf())
333
- || this.hasSlotController.test(ActionSlots.filter_top.valueOf())
334
- || this.hasSlotController.test(ActionSlots.inputs.valueOf());
332
+ || this.hasSlotController.test(ActionSlots.filter_top.valueOf());
333
+
334
+ const hasInputs = this.hasSlotController.test(ActionSlots.inputs.valueOf());
335
335
 
336
336
  // Headers do not need to be re-rendered with new data
337
337
  return html`
338
338
  <div class="table-container" ${ref((el) => (this.tableContainer = el))}>
339
+ ${hasInputs ? html`
340
+ <slot name="${ActionSlots.inputs.valueOf()}" style="display: none"></slot>` : null}
339
341
  ${hasActions ? this.getTableHeader() : html``}
340
342
  ${tableBody}
341
343
  </div>
@@ -420,7 +422,6 @@ export default class ZnDataTable extends ZincElement {
420
422
  'table': true,
421
423
  'table--standalone': this.standalone,
422
424
  'with-hover': !this.unsortable,
423
- 'table-with--checkboxes': !this.hideCheckboxes,
424
425
  })}">
425
426
  <thead>
426
427
  <tr>
@@ -435,11 +436,7 @@ export default class ZnDataTable extends ZincElement {
435
436
  </thead>
436
437
  <tbody>
437
438
  ${this._rows.map((row: Row) => html`
438
- <tr>
439
- ${this.hideCheckboxes ? html`` : html`
440
- <td class="${classMap({'hidden': !hasSelectedRows})}">
441
- <div><input type="checkbox" @change="${this.selectRow}"></div>
442
- </td>`}
439
+ <tr class="${classMap({'table__row--selected': this.isRowSelected(row)})}">
443
440
  ${row.cells.map((value: Cell, index: number) => this.renderCellBody(index, value))}
444
441
  ${this.rowHasActions ? this.renderActions(row) : null}
445
442
  </tr>`)}
@@ -454,7 +451,6 @@ export default class ZnDataTable extends ZincElement {
454
451
  getTableHeader() {
455
452
  return html`
456
453
  <slot name="${ActionSlots.filter_top.valueOf()}"></slot>
457
- <slot name="${ActionSlots.inputs.valueOf()}" style="display: none"></slot>
458
454
  <div class="table__header">
459
455
  <div class="table__header__actions">
460
456
  ${this.getActions()}
@@ -487,7 +483,7 @@ export default class ZnDataTable extends ZincElement {
487
483
  }
488
484
 
489
485
  getRowsSelected() {
490
- if (this.hideCheckboxes || this.selectedRows.length <= 0) return null;
486
+ if (this.selectedRows.length <= 0) return null;
491
487
 
492
488
  return html`
493
489
  <p>${this.numberOfRowsSelected} of ${this._rows.length} rows selected</p>`
@@ -621,7 +617,7 @@ export default class ZnDataTable extends ZincElement {
621
617
  const checked = checkbox.checked;
622
618
 
623
619
  // go through all the checkboxes and check them
624
- for (const row of this.renderRoot.querySelectorAll('tbody input[type="checkbox"]')) {
620
+ for (const row of this.renderRoot.querySelectorAll('tbody zn-checkbox')) {
625
621
  (row as HTMLInputElement).checked = checked;
626
622
  }
627
623
 
@@ -631,34 +627,40 @@ export default class ZnDataTable extends ZincElement {
631
627
  this.requestUpdate();
632
628
  }
633
629
 
634
- selectRow(e: PointerEvent) {
630
+ selectRow(e: Event) {
635
631
  if (!(e.target && (e.target instanceof Element))) {
636
632
  return;
637
633
  }
638
634
 
639
- let target: HTMLElement = e.target as HTMLElement;
635
+ const target: Element | null = e.target;
640
636
  // if is a button or a link continue
641
- if (target.tagName === 'ZN-BUTTON' || target.tagName === 'A' || target.tagName === 'BUTTON') {
642
- return
637
+ if (target?.tagName === 'ZN-BUTTON' || target?.tagName === 'A' || target?.tagName === 'BUTTON') {
638
+ return;
643
639
  }
644
640
 
645
- // traverse parent until we reach a tr
646
- while (target && target.tagName !== 'TR') {
647
- target = target.parentElement as HTMLElement;
641
+ // Find the parent row
642
+ let parent: Element | null = target.closest ? target.closest('tr') : target.parentElement;
643
+ while (parent && parent.tagName !== 'TR') {
644
+ parent = parent.parentElement;
648
645
  }
649
646
 
650
- if (target === null) {
647
+ if (parent === null) {
651
648
  return;
652
649
  }
653
650
 
654
- const checkbox = target.querySelector('input[type="checkbox"]') as HTMLInputElement;
655
- if (checkbox) {
656
- checkbox.checked = !checkbox.checked;
657
- }
651
+ const rows = Array.from(this.renderRoot.querySelectorAll('tbody tr'));
652
+ const index = rows.indexOf(parent as HTMLTableRowElement);
653
+ if (index === -1) return;
658
654
 
659
- this.selectedRows = this._rows.filter((_, index) => {
660
- return (this.renderRoot.querySelectorAll('tbody input[type="checkbox"]')[index] as HTMLInputElement)?.checked;
661
- });
655
+ const row = this._rows[index] as Row;
656
+ if (!row) return;
657
+
658
+ const alreadySelected = this.selectedRows.some((r: Row) => r.id === row.id);
659
+ if (alreadySelected) {
660
+ this.selectedRows = this.selectedRows.filter((r: Row) => r.id !== row.id);
661
+ } else {
662
+ this.selectedRows = [...(this.selectedRows as Row[]), row];
663
+ }
662
664
 
663
665
  this.numberOfRowsSelected = this.selectedRows.length;
664
666
  this.updateKeys();
@@ -669,13 +671,9 @@ export default class ZnDataTable extends ZincElement {
669
671
  const button = event.target as ZnButton;
670
672
  if (button.disabled) return;
671
673
 
672
- (this.renderRoot.querySelectorAll('thead input[type="checkbox"]')[0] as HTMLInputElement).checked = false;
673
- for (const row of this.renderRoot.querySelectorAll('tbody input[type="checkbox"]')) {
674
- (row as HTMLInputElement).checked = false;
675
- }
676
-
677
674
  this.selectedRows = [];
678
675
  this.numberOfRowsSelected = 0;
676
+ this.updateKeys();
679
677
  this.requestUpdate();
680
678
  }
681
679
 
@@ -690,12 +688,30 @@ export default class ZnDataTable extends ZincElement {
690
688
 
691
689
  renderCell(data: Cell) {
692
690
  if (data && typeof data === 'object') {
693
- let content: TemplateResult = html`${data.text}`;
691
+ let content: TemplateResult | ZincElement = html`${data.text}`;
694
692
 
695
693
  if (data.style || data.color) {
694
+ const styleStr = typeof data.style === 'string' ? data.style : '';
695
+ const tokens = new Set(styleStr.split(',').filter(Boolean));
696
+
697
+ const isMono = tokens.has('mono') || tokens.has('code');
698
+ const isBorder = tokens.has('border');
699
+ const isCenter = tokens.has('center');
700
+
701
+ if (tokens.has('bold') || tokens.has('strong')) {
702
+ content = html`<strong>${content}</strong>`;
703
+ }
704
+
705
+ if (tokens.has('italic')) {
706
+ content = html`<em>${content}</em>`;
707
+ }
708
+
696
709
  content = html`
697
- <zn-style ${ifDefined(data.style || nothing)}
698
- color="${ifDefined(data.color || nothing)}">${content}
710
+ <zn-style
711
+ font="${isMono ? 'mono' : nothing}"
712
+ border="${isBorder || nothing}"
713
+ center="${isCenter || nothing}"
714
+ color="${ifDefined(data.color)}">${content}
699
715
  </zn-style>`;
700
716
  }
701
717
 
@@ -827,6 +843,10 @@ export default class ZnDataTable extends ZincElement {
827
843
  </td>`;
828
844
  }
829
845
 
846
+ private isRowSelected(row: Row): boolean {
847
+ return this.selectedRows.some((r: Row) => r.id === row.id);
848
+ }
849
+
830
850
  private getRows(data: Response): Row[] {
831
851
  const sourceRows = data.rows;
832
852