@kubex/zinc 1.0.6 → 1.0.8
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 +464 -34
- package/dist/vscode.html-custom-data.json +38 -6
- package/dist/web-types.json +97 -10
- package/dist/zn.d.ts +73 -8
- package/dist/zn.min.js +393 -349
- package/docs/data/products-table.json +203 -0
- package/docs/pages/components/button.md +25 -0
- package/docs/pages/components/data-table.md +21 -0
- package/docs/pages/components/header.md +7 -0
- package/docs/pages/components/order-table.md +82 -5
- package/package.json +1 -1
- package/src/components/button/button.component.ts +168 -10
- package/src/components/button/button.scss +115 -0
- package/src/components/content-block/content-block.component.ts +186 -107
- package/src/components/content-block/content-block.scss +4 -2
- package/src/components/data-select/data-select.component.ts +50 -10
- package/src/components/data-table/data-table.component.ts +130 -43
- package/src/components/data-table/data-table.scss +85 -7
- package/src/components/data-table-filter/data-table-filter.component.ts +1 -1
- package/src/components/editor/editor.scss +1 -0
- 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/expanding-action/expanding-action.component.ts +1 -1
- package/src/components/header/header.component.ts +8 -2
- package/src/components/header/header.scss +5 -1
- 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/linked-select/linked-select.component.ts +1 -0
- package/src/components/menu/menu.component.ts +0 -1
- package/src/components/navbar/navbar.component.ts +1 -1
- package/src/components/note/note.component.ts +70 -2
- package/src/components/note/note.scss +26 -4
- package/src/components/order-table/order-table.component.ts +49 -28
- package/src/components/panel/panel.component.ts +5 -1
- package/src/components/panel/panel.scss +6 -0
- package/src/components/rating/rating.component.ts +10 -9
- package/src/components/rating/rating.scss +15 -1
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
border-color: transparent;
|
|
24
24
|
height: 100%;
|
|
25
25
|
display: inline-flex;
|
|
26
|
+
position: relative;
|
|
26
27
|
|
|
27
28
|
&.button--info {
|
|
28
29
|
--btn-color: var(--zn-color-info);
|
|
@@ -39,6 +40,10 @@
|
|
|
39
40
|
&.button--success {
|
|
40
41
|
--btn-color: var(--zn-color-success);
|
|
41
42
|
}
|
|
43
|
+
|
|
44
|
+
&__label--hidden {
|
|
45
|
+
visibility: hidden;
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
.button.button--muted-notification {
|
|
@@ -306,6 +311,116 @@
|
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
|
|
314
|
+
.button--loading-bg {
|
|
315
|
+
background-color: #e0e0e0;
|
|
316
|
+
|
|
317
|
+
&-transparent {
|
|
318
|
+
background-color: transparent;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.button--loading {
|
|
323
|
+
&-fill {
|
|
324
|
+
pointer-events: none;
|
|
325
|
+
position: absolute;
|
|
326
|
+
z-index: 1;
|
|
327
|
+
background-color: rgba(var(--btn-color), .85);
|
|
328
|
+
border-radius: inherit;
|
|
329
|
+
transition: width 0s;
|
|
330
|
+
animation: loading-fill-grow var(--loading-duration, 2s) linear forwards;
|
|
331
|
+
inset: -1px;
|
|
332
|
+
|
|
333
|
+
&-transparent {
|
|
334
|
+
background-color: rgba(var(--zn-primary), 0.1);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
&-container {
|
|
339
|
+
position: absolute;
|
|
340
|
+
top: 0;
|
|
341
|
+
left: 0;
|
|
342
|
+
width: 100%;
|
|
343
|
+
height: 100%;
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
justify-content: center;
|
|
347
|
+
pointer-events: none;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
&-text-bottom, &-text-top {
|
|
351
|
+
width: 100%;
|
|
352
|
+
height: 100%;
|
|
353
|
+
display: flex;
|
|
354
|
+
align-items: center;
|
|
355
|
+
justify-content: center;
|
|
356
|
+
font-size: inherit;
|
|
357
|
+
font-weight: inherit;
|
|
358
|
+
user-select: none;
|
|
359
|
+
position: absolute;
|
|
360
|
+
top: 0;
|
|
361
|
+
left: 0;
|
|
362
|
+
pointer-events: none;
|
|
363
|
+
|
|
364
|
+
.loading-countdown {
|
|
365
|
+
margin-left: 3px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
&-left {
|
|
369
|
+
justify-content: flex-start;
|
|
370
|
+
padding-inline-start: 7px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
&-right {
|
|
374
|
+
justify-content: flex-end;
|
|
375
|
+
padding-inline-end: 7px;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
&-text-bottom {
|
|
380
|
+
z-index: 2;
|
|
381
|
+
color: rgba(var(--btn-color), .85);
|
|
382
|
+
|
|
383
|
+
&-transparent {
|
|
384
|
+
color: inherit;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
&-text-top {
|
|
389
|
+
z-index: 3;
|
|
390
|
+
color: inherit;
|
|
391
|
+
/* Animate the reveal from left to right using a mask */
|
|
392
|
+
mask-image: linear-gradient(90deg, #000 0%, #000 100%);
|
|
393
|
+
mask-size: 0 100%;
|
|
394
|
+
mask-repeat: no-repeat;
|
|
395
|
+
animation: loading-text-reveal var(--loading-duration, 2s) linear forwards;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.button--relative-wrapper {
|
|
400
|
+
position: relative;
|
|
401
|
+
display: inline-flex;
|
|
402
|
+
height: 100%;
|
|
403
|
+
width: 100%;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@keyframes loading-fill-grow {
|
|
407
|
+
from {
|
|
408
|
+
width: 0;
|
|
409
|
+
}
|
|
410
|
+
to {
|
|
411
|
+
width: 100%;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
@keyframes loading-text-reveal {
|
|
416
|
+
from {
|
|
417
|
+
mask-size: 0 100%;
|
|
418
|
+
}
|
|
419
|
+
to {
|
|
420
|
+
mask-size: 100% 100%;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
309
424
|
:host([vertical-align="center"]) {
|
|
310
425
|
align-self: center;
|
|
311
426
|
}
|
|
@@ -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 {
|
|
4
|
+
import type {PropertyValues} from 'lit';
|
|
5
|
+
import {html, nothing, unsafeCSS} from 'lit';
|
|
4
6
|
import {property, queryAssignedNodes, queryAsync} from 'lit/decorators.js';
|
|
5
7
|
import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
|
|
6
8
|
import ZincElement from "../../internal/zinc-element";
|
|
7
|
-
import type
|
|
9
|
+
import type ZnTile from "../tile";
|
|
8
10
|
|
|
9
11
|
import styles from './content-block.scss';
|
|
10
12
|
|
|
@@ -27,7 +29,11 @@ export default class ContentBlock extends ZincElement {
|
|
|
27
29
|
@property() avatar = '';
|
|
28
30
|
|
|
29
31
|
@property({type: Boolean, reflect: true}) outbound = false;
|
|
32
|
+
|
|
33
|
+
@property({type: Boolean, attribute: "no-collapse"}) noCollapse = false;
|
|
34
|
+
|
|
30
35
|
@property({type: Boolean, reflect: true}) short = false;
|
|
36
|
+
|
|
31
37
|
@property({attribute: 'default-display', reflect: true}) defaultDisplay: 'text' | 'html' = 'text';
|
|
32
38
|
|
|
33
39
|
@queryAssignedNodes({slot: 'html', flatten: true}) htmlNodes!: Node[];
|
|
@@ -38,83 +44,53 @@ export default class ContentBlock extends ZincElement {
|
|
|
38
44
|
|
|
39
45
|
private _textRows: TextRow[] = [];
|
|
40
46
|
|
|
41
|
-
private
|
|
47
|
+
private _footerObserver?: MutationObserver;
|
|
48
|
+
private _replaceDebounce: number = 0;
|
|
42
49
|
|
|
43
50
|
connectedCallback() {
|
|
44
51
|
super.connectedCallback();
|
|
45
52
|
this.iframe.then((iframe) => {
|
|
46
|
-
|
|
53
|
+
if (iframe.contentDocument) {
|
|
47
54
|
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;
|
|
55
|
+
if (div) {
|
|
56
|
+
let convertedHtml = div?.innerHTML ?? '';
|
|
57
|
+
convertedHtml = convertedHtml.replace(/ /g, ' ');
|
|
58
|
+
convertedHtml = convertedHtml.replace(/</g, '<');
|
|
59
|
+
convertedHtml = convertedHtml.replace(/>/g, '>');
|
|
60
|
+
convertedHtml = convertedHtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
61
|
+
|
|
62
|
+
const baseStyles = "<style>body,html{background-color: #ffffff}img{max-width:100%; max-height: 500px;}</style>";
|
|
63
|
+
iframe.srcdoc = baseStyles + convertedHtml;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
105
66
|
|
|
106
|
-
iframe.
|
|
107
|
-
|
|
108
|
-
|
|
67
|
+
iframe.addEventListener("load", () => {
|
|
68
|
+
setTimeout(() => this._resizeIframe(iframe), 50);
|
|
69
|
+
});
|
|
109
70
|
});
|
|
110
71
|
}
|
|
111
72
|
|
|
112
73
|
disconnectedCallback() {
|
|
113
|
-
|
|
114
|
-
|
|
74
|
+
try {
|
|
75
|
+
if (this._footerObserver) {
|
|
76
|
+
this._footerObserver.disconnect();
|
|
77
|
+
this._footerObserver = undefined;
|
|
78
|
+
}
|
|
79
|
+
if (this._replaceDebounce) {
|
|
80
|
+
clearTimeout(this._replaceDebounce);
|
|
81
|
+
this._replaceDebounce = 0;
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
// Ensure base class cleanup
|
|
85
|
+
// @ts-ignore - base may or may not implement disconnectedCallback
|
|
86
|
+
super.disconnectedCallback?.();
|
|
87
|
+
}
|
|
115
88
|
}
|
|
116
89
|
|
|
117
90
|
private _collapseContent(e: Event) {
|
|
91
|
+
if (this.noCollapse) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
118
94
|
const target = e.target as HTMLElement;
|
|
119
95
|
const headerClick = target.classList.contains('content-block-header') ||
|
|
120
96
|
target.parentElement?.classList.contains('content-block-header');
|
|
@@ -122,6 +98,15 @@ export default class ContentBlock extends ZincElement {
|
|
|
122
98
|
if (headerClick && target.slot !== 'nav') {
|
|
123
99
|
this.short = !this.short;
|
|
124
100
|
this.requestUpdate();
|
|
101
|
+
this.updateComplete.then(() => {
|
|
102
|
+
if (!this.short) {
|
|
103
|
+
this.iframe.then((iframe) => {
|
|
104
|
+
if (iframe && !iframe.classList.contains('hidden')) {
|
|
105
|
+
this._resizeIframe(iframe);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
125
110
|
}
|
|
126
111
|
}
|
|
127
112
|
|
|
@@ -141,10 +126,46 @@ export default class ContentBlock extends ZincElement {
|
|
|
141
126
|
if (textContent) {
|
|
142
127
|
iframe.classList.remove('hidden');
|
|
143
128
|
textContent.classList.add('hidden');
|
|
129
|
+
setTimeout(() => this._resizeIframe(iframe), 0);
|
|
144
130
|
}
|
|
145
131
|
});
|
|
146
132
|
}
|
|
147
133
|
|
|
134
|
+
private _resizeIframe(iframe?: HTMLIFrameElement) {
|
|
135
|
+
const resize = (frame: HTMLIFrameElement) => {
|
|
136
|
+
const doc = frame.contentDocument;
|
|
137
|
+
if (!doc) return;
|
|
138
|
+
|
|
139
|
+
// Allow the iframe to shrink as well as grow
|
|
140
|
+
frame.style.height = 'auto';
|
|
141
|
+
|
|
142
|
+
const body = doc.body;
|
|
143
|
+
const element = doc.documentElement;
|
|
144
|
+
|
|
145
|
+
const heights = [
|
|
146
|
+
body?.scrollHeight ?? 0,
|
|
147
|
+
element?.scrollHeight ?? 0,
|
|
148
|
+
body?.offsetHeight ?? 0,
|
|
149
|
+
element?.offsetHeight ?? 0
|
|
150
|
+
];
|
|
151
|
+
const height = Math.max(...heights);
|
|
152
|
+
|
|
153
|
+
if (height > 0) {
|
|
154
|
+
frame.style.height = `${height}px`;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (iframe) {
|
|
159
|
+
resize(iframe);
|
|
160
|
+
} else {
|
|
161
|
+
this.iframe.then((frame) => {
|
|
162
|
+
if (frame?.contentDocument) {
|
|
163
|
+
requestAnimationFrame(() => resize(frame));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
148
169
|
protected firstUpdated(_changedProperties: PropertyValues) {
|
|
149
170
|
super.firstUpdated(_changedProperties);
|
|
150
171
|
|
|
@@ -167,22 +188,29 @@ export default class ContentBlock extends ZincElement {
|
|
|
167
188
|
const initialShowHtml = hasHtmlSlot && (!hasTextSlot || this.defaultDisplay === 'html');
|
|
168
189
|
|
|
169
190
|
return html`
|
|
170
|
-
<zn-panel flush
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
<zn-panel flush
|
|
192
|
+
tabbed
|
|
193
|
+
flush-footer="${hasFooter || nothing}"
|
|
194
|
+
class="${classMap({
|
|
195
|
+
'content-block--outbound': this.outbound,
|
|
196
|
+
'content-block--short': this.short,
|
|
197
|
+
'content-block--collapsible': !this.noCollapse
|
|
198
|
+
})}">
|
|
174
199
|
|
|
175
200
|
<zn-header class="content-block-header"
|
|
176
|
-
caption="${this.sender}"
|
|
177
201
|
description="${this.truncateText()}"
|
|
178
202
|
@click="${this._collapseContent}">
|
|
179
203
|
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
204
|
+
<slot name="icon">
|
|
205
|
+
<zn-icon src="${this.avatar}"
|
|
206
|
+
library="avatar"
|
|
207
|
+
round></zn-icon>
|
|
208
|
+
</slot>
|
|
209
|
+
|
|
210
|
+
<slot name="caption" slot="caption">${this.sender}</slot>
|
|
183
211
|
|
|
184
212
|
<div slot="actions">
|
|
185
|
-
<small>${this.time}</small>
|
|
213
|
+
<slot name="actions"><small>${this.time}</small></slot>
|
|
186
214
|
${showActions ? html`
|
|
187
215
|
<zn-dropdown>
|
|
188
216
|
<zn-button slot="trigger" icon="more_vert" icon-size="24" color="transparent"
|
|
@@ -222,7 +250,7 @@ export default class ContentBlock extends ZincElement {
|
|
|
222
250
|
</zn-sp>
|
|
223
251
|
|
|
224
252
|
${hasFooter ? html`
|
|
225
|
-
<slot slot="footer" name="footer"></slot>` : ''}
|
|
253
|
+
<slot slot="footer" name="footer" @slotchange="${this._handleSlotChange}"></slot>` : ''}
|
|
226
254
|
</zn-panel>
|
|
227
255
|
`;
|
|
228
256
|
}
|
|
@@ -234,6 +262,88 @@ export default class ContentBlock extends ZincElement {
|
|
|
234
262
|
return trimmed.length > 32 ? trimmed.substring(0, 32) + '...' : trimmed;
|
|
235
263
|
}
|
|
236
264
|
|
|
265
|
+
private _handleSlotChange(e: Event) {
|
|
266
|
+
const slot = e.target as HTMLSlotElement;
|
|
267
|
+
const assignedEls: Element[] = slot.assignedElements({flatten: true});
|
|
268
|
+
if (!assignedEls || assignedEls.length === 0) return;
|
|
269
|
+
|
|
270
|
+
// Reset and attach a new observer to watch for dynamic content inside the footer subtree
|
|
271
|
+
if (this._footerObserver) {
|
|
272
|
+
this._footerObserver.disconnect();
|
|
273
|
+
this._footerObserver = undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const obs = new MutationObserver(() => {
|
|
277
|
+
this._debouncedReplace();
|
|
278
|
+
});
|
|
279
|
+
this._footerObserver = obs;
|
|
280
|
+
|
|
281
|
+
assignedEls.forEach(el => {
|
|
282
|
+
obs.observe(el, {
|
|
283
|
+
subtree: true,
|
|
284
|
+
childList: true,
|
|
285
|
+
attributes: true,
|
|
286
|
+
attributeFilter: ['href', 'download', 'caption']
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Run now and again shortly after to catch async rendering
|
|
291
|
+
this._replaceImagePlaceholders();
|
|
292
|
+
setTimeout(() => this._replaceImagePlaceholders(), 50);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private _debouncedReplace() {
|
|
296
|
+
if (this._replaceDebounce) {
|
|
297
|
+
clearTimeout(this._replaceDebounce);
|
|
298
|
+
}
|
|
299
|
+
this._replaceDebounce = window.setTimeout(() => this._replaceImagePlaceholders(), 50);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private _replaceImagePlaceholders() {
|
|
303
|
+
const textContainer = this.shadowRoot?.querySelector('.text-content') as HTMLDivElement | null;
|
|
304
|
+
if (!textContainer) {
|
|
305
|
+
// If the shadow content hasn't been rendered yet, try after the next update
|
|
306
|
+
this.updateComplete.then(() => this._replaceImagePlaceholders());
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const footer = this.querySelector(':scope > [slot="footer"]') as HTMLElement | null;
|
|
311
|
+
if (!footer) return;
|
|
312
|
+
|
|
313
|
+
const anchors = deepQuerySelectorAll('a[href]', footer, '') as HTMLAnchorElement[];
|
|
314
|
+
if (anchors.length === 0) return;
|
|
315
|
+
|
|
316
|
+
let textContent = textContainer.innerHTML;
|
|
317
|
+
const extensions = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'heic', 'tiff', 'tif', 'avif']);
|
|
318
|
+
anchors.forEach((a) => {
|
|
319
|
+
const candidates = new Set<string>();
|
|
320
|
+
const tile = a.querySelector('zn-tile') as ZnTile | null;
|
|
321
|
+
|
|
322
|
+
const caption = (tile?.caption ?? tile?.getAttribute?.('caption') ?? '').trim();
|
|
323
|
+
if (caption) candidates.add(caption);
|
|
324
|
+
|
|
325
|
+
const downloadAttr = (a.getAttribute('download') || '').trim();
|
|
326
|
+
if (downloadAttr) candidates.add(downloadAttr);
|
|
327
|
+
|
|
328
|
+
const url = a.getAttribute('href') || '';
|
|
329
|
+
candidates.forEach((rawName) => {
|
|
330
|
+
const name = (rawName || '').trim();
|
|
331
|
+
if (!name) return;
|
|
332
|
+
|
|
333
|
+
const base = name.includes('/') ? name.split('/').pop()! : name;
|
|
334
|
+
const lowerBase = base.toLowerCase();
|
|
335
|
+
const ext = lowerBase.includes('.') ? lowerBase.split('.').pop()! : '';
|
|
336
|
+
if (ext && !extensions.has(ext)) return;
|
|
337
|
+
|
|
338
|
+
const alt = base.replace(/"/g, '"');
|
|
339
|
+
textContent = textContent.replace(`[image: ${base}]`, () => `<img src="${url}" alt="${alt}" style="max-height: 500px; max-width: 100%">`);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Update the generated content directly without triggering a re-render
|
|
344
|
+
textContainer.innerHTML = textContent;
|
|
345
|
+
}
|
|
346
|
+
|
|
237
347
|
protected getTextSections(): TextRow[] {
|
|
238
348
|
const textContent = this.querySelector('[slot="text"]') as HTMLDivElement | null;
|
|
239
349
|
const textRows: TextRow[] = [];
|
|
@@ -255,7 +365,6 @@ export default class ContentBlock extends ZincElement {
|
|
|
255
365
|
if (row.startsWith('Sent from')) type = 'reply';
|
|
256
366
|
if (row.trim() === '' && previousType === 'reply') type = 'reply';
|
|
257
367
|
if (type === 'reply' && row.startsWith('>')) row = row.substring(1).trim();
|
|
258
|
-
row = this.transformLineForImages(row);
|
|
259
368
|
|
|
260
369
|
if (previousType === type) {
|
|
261
370
|
if (textRows.length === 0) {
|
|
@@ -275,36 +384,6 @@ export default class ContentBlock extends ZincElement {
|
|
|
275
384
|
return this._textRows;
|
|
276
385
|
}
|
|
277
386
|
|
|
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
387
|
showReply(e: MouseEvent) {
|
|
309
388
|
const target = e.target as HTMLElement;
|
|
310
389
|
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,29 @@ 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
|
+
if (selectedOption && (this.iconPosition !== 'none' || this.iconOnly) && this.selectPrefix) {
|
|
235
|
+
if (this.iconPosition === 'none' && this.iconOnly) {
|
|
236
|
+
this.iconPosition = 'start';
|
|
237
|
+
}
|
|
205
238
|
const slot = this.iconPosition === 'start'
|
|
206
239
|
? selectedOption.querySelector('[slot="prefix"]')
|
|
207
240
|
: selectedOption.querySelector('[slot="suffix"]');
|
|
208
|
-
|
|
209
241
|
if (slot) {
|
|
210
242
|
this.selectPrefix.innerHTML = '';
|
|
211
243
|
this.selectPrefix.appendChild(slot.cloneNode(true));
|
|
212
244
|
} else {
|
|
213
245
|
this.selectPrefix.innerHTML = '';
|
|
214
246
|
}
|
|
215
|
-
} else {
|
|
247
|
+
} else if (this.selectPrefix) {
|
|
216
248
|
this.selectPrefix.innerHTML = '';
|
|
217
249
|
}
|
|
218
250
|
}
|
|
251
|
+
|
|
252
|
+
private getPlaceholder(localProvider: LocalDataProvider<any>) {
|
|
253
|
+
if (this.value) {
|
|
254
|
+
return '';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return `Choose a ${localProvider.getName}`
|
|
258
|
+
}
|
|
219
259
|
}
|