@kubex/zinc 1.0.6 → 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.
- package/dist/custom-elements.json +149 -27
- package/dist/vscode.html-custom-data.json +5 -1
- package/dist/web-types.json +23 -4
- package/dist/zn.d.ts +20 -5
- package/dist/zn.min.js +143 -159
- package/package.json +1 -1
- package/src/components/content-block/content-block.component.ts +169 -101
- package/src/components/data-select/data-select.component.ts +51 -10
- package/src/components/data-table/data-table.component.ts +21 -26
- package/src/components/data-table/data-table.scss +74 -7
- package/src/components/data-table-filter/data-table-filter.component.ts +1 -1
- package/src/components/editor/modules/attachment/attachment.ts +53 -41
- package/src/components/editor/modules/toolbar/toolbar.ts +42 -0
- package/src/components/empty-state/empty-state.component.ts +16 -8
- package/src/components/icon/icon.component.ts +2 -0
- package/src/components/icon/icon.scss +13 -0
- package/src/components/item/item.component.ts +11 -3
- package/src/components/item/item.scss +7 -4
- package/src/components/panel/panel.component.ts +2 -0
- package/src/components/panel/panel.scss +6 -0
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -38,80 +40,47 @@ export default class ContentBlock extends ZincElement {
|
|
|
38
40
|
|
|
39
41
|
private _textRows: TextRow[] = [];
|
|
40
42
|
|
|
41
|
-
private
|
|
43
|
+
private _footerObserver?: MutationObserver;
|
|
44
|
+
private _replaceDebounce: number = 0;
|
|
42
45
|
|
|
43
46
|
connectedCallback() {
|
|
44
47
|
super.connectedCallback();
|
|
45
48
|
this.iframe.then((iframe) => {
|
|
46
|
-
|
|
49
|
+
if (iframe.contentDocument) {
|
|
47
50
|
const div = this.htmlNodes[0] as HTMLDivElement;
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const linkTags = Array.from(document.head.querySelectorAll('link[rel="stylesheet"]'))
|
|
60
|
-
.map((element: Element) => {
|
|
61
|
-
const href = (element as HTMLLinkElement).getAttribute('href') || (element as HTMLLinkElement).href || '';
|
|
62
|
-
return href ? `<link rel="stylesheet" href="${href}">` : '';
|
|
63
|
-
})
|
|
64
|
-
.join('');
|
|
65
|
-
|
|
66
|
-
const baseStyles = `<style>
|
|
67
|
-
html, body {
|
|
68
|
-
background-color: var(--zn-panel-background-color);
|
|
69
|
-
container-type: size;
|
|
70
|
-
margin: 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
hr {
|
|
74
|
-
height: 0;
|
|
75
|
-
border: none;
|
|
76
|
-
border-top: 1px solid rgb(var(--zn-border-color));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
code, kbd, samp, pre, .mono {
|
|
80
|
-
font-family: var(--zn-font-family-mono),sans-serif;
|
|
81
|
-
font-size: 1em;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
small {
|
|
85
|
-
font-size: 80%;
|
|
86
|
-
}
|
|
87
|
-
</style>`;
|
|
88
|
-
|
|
89
|
-
const head = `${linkTags}${baseStyles}`;
|
|
90
|
-
const htmlAttrs = `${themeAttr ? ` t="${themeAttr}"` : ''}`;
|
|
91
|
-
const baseHref = document.baseURI || window.location.href;
|
|
92
|
-
|
|
93
|
-
iframe.srcdoc = `<!doctype html><html${htmlAttrs}><head><base href="${baseHref}" target="_blank">${head}</head><body>${convertedHtml}</body></html>`;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
rebuild();
|
|
97
|
-
|
|
98
|
-
// Observe theme changes on both attribute and theme classes on html and body
|
|
99
|
-
this._themeChangeObserver = new MutationObserver(() => rebuild());
|
|
100
|
-
this._themeChangeObserver.observe(document.documentElement, {attributes: true, attributeFilter: ['t', 'class']});
|
|
101
|
-
this._themeChangeObserver.observe(document.body, {attributes: true, attributeFilter: ['t', 'class']});
|
|
102
|
-
|
|
103
|
-
const doc = iframe.contentDocument;
|
|
104
|
-
if (!doc) return;
|
|
51
|
+
if (div) {
|
|
52
|
+
let convertedHtml = div?.innerHTML ?? '';
|
|
53
|
+
convertedHtml = convertedHtml.replace(/ /g, ' ');
|
|
54
|
+
convertedHtml = convertedHtml.replace(/</g, '<');
|
|
55
|
+
convertedHtml = convertedHtml.replace(/>/g, '>');
|
|
56
|
+
convertedHtml = convertedHtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
57
|
+
|
|
58
|
+
const baseStyles = "<style>body,html{background-color: #ffffff}img{max-width:100%; max-height: 500px;}</style>";
|
|
59
|
+
iframe.srcdoc = baseStyles + convertedHtml;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
105
62
|
|
|
106
|
-
iframe.
|
|
107
|
-
|
|
108
|
-
|
|
63
|
+
iframe.addEventListener("load", () => {
|
|
64
|
+
setTimeout(() => this._resizeIframe(iframe), 50);
|
|
65
|
+
});
|
|
109
66
|
});
|
|
110
67
|
}
|
|
111
68
|
|
|
112
69
|
disconnectedCallback() {
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
}
|
|
115
84
|
}
|
|
116
85
|
|
|
117
86
|
private _collapseContent(e: Event) {
|
|
@@ -122,6 +91,15 @@ export default class ContentBlock extends ZincElement {
|
|
|
122
91
|
if (headerClick && target.slot !== 'nav') {
|
|
123
92
|
this.short = !this.short;
|
|
124
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
|
+
});
|
|
125
103
|
}
|
|
126
104
|
}
|
|
127
105
|
|
|
@@ -141,10 +119,46 @@ export default class ContentBlock extends ZincElement {
|
|
|
141
119
|
if (textContent) {
|
|
142
120
|
iframe.classList.remove('hidden');
|
|
143
121
|
textContent.classList.add('hidden');
|
|
122
|
+
setTimeout(() => this._resizeIframe(iframe), 0);
|
|
144
123
|
}
|
|
145
124
|
});
|
|
146
125
|
}
|
|
147
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
|
+
|
|
148
162
|
protected firstUpdated(_changedProperties: PropertyValues) {
|
|
149
163
|
super.firstUpdated(_changedProperties);
|
|
150
164
|
|
|
@@ -167,10 +181,13 @@ export default class ContentBlock extends ZincElement {
|
|
|
167
181
|
const initialShowHtml = hasHtmlSlot && (!hasTextSlot || this.defaultDisplay === 'html');
|
|
168
182
|
|
|
169
183
|
return html`
|
|
170
|
-
<zn-panel flush
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
})}">
|
|
174
191
|
|
|
175
192
|
<zn-header class="content-block-header"
|
|
176
193
|
caption="${this.sender}"
|
|
@@ -222,7 +239,7 @@ export default class ContentBlock extends ZincElement {
|
|
|
222
239
|
</zn-sp>
|
|
223
240
|
|
|
224
241
|
${hasFooter ? html`
|
|
225
|
-
<slot slot="footer" name="footer"></slot>` : ''}
|
|
242
|
+
<slot slot="footer" name="footer" @slotchange="${this._handleSlotChange}"></slot>` : ''}
|
|
226
243
|
</zn-panel>
|
|
227
244
|
`;
|
|
228
245
|
}
|
|
@@ -234,6 +251,88 @@ export default class ContentBlock extends ZincElement {
|
|
|
234
251
|
return trimmed.length > 32 ? trimmed.substring(0, 32) + '...' : trimmed;
|
|
235
252
|
}
|
|
236
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, '"');
|
|
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
|
+
|
|
237
336
|
protected getTextSections(): TextRow[] {
|
|
238
337
|
const textContent = this.querySelector('[slot="text"]') as HTMLDivElement | null;
|
|
239
338
|
const textRows: TextRow[] = [];
|
|
@@ -255,7 +354,6 @@ export default class ContentBlock extends ZincElement {
|
|
|
255
354
|
if (row.startsWith('Sent from')) type = 'reply';
|
|
256
355
|
if (row.trim() === '' && previousType === 'reply') type = 'reply';
|
|
257
356
|
if (type === 'reply' && row.startsWith('>')) row = row.substring(1).trim();
|
|
258
|
-
row = this.transformLineForImages(row);
|
|
259
357
|
|
|
260
358
|
if (previousType === type) {
|
|
261
359
|
if (textRows.length === 0) {
|
|
@@ -275,36 +373,6 @@ export default class ContentBlock extends ZincElement {
|
|
|
275
373
|
return this._textRows;
|
|
276
374
|
}
|
|
277
375
|
|
|
278
|
-
private transformLineForImages(line: string): string {
|
|
279
|
-
let out = line;
|
|
280
|
-
|
|
281
|
-
// Replace Markdown image syntax  for http(s)
|
|
282
|
-
const mdHttpImg = /!\[([^\]]*)\]\((https?:\/\/[^\s)]+?\.(?:png|jpe?g|gif|webp|bmp|svg))\)/gi;
|
|
283
|
-
out = out.replace(mdHttpImg, (_match, alt, url) => {
|
|
284
|
-
return `<img src="${url}" alt="${alt || 'image'}" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Replace Markdown image syntax with data URI (base64)
|
|
288
|
-
const mdDataImg = /!\[([^\]]*)\]\((data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+)\)/gi;
|
|
289
|
-
out = out.replace(mdDataImg, (_match, alt, url) => {
|
|
290
|
-
return `<img src="${url}" alt="${alt || 'image'}" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// Replace bare http(s) image URLs with <img>
|
|
294
|
-
const httpImg = /(https?:\/\/[^\s]+?\.(?:png|jpe?g|gif|webp|bmp|svg))(?!\S)/gi;
|
|
295
|
-
out = out.replace(httpImg, (_m, url) => {
|
|
296
|
-
return `<img src="${url}" alt="image" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Replace bare data URI images with <img>
|
|
300
|
-
const dataImg = /(data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+)(?!\S)/gi;
|
|
301
|
-
out = out.replace(dataImg, (_m, url) => {
|
|
302
|
-
return `<img src="${url}" alt="image" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
return out;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
376
|
showReply(e: MouseEvent) {
|
|
309
377
|
const target = e.target as HTMLElement;
|
|
310
378
|
const nextElement = target.nextElementSibling;
|
|
@@ -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="
|
|
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
|
-
|
|
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
|
}
|
|
@@ -184,7 +184,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
184
184
|
@property({attribute: "empty-state-icon"}) emptyStateIcon: string = "data_alert";
|
|
185
185
|
|
|
186
186
|
// Hide the checkbox column
|
|
187
|
-
@property({attribute: 'hide-checkboxes', type: Boolean}) hideCheckboxes: boolean;
|
|
187
|
+
@property({attribute: 'hide-checkboxes', type: Boolean}) hideCheckboxes: boolean = true;
|
|
188
188
|
|
|
189
189
|
@property() filters: [] = [];
|
|
190
190
|
|
|
@@ -278,8 +278,6 @@ export default class ZnDataTable extends ZincElement {
|
|
|
278
278
|
|
|
279
279
|
requestParams: Record<string, any> = {};
|
|
280
280
|
|
|
281
|
-
builtinRequestParams: Record<string, any> = {};
|
|
282
|
-
|
|
283
281
|
refresh() {
|
|
284
282
|
// Allow manual refresh to trigger the first data load when no-initial-load is set
|
|
285
283
|
this._initialLoad = false;
|
|
@@ -424,7 +422,6 @@ export default class ZnDataTable extends ZincElement {
|
|
|
424
422
|
'table': true,
|
|
425
423
|
'table--standalone': this.standalone,
|
|
426
424
|
'with-hover': !this.unsortable,
|
|
427
|
-
'table--with-checkboxes': !this.hideCheckboxes,
|
|
428
425
|
})}">
|
|
429
426
|
<thead>
|
|
430
427
|
<tr>
|
|
@@ -439,11 +436,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
439
436
|
</thead>
|
|
440
437
|
<tbody>
|
|
441
438
|
${this._rows.map((row: Row) => html`
|
|
442
|
-
<tr>
|
|
443
|
-
${this.hideCheckboxes ? html`` : html`
|
|
444
|
-
<td class="${classMap({'hidden': !hasSelectedRows})}">
|
|
445
|
-
<div><input type="checkbox" @change="${this.selectRow}"></div>
|
|
446
|
-
</td>`}
|
|
439
|
+
<tr class="${classMap({'table__row--selected': this.isRowSelected(row)})}">
|
|
447
440
|
${row.cells.map((value: Cell, index: number) => this.renderCellBody(index, value))}
|
|
448
441
|
${this.rowHasActions ? this.renderActions(row) : null}
|
|
449
442
|
</tr>`)}
|
|
@@ -490,7 +483,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
490
483
|
}
|
|
491
484
|
|
|
492
485
|
getRowsSelected() {
|
|
493
|
-
if (this.
|
|
486
|
+
if (this.selectedRows.length <= 0) return null;
|
|
494
487
|
|
|
495
488
|
return html`
|
|
496
489
|
<p>${this.numberOfRowsSelected} of ${this._rows.length} rows selected</p>`
|
|
@@ -624,7 +617,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
624
617
|
const checked = checkbox.checked;
|
|
625
618
|
|
|
626
619
|
// go through all the checkboxes and check them
|
|
627
|
-
for (const row of this.renderRoot.querySelectorAll('tbody
|
|
620
|
+
for (const row of this.renderRoot.querySelectorAll('tbody zn-checkbox')) {
|
|
628
621
|
(row as HTMLInputElement).checked = checked;
|
|
629
622
|
}
|
|
630
623
|
|
|
@@ -655,17 +648,19 @@ export default class ZnDataTable extends ZincElement {
|
|
|
655
648
|
return;
|
|
656
649
|
}
|
|
657
650
|
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (!isCheckboxTarget) {
|
|
662
|
-
checkbox.checked = !checkbox.checked;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
651
|
+
const rows = Array.from(this.renderRoot.querySelectorAll('tbody tr'));
|
|
652
|
+
const index = rows.indexOf(parent as HTMLTableRowElement);
|
|
653
|
+
if (index === -1) return;
|
|
665
654
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
+
}
|
|
669
664
|
|
|
670
665
|
this.numberOfRowsSelected = this.selectedRows.length;
|
|
671
666
|
this.updateKeys();
|
|
@@ -676,13 +671,9 @@ export default class ZnDataTable extends ZincElement {
|
|
|
676
671
|
const button = event.target as ZnButton;
|
|
677
672
|
if (button.disabled) return;
|
|
678
673
|
|
|
679
|
-
(this.renderRoot.querySelectorAll('thead input[type="checkbox"]')[0] as HTMLInputElement).checked = false;
|
|
680
|
-
for (const row of this.renderRoot.querySelectorAll('tbody input[type="checkbox"]')) {
|
|
681
|
-
(row as HTMLInputElement).checked = false;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
674
|
this.selectedRows = [];
|
|
685
675
|
this.numberOfRowsSelected = 0;
|
|
676
|
+
this.updateKeys();
|
|
686
677
|
this.requestUpdate();
|
|
687
678
|
}
|
|
688
679
|
|
|
@@ -852,6 +843,10 @@ export default class ZnDataTable extends ZincElement {
|
|
|
852
843
|
</td>`;
|
|
853
844
|
}
|
|
854
845
|
|
|
846
|
+
private isRowSelected(row: Row): boolean {
|
|
847
|
+
return this.selectedRows.some((r: Row) => r.id === row.id);
|
|
848
|
+
}
|
|
849
|
+
|
|
855
850
|
private getRows(data: Response): Row[] {
|
|
856
851
|
const sourceRows = data.rows;
|
|
857
852
|
|