@kubex/zinc 1.1.45 → 1.1.47
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 +113 -8
- package/dist/vscode.html-custom-data.json +12 -2
- package/dist/web-types.json +29 -3
- package/dist/zn.d.ts +24 -4
- package/dist/zn.min.js +131 -117
- package/docs/pages/components/content-block.md +49 -0
- package/package.json +1 -1
- package/src/components/chat-message-attachment/chat-message-attachment.component.ts +6 -6
- package/src/components/content-block/content-block.component.ts +48 -2
- package/src/components/content-block/content-block.scss +44 -1
- package/src/components/menu/menu.component.ts +14 -9
- package/src/components/menu/menu.scss +8 -3
- package/src/components/navbar/navbar.component.ts +46 -16
- package/src/components/navbar/navbar.scss +2 -2
- package/src/components/navbar/navbar.test.ts +35 -0
|
@@ -52,6 +52,55 @@ 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
|
+
|
|
84
|
+
### Attachments
|
|
85
|
+
|
|
86
|
+
Attachments render in a row at the bottom of the content and are only visible while the block
|
|
87
|
+
is expanded. Use `zn-chat-message-attachment`, which assigns itself to the `attachments` slot.
|
|
88
|
+
When a block has attachments, a paperclip icon and count are shown in the header after the
|
|
89
|
+
preview text.
|
|
90
|
+
|
|
91
|
+
```html:preview
|
|
92
|
+
|
|
93
|
+
<zn-content-block
|
|
94
|
+
time="16 Jun 2025, 13:00"
|
|
95
|
+
sender="John Smith"
|
|
96
|
+
avatar="JS"
|
|
97
|
+
>
|
|
98
|
+
<div slot="text">Text formatted message goes here.</div>
|
|
99
|
+
<zn-chat-message-attachment href="/" name="filename1.txt" download></zn-chat-message-attachment>
|
|
100
|
+
<zn-chat-message-attachment href="/" name="filename2.png" download></zn-chat-message-attachment>
|
|
101
|
+
</zn-content-block>
|
|
102
|
+
```
|
|
103
|
+
|
|
55
104
|
### Content Footer
|
|
56
105
|
|
|
57
106
|
Basic Footer
|
package/package.json
CHANGED
|
@@ -6,10 +6,10 @@ import ZnIcon from "../icon";
|
|
|
6
6
|
import styles from './chat-message-attachment.scss';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @summary A single file or link attachment for a `zn-chat-message
|
|
10
|
-
* icon and a label as a link, styled to match the message's attachments row. It is
|
|
11
|
-
* intended to be used only inside a `zn-chat-message` and is automatically
|
|
12
|
-
* that component's `attachments` slot.
|
|
9
|
+
* @summary A single file or link attachment for a `zn-chat-message` or `zn-content-block`.
|
|
10
|
+
* Renders an icon and a label as a link, styled to match the message's attachments row. It is
|
|
11
|
+
* intended to be used only inside a `zn-chat-message` or `zn-content-block` and is automatically
|
|
12
|
+
* placed in that component's `attachments` slot.
|
|
13
13
|
* @documentation https://zinc.style/components/chat-message-attachment
|
|
14
14
|
* @status experimental
|
|
15
15
|
* @since 1.0
|
|
@@ -51,12 +51,12 @@ export default class ZnChatMessageAttachment extends ZincElement {
|
|
|
51
51
|
connectedCallback() {
|
|
52
52
|
super.connectedCallback();
|
|
53
53
|
|
|
54
|
-
if (this.closest('zn-chat-message')) {
|
|
54
|
+
if (this.closest('zn-chat-message, zn-content-block')) {
|
|
55
55
|
if (!this.slot) {
|
|
56
56
|
this.slot = 'attachments';
|
|
57
57
|
}
|
|
58
58
|
} else {
|
|
59
|
-
console.warn('<zn-chat-message-attachment> can only be used inside a <zn-chat-message>.', this);
|
|
59
|
+
console.warn('<zn-chat-message-attachment> can only be used inside a <zn-chat-message> or <zn-content-block>.', this);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -3,7 +3,7 @@ import {deepQuerySelectorAll} from "../../utilities/query";
|
|
|
3
3
|
import {HasSlotController} from "../../internal/slot";
|
|
4
4
|
import {html, unsafeCSS} from 'lit';
|
|
5
5
|
import {MutationController} from '@lit-labs/observers/mutation-controller.js';
|
|
6
|
-
import {property, queryAssignedNodes, queryAsync} from 'lit/decorators.js';
|
|
6
|
+
import {property, queryAssignedNodes, queryAsync, state} from 'lit/decorators.js';
|
|
7
7
|
import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
|
|
8
8
|
import ZincElement from "../../internal/zinc-element";
|
|
9
9
|
import type {PropertyValues} from 'lit';
|
|
@@ -21,6 +21,12 @@ interface TextRow {
|
|
|
21
21
|
* @documentation https://zinc.style/components/content-block
|
|
22
22
|
* @status experimental
|
|
23
23
|
* @since 1.0
|
|
24
|
+
*
|
|
25
|
+
* @slot attachments - Attachments displayed at the bottom of the content when the block is
|
|
26
|
+
* expanded. Use `zn-chat-message-attachment`, which auto-assigns itself to this slot.
|
|
27
|
+
*
|
|
28
|
+
* @csspart attachments - The attachments row at the bottom of the content.
|
|
29
|
+
* @csspart header-attachments - The attachment count indicator in the header.
|
|
24
30
|
*/
|
|
25
31
|
export default class ContentBlock extends ZincElement {
|
|
26
32
|
static styles = unsafeCSS(styles);
|
|
@@ -29,6 +35,16 @@ export default class ContentBlock extends ZincElement {
|
|
|
29
35
|
@property() sender = '';
|
|
30
36
|
@property() avatar = '';
|
|
31
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
|
+
|
|
32
48
|
@property({type: Boolean, reflect: true}) outbound = false;
|
|
33
49
|
|
|
34
50
|
@property({type: Boolean, attribute: "no-collapse"}) noCollapse = false;
|
|
@@ -43,6 +59,8 @@ export default class ContentBlock extends ZincElement {
|
|
|
43
59
|
|
|
44
60
|
private readonly hasSlotController = new HasSlotController(this, 'text', 'html');
|
|
45
61
|
|
|
62
|
+
@state() private attachmentCount = 0;
|
|
63
|
+
|
|
46
64
|
private _textRows: TextRow[] = [];
|
|
47
65
|
|
|
48
66
|
private readonly _footerObserver = new MutationController(this, {
|
|
@@ -163,9 +181,21 @@ export default class ContentBlock extends ZincElement {
|
|
|
163
181
|
}
|
|
164
182
|
}
|
|
165
183
|
|
|
184
|
+
private handleAttachmentsSlotChange() {
|
|
185
|
+
this.syncAttachmentCount();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private syncAttachmentCount() {
|
|
189
|
+
const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name="attachments"]');
|
|
190
|
+
this.attachmentCount = slot?.assignedElements().length ?? 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
166
193
|
protected firstUpdated(_changedProperties: PropertyValues) {
|
|
167
194
|
super.firstUpdated(_changedProperties);
|
|
168
195
|
|
|
196
|
+
this.syncAttachmentCount();
|
|
197
|
+
Promise.resolve().then(() => this.syncAttachmentCount());
|
|
198
|
+
|
|
169
199
|
const textContent = this.shadowRoot?.querySelectorAll('.text-section');
|
|
170
200
|
if (textContent && textContent.length > 0) {
|
|
171
201
|
textContent.forEach((content) => {
|
|
@@ -205,7 +235,16 @@ export default class ContentBlock extends ZincElement {
|
|
|
205
235
|
<span class="content-block-header__caption">
|
|
206
236
|
<slot name="caption">${this.sender}</slot>
|
|
207
237
|
</span>
|
|
208
|
-
|
|
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>`}
|
|
244
|
+
${this.attachmentCount > 0 ? html`
|
|
245
|
+
<span class="content-block-header__attachments" part="header-attachments">
|
|
246
|
+
<zn-icon src="paperclip@lu" size="14"></zn-icon>${this.attachmentCount}
|
|
247
|
+
</span>` : ''}
|
|
209
248
|
</div>
|
|
210
249
|
|
|
211
250
|
<div class="content-block-header__actions">
|
|
@@ -246,6 +285,13 @@ export default class ContentBlock extends ZincElement {
|
|
|
246
285
|
</div>
|
|
247
286
|
</zn-sp>
|
|
248
287
|
|
|
288
|
+
<slot
|
|
289
|
+
name="attachments"
|
|
290
|
+
part="attachments"
|
|
291
|
+
class="content-block__attachments ${this.attachmentCount > 0 ? 'content-block__attachments--visible' : ''}"
|
|
292
|
+
@slotchange=${this.handleAttachmentsSlotChange}
|
|
293
|
+
></slot>
|
|
294
|
+
|
|
249
295
|
${hasFooter ? html`
|
|
250
296
|
<slot slot="footer" name="footer" @slotchange="${this._handleSlotChange}"></slot>` : ''}
|
|
251
297
|
</div>
|
|
@@ -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;
|
|
@@ -69,6 +85,16 @@
|
|
|
69
85
|
display: none;
|
|
70
86
|
}
|
|
71
87
|
|
|
88
|
+
.content-block-header__attachments {
|
|
89
|
+
display: inline-flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: var(--zn-spacing-2x-small);
|
|
92
|
+
font-size: var(--zn-font-size-small);
|
|
93
|
+
color: rgb(var(--zn-text-muted));
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
margin-left: auto;
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
.content-block-header__actions {
|
|
73
99
|
display: flex;
|
|
74
100
|
align-items: center;
|
|
@@ -77,8 +103,25 @@
|
|
|
77
103
|
flex-shrink: 0;
|
|
78
104
|
}
|
|
79
105
|
|
|
106
|
+
.content-block__attachments {
|
|
107
|
+
display: none;
|
|
108
|
+
flex-direction: row;
|
|
109
|
+
flex-wrap: wrap;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: var(--zn-spacing-small);
|
|
112
|
+
padding-block-end: var(--zn-spacing-small);
|
|
113
|
+
|
|
114
|
+
&--visible {
|
|
115
|
+
display: flex;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
::slotted(zn-chat-message-attachment) {
|
|
119
|
+
min-width: 0;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
80
123
|
.content-block--short {
|
|
81
|
-
zn-sp {
|
|
124
|
+
zn-sp, .content-block__attachments {
|
|
82
125
|
display: none;
|
|
83
126
|
}
|
|
84
127
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
2
|
-
import {ifDefined} from "lit/directives/if-defined.js";
|
|
3
|
-
import {property, query} from 'lit/decorators.js';
|
|
1
|
+
import { type CSSResultGroup, html, unsafeCSS } from 'lit';
|
|
2
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
3
|
+
import { property, query } from 'lit/decorators.js';
|
|
4
4
|
import ZincElement from '../../internal/zinc-element';
|
|
5
5
|
import ZnConfirm from "../confirm";
|
|
6
6
|
import ZnDropdown from "../dropdown";
|
|
@@ -9,6 +9,7 @@ import ZnMenuItem from "../menu-item";
|
|
|
9
9
|
import ZnTooltip from "../tooltip";
|
|
10
10
|
|
|
11
11
|
import styles from './menu.scss';
|
|
12
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
12
13
|
|
|
13
14
|
interface NavItem {
|
|
14
15
|
title: string;
|
|
@@ -56,12 +57,12 @@ export default class ZnMenu extends ZincElement {
|
|
|
56
57
|
|
|
57
58
|
@query('slot') defaultSlot: HTMLSlotElement;
|
|
58
59
|
|
|
59
|
-
@property({attribute: 'actions', type: Array}) actions = [];
|
|
60
|
+
@property({ attribute: 'actions', type: Array }) actions = [];
|
|
60
61
|
|
|
61
62
|
/** The menu's visual style. `shell` renders the app-shell header dropdown
|
|
62
63
|
* look: a padded panel with floating rounded items. Propagated to the
|
|
63
64
|
* menu's items. */
|
|
64
|
-
@property({reflect: true}) variant: 'default' | 'shell' = 'default';
|
|
65
|
+
@property({ reflect: true }) variant: 'default' | 'shell' = 'default';
|
|
65
66
|
|
|
66
67
|
connectedCallback() {
|
|
67
68
|
super.connectedCallback();
|
|
@@ -70,7 +71,7 @@ export default class ZnMenu extends ZincElement {
|
|
|
70
71
|
|
|
71
72
|
/** @internal Gets all slotted menu items, ignoring dividers, headers, and other elements. */
|
|
72
73
|
getAllItems() {
|
|
73
|
-
return [...this.defaultSlot.assignedElements({flatten: true})].filter((el: HTMLElement) => {
|
|
74
|
+
return [...this.defaultSlot.assignedElements({ flatten: true })].filter((el: HTMLElement) => {
|
|
74
75
|
return !(el.inert || !this.isMenuItem(el));
|
|
75
76
|
|
|
76
77
|
}) as ZnMenuItem[];
|
|
@@ -100,15 +101,19 @@ export default class ZnMenu extends ZincElement {
|
|
|
100
101
|
render() {
|
|
101
102
|
return html`
|
|
102
103
|
<div
|
|
104
|
+
class="${classMap({
|
|
105
|
+
'menu': true,
|
|
106
|
+
'menu--shell': this.variant === 'shell',
|
|
107
|
+
})}"
|
|
103
108
|
@slotchange=${this.handleSlotChange}
|
|
104
109
|
@keydown=${this.handleKeyDown}
|
|
105
110
|
@mousedown=${this.handleMouseDown}>
|
|
106
111
|
<slot></slot>
|
|
107
112
|
${this.actions.map((item: NavItem) => {
|
|
108
|
-
if(
|
|
113
|
+
if (item === null || item === undefined) {
|
|
109
114
|
return html``; // Skip null or undefined items
|
|
110
115
|
}
|
|
111
|
-
if (
|
|
116
|
+
if (item?.confirm) {
|
|
112
117
|
return html`
|
|
113
118
|
<zn-confirm trigger="${ifDefined(item.confirm?.trigger)}"
|
|
114
119
|
type="${ifDefined(item.confirm?.type)}"
|
|
@@ -177,7 +182,7 @@ export default class ZnMenu extends ZincElement {
|
|
|
177
182
|
// get the parent dropdown and close it
|
|
178
183
|
(closestMenu?.closest('zn-dropdown') as ZnDropdown | null)?.hide();
|
|
179
184
|
|
|
180
|
-
this.emit('zn-select', {detail: {item}});
|
|
185
|
+
this.emit('zn-select', { detail: { item } });
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
private handleKeyDown(event: KeyboardEvent) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
@use "../../wc";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
.menu {
|
|
4
4
|
display: block;
|
|
5
5
|
position: relative;
|
|
6
6
|
padding: 1px;
|
|
7
|
-
border: 1px solid rgb(var(--zn-border-color));
|
|
7
|
+
border: 1px solid rgb(var(--zn-border-color)) !important;
|
|
8
8
|
border-radius: var(--zn-border-radius);
|
|
9
9
|
width: fit-content;
|
|
10
10
|
min-width: var(--zn-size-spanel);
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
// Shell variant — the app-shell header dropdown look (as used by Rubix):
|
|
21
21
|
// padded panel with floating rounded items, translucent blurred background
|
|
22
|
-
|
|
22
|
+
.menu--shell {
|
|
23
23
|
display: flex;
|
|
24
24
|
flex-direction: column;
|
|
25
25
|
gap: var(--zn-spacing-2x-small);
|
|
@@ -35,3 +35,8 @@
|
|
|
35
35
|
background-color: rgba(var(--zn-color-navigation, 255, 255, 255), 0.95);
|
|
36
36
|
backdrop-filter: blur(3px);
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
::slotted(zn-menu-item + zn-menu-item) {
|
|
41
|
+
border-top: 1px solid rgb(var(--zn-border-color));
|
|
42
|
+
}
|
|
@@ -62,8 +62,8 @@ export default class ZnNavbar extends ZincElement {
|
|
|
62
62
|
@property({attribute: 'store-ttl', type: Number, reflect: true}) storeTtl = 0;
|
|
63
63
|
@property({attribute: 'local-storage', type: Boolean, reflect: true}) localStorage: boolean;
|
|
64
64
|
|
|
65
|
-
private _preItems:
|
|
66
|
-
private _postItems:
|
|
65
|
+
private _preItems: HTMLElement[] = [];
|
|
66
|
+
private _postItems: HTMLElement[] = [];
|
|
67
67
|
@property()
|
|
68
68
|
private _appended: Element[];
|
|
69
69
|
private _expanding: Element[] = [];
|
|
@@ -79,6 +79,7 @@ export default class ZnNavbar extends ZincElement {
|
|
|
79
79
|
private _expandable: HTMLElement | null = null;
|
|
80
80
|
private _extendedMenu: HTMLElement | null = null;
|
|
81
81
|
private readonly _cloneSources = new WeakMap<HTMLElement, HTMLElement>();
|
|
82
|
+
private readonly _lightDomClones = new WeakMap<HTMLElement, HTMLElement>();
|
|
82
83
|
private _navItemsGap: number = 0;
|
|
83
84
|
private _expandableMargin: number = 0;
|
|
84
85
|
private _totalItemWidth: number = 0;
|
|
@@ -91,6 +92,40 @@ export default class ZnNavbar extends ZincElement {
|
|
|
91
92
|
this._appended = [...(this._appended || []), item];
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
private _cloneLightItem(item: HTMLElement): HTMLElement {
|
|
96
|
+
const existing = this._lightDomClones.get(item);
|
|
97
|
+
if (existing) {
|
|
98
|
+
return existing;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const clone = item.cloneNode(true) as HTMLElement;
|
|
102
|
+
this._cloneSources.set(clone, item);
|
|
103
|
+
this._lightDomClones.set(item, clone);
|
|
104
|
+
clone.addEventListener('click', () => item.click());
|
|
105
|
+
return clone;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _syncLightDomItems() {
|
|
109
|
+
const preItems: HTMLElement[] = [];
|
|
110
|
+
const postItems: HTMLElement[] = [];
|
|
111
|
+
|
|
112
|
+
Array.from(this.children).forEach(child => {
|
|
113
|
+
if (!(child instanceof HTMLElement) || child.tagName !== 'LI') {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const clone = this._cloneLightItem(child);
|
|
118
|
+
if (child.hasAttribute('suffix')) {
|
|
119
|
+
postItems.push(clone);
|
|
120
|
+
} else {
|
|
121
|
+
preItems.push(clone);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this._preItems = preItems;
|
|
126
|
+
this._postItems = postItems;
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
addExpandingAction(action: Element) {
|
|
95
130
|
if (!this._expanding.includes(action)) {
|
|
96
131
|
this._expanding = [...this._expanding, action];
|
|
@@ -141,31 +176,26 @@ export default class ZnNavbar extends ZincElement {
|
|
|
141
176
|
}
|
|
142
177
|
|
|
143
178
|
private _adoptNewLightItems(mutations: MutationRecord[]) {
|
|
144
|
-
const ul = this.shadowRoot?.querySelector('ul');
|
|
145
|
-
if (!ul) return;
|
|
146
|
-
const moreItem = ul.querySelector('li.more');
|
|
147
179
|
for (const m of mutations) {
|
|
148
180
|
for (const node of Array.from(m.addedNodes)) {
|
|
149
181
|
if (node instanceof Element && node.tagName === 'ZN-EXPANDING-ACTION') {
|
|
150
182
|
this.addExpandingAction(node);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!(node instanceof HTMLLIElement)) continue;
|
|
155
|
-
if (node.hasAttribute('suffix')) continue;
|
|
156
|
-
if (moreItem) {
|
|
157
|
-
ul.insertBefore(node, moreItem);
|
|
158
|
-
} else {
|
|
159
|
-
ul.appendChild(node);
|
|
160
183
|
}
|
|
161
184
|
}
|
|
162
185
|
}
|
|
186
|
+
|
|
187
|
+
if (mutations.some(mutation =>
|
|
188
|
+
Array.from(mutation.addedNodes).some(node => node instanceof HTMLLIElement) ||
|
|
189
|
+
Array.from(mutation.removedNodes).some(node => node instanceof HTMLLIElement)
|
|
190
|
+
)) {
|
|
191
|
+
this._syncLightDomItems();
|
|
192
|
+
this.requestUpdate();
|
|
193
|
+
}
|
|
163
194
|
}
|
|
164
195
|
|
|
165
196
|
connectedCallback() {
|
|
166
197
|
super.connectedCallback();
|
|
167
|
-
this.
|
|
168
|
-
this._postItems = this.querySelectorAll('li[suffix]');
|
|
198
|
+
this._syncLightDomItems();
|
|
169
199
|
this._expanding = Array.from(this.querySelectorAll('zn-expanding-action'));
|
|
170
200
|
|
|
171
201
|
if (!this.masterId) {
|
|
@@ -242,7 +242,7 @@ ul.navbar.navbar--stacked.navbar--icon-bar {
|
|
|
242
242
|
|
|
243
243
|
:host(:not([stacked]):not([icon-bar]):not([icon-only])) {
|
|
244
244
|
ul.navbar {
|
|
245
|
-
min-width:
|
|
245
|
+
min-width: 200px;
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -279,7 +279,7 @@ ul.navbar.navbar--stacked.navbar--icon-bar {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
> li.last-visible {
|
|
283
283
|
border-top-right-radius: 6px;
|
|
284
284
|
}
|
|
285
285
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '../../../dist/zn.min.js';
|
|
2
2
|
import {aTimeout, expect, fixture, html, waitUntil} from '@open-wc/testing';
|
|
3
|
+
import {render} from 'lit';
|
|
3
4
|
import type ZnNavbar from './navbar.component';
|
|
4
5
|
|
|
5
6
|
describe('<zn-navbar>', () => {
|
|
@@ -9,6 +10,40 @@ describe('<zn-navbar>', () => {
|
|
|
9
10
|
expect(el).to.exist;
|
|
10
11
|
});
|
|
11
12
|
|
|
13
|
+
it('does not move Lit-owned light DOM items when the parent rerenders', async () => {
|
|
14
|
+
const host = document.createElement('div');
|
|
15
|
+
document.body.append(host);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
render(html`
|
|
19
|
+
<zn-navbar>
|
|
20
|
+
<li tab="one">One</li>
|
|
21
|
+
</zn-navbar>
|
|
22
|
+
`, host);
|
|
23
|
+
await aTimeout(20);
|
|
24
|
+
|
|
25
|
+
render(html`
|
|
26
|
+
<zn-navbar>
|
|
27
|
+
<li tab="one">One</li>
|
|
28
|
+
<li tab="two">Two</li>
|
|
29
|
+
</zn-navbar>
|
|
30
|
+
`, host);
|
|
31
|
+
await aTimeout(20);
|
|
32
|
+
|
|
33
|
+
const navbar = host.querySelector<ZnNavbar>('zn-navbar')!;
|
|
34
|
+
const shadowItems = navbar.shadowRoot!.querySelectorAll('li[tab]');
|
|
35
|
+
const lightItems = navbar.querySelectorAll('li[tab]');
|
|
36
|
+
|
|
37
|
+
expect(lightItems.length).to.equal(2);
|
|
38
|
+
expect(shadowItems.length).to.equal(2);
|
|
39
|
+
expect(shadowItems[0]).to.not.equal(lightItems[0]);
|
|
40
|
+
expect(shadowItems[1].textContent?.trim()).to.equal('Two');
|
|
41
|
+
} finally {
|
|
42
|
+
render(html``, host);
|
|
43
|
+
host.remove();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
12
47
|
it('reserves at least 200px for visible nav items when expandables need space', async () => {
|
|
13
48
|
const el = await fixture<ZnNavbar>(html`
|
|
14
49
|
<zn-navbar>
|