@kubex/zinc 1.1.159 → 1.1.161
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 +148 -77
- package/dist/vscode.html-custom-data.json +21 -12
- package/dist/web-types.json +35 -25
- package/dist/zn.d.ts +122 -110
- package/dist/zn.min.js +186 -183
- package/docs/pages/components/page.md +15 -0
- package/package.json +1 -1
- package/src/components/button/button.component.ts +4 -0
- package/src/components/button/button.test.ts +10 -0
- package/src/components/content-block/content-block.component.ts +2 -3
- package/src/components/flow-builder/modules/flow-node/flow-node.test.ts +2 -3
- package/src/components/flow-builder/modules/flow-step-group/flow-step-group.test.ts +14 -8
- package/src/components/form-actions/form-actions.test.ts +4 -1
- package/src/components/form-group/form-group.component.ts +45 -15
- package/src/components/navbar/navbar.test.ts +3 -2
- package/src/components/page/page.component.ts +25 -4
- package/src/components/page/page.test.ts +68 -16
- package/src/components/sp/sp.test.ts +2 -2
- package/src/components/tabs/tabs.component.ts +1 -1
- package/src/components/theme-editor/theme-editor.component.ts +3 -2
- package/src/components/theme-editor/theme-editor.test.ts +30 -28
- package/src/components/translation-group/translation-group.test.ts +0 -2
|
@@ -133,6 +133,21 @@ Use `priority` to control tab order. Lower numbers appear first after the empty-
|
|
|
133
133
|
</zn-page>
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
### Linking to a Tab
|
|
137
|
+
|
|
138
|
+
Give any element in the page content a `tab` attribute holding a tab's `id` to open that tab when it is clicked. Links inside a nested `zn-page` or `zn-tabs` open tabs in that nested container.
|
|
139
|
+
|
|
140
|
+
```html:preview
|
|
141
|
+
<zn-page caption="Scorecard">
|
|
142
|
+
<zn-tab caption="Details" id="details">
|
|
143
|
+
<zn-button tab="section-general" icon="edit">Edit General section</zn-button>
|
|
144
|
+
</zn-tab>
|
|
145
|
+
<zn-tab caption="General" id="section-general">
|
|
146
|
+
General section settings
|
|
147
|
+
</zn-tab>
|
|
148
|
+
</zn-page>
|
|
149
|
+
```
|
|
150
|
+
|
|
136
151
|
### Dynamic Tabs
|
|
137
152
|
|
|
138
153
|
Set `uri` on `zn-tab` to create a dynamic tab. The generated navigation item receives `tab-uri`.
|
package/package.json
CHANGED
|
@@ -128,6 +128,9 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
128
128
|
/** Accessible name for the button. Icon-only buttons fall back to `tooltip`. */
|
|
129
129
|
@property() label: string;
|
|
130
130
|
|
|
131
|
+
/** Toggle state, forwarded to the inner button's `aria-pressed`. */
|
|
132
|
+
@property({attribute: 'aria-pressed'}) pressed: 'true' | 'false' | 'mixed';
|
|
133
|
+
|
|
131
134
|
// Auto Click Specific
|
|
132
135
|
@property({type: Boolean, attribute: 'auto-click'}) autoClick = false;
|
|
133
136
|
@property({type: Number, attribute: 'auto-click-delay'}) autoClickDelay = 2000;
|
|
@@ -436,6 +439,7 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
436
439
|
rel="${ifDefined(isLink ? this.rel : undefined)}"
|
|
437
440
|
gaid="${ifDefined(this.gaid)}"
|
|
438
441
|
aria-label="${ifDefined(ariaLabel)}"
|
|
442
|
+
aria-pressed="${ifDefined(isLink ? undefined : this.pressed)}"
|
|
439
443
|
data-notification="${ifDefined(notification)}"
|
|
440
444
|
disabled="${this.disabled || nothing}"
|
|
441
445
|
style=${styleMap(buttonStyles)}
|
|
@@ -29,4 +29,14 @@ describe('<zn-button>', () => {
|
|
|
29
29
|
expect(el.color).to.equal('default');
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
it('forwards aria-pressed to the inner button', async () => {
|
|
33
|
+
const el = await fixture<ZnButton>(html`<zn-button icon="sun" label="Toggle" aria-pressed="true"></zn-button>`);
|
|
34
|
+
const base = el.shadowRoot!.querySelector('[part~="base"]')!;
|
|
35
|
+
expect(base.getAttribute('aria-pressed')).to.equal('true');
|
|
36
|
+
|
|
37
|
+
el.setAttribute('aria-pressed', 'false');
|
|
38
|
+
await el.updateComplete;
|
|
39
|
+
expect(base.getAttribute('aria-pressed')).to.equal('false');
|
|
40
|
+
});
|
|
41
|
+
|
|
32
42
|
});
|
|
@@ -4,7 +4,6 @@ import {HasSlotController} from "../../internal/slot";
|
|
|
4
4
|
import {html, unsafeCSS} from 'lit';
|
|
5
5
|
import {MutationController} from '@lit-labs/observers/mutation-controller.js';
|
|
6
6
|
import {property, queryAssignedNodes, queryAsync, state} from 'lit/decorators.js';
|
|
7
|
-
import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
|
|
8
7
|
import ZincElement from "../../internal/zinc-element";
|
|
9
8
|
import type {PropertyValues} from 'lit';
|
|
10
9
|
import type ZnTile from "../tile";
|
|
@@ -280,7 +279,7 @@ export default class ContentBlock extends ZincElement {
|
|
|
280
279
|
'text-section--text': section.type === 'text',
|
|
281
280
|
'hidden': section.type === 'reply'
|
|
282
281
|
})}>
|
|
283
|
-
${section.lines.map((line) => html`${
|
|
282
|
+
${section.lines.map((line) => html`${line}<br>`)}
|
|
284
283
|
</div>
|
|
285
284
|
`)}
|
|
286
285
|
</div>
|
|
@@ -375,7 +374,7 @@ export default class ContentBlock extends ZincElement {
|
|
|
375
374
|
const textRows: TextRow[] = [];
|
|
376
375
|
|
|
377
376
|
if (textContent) {
|
|
378
|
-
const text = textContent.innerText;
|
|
377
|
+
const text = textContent.innerText.replace(/<br\s*\/?>/gi, '\n');
|
|
379
378
|
const rows = text.split('\n');
|
|
380
379
|
let previousType: TextRow['type'] | null = null;
|
|
381
380
|
let forceReply = false;
|
|
@@ -19,7 +19,7 @@ describe('<zn-flow-node>', () => {
|
|
|
19
19
|
expect(el).to.exist;
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it('should render the type label and
|
|
22
|
+
it('should render the type label and a single output stem', async () => {
|
|
23
23
|
const el = await fixture<ZnFlowNode>(html`
|
|
24
24
|
<zn-flow-node></zn-flow-node>`);
|
|
25
25
|
el.node = node;
|
|
@@ -27,8 +27,7 @@ describe('<zn-flow-node>', () => {
|
|
|
27
27
|
await el.updateComplete;
|
|
28
28
|
|
|
29
29
|
expect(el.shadowRoot?.querySelector('.title')?.textContent).to.contain('Conditional Split');
|
|
30
|
-
expect(el.shadowRoot?.querySelectorAll('.port--out')?.length).to.equal(
|
|
31
|
-
expect(el.shadowRoot?.querySelector('.port-label')?.textContent).to.contain('TRUE');
|
|
30
|
+
expect(el.shadowRoot?.querySelectorAll('.port--out')?.length).to.equal(1);
|
|
32
31
|
});
|
|
33
32
|
|
|
34
33
|
it('should emit flow-node-grab when the card body is pressed', async () => {
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import '../../../../../dist/zn.min.js';
|
|
2
2
|
import {expect, fixture, html} from '@open-wc/testing';
|
|
3
|
+
import type ZnCollapsible from '../../../collapsible/collapsible.component';
|
|
3
4
|
import type ZnFlowStepGroup from './flow-step-group.component';
|
|
4
5
|
|
|
6
|
+
async function collapsibleOf(el: ZnFlowStepGroup): Promise<ZnCollapsible> {
|
|
7
|
+
const collapsible = el.shadowRoot!.querySelector<ZnCollapsible>('zn-collapsible')!;
|
|
8
|
+
await collapsible.updateComplete;
|
|
9
|
+
return collapsible;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
describe('<zn-flow-step-group>', () => {
|
|
6
|
-
it('should render a
|
|
13
|
+
it('should render a collapsible with its caption', async () => {
|
|
7
14
|
const el = await fixture<ZnFlowStepGroup>(html`
|
|
8
15
|
<zn-flow-step-group caption="Contacts"></zn-flow-step-group>`);
|
|
9
|
-
|
|
16
|
+
const collapsible = await collapsibleOf(el);
|
|
17
|
+
expect(collapsible.caption).to.equal('Contacts');
|
|
18
|
+
expect(collapsible.expanded).to.equal(true);
|
|
10
19
|
});
|
|
11
20
|
|
|
12
|
-
it('should
|
|
21
|
+
it('should start closed when collapsed', async () => {
|
|
13
22
|
const el = await fixture<ZnFlowStepGroup>(html`
|
|
14
|
-
<zn-flow-step-group caption="Contacts"></zn-flow-step-group>`);
|
|
15
|
-
|
|
16
|
-
header.click();
|
|
17
|
-
await el.updateComplete;
|
|
18
|
-
expect(el.collapsed).to.equal(true);
|
|
23
|
+
<zn-flow-step-group caption="Contacts" collapsed></zn-flow-step-group>`);
|
|
24
|
+
expect((await collapsibleOf(el)).expanded).to.equal(false);
|
|
19
25
|
});
|
|
20
26
|
});
|
|
@@ -5,6 +5,9 @@ import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
|
|
5
5
|
const clickButton = (button: HTMLElement) =>
|
|
6
6
|
button.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true, cancelable: true }));
|
|
7
7
|
|
|
8
|
+
// The test harness loads no theme stylesheet, so a layout test stamps on the spacing token a themed page provides.
|
|
9
|
+
const spacingTokens = '--zn-spacing-small: 16px;';
|
|
10
|
+
|
|
8
11
|
describe('<zn-form-actions>', () => {
|
|
9
12
|
it('should render a component', async () => {
|
|
10
13
|
const el = await fixture(html` <zn-form-actions></zn-form-actions> `);
|
|
@@ -24,7 +27,7 @@ describe('<zn-form-actions>', () => {
|
|
|
24
27
|
|
|
25
28
|
it('holds align="start" children left and the rest right, one gap apart on each side', async () => {
|
|
26
29
|
const el = await fixture<HTMLElement>(html`
|
|
27
|
-
<zn-form-actions with-cancel style="width: 600px">
|
|
30
|
+
<zn-form-actions with-cancel style="width: 600px; ${spacingTokens}">
|
|
28
31
|
<zn-button align="start" id="translate">Translate</zn-button>
|
|
29
32
|
<zn-button align="start" id="slug">Slug</zn-button>
|
|
30
33
|
</zn-form-actions>
|
|
@@ -54,12 +54,18 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
54
54
|
private rebind: boolean = false;
|
|
55
55
|
private offset: number = 0;
|
|
56
56
|
private resizeObserver: ResizeObserver | null = null;
|
|
57
|
+
private observedAncestors: Set<HTMLElement> = new Set();
|
|
57
58
|
|
|
58
59
|
connectedCallback() {
|
|
59
60
|
super.connectedCallback();
|
|
60
61
|
|
|
61
|
-
// Whether an ancestor scrolls depends on how tall this form has grown.
|
|
62
|
-
|
|
62
|
+
// Whether an ancestor scrolls depends on how tall this form has grown. Re-measured before paint rather than on the
|
|
63
|
+
// next frame, or the label is drawn for a frame against the old layout and flickers.
|
|
64
|
+
this.resizeObserver ??= new ResizeObserver(() => {
|
|
65
|
+
this.rebind = false;
|
|
66
|
+
this.bind();
|
|
67
|
+
this.positionLabel();
|
|
68
|
+
});
|
|
63
69
|
this.resizeObserver.observe(this);
|
|
64
70
|
window.addEventListener('resize', this.onViewportResize);
|
|
65
71
|
}
|
|
@@ -67,6 +73,7 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
67
73
|
disconnectedCallback() {
|
|
68
74
|
super.disconnectedCallback();
|
|
69
75
|
this.resizeObserver?.disconnect();
|
|
76
|
+
this.observedAncestors.clear();
|
|
70
77
|
window.removeEventListener('resize', this.onViewportResize);
|
|
71
78
|
this.detach();
|
|
72
79
|
cancelAnimationFrame(this.frame);
|
|
@@ -82,7 +89,7 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
82
89
|
return this.shadowRoot?.querySelector('.form-control__text') ?? null;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
/** Coalesces scroll and resize work into one frame
|
|
92
|
+
/** Coalesces scroll and resize work into one frame. */
|
|
86
93
|
private schedule(rebind: boolean = false) {
|
|
87
94
|
this.rebind ||= rebind;
|
|
88
95
|
if (this.frame) return;
|
|
@@ -120,6 +127,7 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
120
127
|
// A `top` inset against a box that never scrolls has nothing to hold the label back from:
|
|
121
128
|
// it only pushes the label down the page, so drop it and stand in for sticky below.
|
|
122
129
|
column.style.top = '0px';
|
|
130
|
+
this.observeAncestors(column, scroller);
|
|
123
131
|
if (!scroller) return;
|
|
124
132
|
|
|
125
133
|
this.tracked = scroller;
|
|
@@ -154,24 +162,26 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
154
162
|
// The stylesheet drops sticky while the columns are stacked, and there is nothing to follow.
|
|
155
163
|
if (getComputedStyle(column).position !== 'sticky') return null;
|
|
156
164
|
|
|
165
|
+
if (!('rangeStart' in Animation.prototype)) return null;
|
|
166
|
+
|
|
157
167
|
const travel = Math.max(0, fieldset.clientHeight - column.offsetHeight);
|
|
158
|
-
|
|
159
|
-
if (travel < 1 || range < 1) return null;
|
|
168
|
+
if (travel < 1) return null;
|
|
160
169
|
|
|
161
170
|
const start = scroller.scrollTop + column.getBoundingClientRect().top
|
|
162
171
|
- this.visibleTop(scroller) - this.stickyTop;
|
|
163
|
-
const held = (scroll: number) => Math.min(Math.max(scroll - start, 0), travel);
|
|
164
172
|
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return new Animation(
|
|
172
|
-
new KeyframeEffect(column, keyframes, { fill: 'both' }),
|
|
173
|
+
const animation = new Animation(
|
|
174
|
+
new KeyframeEffect(column, [
|
|
175
|
+
{ transform: 'translateY(0px)' },
|
|
176
|
+
{ transform: `translateY(${travel}px)` }
|
|
177
|
+
], { fill: 'both' }),
|
|
173
178
|
new ScrollTimeline({ source: scroller, axis: 'block' })
|
|
174
|
-
);
|
|
179
|
+
) as Animation & { rangeStart: string; rangeEnd: string };
|
|
180
|
+
|
|
181
|
+
// In scroll pixels rather than progress, so the scroller growing or shrinking can't shift the label.
|
|
182
|
+
animation.rangeStart = `${start}px`;
|
|
183
|
+
animation.rangeEnd = `${start + travel}px`;
|
|
184
|
+
return animation;
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
/** Drops everything this component has put on the label or on the scroller. */
|
|
@@ -190,6 +200,26 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
190
200
|
column.style.transform = '';
|
|
191
201
|
}
|
|
192
202
|
|
|
203
|
+
/**
|
|
204
|
+
* The label's travel is measured against the scroller, so content growing elsewhere in it — a sibling group revealing
|
|
205
|
+
* more fields — leaves the label at a stale position until something re-measures. Any such growth resizes a box
|
|
206
|
+
* between this group and the scroller.
|
|
207
|
+
*/
|
|
208
|
+
private observeAncestors(from: HTMLElement, scroller: HTMLElement | null) {
|
|
209
|
+
const ancestors = this.ancestors(from);
|
|
210
|
+
const end = scroller ? ancestors.indexOf(scroller) : -1;
|
|
211
|
+
const wanted = new Set(ancestors.slice(0, end < 0 ? undefined : end).filter(element => element !== this));
|
|
212
|
+
|
|
213
|
+
// Observing fires once straight away, so re-observing on every bind would rebind every frame.
|
|
214
|
+
for (const element of this.observedAncestors) {
|
|
215
|
+
if (!wanted.has(element)) this.resizeObserver?.unobserve(element);
|
|
216
|
+
}
|
|
217
|
+
for (const element of wanted) {
|
|
218
|
+
if (!this.observedAncestors.has(element)) this.resizeObserver?.observe(element);
|
|
219
|
+
}
|
|
220
|
+
this.observedAncestors = wanted;
|
|
221
|
+
}
|
|
222
|
+
|
|
193
223
|
/** The document scrolls through the window, every other scroller reports its own events. */
|
|
194
224
|
private scrollTarget(scroller: HTMLElement | null): EventTarget | null {
|
|
195
225
|
if (!scroller) return null;
|
|
@@ -154,7 +154,7 @@ describe('<zn-navbar>', () => {
|
|
|
154
154
|
expect(nav.classList.contains('has-hidden')).to.equal(true);
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
it('rounds the last visible nav item when items are hidden
|
|
157
|
+
it('rounds the more button instead of the last visible nav item when items are hidden', async () => {
|
|
158
158
|
const el = await fixture<ZnNavbar>(html`
|
|
159
159
|
<zn-navbar>
|
|
160
160
|
<li>One</li>
|
|
@@ -187,7 +187,8 @@ describe('<zn-navbar>', () => {
|
|
|
187
187
|
expect(nav.classList.contains('has-hidden')).to.equal(true);
|
|
188
188
|
expect(items[0].classList.contains('last-visible')).to.equal(true);
|
|
189
189
|
expect(items[1].classList.contains('hidden')).to.equal(true);
|
|
190
|
-
expect(getComputedStyle(items[0]).borderTopRightRadius).to.equal('
|
|
190
|
+
expect(getComputedStyle(items[0]).borderTopRightRadius).to.equal('0px');
|
|
191
|
+
expect(getComputedStyle(more).borderTopRightRadius).to.equal('6px');
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
it('keeps the left border when only the more button is visible', async () => {
|
|
@@ -2,6 +2,7 @@ import {classMap} from 'lit/directives/class-map.js';
|
|
|
2
2
|
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
3
3
|
import {HasSlotController} from '../../internal/slot';
|
|
4
4
|
import {property, state} from 'lit/decorators.js';
|
|
5
|
+
import {tabContainerSelector} from '../tabs/tabs.component';
|
|
5
6
|
import ZnButton from '../button';
|
|
6
7
|
import ZnCopyButton from '../copy-button';
|
|
7
8
|
import ZnExpandingAction from '../expanding-action';
|
|
@@ -30,6 +31,7 @@ interface TabDefinition {
|
|
|
30
31
|
* @since 1.0
|
|
31
32
|
*
|
|
32
33
|
* @slot - Page content. Use zn-tab for named tabs and header-action/header-actions for header actions.
|
|
34
|
+
* Clicking an element in the content with `tab="<tab id>"` opens that tab.
|
|
33
35
|
* @slot description - Rich subtitle/description content. Falls back to the `summary` attribute when empty.
|
|
34
36
|
* @slot bottom - Content rendered below the navbar row (e.g. chips, filters). Forwarded to the navbar's bottom slot.
|
|
35
37
|
*/
|
|
@@ -68,6 +70,7 @@ export default class ZnPage extends ZnTabs {
|
|
|
68
70
|
const connected = super.connectedCallback();
|
|
69
71
|
window.addEventListener('alt-press', this.handleAltPress);
|
|
70
72
|
window.addEventListener('alt-up', this.handleAltUp);
|
|
73
|
+
this.addEventListener('click', this.handleContentTabClick);
|
|
71
74
|
this.prepareTabs();
|
|
72
75
|
this.refreshExpandingActionsState();
|
|
73
76
|
this.tabObserver = new MutationObserver((mutations) => {
|
|
@@ -90,6 +93,7 @@ export default class ZnPage extends ZnTabs {
|
|
|
90
93
|
super.disconnectedCallback();
|
|
91
94
|
window.removeEventListener('alt-press', this.handleAltPress);
|
|
92
95
|
window.removeEventListener('alt-up', this.handleAltUp);
|
|
96
|
+
this.removeEventListener('click', this.handleContentTabClick);
|
|
93
97
|
this.tabObserver?.disconnect();
|
|
94
98
|
this.tabObserver = null;
|
|
95
99
|
this.actionObserver?.disconnect();
|
|
@@ -280,6 +284,23 @@ export default class ZnPage extends ZnTabs {
|
|
|
280
284
|
}
|
|
281
285
|
}
|
|
282
286
|
|
|
287
|
+
// Only elements whose nearest tab container is this page count, so links inside a nested
|
|
288
|
+
// zn-tabs or zn-page drive that container instead.
|
|
289
|
+
private handleContentTabClick = (event: MouseEvent) => {
|
|
290
|
+
const link = event.composedPath().find((node): node is HTMLElement =>
|
|
291
|
+
node instanceof HTMLElement && node.hasAttribute('tab') && node.closest(tabContainerSelector) === this
|
|
292
|
+
);
|
|
293
|
+
if (!link || link.hasAttribute('disabled')) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const definition = this.tabDefinitions.find(tab => tab.id === link.getAttribute('tab'));
|
|
298
|
+
if (definition) {
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
this.activateTabDefinition(definition, true, true);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
283
304
|
private handleNavigationSelect(event: ZnSelectEvent) {
|
|
284
305
|
const item = event.detail.item as HTMLElement;
|
|
285
306
|
if (this.getNavigationItemPage(item) !== this) {
|
|
@@ -383,16 +404,16 @@ export default class ZnPage extends ZnTabs {
|
|
|
383
404
|
return this.pageHistoryKey;
|
|
384
405
|
}
|
|
385
406
|
|
|
386
|
-
private activateTabDefinition(tab: TabDefinition, store = false) {
|
|
407
|
+
private activateTabDefinition(tab: TabDefinition, store = false, pushHistory = false) {
|
|
387
408
|
if (tab.uri) {
|
|
388
409
|
const navItem = this.findNavItemForUri(tab.uri);
|
|
389
410
|
if (navItem) {
|
|
390
|
-
this.clickTab(navItem, false);
|
|
411
|
+
this.clickTab(navItem, false, pushHistory);
|
|
391
412
|
this.syncNavigationActive(navItem);
|
|
392
413
|
return;
|
|
393
414
|
}
|
|
394
415
|
}
|
|
395
|
-
this.activateTab(tab.id, store);
|
|
416
|
+
this.activateTab(tab.id, store, pushHistory);
|
|
396
417
|
}
|
|
397
418
|
|
|
398
419
|
private findNavItemForUri(uri: string): HTMLElement | null {
|
|
@@ -517,7 +538,7 @@ export default class ZnPage extends ZnTabs {
|
|
|
517
538
|
</div>
|
|
518
539
|
|
|
519
540
|
<div class="page__tabs">
|
|
520
|
-
<div id="content" class="page__content" part="content">
|
|
541
|
+
<div id="content" class="page__content" part="content" @scroll="${this.handlePageScroll}">
|
|
521
542
|
${this.tabDefinitions
|
|
522
543
|
.filter(tab => tab.slotName !== null)
|
|
523
544
|
.map(tab => html`
|
|
@@ -17,9 +17,9 @@ describe('<zn-page>', () => {
|
|
|
17
17
|
await aTimeout(20);
|
|
18
18
|
|
|
19
19
|
const header = el.shadowRoot!.querySelector('[part="header"]')!;
|
|
20
|
-
const caption = el.shadowRoot!.querySelector('
|
|
21
|
-
const description = el.shadowRoot!.querySelector('
|
|
22
|
-
const headerRight = el.shadowRoot!.querySelector('
|
|
20
|
+
const caption = el.shadowRoot!.querySelector('.header__caption')!;
|
|
21
|
+
const description = el.shadowRoot!.querySelector('.header__description')!;
|
|
22
|
+
const headerRight = el.shadowRoot!.querySelector('.header__right')!;
|
|
23
23
|
const navbar = el.shadowRoot!.querySelector('zn-navbar')!;
|
|
24
24
|
const navItems = navbar.shadowRoot!.querySelectorAll('li:not(.more)');
|
|
25
25
|
const actionsSlot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name="actions"]')!;
|
|
@@ -350,6 +350,58 @@ describe('<zn-page>', () => {
|
|
|
350
350
|
expect(innerDynamicPanel.hasAttribute('selected')).to.equal(true);
|
|
351
351
|
});
|
|
352
352
|
|
|
353
|
+
it('opens a tab when an element in the content with a tab attribute is clicked', async () => {
|
|
354
|
+
const el = await fixture<ZnPage>(html`
|
|
355
|
+
<zn-page caption="Content Tab Link">
|
|
356
|
+
<zn-tab caption="Overview" id="overview">
|
|
357
|
+
<zn-button tab="details">Open details</zn-button>
|
|
358
|
+
<zn-button tab="details" disabled>Disabled link</zn-button>
|
|
359
|
+
</zn-tab>
|
|
360
|
+
<zn-tab caption="Details" id="details">Details Content</zn-tab>
|
|
361
|
+
</zn-page>
|
|
362
|
+
`);
|
|
363
|
+
await aTimeout(40);
|
|
364
|
+
|
|
365
|
+
// zn-button.click() skips event dispatch, so click the rendered button as a user would.
|
|
366
|
+
const [link, disabledLink] = Array.from(el.querySelectorAll('zn-button'))
|
|
367
|
+
.map(button => button.shadowRoot!.querySelector<HTMLElement>('button')!);
|
|
368
|
+
|
|
369
|
+
disabledLink.click();
|
|
370
|
+
await aTimeout(40);
|
|
371
|
+
expect(el.getAttribute('active')).to.equal('overview');
|
|
372
|
+
|
|
373
|
+
link.click();
|
|
374
|
+
await aTimeout(40);
|
|
375
|
+
expect(el.getAttribute('active')).to.equal('details');
|
|
376
|
+
expect(el.shadowRoot!.querySelector('#details')!.hasAttribute('selected')).to.equal(true);
|
|
377
|
+
const activeItem = el.shadowRoot!.querySelector<HTMLElement>('zn-navbar li.active');
|
|
378
|
+
expect(activeItem?.getAttribute('tab')).to.equal('details');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('leaves content tab links inside a nested page to that page', async () => {
|
|
382
|
+
const outerPage = await fixture<ZnPage>(html`
|
|
383
|
+
<zn-page caption="Nested Content Tab Link">
|
|
384
|
+
<zn-tab caption="Outer One" id="outer-one">
|
|
385
|
+
<zn-page caption="Nested Content Tab Link Inner" nested>
|
|
386
|
+
<zn-tab caption="Inner One" id="inner-one">
|
|
387
|
+
<zn-button tab="inner-two">Inner link</zn-button>
|
|
388
|
+
</zn-tab>
|
|
389
|
+
<zn-tab caption="Inner Two" id="inner-two">Inner Two Content</zn-tab>
|
|
390
|
+
</zn-page>
|
|
391
|
+
</zn-tab>
|
|
392
|
+
<zn-tab caption="Outer Two" id="inner-two">Outer Two Content</zn-tab>
|
|
393
|
+
</zn-page>
|
|
394
|
+
`);
|
|
395
|
+
await aTimeout(80);
|
|
396
|
+
|
|
397
|
+
const innerPage = outerPage.querySelector<ZnPage>('zn-page')!;
|
|
398
|
+
innerPage.querySelector('zn-button')!.shadowRoot!.querySelector<HTMLElement>('button')!.click();
|
|
399
|
+
await aTimeout(40);
|
|
400
|
+
|
|
401
|
+
expect(innerPage.getAttribute('active')).to.equal('inner-two');
|
|
402
|
+
expect(outerPage.getAttribute('active')).to.equal('outer-one');
|
|
403
|
+
});
|
|
404
|
+
|
|
353
405
|
it('keeps nested page tab selections scoped to the nested page', async () => {
|
|
354
406
|
const outerPage = await fixture<ZnPage>(html`
|
|
355
407
|
<zn-page caption="Outer Page">
|
|
@@ -536,25 +588,25 @@ describe('<zn-page>', () => {
|
|
|
536
588
|
`);
|
|
537
589
|
await aTimeout(20);
|
|
538
590
|
|
|
539
|
-
const
|
|
591
|
+
const content = el.shadowRoot!.querySelector<HTMLElement>('#content')!;
|
|
540
592
|
const header = el.shadowRoot!.querySelector<HTMLElement>('.page__header')!;
|
|
541
593
|
|
|
542
594
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(false);
|
|
543
595
|
|
|
544
|
-
|
|
545
|
-
|
|
596
|
+
content.scrollTop = 10;
|
|
597
|
+
content.dispatchEvent(new Event('scroll'));
|
|
546
598
|
await el.updateComplete;
|
|
547
599
|
|
|
548
600
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(false);
|
|
549
601
|
|
|
550
|
-
|
|
551
|
-
|
|
602
|
+
content.scrollTop = 25;
|
|
603
|
+
content.dispatchEvent(new Event('scroll'));
|
|
552
604
|
await el.updateComplete;
|
|
553
605
|
|
|
554
606
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(true);
|
|
555
607
|
|
|
556
|
-
|
|
557
|
-
|
|
608
|
+
content.scrollTop = 0;
|
|
609
|
+
content.dispatchEvent(new Event('scroll'));
|
|
558
610
|
await el.updateComplete;
|
|
559
611
|
|
|
560
612
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(false);
|
|
@@ -604,18 +656,18 @@ describe('<zn-page>', () => {
|
|
|
604
656
|
await aTimeout(20);
|
|
605
657
|
|
|
606
658
|
const page = el.querySelector<ZnPage>('zn-page')!;
|
|
607
|
-
const
|
|
659
|
+
const content = page.shadowRoot!.querySelector<HTMLElement>('.page__tabs')!;
|
|
608
660
|
const header = page.shadowRoot!.querySelector<HTMLElement>('.page__header')!;
|
|
609
661
|
|
|
610
|
-
expect(getComputedStyle(
|
|
662
|
+
expect(getComputedStyle(content).backgroundColor).to.equal('rgb(12, 34, 56)');
|
|
611
663
|
expect(getComputedStyle(header).backgroundColor).to.equal('rgba(12, 34, 56, 0.95)');
|
|
612
664
|
});
|
|
613
665
|
|
|
614
|
-
it('uses the panel background
|
|
666
|
+
it('uses the panel background for a page flagged as sitting in a panel', async () => {
|
|
615
667
|
const el = await fixture<HTMLElement>(html`
|
|
616
668
|
<div style="--zn-body: 12, 34, 56; --zn-panel: 1, 2, 3;">
|
|
617
669
|
<zn-panel flush>
|
|
618
|
-
<zn-page caption="Page Title" modal>
|
|
670
|
+
<zn-page caption="Page Title" modal panel panel-head>
|
|
619
671
|
<zn-tab caption="Overview">Overview Content</zn-tab>
|
|
620
672
|
</zn-page>
|
|
621
673
|
</zn-panel>
|
|
@@ -624,10 +676,10 @@ describe('<zn-page>', () => {
|
|
|
624
676
|
await aTimeout(20);
|
|
625
677
|
|
|
626
678
|
const page = el.querySelector<ZnPage>('zn-page')!;
|
|
627
|
-
const
|
|
679
|
+
const content = page.shadowRoot!.querySelector<HTMLElement>('.page__tabs')!;
|
|
628
680
|
const header = page.shadowRoot!.querySelector<HTMLElement>('.page__header')!;
|
|
629
681
|
|
|
630
|
-
expect(getComputedStyle(
|
|
682
|
+
expect(getComputedStyle(content).backgroundColor).to.equal('rgb(1, 2, 3)');
|
|
631
683
|
expect(getComputedStyle(header).backgroundColor).to.equal('rgba(1, 2, 3, 0.95)');
|
|
632
684
|
});
|
|
633
685
|
|
|
@@ -39,10 +39,10 @@ describe('<zn-sp>', () => {
|
|
|
39
39
|
expect(getComputedStyle(parent.querySelector('form')!).display).to.equal('block');
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it('should fill its parent container height', async () => {
|
|
42
|
+
it('should fill its parent container height when growing', async () => {
|
|
43
43
|
const parent = await fixture(html`
|
|
44
44
|
<div style="height: 300px;">
|
|
45
|
-
<zn-sp>
|
|
45
|
+
<zn-sp grow>
|
|
46
46
|
<div>Content</div>
|
|
47
47
|
</zn-sp>
|
|
48
48
|
</div>
|
|
@@ -25,7 +25,7 @@ import styles from './tabs.scss';
|
|
|
25
25
|
// Every element that manages its own tabs (ZnTabs and its subclasses). Tab
|
|
26
26
|
// registration must stop at these so an outer container never claims a nested
|
|
27
27
|
// container's tabs.
|
|
28
|
-
const tabContainerSelector = 'zn-tabs, zn-page, zn-page-nav';
|
|
28
|
+
export const tabContainerSelector = 'zn-tabs, zn-page, zn-page-nav';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @summary Short summary of the component's intended use.
|
|
@@ -830,8 +830,8 @@ export default class ZnThemeEditor extends ZincElement {
|
|
|
830
830
|
icon-size="20"
|
|
831
831
|
icon="${this.mode === 'dark' ? 'sun@lu' : 'moon@lu'}"
|
|
832
832
|
data-mode-toggle
|
|
833
|
+
label="${this.mode === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}"
|
|
833
834
|
tooltip="${this.mode === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}"
|
|
834
|
-
aria-label="${this.mode === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}"
|
|
835
835
|
@click="${this._toggleMode}"></zn-button>
|
|
836
836
|
</div>
|
|
837
837
|
<div class="editor__controls-body">
|
|
@@ -881,7 +881,8 @@ export default class ZnThemeEditor extends ZincElement {
|
|
|
881
881
|
icon-size="20"
|
|
882
882
|
icon="${d.icon}@lu"
|
|
883
883
|
data-device="${d.id}"
|
|
884
|
-
|
|
884
|
+
label="${d.label}"
|
|
885
|
+
tooltip="${d.label}"
|
|
885
886
|
aria-pressed="${this.device === d.id ? 'true' : 'false'}"
|
|
886
887
|
@click="${() => this._setDevice(d.id)}"></zn-button>`)}
|
|
887
888
|
</zn-button-group>
|