@kubex/zinc 1.1.97 → 1.1.99
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/AGENTS.md +5 -0
- package/CLAUDE.md +5 -0
- package/dist/chunks/zn.R2EAU6OS.js +1 -0
- package/dist/custom-elements.json +14747 -12528
- package/dist/vscode.html-custom-data.json +386 -61
- package/dist/web-types.json +1232 -481
- package/dist/zn.d.ts +529 -11
- package/dist/zn.min.js +649 -478
- package/docs/pages/components/background.md +143 -0
- package/docs/pages/components/button.md +11 -1
- package/docs/pages/components/chart.md +165 -3
- package/docs/pages/components/checkbox-group.md +13 -1
- package/docs/pages/components/checkbox.md +59 -1
- package/docs/pages/components/icon-picker.md +8 -0
- package/docs/pages/components/radio-group.md +12 -0
- package/docs/pages/components/radio.md +188 -0
- package/docs/pages/components/remarkd-editor.md +22 -0
- package/docs/pages/components/scroll-container.md +14 -15
- package/docs/pages/components/thumbnail-group.md +216 -0
- package/docs/pages/components/thumbnail.md +349 -0
- package/docs/pages/components/well.md +34 -0
- package/package.json +1 -1
- package/src/components/background/background.component.ts +111 -0
- package/src/components/background/background.scss +236 -0
- package/src/components/background/background.test.ts +120 -0
- package/src/components/background/index.ts +12 -0
- package/src/components/button/button.component.ts +8 -0
- package/src/components/chart/builders.test.ts +190 -1
- package/src/components/chart/builders.ts +246 -4
- package/src/components/chart/chart.component.ts +33 -1
- package/src/components/chart/chart.test.ts +66 -1
- package/src/components/chart/echarts-loader.ts +10 -0
- package/src/components/checkbox/checkbox.component.ts +62 -6
- package/src/components/checkbox/checkbox.scss +1 -0
- package/src/components/checkbox/checkbox.test.ts +56 -0
- package/src/components/checkbox-group/checkbox-group.component.ts +15 -0
- package/src/components/checkbox-group/checkbox-group.scss +23 -5
- package/src/components/checkbox-group/checkbox-group.test.ts +15 -0
- package/src/components/collapsible/collapsible.component.ts +49 -4
- package/src/components/collapsible/collapsible.scss +33 -14
- package/src/components/confirm/confirm.component.ts +3 -3
- package/src/components/confirm/confirm.test.ts +15 -0
- package/src/components/dialog/dialog.component.ts +3 -2
- package/src/components/editor/editor.component.ts +7 -6
- package/src/components/header/header.scss +2 -2
- package/src/components/icon-picker/icon-picker.component.ts +3 -0
- package/src/components/icon-picker/lucide-icons.ts +1756 -0
- package/src/components/navbar/navbar.scss +11 -0
- package/src/components/page/page.scss +1 -1
- package/src/components/radio/radio.component.ts +63 -7
- package/src/components/radio/radio.scss +1 -0
- package/src/components/radio/radio.test.ts +56 -0
- package/src/components/radio-group/radio-group.component.ts +16 -3
- package/src/components/radio-group/radio-group.scss +23 -4
- package/src/components/radio-group/radio-group.test.ts +15 -0
- package/src/components/remarkd-editor/remarkd-editor.component.ts +118 -5
- package/src/components/remarkd-editor/remarkd-editor.scss +8 -1
- package/src/components/remarkd-editor/remarkd-editor.test.ts +135 -0
- package/src/components/scroll-container/scroll-container.component.ts +15 -1
- package/src/components/scroll-container/scroll-container.scss +4 -2
- package/src/components/scroll-container/scroll-container.test.ts +13 -0
- package/src/components/slideout/slideout.component.ts +3 -2
- package/src/components/split-pane/split-pane.scss +21 -15
- package/src/components/thumbnail/index.ts +12 -0
- package/src/components/thumbnail/thumbnail.component.ts +460 -0
- package/src/components/thumbnail/thumbnail.scss +360 -0
- package/src/components/thumbnail/thumbnail.test.ts +379 -0
- package/src/components/thumbnail-group/index.ts +12 -0
- package/src/components/thumbnail-group/thumbnail-group.component.ts +268 -0
- package/src/components/thumbnail-group/thumbnail-group.scss +86 -0
- package/src/components/thumbnail-group/thumbnail-group.test.ts +151 -0
- package/src/components/tile/tile.scss +4 -2
- package/src/components/translation-group/translation-group.test.ts +22 -0
- package/src/components/translations/translations.component.ts +18 -3
- package/src/components/well/well.component.ts +22 -4
- package/src/components/well/well.scss +24 -0
- package/src/components/well/well.test.ts +43 -0
- package/src/form-control.scss +3 -1
- package/src/internal/conditional.ts +2 -2
- package/src/internal/form.ts +2 -1
- package/src/internal/selection-card.ts +19 -0
- package/src/selection-card.scss +187 -0
- package/src/utilities/query.test.ts +18 -1
- package/src/utilities/query.ts +8 -0
- package/src/zinc.ts +3 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
@use "../../wc";
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
position: relative;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
min-height: 0;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
isolation: isolate;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.background {
|
|
13
|
+
position: relative;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
min-height: inherit;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
background-color: var(--_zn-background-color, var(--zn-background-color, transparent));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.background__image,
|
|
23
|
+
.background__overlay,
|
|
24
|
+
.background__floating-icons {
|
|
25
|
+
position: absolute;
|
|
26
|
+
inset: 0;
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.background__image {
|
|
33
|
+
width: 108%;
|
|
34
|
+
height: 108%;
|
|
35
|
+
max-width: none;
|
|
36
|
+
inset: -4%;
|
|
37
|
+
object-fit: cover;
|
|
38
|
+
object-position: var(--zn-background-image-position, center);
|
|
39
|
+
opacity: 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:host([image-strength="soft"]) .background__image {
|
|
43
|
+
opacity: 0.3;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:host([image-strength="medium"]) .background__image {
|
|
47
|
+
opacity: 0.62;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.background__overlay {
|
|
51
|
+
background: linear-gradient(
|
|
52
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
53
|
+
rgb(255 255 255 / 18%),
|
|
54
|
+
rgb(255 255 255 / 4%)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:host([overlay="none"]) .background__overlay {
|
|
59
|
+
display: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:host([overlay="strong"]) .background__overlay {
|
|
63
|
+
background: linear-gradient(
|
|
64
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
65
|
+
rgb(255 255 255 / 48%),
|
|
66
|
+
rgb(255 255 255 / 18%)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:host([overlay-tone="dark"]) .background__overlay {
|
|
71
|
+
background: linear-gradient(
|
|
72
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
73
|
+
rgb(4 12 32 / 28%),
|
|
74
|
+
rgb(4 12 32 / 6%)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:host([overlay-tone="dark"][overlay="strong"]) .background__overlay {
|
|
79
|
+
background: linear-gradient(
|
|
80
|
+
var(--zn-background-overlay-angle, 110deg),
|
|
81
|
+
rgb(4 12 32 / 62%),
|
|
82
|
+
rgb(4 12 32 / 28%)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:host([motion="drift"]) .background__image {
|
|
87
|
+
animation: zn-background-drift 28s ease-in-out infinite alternate;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
:host([motion="breathe"]) .background__image {
|
|
91
|
+
animation: zn-background-breathe 18s ease-in-out infinite;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:host([paused]) .background__image {
|
|
95
|
+
animation-play-state: paused;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.background__floating-icons {
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
color: var(--zn-background-floating-icon-color, currentColor);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.background__floating-icon {
|
|
104
|
+
--_zn-background-icon-opacity-min: 0.01;
|
|
105
|
+
--_zn-background-icon-opacity-max: 0.18;
|
|
106
|
+
position: absolute;
|
|
107
|
+
left: 8%;
|
|
108
|
+
top: 13%;
|
|
109
|
+
opacity: var(--_zn-background-icon-opacity-min);
|
|
110
|
+
animation: zn-background-icon-float 11s ease-in-out infinite alternate both;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.background__floating-icon:nth-child(2) {
|
|
114
|
+
--_zn-background-icon-opacity-min: 0.08;
|
|
115
|
+
--_zn-background-icon-opacity-max: 0.34;
|
|
116
|
+
left: auto;
|
|
117
|
+
right: 9%;
|
|
118
|
+
top: 9%;
|
|
119
|
+
animation-delay: -4s;
|
|
120
|
+
animation-duration: 14s;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.background__floating-icon:nth-child(3) {
|
|
124
|
+
--_zn-background-icon-opacity-min: 0.03;
|
|
125
|
+
--_zn-background-icon-opacity-max: 0.5;
|
|
126
|
+
left: 2%;
|
|
127
|
+
top: 47%;
|
|
128
|
+
animation-delay: -7s;
|
|
129
|
+
animation-duration: 13s;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.background__floating-icon:nth-child(4) {
|
|
133
|
+
--_zn-background-icon-opacity-min: 0.12;
|
|
134
|
+
--_zn-background-icon-opacity-max: 0.38;
|
|
135
|
+
left: auto;
|
|
136
|
+
right: 12%;
|
|
137
|
+
top: 45%;
|
|
138
|
+
animation-delay: -2s;
|
|
139
|
+
animation-duration: 10s;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.background__floating-icon:nth-child(5) {
|
|
143
|
+
--_zn-background-icon-opacity-min: 0.05;
|
|
144
|
+
--_zn-background-icon-opacity-max: 0.29;
|
|
145
|
+
left: 21%;
|
|
146
|
+
top: auto;
|
|
147
|
+
bottom: 4%;
|
|
148
|
+
animation-delay: -5s;
|
|
149
|
+
animation-duration: 15s;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.background__floating-icon:nth-child(6) {
|
|
153
|
+
--_zn-background-icon-opacity-min: 0.01;
|
|
154
|
+
--_zn-background-icon-opacity-max: 0.42;
|
|
155
|
+
left: auto;
|
|
156
|
+
right: 4%;
|
|
157
|
+
top: auto;
|
|
158
|
+
bottom: 3%;
|
|
159
|
+
animation-delay: -9s;
|
|
160
|
+
animation-duration: 12s;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.background__floating-icon:nth-child(7) {
|
|
164
|
+
--_zn-background-icon-opacity-min: 0.09;
|
|
165
|
+
--_zn-background-icon-opacity-max: 0.31;
|
|
166
|
+
left: 42%;
|
|
167
|
+
top: 7%;
|
|
168
|
+
animation-delay: -6s;
|
|
169
|
+
animation-duration: 16s;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.background__floating-icon:nth-child(8) {
|
|
173
|
+
--_zn-background-icon-opacity-min: 0.04;
|
|
174
|
+
--_zn-background-icon-opacity-max: 0.46;
|
|
175
|
+
left: 54%;
|
|
176
|
+
top: auto;
|
|
177
|
+
bottom: 8%;
|
|
178
|
+
animation-delay: -11s;
|
|
179
|
+
animation-duration: 17s;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
:host([paused]) .background__floating-icon {
|
|
183
|
+
animation-play-state: paused;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.background__content {
|
|
187
|
+
position: relative;
|
|
188
|
+
z-index: 1;
|
|
189
|
+
box-sizing: border-box;
|
|
190
|
+
width: 100%;
|
|
191
|
+
height: 100%;
|
|
192
|
+
min-height: inherit;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@keyframes zn-background-drift {
|
|
196
|
+
from {
|
|
197
|
+
transform: scale(1.04) translate3d(-1.2%, -0.6%, 0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
to {
|
|
201
|
+
transform: scale(1.04) translate3d(1.2%, 0.6%, 0);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@keyframes zn-background-breathe {
|
|
206
|
+
0%, 100% {
|
|
207
|
+
transform: scale(1.02);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
50% {
|
|
211
|
+
transform: scale(1.07);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@keyframes zn-background-icon-float {
|
|
216
|
+
from {
|
|
217
|
+
opacity: var(--_zn-background-icon-opacity-min);
|
|
218
|
+
transform: translate3d(-8%, -12%, 0) scale(0.88) rotate(-8deg);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
to {
|
|
222
|
+
opacity: var(--_zn-background-icon-opacity-max);
|
|
223
|
+
transform: translate3d(8%, 12%, 0) scale(1.06) rotate(8deg);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@media (prefers-reduced-motion: reduce) {
|
|
228
|
+
.background__image,
|
|
229
|
+
.background__floating-icon {
|
|
230
|
+
animation: none;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.background__floating-icon {
|
|
234
|
+
opacity: var(--_zn-background-icon-opacity-max);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import '../../../dist/zn.min.js';
|
|
2
|
+
import {expect, fixture, html, waitUntil} from '@open-wc/testing';
|
|
3
|
+
import type ZnBackground from './background.component';
|
|
4
|
+
|
|
5
|
+
const transparentImage = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
|
6
|
+
|
|
7
|
+
describe('<zn-background>', () => {
|
|
8
|
+
it('renders slotted content above the background layers', async () => {
|
|
9
|
+
const el = await fixture<ZnBackground>(html`
|
|
10
|
+
<zn-background><p>Lesson content</p></zn-background>`);
|
|
11
|
+
|
|
12
|
+
const content = el.shadowRoot!.querySelector<HTMLElement>('.background__content')!;
|
|
13
|
+
const slot = content.querySelector<HTMLSlotElement>('slot')!;
|
|
14
|
+
|
|
15
|
+
expect(slot.assignedElements()[0].textContent).to.equal('Lesson content');
|
|
16
|
+
expect(getComputedStyle(content).zIndex).to.equal('1');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('composes the configured colour and decorative image', async () => {
|
|
20
|
+
const el = await fixture<ZnBackground>(html`
|
|
21
|
+
<zn-background color="#123456" image=${transparentImage}></zn-background>`);
|
|
22
|
+
|
|
23
|
+
const base = el.shadowRoot!.querySelector<HTMLElement>('.background')!;
|
|
24
|
+
const image = el.shadowRoot!.querySelector<HTMLImageElement>('.background__image')!;
|
|
25
|
+
|
|
26
|
+
expect(getComputedStyle(base).backgroundColor).to.equal('rgb(18, 52, 86)');
|
|
27
|
+
expect(image.src).to.equal(transparentImage);
|
|
28
|
+
expect(image.alt).to.equal('');
|
|
29
|
+
expect(image.getAttribute('part')).to.equal('image');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('does not render an image element without an image URL', async () => {
|
|
33
|
+
const el = await fixture<ZnBackground>(html`<zn-background></zn-background>`);
|
|
34
|
+
|
|
35
|
+
expect(el.shadowRoot!.querySelector('.background__image')).to.not.exist;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('applies soft, medium and full image strengths', async () => {
|
|
39
|
+
const el = await fixture<ZnBackground>(html`
|
|
40
|
+
<zn-background image=${transparentImage} image-strength="soft"></zn-background>`);
|
|
41
|
+
const image = el.shadowRoot!.querySelector<HTMLImageElement>('.background__image')!;
|
|
42
|
+
|
|
43
|
+
expect(getComputedStyle(image).opacity).to.equal('0.3');
|
|
44
|
+
el.imageStrength = 'medium';
|
|
45
|
+
await el.updateComplete;
|
|
46
|
+
expect(getComputedStyle(image).opacity).to.equal('0.62');
|
|
47
|
+
el.imageStrength = 'full';
|
|
48
|
+
await el.updateComplete;
|
|
49
|
+
expect(getComputedStyle(image).opacity).to.equal('1');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('supports drift and breathe motion and can pause it', async () => {
|
|
53
|
+
const el = await fixture<ZnBackground>(html`
|
|
54
|
+
<zn-background image=${transparentImage} motion="drift"></zn-background>`);
|
|
55
|
+
const image = el.shadowRoot!.querySelector<HTMLImageElement>('.background__image')!;
|
|
56
|
+
|
|
57
|
+
expect(getComputedStyle(image).animationName).to.equal('zn-background-drift');
|
|
58
|
+
el.motion = 'breathe';
|
|
59
|
+
el.paused = true;
|
|
60
|
+
await el.updateComplete;
|
|
61
|
+
expect(getComputedStyle(image).animationName).to.equal('zn-background-breathe');
|
|
62
|
+
expect(getComputedStyle(image).animationPlayState).to.equal('paused');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('supports none, soft and strong light or dark overlays', async () => {
|
|
66
|
+
const el = await fixture<ZnBackground>(html`
|
|
67
|
+
<zn-background overlay="none"></zn-background>`);
|
|
68
|
+
const overlay = el.shadowRoot!.querySelector<HTMLElement>('.background__overlay')!;
|
|
69
|
+
|
|
70
|
+
expect(getComputedStyle(overlay).display).to.equal('none');
|
|
71
|
+
el.overlay = 'soft';
|
|
72
|
+
await el.updateComplete;
|
|
73
|
+
const lightGradient = getComputedStyle(overlay).backgroundImage;
|
|
74
|
+
expect(lightGradient).to.contain('255, 255, 255');
|
|
75
|
+
|
|
76
|
+
el.overlay = 'strong';
|
|
77
|
+
el.overlayTone = 'dark';
|
|
78
|
+
await el.updateComplete;
|
|
79
|
+
const darkGradient = getComputedStyle(overlay).backgroundImage;
|
|
80
|
+
expect(darkGradient).to.contain('4, 12, 32');
|
|
81
|
+
expect(darkGradient).to.not.equal(lightGradient);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders up to eight comma-separated floating Zinc icons', async () => {
|
|
85
|
+
const el = await fixture<ZnBackground>(html`
|
|
86
|
+
<zn-background
|
|
87
|
+
floating-icons="school, lightbulb, quiz, psychology, star, rocket, science, public, hidden">
|
|
88
|
+
</zn-background>`);
|
|
89
|
+
const icons = el.shadowRoot!.querySelectorAll<HTMLElement>('.background__floating-icon');
|
|
90
|
+
|
|
91
|
+
expect(icons).to.have.lengthOf(8);
|
|
92
|
+
expect(icons[0].getAttribute('src')).to.equal('school');
|
|
93
|
+
expect(icons[7].getAttribute('src')).to.equal('public');
|
|
94
|
+
expect(el.shadowRoot!.querySelector('.background__floating-icons')!.getAttribute('aria-hidden')).to.equal('true');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('sizes floating icons to five percent of the background width', async () => {
|
|
98
|
+
const el = await fixture<ZnBackground>(html`
|
|
99
|
+
<zn-background floating-icons="school" style="width: 1000px;"></zn-background>`);
|
|
100
|
+
const icon = el.shadowRoot!.querySelector<HTMLElement & {size: number}>('.background__floating-icon')!;
|
|
101
|
+
|
|
102
|
+
await waitUntil(() => icon.size === 50);
|
|
103
|
+
expect(icon.size).to.equal(50);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('caps floating icons at 150 pixels and keeps opacity within one to fifty percent', async () => {
|
|
107
|
+
const el = await fixture<ZnBackground>(html`
|
|
108
|
+
<zn-background floating-icons="school,lightbulb" style="width: 4000px;"></zn-background>`);
|
|
109
|
+
const icons = [...el.shadowRoot!.querySelectorAll<HTMLElement & {size: number}>('.background__floating-icon')];
|
|
110
|
+
|
|
111
|
+
await waitUntil(() => icons[0].size === 150);
|
|
112
|
+
expect(icons[0].size).to.equal(150);
|
|
113
|
+
icons.forEach(icon => {
|
|
114
|
+
const styles = getComputedStyle(icon);
|
|
115
|
+
expect(Number(styles.getPropertyValue('--_zn-background-icon-opacity-min'))).to.be.at.least(0.01);
|
|
116
|
+
expect(Number(styles.getPropertyValue('--_zn-background-icon-opacity-max'))).to.be.at.most(0.5);
|
|
117
|
+
expect(styles.animationName).to.equal('zn-background-icon-float');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ZnBackground from './background.component';
|
|
2
|
+
|
|
3
|
+
export * from './background.component';
|
|
4
|
+
export default ZnBackground;
|
|
5
|
+
|
|
6
|
+
ZnBackground.define('zn-background');
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'zn-background': ZnBackground;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -121,6 +121,9 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
121
121
|
// Tooltip Specific
|
|
122
122
|
@property() tooltip: string;
|
|
123
123
|
|
|
124
|
+
/** Accessible name for the button. Icon-only buttons fall back to `tooltip`. */
|
|
125
|
+
@property() label: string;
|
|
126
|
+
|
|
124
127
|
// Auto Click Specific
|
|
125
128
|
@property({type: Boolean, attribute: 'auto-click'}) autoClick = false;
|
|
126
129
|
@property({type: Number, attribute: 'auto-click-delay'}) autoClickDelay = 2000;
|
|
@@ -369,6 +372,10 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
369
372
|
library="${ifDefined(this.iconLibrary)}"></zn-icon>`
|
|
370
373
|
: '';
|
|
371
374
|
const tag = isLink ? literal`a` : literal`button`;
|
|
375
|
+
// An icon-only button has no text to name it, so the tooltip doubles as the
|
|
376
|
+
// accessible name. Buttons with content keep their text as the name.
|
|
377
|
+
const hasContent = this.hasSlotController.test('[default]') || !!this.content;
|
|
378
|
+
const ariaLabel = this.label || (hasContent ? undefined : this.tooltip);
|
|
372
379
|
const iconButtonStyles = this.iconButton ? {
|
|
373
380
|
'--icon-button-color': this.getIconButtonColor(this.color),
|
|
374
381
|
'--icon-button-hover-color': this.getIconButtonColor(this.hoverColor || 'primary')
|
|
@@ -412,6 +419,7 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
412
419
|
data-target="${ifDefined(isLink ? this.dataTarget : undefined)}"
|
|
413
420
|
rel="${ifDefined(isLink ? this.rel : undefined)}"
|
|
414
421
|
gaid="${ifDefined(this.gaid)}"
|
|
422
|
+
aria-label="${ifDefined(ariaLabel)}"
|
|
415
423
|
data-notification="${ifDefined(this.notification)}"
|
|
416
424
|
disabled="${this.disabled || nothing}"
|
|
417
425
|
style=${styleMap(iconButtonStyles)}
|
|
@@ -3,8 +3,16 @@ import {
|
|
|
3
3
|
buildAreaOption,
|
|
4
4
|
buildBarOption,
|
|
5
5
|
type BuilderProps,
|
|
6
|
+
buildFunnelOption,
|
|
7
|
+
buildGaugeOption,
|
|
8
|
+
buildHeatmapOption,
|
|
6
9
|
buildLineOption,
|
|
10
|
+
buildPieOption,
|
|
11
|
+
buildRadarOption,
|
|
7
12
|
buildSankeyOption,
|
|
13
|
+
buildScatterOption,
|
|
14
|
+
buildSunburstOption,
|
|
15
|
+
buildTreemapOption,
|
|
8
16
|
} from './builders';
|
|
9
17
|
import { expect } from '@open-wc/testing';
|
|
10
18
|
|
|
@@ -145,6 +153,187 @@ describe('buildAreaOption', () => {
|
|
|
145
153
|
});
|
|
146
154
|
});
|
|
147
155
|
|
|
156
|
+
describe('buildPieOption', () => {
|
|
157
|
+
const pieProps: BuilderProps = {
|
|
158
|
+
type: 'pie',
|
|
159
|
+
data: [{ name: 'Insights', data: [8, 1] }],
|
|
160
|
+
categories: ['Configuration suggestion', 'Insecure configuration'],
|
|
161
|
+
stacked: false,
|
|
162
|
+
enableAnimations: false,
|
|
163
|
+
datapointSize: 1,
|
|
164
|
+
theme: 'light',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
it('maps categories and numeric values into pie slices', () => {
|
|
168
|
+
const opt = buildPieOption(pieProps);
|
|
169
|
+
const series = (opt.series as any[])[0];
|
|
170
|
+
expect(series.type).to.equal('pie');
|
|
171
|
+
expect(series.name).to.equal('Insights');
|
|
172
|
+
expect(series.data).to.deep.equal([
|
|
173
|
+
{ name: 'Configuration suggestion', value: 8 },
|
|
174
|
+
{ name: 'Insecure configuration', value: 1 },
|
|
175
|
+
]);
|
|
176
|
+
expect(series.radius).to.deep.equal([0, '70%']);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('uses inner and outer radii for a donut', () => {
|
|
180
|
+
const opt = buildPieOption({
|
|
181
|
+
...pieProps,
|
|
182
|
+
type: 'donut',
|
|
183
|
+
innerRadius: '45%',
|
|
184
|
+
outerRadius: '80%',
|
|
185
|
+
});
|
|
186
|
+
expect((opt.series as any[])[0].radius).to.deep.equal(['45%', '80%']);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('accepts named ECharts-style slices and maps their colors', () => {
|
|
190
|
+
const opt = buildPieOption({
|
|
191
|
+
...pieProps,
|
|
192
|
+
data: [{
|
|
193
|
+
name: 'Insights',
|
|
194
|
+
data: [
|
|
195
|
+
{ name: 'Moderate', value: 4, color: '#f5a623' },
|
|
196
|
+
{ name: 'Low', value: 5 },
|
|
197
|
+
],
|
|
198
|
+
}],
|
|
199
|
+
});
|
|
200
|
+
expect((opt.series as any[])[0].data).to.deep.equal([
|
|
201
|
+
{ name: 'Moderate', value: 4, itemStyle: { color: '#f5a623' } },
|
|
202
|
+
{ name: 'Low', value: 5 },
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('uses item tooltips, optional labels, and value suffixes', () => {
|
|
207
|
+
const opt = buildPieOption({ ...pieProps, showLabels: true, yAxisAppend: '%' });
|
|
208
|
+
const formatter = (opt.tooltip as any).valueFormatter as (value: number) => string;
|
|
209
|
+
expect((opt.tooltip as any).trigger).to.equal('item');
|
|
210
|
+
expect((opt.series as any[])[0].label.show).to.equal(true);
|
|
211
|
+
expect(formatter(42)).to.equal('42%');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('has no cartesian axes or grid', () => {
|
|
215
|
+
const opt = buildPieOption(pieProps);
|
|
216
|
+
expect(opt.xAxis).to.be.undefined;
|
|
217
|
+
expect(opt.yAxis).to.be.undefined;
|
|
218
|
+
expect(opt.grid).to.be.undefined;
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('additional native chart builders', () => {
|
|
223
|
+
it('builds a scatter chart with numeric axes and coordinate normalization', () => {
|
|
224
|
+
const opt = buildScatterOption({
|
|
225
|
+
...baseProps,
|
|
226
|
+
type: 'scatter',
|
|
227
|
+
datapointSize: 12,
|
|
228
|
+
data: [{ name: 'Latency', data: [[1, 20], { x: 2, y: 35 }] }],
|
|
229
|
+
});
|
|
230
|
+
expect((opt.series as any[])[0]).to.include({ type: 'scatter', symbolSize: 12 });
|
|
231
|
+
expect((opt.series as any[])[0].data).to.deep.equal([[1, 20], [2, 35]]);
|
|
232
|
+
expect((opt.xAxis as any).type).to.equal('value');
|
|
233
|
+
expect((opt.yAxis as any).type).to.equal('value');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('builds radar indicators and values from categories and series', () => {
|
|
237
|
+
const opt = buildRadarOption({
|
|
238
|
+
...baseProps,
|
|
239
|
+
type: 'radar',
|
|
240
|
+
categories: ['Security', 'Performance', 'Reliability'],
|
|
241
|
+
data: [
|
|
242
|
+
{ name: 'Current', data: [80, 65, 90] },
|
|
243
|
+
{ name: 'Target', data: [90, 90, 95] },
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
expect((opt.radar as any).indicator).to.deep.equal([
|
|
247
|
+
{ name: 'Security' },
|
|
248
|
+
{ name: 'Performance' },
|
|
249
|
+
{ name: 'Reliability' },
|
|
250
|
+
]);
|
|
251
|
+
expect((opt.series as any[])[0].data[0]).to.deep.equal({
|
|
252
|
+
name: 'Current',
|
|
253
|
+
value: [80, 65, 90],
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('uses explicit radar indicators when supplied', () => {
|
|
258
|
+
const indicators = [
|
|
259
|
+
{ name: 'Security', max: 100 },
|
|
260
|
+
{ name: 'Latency', max: 500, min: 0 },
|
|
261
|
+
];
|
|
262
|
+
const opt = buildRadarOption({ ...baseProps, type: 'radar', indicators });
|
|
263
|
+
expect((opt.radar as any).indicator).to.deep.equal(indicators);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('builds a gauge with bounds, progress, and a value suffix', () => {
|
|
267
|
+
const opt = buildGaugeOption({
|
|
268
|
+
...baseProps,
|
|
269
|
+
type: 'gauge',
|
|
270
|
+
data: [{ name: 'Health', data: [82] }],
|
|
271
|
+
minValue: 20,
|
|
272
|
+
maxValue: 120,
|
|
273
|
+
yAxisAppend: '%',
|
|
274
|
+
});
|
|
275
|
+
const series = (opt.series as any[])[0];
|
|
276
|
+
expect(series).to.include({ type: 'gauge', min: 20, max: 120 });
|
|
277
|
+
expect(series.data).to.deep.equal([{ name: 'Health', value: 82 }]);
|
|
278
|
+
expect(series.detail.formatter(82)).to.equal('82%');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('builds funnel stages from categories and numeric values', () => {
|
|
282
|
+
const opt = buildFunnelOption({
|
|
283
|
+
...baseProps,
|
|
284
|
+
type: 'funnel',
|
|
285
|
+
categories: ['Visitors', 'Trials', 'Customers'],
|
|
286
|
+
data: [{ name: 'Conversion', data: [1000, 320, 85] }],
|
|
287
|
+
});
|
|
288
|
+
const series = (opt.series as any[])[0];
|
|
289
|
+
expect(series.type).to.equal('funnel');
|
|
290
|
+
expect(series.data).to.deep.equal([
|
|
291
|
+
{ name: 'Visitors', value: 1000 },
|
|
292
|
+
{ name: 'Trials', value: 320 },
|
|
293
|
+
{ name: 'Customers', value: 85 },
|
|
294
|
+
]);
|
|
295
|
+
expect(series.label.show).to.equal(true);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('builds a categorical heatmap and derives its visual range', () => {
|
|
299
|
+
const data = [[0, 0, 5], [1, 0, 12], [0, 1, 3], [1, 1, 9]];
|
|
300
|
+
const opt = buildHeatmapOption({
|
|
301
|
+
...baseProps,
|
|
302
|
+
type: 'heatmap',
|
|
303
|
+
categories: ['Mon', 'Tue'],
|
|
304
|
+
yCategories: ['API', 'Web'],
|
|
305
|
+
data: [{ name: 'Requests', data }],
|
|
306
|
+
});
|
|
307
|
+
expect((opt.xAxis as any).data).to.deep.equal(['Mon', 'Tue']);
|
|
308
|
+
expect((opt.yAxis as any).data).to.deep.equal(['API', 'Web']);
|
|
309
|
+
expect((opt.visualMap as any)).to.include({ min: 3, max: 12 });
|
|
310
|
+
expect((opt.series as any[])[0].data).to.equal(data);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('passes hierarchical data to treemap and sunburst charts', () => {
|
|
314
|
+
const hierarchy = [{
|
|
315
|
+
name: 'Security',
|
|
316
|
+
children: [
|
|
317
|
+
{ name: 'Configuration', value: 8 },
|
|
318
|
+
{ name: 'Insecure', value: 1 },
|
|
319
|
+
],
|
|
320
|
+
}];
|
|
321
|
+
const data = [{ name: 'Insights', data: hierarchy }];
|
|
322
|
+
const treemap = buildTreemapOption({ ...baseProps, type: 'treemap', data });
|
|
323
|
+
const sunburst = buildSunburstOption({
|
|
324
|
+
...baseProps,
|
|
325
|
+
type: 'sunburst',
|
|
326
|
+
data,
|
|
327
|
+
innerRadius: '10%',
|
|
328
|
+
outerRadius: '85%',
|
|
329
|
+
});
|
|
330
|
+
expect((treemap.series as any[])[0].data).to.equal(hierarchy);
|
|
331
|
+
expect((treemap.series as any[])[0].label.show).to.equal(true);
|
|
332
|
+
expect((sunburst.series as any[])[0].data).to.equal(hierarchy);
|
|
333
|
+
expect((sunburst.series as any[])[0].radius).to.deep.equal(['10%', '85%']);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
148
337
|
describe('buildSankeyOption', () => {
|
|
149
338
|
const sankeyProps: BuilderProps = {
|
|
150
339
|
type: 'sankey',
|
|
@@ -208,4 +397,4 @@ describe('buildSankeyOption', () => {
|
|
|
208
397
|
const opt = buildSankeyOption(sankeyProps);
|
|
209
398
|
expect((opt.tooltip as any).trigger).to.equal('item');
|
|
210
399
|
});
|
|
211
|
-
});
|
|
400
|
+
});
|