@kubex/zinc 1.1.46 → 1.1.48
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 +122 -74
- package/dist/vscode.html-custom-data.json +21 -11
- package/dist/web-types.json +43 -23
- package/dist/zn.d.ts +9 -0
- package/dist/zn.min.js +62 -58
- package/docs/pages/components/content-block.md +29 -0
- package/package.json +1 -1
- package/src/components/channel-tile/channel-tile.component.ts +14 -2
- package/src/components/channel-tile/channel-tile.scss +2 -2
- package/src/components/content-block/content-block.component.ts +16 -1
- package/src/components/content-block/content-block.scss +16 -0
- package/src/components/file/file.component.ts +16 -8
- package/src/components/file/file.test.ts +27 -3
- package/src/components/textarea/textarea.scss +6 -0
|
@@ -52,6 +52,35 @@ layout: component
|
|
|
52
52
|
</zn-content-block>
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### Subject
|
|
56
|
+
|
|
57
|
+
When a subject is available it is always shown in the header after the sender — expanded or
|
|
58
|
+
collapsed — in place of the truncated body preview. When `subject` is empty,
|
|
59
|
+
`subject-placeholder` is shown dimmed in its place. Omit both attributes to fall back to the
|
|
60
|
+
body preview (e.g. notes).
|
|
61
|
+
|
|
62
|
+
```html:preview
|
|
63
|
+
|
|
64
|
+
<zn-content-block
|
|
65
|
+
time="16 Jun 2025, 13:00"
|
|
66
|
+
sender="John Smith"
|
|
67
|
+
avatar="JS"
|
|
68
|
+
subject="Re: Help with my order"
|
|
69
|
+
>
|
|
70
|
+
<div slot="text">Text formatted message goes here.</div>
|
|
71
|
+
</zn-content-block>
|
|
72
|
+
|
|
73
|
+
<zn-content-block
|
|
74
|
+
time="16 Jun 2025, 13:05"
|
|
75
|
+
sender="John Smith"
|
|
76
|
+
avatar="JS"
|
|
77
|
+
subject=""
|
|
78
|
+
subject-placeholder="This message has no subject."
|
|
79
|
+
>
|
|
80
|
+
<div slot="text">Text formatted message goes here.</div>
|
|
81
|
+
</zn-content-block>
|
|
82
|
+
```
|
|
83
|
+
|
|
55
84
|
### Attachments
|
|
56
85
|
|
|
57
86
|
Attachments render in a row at the bottom of the content and are only visible while the block
|
package/package.json
CHANGED
|
@@ -176,6 +176,13 @@ export default class ZnChannelTile extends ZincElement {
|
|
|
176
176
|
return Math.min(100, Math.max(0, (elapsed / total) * 100));
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
private _remainingSeconds(): number | null {
|
|
180
|
+
// Only surfaced for auto-accept countdowns, not plain reservation windows.
|
|
181
|
+
const until = this._reservedUntilMs();
|
|
182
|
+
if (!until || !this.autoAcceptDelay) return null;
|
|
183
|
+
return Math.max(0, Math.ceil((until - Date.now()) / 1000));
|
|
184
|
+
}
|
|
185
|
+
|
|
179
186
|
private _handleReject = (e: MouseEvent) => {
|
|
180
187
|
e.preventDefault();
|
|
181
188
|
e.stopPropagation();
|
|
@@ -193,6 +200,7 @@ export default class ZnChannelTile extends ZincElement {
|
|
|
193
200
|
protected render(): unknown {
|
|
194
201
|
const title = this.title || (this.available ? 'Available' : '');
|
|
195
202
|
const countdown = this._isCountingDown() ? this._countdownPercent() : null;
|
|
203
|
+
const remaining = this._isCountingDown() ? this._remainingSeconds() : null;
|
|
196
204
|
|
|
197
205
|
return html`
|
|
198
206
|
<div
|
|
@@ -215,8 +223,12 @@ export default class ZnChannelTile extends ZincElement {
|
|
|
215
223
|
<div class="channel-tile__body">
|
|
216
224
|
${this._renderLeading()}
|
|
217
225
|
<div class="channel-tile__content">
|
|
218
|
-
|
|
219
|
-
|
|
226
|
+
${remaining !== null
|
|
227
|
+
? html`
|
|
228
|
+
<h3 class="channel-tile__title">${this.subtitle} (${remaining}s)</h3>`
|
|
229
|
+
: html`
|
|
230
|
+
<h3 class="channel-tile__title"><slot name="title">${title}</slot></h3>
|
|
231
|
+
<p class="channel-tile__subtitle"><slot name="subtitle">${this.subtitle}</slot></p>`}
|
|
220
232
|
</div>
|
|
221
233
|
<slot name="footer" class="channel-tile__footer"></slot>
|
|
222
234
|
</div>
|
|
@@ -69,11 +69,11 @@
|
|
|
69
69
|
|
|
70
70
|
.channel-tile__overlay {
|
|
71
71
|
position: absolute;
|
|
72
|
-
top:
|
|
72
|
+
top: 24px; // sit below the header strip (fixed 24px)
|
|
73
73
|
left: 0;
|
|
74
74
|
bottom: 0;
|
|
75
75
|
width: 0;
|
|
76
|
-
background-color:
|
|
76
|
+
background-color: rgb(154, 232, 162);
|
|
77
77
|
transition: width 0.2s linear;
|
|
78
78
|
pointer-events: none;
|
|
79
79
|
z-index: 0;
|
|
@@ -35,6 +35,16 @@ export default class ContentBlock extends ZincElement {
|
|
|
35
35
|
@property() sender = '';
|
|
36
36
|
@property() avatar = '';
|
|
37
37
|
|
|
38
|
+
/** The message subject, shown in the header after the sender. */
|
|
39
|
+
@property() subject = '';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Placeholder shown dimmed in place of the subject when `subject` is empty
|
|
43
|
+
* (e.g. "This message has no subject."). Omit both for content without a
|
|
44
|
+
* subject line (e.g. notes).
|
|
45
|
+
*/
|
|
46
|
+
@property({attribute: 'subject-placeholder'}) subjectPlaceholder = '';
|
|
47
|
+
|
|
38
48
|
@property({type: Boolean, reflect: true}) outbound = false;
|
|
39
49
|
|
|
40
50
|
@property({type: Boolean, attribute: "no-collapse"}) noCollapse = false;
|
|
@@ -225,7 +235,12 @@ export default class ContentBlock extends ZincElement {
|
|
|
225
235
|
<span class="content-block-header__caption">
|
|
226
236
|
<slot name="caption">${this.sender}</slot>
|
|
227
237
|
</span>
|
|
228
|
-
|
|
238
|
+
${this.subject || this.subjectPlaceholder ? html`
|
|
239
|
+
<span class="${classMap({
|
|
240
|
+
'content-block-header__subject': true,
|
|
241
|
+
'content-block-header__subject--empty': !this.subject
|
|
242
|
+
})}">${this.subject || this.subjectPlaceholder}</span>` : html`
|
|
243
|
+
<span class="content-block-header__description">${this.truncateText()}</span>`}
|
|
229
244
|
${this.attachmentCount > 0 ? html`
|
|
230
245
|
<span class="content-block-header__attachments" part="header-attachments">
|
|
231
246
|
<zn-icon src="paperclip@lu" size="14"></zn-icon>${this.attachmentCount}
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
-webkit-font-smoothing: antialiased;
|
|
7
7
|
-moz-osx-font-smoothing: grayscale;
|
|
8
8
|
|
|
9
|
+
background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
|
|
10
|
+
border-bottom: 1px solid rgb(var(--zn-border-color));
|
|
11
|
+
|
|
9
12
|
padding-inline: calc(var(--zn-gutter, 0) + var(--zn-base-gap, 0));
|
|
10
13
|
|
|
11
14
|
*, *::before, *::after {
|
|
@@ -58,6 +61,19 @@
|
|
|
58
61
|
text-overflow: ellipsis;
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
.content-block-header__subject {
|
|
65
|
+
flex: 1 1 auto;
|
|
66
|
+
min-width: 0;
|
|
67
|
+
font-size: var(--zn-font-size-medium);
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
text-overflow: ellipsis;
|
|
71
|
+
|
|
72
|
+
&--empty {
|
|
73
|
+
opacity: 0.5;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
.content-block-header__description {
|
|
62
78
|
flex: 1 1 auto;
|
|
63
79
|
min-width: 0;
|
|
@@ -421,18 +421,26 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
|
|
|
421
421
|
// Use the transferred file list from the drag drop interface
|
|
422
422
|
const hasTrigger = this.hasSlotController.test('trigger');
|
|
423
423
|
if (!hasTrigger) {
|
|
424
|
-
const
|
|
425
|
-
const
|
|
424
|
+
const inputChosen = this.inputChosen;
|
|
425
|
+
const dropareaIcon = this.dropareaIcon;
|
|
426
426
|
|
|
427
|
-
if (this.droparea) {
|
|
428
|
-
const dropIconAnimation = getAnimation(
|
|
427
|
+
if (this.droparea && dropareaIcon) {
|
|
428
|
+
const dropIconAnimation = getAnimation(dropareaIcon, 'file.iconDrop', {dir: this.localize.dir()});
|
|
429
429
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
430
|
-
animateTo(
|
|
430
|
+
animateTo(dropareaIcon, dropIconAnimation.keyframes, dropIconAnimation.options);
|
|
431
431
|
}
|
|
432
|
-
|
|
433
|
-
|
|
432
|
+
|
|
433
|
+
if (inputChosen) {
|
|
434
|
+
const disappearAnimation = getAnimation(inputChosen, 'file.text.disappear', {dir: this.localize.dir()});
|
|
435
|
+
await animateTo(inputChosen, disappearAnimation.keyframes, disappearAnimation.options);
|
|
436
|
+
}
|
|
437
|
+
|
|
434
438
|
this.handleFiles(files);
|
|
435
|
-
|
|
439
|
+
|
|
440
|
+
if (inputChosen) {
|
|
441
|
+
const appearAnimation = getAnimation(inputChosen, 'file.text.appear', {dir: this.localize.dir()});
|
|
442
|
+
await animateTo(inputChosen, appearAnimation.keyframes, appearAnimation.options);
|
|
443
|
+
}
|
|
436
444
|
} else {
|
|
437
445
|
this.handleFiles(files);
|
|
438
446
|
}
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
import '../../../dist/zn.min.js';
|
|
2
|
-
import {
|
|
2
|
+
import {expect, fixture, html, oneEvent} from '@open-wc/testing';
|
|
3
|
+
import type ZnFile from './file.component';
|
|
3
4
|
|
|
4
|
-
describe('<zn-
|
|
5
|
+
describe('<zn-file>', () => {
|
|
5
6
|
it('should render a component', async () => {
|
|
6
|
-
const el = await fixture(html
|
|
7
|
+
const el = await fixture(html`<zn-file></zn-file>`);
|
|
7
8
|
|
|
8
9
|
expect(el).to.exist;
|
|
9
10
|
});
|
|
11
|
+
|
|
12
|
+
for (const droparea of [false, true]) {
|
|
13
|
+
it(`accepts a drop when the ${droparea ? 'droparea' : 'standard input'} has no animation target`, async () => {
|
|
14
|
+
const el = await fixture<ZnFile>(html`<zn-file ?droparea=${droparea}></zn-file>`);
|
|
15
|
+
const dataTransfer = new DataTransfer();
|
|
16
|
+
dataTransfer.items.add(new File(['content'], 'example.txt', {type: 'text/plain'}));
|
|
17
|
+
const dropHandler = el as unknown as {
|
|
18
|
+
handleTransferItems: (items: DataTransferItemList | null) => Promise<FileList>;
|
|
19
|
+
};
|
|
20
|
+
dropHandler.handleTransferItems = () => Promise.resolve(dataTransfer.files);
|
|
21
|
+
|
|
22
|
+
const changed = oneEvent(el, 'zn-change');
|
|
23
|
+
el.shadowRoot!.querySelector<HTMLElement>('.form-control')!.dispatchEvent(new DragEvent('drop', {
|
|
24
|
+
bubbles: true,
|
|
25
|
+
cancelable: true,
|
|
26
|
+
dataTransfer
|
|
27
|
+
}));
|
|
28
|
+
await changed;
|
|
29
|
+
|
|
30
|
+
expect(el.files).to.have.length(1);
|
|
31
|
+
expect(el.files![0].name).to.equal('example.txt');
|
|
32
|
+
});
|
|
33
|
+
}
|
|
10
34
|
});
|