@kubex/zinc 1.1.160 → 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 +967 -921
- package/dist/vscode.html-custom-data.json +104 -95
- package/dist/web-types.json +206 -196
- package/dist/zn.d.ts +10 -1
- package/dist/zn.min.js +71 -68
- 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 +1 -1
- package/src/components/page/page.test.ts +16 -16
- package/src/components/sp/sp.test.ts +2 -2
- 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
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 () => {
|
|
@@ -538,7 +538,7 @@ export default class ZnPage extends ZnTabs {
|
|
|
538
538
|
</div>
|
|
539
539
|
|
|
540
540
|
<div class="page__tabs">
|
|
541
|
-
<div id="content" class="page__content" part="content">
|
|
541
|
+
<div id="content" class="page__content" part="content" @scroll="${this.handlePageScroll}">
|
|
542
542
|
${this.tabDefinitions
|
|
543
543
|
.filter(tab => tab.slotName !== null)
|
|
544
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"]')!;
|
|
@@ -588,25 +588,25 @@ describe('<zn-page>', () => {
|
|
|
588
588
|
`);
|
|
589
589
|
await aTimeout(20);
|
|
590
590
|
|
|
591
|
-
const
|
|
591
|
+
const content = el.shadowRoot!.querySelector<HTMLElement>('#content')!;
|
|
592
592
|
const header = el.shadowRoot!.querySelector<HTMLElement>('.page__header')!;
|
|
593
593
|
|
|
594
594
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(false);
|
|
595
595
|
|
|
596
|
-
|
|
597
|
-
|
|
596
|
+
content.scrollTop = 10;
|
|
597
|
+
content.dispatchEvent(new Event('scroll'));
|
|
598
598
|
await el.updateComplete;
|
|
599
599
|
|
|
600
600
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(false);
|
|
601
601
|
|
|
602
|
-
|
|
603
|
-
|
|
602
|
+
content.scrollTop = 25;
|
|
603
|
+
content.dispatchEvent(new Event('scroll'));
|
|
604
604
|
await el.updateComplete;
|
|
605
605
|
|
|
606
606
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(true);
|
|
607
607
|
|
|
608
|
-
|
|
609
|
-
|
|
608
|
+
content.scrollTop = 0;
|
|
609
|
+
content.dispatchEvent(new Event('scroll'));
|
|
610
610
|
await el.updateComplete;
|
|
611
611
|
|
|
612
612
|
expect(header.classList.contains('page__header--scrolled-no-navigation')).to.equal(false);
|
|
@@ -656,18 +656,18 @@ describe('<zn-page>', () => {
|
|
|
656
656
|
await aTimeout(20);
|
|
657
657
|
|
|
658
658
|
const page = el.querySelector<ZnPage>('zn-page')!;
|
|
659
|
-
const
|
|
659
|
+
const content = page.shadowRoot!.querySelector<HTMLElement>('.page__tabs')!;
|
|
660
660
|
const header = page.shadowRoot!.querySelector<HTMLElement>('.page__header')!;
|
|
661
661
|
|
|
662
|
-
expect(getComputedStyle(
|
|
662
|
+
expect(getComputedStyle(content).backgroundColor).to.equal('rgb(12, 34, 56)');
|
|
663
663
|
expect(getComputedStyle(header).backgroundColor).to.equal('rgba(12, 34, 56, 0.95)');
|
|
664
664
|
});
|
|
665
665
|
|
|
666
|
-
it('uses the panel background
|
|
666
|
+
it('uses the panel background for a page flagged as sitting in a panel', async () => {
|
|
667
667
|
const el = await fixture<HTMLElement>(html`
|
|
668
668
|
<div style="--zn-body: 12, 34, 56; --zn-panel: 1, 2, 3;">
|
|
669
669
|
<zn-panel flush>
|
|
670
|
-
<zn-page caption="Page Title" modal>
|
|
670
|
+
<zn-page caption="Page Title" modal panel panel-head>
|
|
671
671
|
<zn-tab caption="Overview">Overview Content</zn-tab>
|
|
672
672
|
</zn-page>
|
|
673
673
|
</zn-panel>
|
|
@@ -676,10 +676,10 @@ describe('<zn-page>', () => {
|
|
|
676
676
|
await aTimeout(20);
|
|
677
677
|
|
|
678
678
|
const page = el.querySelector<ZnPage>('zn-page')!;
|
|
679
|
-
const
|
|
679
|
+
const content = page.shadowRoot!.querySelector<HTMLElement>('.page__tabs')!;
|
|
680
680
|
const header = page.shadowRoot!.querySelector<HTMLElement>('.page__header')!;
|
|
681
681
|
|
|
682
|
-
expect(getComputedStyle(
|
|
682
|
+
expect(getComputedStyle(content).backgroundColor).to.equal('rgb(1, 2, 3)');
|
|
683
683
|
expect(getComputedStyle(header).backgroundColor).to.equal('rgba(1, 2, 3, 0.95)');
|
|
684
684
|
});
|
|
685
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>
|
|
@@ -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>
|
|
@@ -19,6 +19,11 @@ function spyOnFrame(el: Element): ThemeCall[] {
|
|
|
19
19
|
return calls;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// zn-button overrides click() without dispatching a DOM event, so fire a real one.
|
|
23
|
+
function press(el: Element) {
|
|
24
|
+
el.dispatchEvent(new MouseEvent('click', {bubbles: true, composed: true, cancelable: true}));
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
describe('<zn-theme-editor>', () => {
|
|
23
28
|
const FIXTURE = html`
|
|
24
29
|
<zn-theme-editor src="about:blank" frame-origin="https://site.example" debounce="10">
|
|
@@ -174,7 +179,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
174
179
|
const el = await fixture(FIXTURE);
|
|
175
180
|
const calls = spyOnFrame(el);
|
|
176
181
|
|
|
177
|
-
el.shadowRoot!.querySelector
|
|
182
|
+
press(el.shadowRoot!.querySelector('[data-device="mobile"]')!);
|
|
178
183
|
await (el as HTMLElement & {updateComplete: Promise<unknown>}).updateComplete;
|
|
179
184
|
|
|
180
185
|
expect((el as HTMLElement & {device: string}).device).to.equal('mobile');
|
|
@@ -192,7 +197,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
192
197
|
detail = (e as CustomEvent<{device: string}>).detail;
|
|
193
198
|
});
|
|
194
199
|
|
|
195
|
-
el.shadowRoot!.querySelector
|
|
200
|
+
press(el.shadowRoot!.querySelector('[data-device="tablet"]')!);
|
|
196
201
|
|
|
197
202
|
await waitUntil(() => detail !== null);
|
|
198
203
|
expect(detail!.device).to.equal('tablet');
|
|
@@ -205,7 +210,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
205
210
|
expect(desktop.getAttribute('aria-pressed')).to.equal('true');
|
|
206
211
|
expect(mobile.getAttribute('aria-pressed')).to.equal('false');
|
|
207
212
|
|
|
208
|
-
mobile
|
|
213
|
+
press(mobile);
|
|
209
214
|
await (el as HTMLElement & {updateComplete: Promise<unknown>}).updateComplete;
|
|
210
215
|
|
|
211
216
|
expect(desktop.getAttribute('aria-pressed')).to.equal('false');
|
|
@@ -216,7 +221,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
216
221
|
const el = await fixture(FIXTURE);
|
|
217
222
|
const calls = spyOnFrame(el);
|
|
218
223
|
|
|
219
|
-
el.shadowRoot!.querySelector
|
|
224
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
220
225
|
|
|
221
226
|
await waitUntil(() => calls.length === 1);
|
|
222
227
|
expect((el as HTMLElement & {mode: string}).mode).to.equal('dark');
|
|
@@ -232,7 +237,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
232
237
|
</zn-theme-editor>`);
|
|
233
238
|
const calls = spyOnFrame(el);
|
|
234
239
|
|
|
235
|
-
el.shadowRoot!.querySelector
|
|
240
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
236
241
|
await waitUntil(() => calls.length === 1);
|
|
237
242
|
|
|
238
243
|
expect(calls[0]['values']).to.deep.equal({background: '#000000'});
|
|
@@ -256,7 +261,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
256
261
|
const el = await fixture(FIXTURE);
|
|
257
262
|
const calls = spyOnFrame(el);
|
|
258
263
|
|
|
259
|
-
el.shadowRoot!.querySelector
|
|
264
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
260
265
|
await waitUntil(() => calls.length === 1);
|
|
261
266
|
|
|
262
267
|
expect(calls[0]['values']).to.deep.equal({radius: '8'});
|
|
@@ -269,7 +274,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
269
274
|
const calls = spyOnFrame(el);
|
|
270
275
|
const input = el.querySelector('zn-input')! as HTMLElement & {value: string};
|
|
271
276
|
|
|
272
|
-
el.shadowRoot!.querySelector
|
|
277
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!); // -> dark, radius '8'
|
|
273
278
|
await waitUntil(() => calls.length === 1);
|
|
274
279
|
|
|
275
280
|
input.value = '99';
|
|
@@ -277,7 +282,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
277
282
|
await waitUntil(() => calls.length === 2);
|
|
278
283
|
expect(calls[1]['values']).to.deep.equal({radius: '99'});
|
|
279
284
|
|
|
280
|
-
el.shadowRoot!.querySelector
|
|
285
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!); // -> light, untouched
|
|
281
286
|
await waitUntil(() => calls.length === 3);
|
|
282
287
|
expect(calls[2]['values']).to.deep.equal({radius: '8'});
|
|
283
288
|
expect(input.value).to.equal('8');
|
|
@@ -290,7 +295,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
290
295
|
</zn-theme-editor>`);
|
|
291
296
|
const calls = spyOnFrame(el);
|
|
292
297
|
|
|
293
|
-
el.shadowRoot!.querySelector
|
|
298
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
294
299
|
await waitUntil(() => calls.length === 1);
|
|
295
300
|
|
|
296
301
|
expect(calls[0]['values']).to.deep.equal({rounded: true});
|
|
@@ -305,7 +310,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
305
310
|
</zn-theme-editor>`);
|
|
306
311
|
const calls = spyOnFrame(el);
|
|
307
312
|
|
|
308
|
-
el.shadowRoot!.querySelector
|
|
313
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
309
314
|
await waitUntil(() => calls.length === 1);
|
|
310
315
|
|
|
311
316
|
expect(calls[0]['values']).to.deep.equal({rounded: true});
|
|
@@ -334,7 +339,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
334
339
|
input.value = '99';
|
|
335
340
|
input.dispatchEvent(new CustomEvent('zn-input', {bubbles: true, composed: true}));
|
|
336
341
|
// toggle before the 50ms push debounce fires - no await in between
|
|
337
|
-
el.shadowRoot!.querySelector
|
|
342
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
338
343
|
|
|
339
344
|
await waitUntil(() => calls.length >= 1);
|
|
340
345
|
// give the (now-cancelled) original debounce timer a chance to prove it's gone
|
|
@@ -572,7 +577,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
572
577
|
expect((el as HTMLElement & {values: {light: Record<string, unknown>; dark: Record<string, unknown>}}).values)
|
|
573
578
|
.to.deep.equal({light: {accent: '#ffffff'}, dark: {accent: '#000000'}});
|
|
574
579
|
|
|
575
|
-
el.shadowRoot!.querySelector
|
|
580
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
576
581
|
await waitUntil(() => calls.length === 1);
|
|
577
582
|
|
|
578
583
|
expect(calls[0]['values']).to.deep.equal({accent: '#000000'});
|
|
@@ -1378,7 +1383,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
1378
1383
|
</zn-theme-editor>`);
|
|
1379
1384
|
spyOnFrame(el);
|
|
1380
1385
|
|
|
1381
|
-
el.shadowRoot!.querySelector
|
|
1386
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
1382
1387
|
|
|
1383
1388
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1384
1389
|
expect(fetchCalls.length).to.equal(0);
|
|
@@ -1400,7 +1405,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
1400
1405
|
</zn-theme-editor>`);
|
|
1401
1406
|
spyOnFrame(el);
|
|
1402
1407
|
|
|
1403
|
-
el.shadowRoot!.querySelector
|
|
1408
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
1404
1409
|
|
|
1405
1410
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1406
1411
|
expect(fetchCalls.length).to.equal(0);
|
|
@@ -1423,7 +1428,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
1423
1428
|
</zn-theme-editor>`);
|
|
1424
1429
|
const calls = spyOnFrame(el);
|
|
1425
1430
|
|
|
1426
|
-
el.shadowRoot!.querySelector
|
|
1431
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
1427
1432
|
|
|
1428
1433
|
await waitUntil(() => calls.length === 1);
|
|
1429
1434
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -1443,8 +1448,8 @@ describe('<zn-theme-editor>', () => {
|
|
|
1443
1448
|
</zn-theme-editor>`);
|
|
1444
1449
|
spyOnFrame(el);
|
|
1445
1450
|
|
|
1446
|
-
el.shadowRoot!.querySelector
|
|
1447
|
-
el.shadowRoot!.querySelector
|
|
1451
|
+
press(el.shadowRoot!.querySelector('[data-mode-toggle]')!);
|
|
1452
|
+
press(el.shadowRoot!.querySelector('[data-device="mobile"]')!);
|
|
1448
1453
|
|
|
1449
1454
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1450
1455
|
expect(fetchCalls.length).to.equal(0);
|
|
@@ -1522,11 +1527,8 @@ describe('<zn-theme-editor>', () => {
|
|
|
1522
1527
|
let fetchCalls: {uri: string; init?: RequestInit}[];
|
|
1523
1528
|
const realFetch = window.fetch;
|
|
1524
1529
|
|
|
1525
|
-
// zn-button overrides click() to call its handler directly without dispatching -
|
|
1526
|
-
// a real click event is needed for the theme-editor's own @click listener to fire.
|
|
1527
1530
|
function clickButton(el: Element) {
|
|
1528
|
-
|
|
1529
|
-
button.dispatchEvent(new MouseEvent('click', {bubbles: true, composed: true, cancelable: true}));
|
|
1531
|
+
press(el.shadowRoot!.querySelector('zn-button.editor__submit')!);
|
|
1530
1532
|
}
|
|
1531
1533
|
|
|
1532
1534
|
beforeEach(() => {
|
|
@@ -1543,7 +1545,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
1543
1545
|
|
|
1544
1546
|
it('renders no button without submit-label', async () => {
|
|
1545
1547
|
const el = await fixture(FIXTURE);
|
|
1546
|
-
expect(el.shadowRoot!.querySelector('zn-button')).to.not.exist;
|
|
1548
|
+
expect(el.shadowRoot!.querySelector('zn-button.editor__submit')).to.not.exist;
|
|
1547
1549
|
});
|
|
1548
1550
|
|
|
1549
1551
|
it('renders the button in the toolbar, not the footer, and leaves the footer slot working', async () => {
|
|
@@ -1553,11 +1555,11 @@ describe('<zn-theme-editor>', () => {
|
|
|
1553
1555
|
<span slot="footer">extra</span>
|
|
1554
1556
|
</zn-theme-editor>`);
|
|
1555
1557
|
|
|
1556
|
-
const button = el.shadowRoot!.querySelector('zn-button')!;
|
|
1558
|
+
const button = el.shadowRoot!.querySelector('zn-button.editor__submit')!;
|
|
1557
1559
|
expect(button).to.exist;
|
|
1558
1560
|
expect(button.textContent?.trim()).to.equal('Save theme');
|
|
1559
|
-
expect(el.shadowRoot!.querySelector('[part="toolbar"] zn-button')).to.equal(button);
|
|
1560
|
-
expect(el.shadowRoot!.querySelector('[part="footer"] zn-button')).to.not.exist;
|
|
1561
|
+
expect(el.shadowRoot!.querySelector('[part="toolbar"] zn-button.editor__submit')).to.equal(button);
|
|
1562
|
+
expect(el.shadowRoot!.querySelector('[part="footer"] zn-button.editor__submit')).to.not.exist;
|
|
1561
1563
|
expect(el.shadowRoot!.querySelector('[part="footer"] slot[name="footer"]')).to.exist;
|
|
1562
1564
|
});
|
|
1563
1565
|
|
|
@@ -1567,7 +1569,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
1567
1569
|
<zn-input name="radius" label="Radius" value="8"></zn-input>
|
|
1568
1570
|
</zn-theme-editor>`);
|
|
1569
1571
|
|
|
1570
|
-
expect(el.shadowRoot!.querySelector('zn-button')).to.exist;
|
|
1572
|
+
expect(el.shadowRoot!.querySelector('zn-button.editor__submit')).to.exist;
|
|
1571
1573
|
expect(el.shadowRoot!.querySelector('[part="footer"]')).to.not.exist;
|
|
1572
1574
|
});
|
|
1573
1575
|
|
|
@@ -1681,7 +1683,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
1681
1683
|
await waitUntil(() => fetchCalls.length === 1);
|
|
1682
1684
|
await (el as HTMLElement & {updateComplete: Promise<unknown>}).updateComplete;
|
|
1683
1685
|
|
|
1684
|
-
const button = el.shadowRoot!.querySelector('zn-button')! as HTMLElement & {loading: boolean};
|
|
1686
|
+
const button = el.shadowRoot!.querySelector('zn-button.editor__submit')! as HTMLElement & {loading: boolean};
|
|
1685
1687
|
expect(button.loading).to.be.true;
|
|
1686
1688
|
|
|
1687
1689
|
clickButton(el); // second click while in flight - must not stack
|
|
@@ -1729,7 +1731,7 @@ describe('<zn-theme-editor>', () => {
|
|
|
1729
1731
|
|
|
1730
1732
|
clickButton(el); // flushes '99' and bypasses the save debounce, but a save is already in flight
|
|
1731
1733
|
await (el as HTMLElement & {updateComplete: Promise<unknown>}).updateComplete;
|
|
1732
|
-
const button = el.shadowRoot!.querySelector('zn-button')! as HTMLElement & {loading: boolean};
|
|
1734
|
+
const button = el.shadowRoot!.querySelector('zn-button.editor__submit')! as HTMLElement & {loading: boolean};
|
|
1733
1735
|
expect(button.loading).to.be.true;
|
|
1734
1736
|
|
|
1735
1737
|
await new Promise(resolve => setTimeout(resolve, 20));
|
|
@@ -198,7 +198,6 @@ describe('<zn-translation-group>', () => {
|
|
|
198
198
|
</zn-translation-group>`);
|
|
199
199
|
// The input tokens live in the theme stylesheet, which the bundle under test does not carry.
|
|
200
200
|
group.style.setProperty('--zn-input-background-color', 'rgb(1, 2, 3)');
|
|
201
|
-
group.style.setProperty('--zn-shadow-x-small', '0 1px 2px rgb(7, 8, 9)');
|
|
202
201
|
group.style.setProperty('--zn-input-border-width', '1px');
|
|
203
202
|
group.style.setProperty('--zn-input-border-color', 'rgb(9, 9, 9)');
|
|
204
203
|
await group.updateComplete;
|
|
@@ -208,7 +207,6 @@ describe('<zn-translation-group>', () => {
|
|
|
208
207
|
const styles = getComputedStyle(select.shadowRoot!.querySelector<HTMLElement>('[part~="combobox"]')!);
|
|
209
208
|
|
|
210
209
|
expect(styles.backgroundColor, 'the fill').to.equal('rgb(1, 2, 3)');
|
|
211
|
-
expect(styles.boxShadow, 'the shadow').to.include('rgb(7, 8, 9)');
|
|
212
210
|
expect(styles.borderTopWidth, 'the border').to.equal('1px');
|
|
213
211
|
expect(styles.borderTopColor, 'the border').to.equal('rgb(9, 9, 9)');
|
|
214
212
|
});
|