@fusion.dev/helix-headless 0.152.0 → 0.154.0
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/custom-elements.json +1143 -62
- package/dist/area-chart/root/area-chart-root.d.ts +16 -1
- package/dist/area-chart/root/area-chart-root.js +3 -3
- package/dist/badge/root/badge-root.d.ts +15 -2
- package/dist/badge/root/badge-root.js +20 -3
- package/dist/bar-chart/root/bar-chart-root.d.ts +16 -1
- package/dist/bar-chart/root/bar-chart-root.js +3 -3
- package/dist/card/root/card-root.d.ts +31 -11
- package/dist/card/root/card-root.js +25 -11
- package/dist/code/root/code-root.d.ts +7 -0
- package/dist/code/root/code-root.js +1 -1
- package/dist/code-block/root/code-block-root.d.ts +37 -1
- package/dist/code-block/root/code-block-root.js +38 -5
- package/dist/empty/root/empty-root.d.ts +19 -1
- package/dist/empty/root/empty-root.js +13 -4
- package/dist/heatmap/root/heatmap-root.d.ts +3 -0
- package/dist/heatmap/root/heatmap-root.js +2 -8
- package/dist/icon/fetch.d.ts +20 -0
- package/dist/icon/fetch.js +70 -0
- package/dist/icon/root/icon-root.d.ts +27 -5
- package/dist/icon/root/icon-root.js +41 -17
- package/dist/line-chart/root/line-chart-root.d.ts +16 -1
- package/dist/line-chart/root/line-chart-root.js +3 -3
- package/dist/loader/root/loader-root.d.ts +5 -0
- package/dist/progress/root/progress-root.d.ts +16 -1
- package/dist/progress/root/progress-root.js +12 -4
- package/dist/quote/root/quote-root.d.ts +17 -1
- package/dist/quote/root/quote-root.js +15 -2
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +16 -1
- package/dist/scatter-chart/root/scatter-chart-root.js +3 -3
- package/dist/skeleton/root/skeleton-root.d.ts +7 -0
- package/dist/skeleton/root/skeleton-root.js +4 -1
- package/dist/stat/group/stat-group.d.ts +23 -4
- package/dist/stat/group/stat-group.js +13 -1
- package/dist/stat/root/stat-root.d.ts +46 -1
- package/dist/stat/root/stat-root.js +29 -5
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1434 -62
- package/types/vue.d.ts +297 -24
- package/types/words.d.ts +69 -0
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { ArrivalController, customElement, HelixElement, headingIn, headingLevel, slottedWords, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { ArrivalController, customElement, HelixElement, headingIn, headingLevel, slotFilled, slottedWords, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
|
|
@@ -13,6 +13,11 @@ let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
|
|
|
13
13
|
this.heading = '';
|
|
14
14
|
this.noAnnounce = false;
|
|
15
15
|
this.looseHeading = false;
|
|
16
|
+
this.readRegions = () => {
|
|
17
|
+
const filled = (name) => slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`));
|
|
18
|
+
this.toggleState('has-icon', filled('icon'));
|
|
19
|
+
this.toggleState('has-actions', filled('actions'));
|
|
20
|
+
};
|
|
16
21
|
this.arrival = new ArrivalController(this, {
|
|
17
22
|
arriving: () => !this.serverRendered && document.readyState === 'complete',
|
|
18
23
|
text: () => [slottedWords(this, ['heading']) || this.heading.trim(), slottedWords(this, [''])]
|
|
@@ -31,6 +36,10 @@ let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
|
|
|
31
36
|
super.firstUpdated(changed);
|
|
32
37
|
this.finishHydration();
|
|
33
38
|
}
|
|
39
|
+
updated(changed) {
|
|
40
|
+
super.updated(changed);
|
|
41
|
+
this.readRegions();
|
|
42
|
+
}
|
|
34
43
|
renderHeading(hidden) {
|
|
35
44
|
const level = headingLevel(this.headingLevel);
|
|
36
45
|
const slot = html `<slot
|
|
@@ -58,10 +67,10 @@ let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
|
|
|
58
67
|
render() {
|
|
59
68
|
const say = () => this.arrival.say();
|
|
60
69
|
const held = this.arrival.said !== '' && !this.noAnnounce;
|
|
61
|
-
return html `<slot name="icon"></slot>
|
|
70
|
+
return html `<slot name="icon" part="icon" @slotchange=${this.readRegions}></slot>
|
|
62
71
|
${this.renderHeading(held)}
|
|
63
|
-
<slot aria-hidden=${held ? 'true' : nothing} @slotchange=${say}></slot>
|
|
64
|
-
<slot name="actions"></slot>
|
|
72
|
+
<slot aria-hidden=${held ? 'true' : nothing} part="message" @slotchange=${say}></slot>
|
|
73
|
+
<slot name="actions" part="actions" @slotchange=${this.readRegions}></slot>
|
|
65
74
|
${this.noAnnounce
|
|
66
75
|
? nothing
|
|
67
76
|
: html `<span part="live" role="status">${this.arrival.said}</span>`}`;
|
|
@@ -328,6 +328,9 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
|
|
|
328
328
|
* in a browser is still known, and still empty: a `for` naming no table
|
|
329
329
|
* should say so rather than sit blank, which is why this asks
|
|
330
330
|
* `searchableById` and not `elementById`.
|
|
331
|
+
*
|
|
332
|
+
* `readingsKnown` asks it for every chart, and for `data` too, which a
|
|
333
|
+
* server does not draw.
|
|
331
334
|
*/
|
|
332
335
|
private get sourceKnown();
|
|
333
336
|
/**
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, byRow, CHART_PLOT, commaList, customElement, drawTooltip, formatWords, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, pressedValue, rangeAdd, requestValue, StatusController,
|
|
7
|
+
import { AriaForwardController, byRow, CHART_PLOT, commaList, customElement, drawTooltip, formatWords, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, pressedValue, rangeAdd, requestValue, StatusController, words, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import '../../tooltip/index.js';
|
|
@@ -110,13 +110,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
|
|
|
110
110
|
return byRow(series, this.sourceRows.length).map((point) => point ?? { value: undefined });
|
|
111
111
|
}
|
|
112
112
|
get sourceKnown() {
|
|
113
|
-
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
if (this.hydrating) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
return searchableById(this);
|
|
113
|
+
return this.readingsKnown;
|
|
120
114
|
}
|
|
121
115
|
get rowCount() {
|
|
122
116
|
return Math.max(1, Math.floor(this.rows) || 1);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HelixIcon } from '@fusion.dev/helix-icons';
|
|
2
|
+
/**
|
|
3
|
+
* Reads an SVG file's text as an icon, or `undefined` where it is no SVG.
|
|
4
|
+
*
|
|
5
|
+
* **What could run is taken out**, because the markup is drawn as it is: every
|
|
6
|
+
* `<script>` and `<foreignObject>`, every `on…` attribute, every link to a
|
|
7
|
+
* `javascript:` URL, and every animation that writes a link or a handler. The
|
|
8
|
+
* root's paint attributes are kept on a group around the shapes, and its
|
|
9
|
+
* `viewBox`, or its width and height, is the box.
|
|
10
|
+
*
|
|
11
|
+
* @param text The file's text.
|
|
12
|
+
* @param name What the icon is known by, its URL.
|
|
13
|
+
*/
|
|
14
|
+
export declare const readIconFile: (text: string, name: string) => HelixIcon | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The icon in the SVG file at `src`: at once where it has been fetched, or a
|
|
17
|
+
* promise of it the first time, fetched once a page however many elements ask.
|
|
18
|
+
* `undefined` where the file could not be fetched or is no SVG.
|
|
19
|
+
*/
|
|
20
|
+
export declare const fetchIcon: (src: string) => HelixIcon | Promise<HelixIcon | undefined> | undefined;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const PAINT = [
|
|
2
|
+
'fill',
|
|
3
|
+
'fill-rule',
|
|
4
|
+
'stroke',
|
|
5
|
+
'stroke-linecap',
|
|
6
|
+
'stroke-linejoin',
|
|
7
|
+
'stroke-miterlimit',
|
|
8
|
+
'stroke-width',
|
|
9
|
+
];
|
|
10
|
+
const KEY = Symbol.for('helix.icon-fetched');
|
|
11
|
+
const fetched = () => {
|
|
12
|
+
const held = globalThis;
|
|
13
|
+
let found = held[KEY];
|
|
14
|
+
if (!found) {
|
|
15
|
+
found = { answers: new Map() };
|
|
16
|
+
held[KEY] = found;
|
|
17
|
+
}
|
|
18
|
+
return found;
|
|
19
|
+
};
|
|
20
|
+
export const readIconFile = (text, name) => {
|
|
21
|
+
const parsed = new DOMParser().parseFromString(text, 'image/svg+xml');
|
|
22
|
+
const root = parsed.documentElement;
|
|
23
|
+
if (root?.localName !== 'svg' || parsed.getElementsByTagName('parsererror').length > 0) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
for (const unsafe of [...root.querySelectorAll('script, foreignObject')]) {
|
|
27
|
+
unsafe.remove();
|
|
28
|
+
}
|
|
29
|
+
for (const animation of [...root.querySelectorAll('animate, set')]) {
|
|
30
|
+
const target = (animation.getAttribute('attributeName') ?? '').toLowerCase();
|
|
31
|
+
if (target.endsWith('href') || target.startsWith('on')) {
|
|
32
|
+
animation.remove();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const element of [root, ...root.querySelectorAll('*')]) {
|
|
36
|
+
for (const attribute of [...element.attributes]) {
|
|
37
|
+
const link = attribute.localName === 'href' && /^\s*javascript:/i.test(attribute.value);
|
|
38
|
+
if (attribute.localName.toLowerCase().startsWith('on') || link) {
|
|
39
|
+
element.removeAttribute(attribute.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const width = Number.parseFloat(root.getAttribute('width') ?? '') || 24;
|
|
44
|
+
const height = Number.parseFloat(root.getAttribute('height') ?? '') || width;
|
|
45
|
+
const viewBox = root.getAttribute('viewBox') ?? `0 0 ${width} ${height}`;
|
|
46
|
+
const paint = PAINT.filter((one) => root.hasAttribute(one))
|
|
47
|
+
.map((one) => `${one}="${(root.getAttribute(one) ?? '').replaceAll('"', '"')}"`)
|
|
48
|
+
.join(' ');
|
|
49
|
+
const shapes = [...root.childNodes]
|
|
50
|
+
.map((node) => (node instanceof Element ? new XMLSerializer().serializeToString(node) : ''))
|
|
51
|
+
.join('');
|
|
52
|
+
return { content: paint ? `<g ${paint}>${shapes}</g>` : shapes, name, viewBox };
|
|
53
|
+
};
|
|
54
|
+
export const fetchIcon = (src) => {
|
|
55
|
+
const held = fetched();
|
|
56
|
+
if (held.answers.has(src)) {
|
|
57
|
+
return held.answers.get(src);
|
|
58
|
+
}
|
|
59
|
+
const pending = fetch(src)
|
|
60
|
+
.then((response) => (response.ok ? response.text() : undefined))
|
|
61
|
+
.then((text) => (text === undefined ? undefined : readIconFile(text, src)))
|
|
62
|
+
.catch(() => undefined);
|
|
63
|
+
held.answers.set(src, pending);
|
|
64
|
+
void pending.then((icon) => {
|
|
65
|
+
if (held.answers.get(src) === pending) {
|
|
66
|
+
held.answers.set(src, icon);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return pending;
|
|
70
|
+
};
|
|
@@ -27,8 +27,16 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
27
27
|
* a glyph that came later is drawn. A glyph registered after the element
|
|
28
28
|
* rendered is drawn then.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* **Or point `src` at an SVG file**, such as `src="/icons/rocket.svg"`, and
|
|
31
|
+
* the element fetches it and draws its shapes in `currentColor` where the file
|
|
32
|
+
* does not color them. A file is fetched once a page, however many icons name
|
|
33
|
+
* it, and `load` fires when it is drawn. Scripts, event handlers and
|
|
34
|
+
* `javascript:` links in the file are taken out before it is drawn. A server
|
|
35
|
+
* fetches nothing, so a served icon with a `src` is drawn once the script
|
|
36
|
+
* runs.
|
|
37
|
+
*
|
|
38
|
+
* A name with no match, or a `src` that is no SVG, renders nothing, and the
|
|
39
|
+
* element fires `error`, so a typo in a name can be heard. It waits until the page has loaded and painted
|
|
32
40
|
* first, so a library a later module registers is not taken for a typo.
|
|
33
41
|
*
|
|
34
42
|
* An icon is decorative by default and hidden from assistive technology. Set
|
|
@@ -47,8 +55,8 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
47
55
|
* @aliases Glyph, Symbol
|
|
48
56
|
* @documentation https://fusion.dev/helix/components/icon
|
|
49
57
|
*
|
|
50
|
-
* @event {Event} error - `name` matches no registered icon and no library answers it, so nothing is drawn.
|
|
51
|
-
* @event {Event} load - A glyph a library gave later for `name` is drawn.
|
|
58
|
+
* @event {Event} error - `name` matches no registered icon and no library answers it, or `src` is no SVG, so nothing is drawn.
|
|
59
|
+
* @event {Event} load - A glyph a library gave later for `name`, or the file at `src`, is drawn.
|
|
52
60
|
*
|
|
53
61
|
* @csspart svg - The rendered SVG.
|
|
54
62
|
*/
|
|
@@ -97,6 +105,13 @@ export declare class HelixHeadlessIcon extends HelixElement {
|
|
|
97
105
|
* prefix.
|
|
98
106
|
*/
|
|
99
107
|
name: string;
|
|
108
|
+
/**
|
|
109
|
+
* The URL of an SVG file to draw, fetched once a page and drawn in
|
|
110
|
+
* `currentColor` where the file does not color its shapes.
|
|
111
|
+
*
|
|
112
|
+
* Wins over `name`; `icon` wins over both. Absent draws `name`.
|
|
113
|
+
*/
|
|
114
|
+
src: string;
|
|
100
115
|
/**
|
|
101
116
|
* Hears an `aria-label` written on the element, which no property mirrors.
|
|
102
117
|
*
|
|
@@ -120,11 +135,18 @@ export declare class HelixHeadlessIcon extends HelixElement {
|
|
|
120
135
|
private arrived;
|
|
121
136
|
/** Stops listening to the registry. */
|
|
122
137
|
private unwatch;
|
|
138
|
+
/** What the glyph is asked for by: `src` where it is set, and `name` otherwise. */
|
|
139
|
+
private get asked();
|
|
123
140
|
connectedCallback(): void;
|
|
124
141
|
disconnectedCallback(): void;
|
|
125
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* What `src` or the registry gives now, starting the wait for a late glyph.
|
|
144
|
+
* A file is fetched only where the page can be read: a server fetches
|
|
145
|
+
* nothing, and a hydrating render draws what the server sent.
|
|
146
|
+
*/
|
|
126
147
|
private lookup;
|
|
127
148
|
protected willUpdate(changed: PropertyValues): void;
|
|
149
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
128
150
|
protected updated(changed: PropertyValues): void;
|
|
129
151
|
/** Hands the drawn `<svg>` to the mutator of the library `name` asks, where it has one. */
|
|
130
152
|
private mutate;
|
|
@@ -8,6 +8,7 @@ import { auditName, customElement, HelixElement, hostAria, reportRuntime, } from
|
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
11
|
+
import { fetchIcon } from '../fetch.js';
|
|
11
12
|
import { iconMutator, resolveIcon, watchIcons } from '../registry.js';
|
|
12
13
|
import { afterPageSettles } from '../settle.js';
|
|
13
14
|
let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
|
|
@@ -16,6 +17,7 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
|
|
|
16
17
|
this.label = '';
|
|
17
18
|
this.mirror = false;
|
|
18
19
|
this.name = '';
|
|
20
|
+
this.src = '';
|
|
19
21
|
this.missed = '';
|
|
20
22
|
this.waiting = false;
|
|
21
23
|
this.arrived = '';
|
|
@@ -30,10 +32,13 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
|
|
|
30
32
|
this.requestUpdate();
|
|
31
33
|
}
|
|
32
34
|
}
|
|
35
|
+
get asked() {
|
|
36
|
+
return this.src || this.name;
|
|
37
|
+
}
|
|
33
38
|
connectedCallback() {
|
|
34
39
|
super.connectedCallback();
|
|
35
40
|
this.unwatch = watchIcons(() => {
|
|
36
|
-
if (this.icon === undefined && this.name !== '') {
|
|
41
|
+
if (this.icon === undefined && this.src === '' && this.name !== '') {
|
|
37
42
|
this.requestUpdate();
|
|
38
43
|
}
|
|
39
44
|
});
|
|
@@ -44,15 +49,21 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
|
|
|
44
49
|
this.unwatch = undefined;
|
|
45
50
|
}
|
|
46
51
|
lookup() {
|
|
47
|
-
const
|
|
48
|
-
|
|
52
|
+
const asked = this.asked;
|
|
53
|
+
const found = this.src !== ''
|
|
54
|
+
? this.childrenKnown
|
|
55
|
+
? fetchIcon(this.src)
|
|
56
|
+
: undefined
|
|
57
|
+
: this.name === ''
|
|
58
|
+
? undefined
|
|
59
|
+
: resolveIcon(this.name);
|
|
60
|
+
this.waiting = found instanceof Promise || (this.src !== '' && !this.childrenKnown);
|
|
49
61
|
if (!(found instanceof Promise)) {
|
|
50
62
|
return found;
|
|
51
63
|
}
|
|
52
|
-
const name = this.name;
|
|
53
64
|
void found.then(() => {
|
|
54
|
-
if (this.
|
|
55
|
-
this.arrived =
|
|
65
|
+
if (this.asked === asked && this.isConnected) {
|
|
66
|
+
this.arrived = asked;
|
|
56
67
|
this.requestUpdate();
|
|
57
68
|
}
|
|
58
69
|
});
|
|
@@ -64,19 +75,26 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
|
|
|
64
75
|
hostAria(this.internals, { role: named ? 'img' : '' });
|
|
65
76
|
this.drawn = this.icon ?? this.lookup();
|
|
66
77
|
}
|
|
78
|
+
firstUpdated(changed) {
|
|
79
|
+
super.firstUpdated(changed);
|
|
80
|
+
if (this.src !== '') {
|
|
81
|
+
this.finishHydration();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
67
84
|
updated(changed) {
|
|
68
85
|
super.updated(changed);
|
|
69
86
|
if (this.drawn !== undefined) {
|
|
70
87
|
this.mutate();
|
|
71
88
|
this.auditControl();
|
|
72
89
|
}
|
|
73
|
-
|
|
90
|
+
const asked = this.asked;
|
|
91
|
+
if (this.arrived === asked && this.drawn !== undefined) {
|
|
74
92
|
this.dispatchEvent(new Event('load', { bubbles: false, cancelable: false, composed: false }));
|
|
75
93
|
}
|
|
76
94
|
this.arrived = '';
|
|
77
|
-
if (this.drawn === undefined &&
|
|
78
|
-
if (this.missed !==
|
|
79
|
-
this.pending =
|
|
95
|
+
if (this.drawn === undefined && asked !== '' && !this.waiting) {
|
|
96
|
+
if (this.missed !== asked && this.pending !== asked) {
|
|
97
|
+
this.pending = asked;
|
|
80
98
|
afterPageSettles(() => this.reportMissing());
|
|
81
99
|
}
|
|
82
100
|
}
|
|
@@ -86,7 +104,7 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
|
|
|
86
104
|
}
|
|
87
105
|
mutate() {
|
|
88
106
|
const svg = this.renderRoot.querySelector('svg');
|
|
89
|
-
const mutator = this.icon === undefined ? iconMutator(this.name) : undefined;
|
|
107
|
+
const mutator = this.icon === undefined && this.src === '' ? iconMutator(this.name) : undefined;
|
|
90
108
|
if (svg && mutator) {
|
|
91
109
|
mutator(svg);
|
|
92
110
|
}
|
|
@@ -102,18 +120,21 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
|
|
|
102
120
|
}
|
|
103
121
|
}
|
|
104
122
|
reportMissing() {
|
|
105
|
-
const
|
|
123
|
+
const asked = this.pending;
|
|
106
124
|
this.pending = '';
|
|
107
|
-
if (this.
|
|
125
|
+
if (this.asked !== asked || this.drawn !== undefined || this.waiting || !this.isConnected) {
|
|
108
126
|
return;
|
|
109
127
|
}
|
|
110
|
-
this.missed =
|
|
128
|
+
this.missed = asked;
|
|
111
129
|
this.dispatchEvent(new Event('error', { bubbles: false, cancelable: false, composed: false }));
|
|
112
130
|
reportRuntime({
|
|
113
131
|
kind: 'unknown-icon',
|
|
114
|
-
message:
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
message: this.src === ''
|
|
133
|
+
? `<${this.localName} name="${asked}"> names no registered icon, so it draws ` +
|
|
134
|
+
'nothing. Check the name, register the glyph with `registerIcons`, or import it ' +
|
|
135
|
+
'and pass it as `icon`.'
|
|
136
|
+
: `<${this.localName} src="${asked}"> could not be fetched as an SVG file, so it ` +
|
|
137
|
+
'draws nothing. Check the URL, and that the file is an `<svg>`.',
|
|
117
138
|
tag: this.localName,
|
|
118
139
|
});
|
|
119
140
|
}
|
|
@@ -148,6 +169,9 @@ __decorate([
|
|
|
148
169
|
__decorate([
|
|
149
170
|
property()
|
|
150
171
|
], HelixHeadlessIcon.prototype, "name", void 0);
|
|
172
|
+
__decorate([
|
|
173
|
+
property()
|
|
174
|
+
], HelixHeadlessIcon.prototype, "src", void 0);
|
|
151
175
|
HelixHeadlessIcon = __decorate([
|
|
152
176
|
customElement('helix-headless-icon')
|
|
153
177
|
], HelixHeadlessIcon);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Curve, HelixElement, type Series } from '@fusion.dev/helix-core';
|
|
2
2
|
import { nothing, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../tooltip/index.js';
|
|
4
|
-
declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
|
|
4
|
+
declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartStatusInterface);
|
|
5
5
|
/**
|
|
6
6
|
* @summary A chart that draws series over time as lines, read from a table.
|
|
7
7
|
*
|
|
@@ -45,12 +45,25 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
45
45
|
* tap or key, and `helix-reading-press` says a reading was clicked, tapped, or
|
|
46
46
|
* pressed with Enter or Space, for a page that opens what a reading stands for.
|
|
47
47
|
*
|
|
48
|
+
* **`data` takes the readings as data** where the page has no table to point
|
|
49
|
+
* `for` at, in a table's shape: the column headings, then one row per reading
|
|
50
|
+
* with its label first. A table is still the better source where the page can
|
|
51
|
+
* show one, since it reads before any script runs.
|
|
52
|
+
*
|
|
53
|
+
* **A chart with nothing to draw says so.** A table with no readings shows
|
|
54
|
+
* `empty-text`, "No readings", in the plot, and `loading` shows `loading-text`
|
|
55
|
+
* there until readings arrive. The `empty` and `loading` slots take markup in
|
|
56
|
+
* their place, such as a link to add the first reading.
|
|
57
|
+
*
|
|
48
58
|
* @since 0.1.0
|
|
49
59
|
* @status stable
|
|
50
60
|
* @category charts
|
|
51
61
|
* @aliases Time Series, Trend Chart, Multi-line Chart
|
|
52
62
|
* @documentation https://fusion.dev/helix/components/line-chart
|
|
53
63
|
*
|
|
64
|
+
* @slot empty - Content shown in place of the drawing when the table has no readings, such as a link to add the first. Falls back to `empty-text`.
|
|
65
|
+
* @slot loading - Content shown in place of the drawing while `loading` is set and there are no readings yet. Falls back to `loading-text`.
|
|
66
|
+
*
|
|
54
67
|
* @event {HelixReadingChangeEvent} helix-reading-change - The reading shown moved, or was put away: `index` is the row, or `-1` for none, with its `label`, the `series` the keys rest on, and the `pointerType` that moved it.
|
|
55
68
|
* @event {HelixReadingPressEvent} helix-reading-press - A reading was clicked, tapped, or pressed with Enter or Space, with the same detail as `helix-reading-change`.
|
|
56
69
|
*
|
|
@@ -58,6 +71,7 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
58
71
|
* @csspart axis - The numbers of the scale beside the plot, under `value-axis`.
|
|
59
72
|
* @csspart band - The band around the reading under the pointer.
|
|
60
73
|
* @csspart baseline - The rule at zero, where zero is inside the range.
|
|
74
|
+
* @csspart empty - The words in place of the drawing when the table has no readings: the `empty` slot, falling back to `empty-text`. Not drawn while the table cannot be read yet, on the server and during hydration.
|
|
61
75
|
* @csspart gridline - One rule across the plot, under `gridlines`.
|
|
62
76
|
* @csspart guide - A rule down the reading a pointer is resting on.
|
|
63
77
|
* @csspart label - One reading's name.
|
|
@@ -65,6 +79,7 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
65
79
|
* @csspart line - The line for one series.
|
|
66
80
|
* @csspart line-dashed - A `line` whose series `dashed` names. Set alongside `line`.
|
|
67
81
|
* @csspart live - The readout: a polite live region that says the reading the keys moved to. Hide it visually.
|
|
82
|
+
* @csspart loading - The words in place of the drawing while `loading` is set and there are no readings: the `loading` slot, falling back to `loading-text`.
|
|
68
83
|
* @csspart mark - The invisible point the tooltip is anchored to.
|
|
69
84
|
* @csspart marker - The mark on one reading, under `markers`, and on a reading with a blank on either side. Placed with `inset-inline-start` and `inset-block-start`, and carries its series color as `--chart-series`.
|
|
70
85
|
* @csspart marker-lone - A `marker` on a reading with a blank on either side, which no line reaches. Set alongside `marker`.
|
|
@@ -4,11 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { byRow, CHART_PLOT, commaList, customElement, drawTooltip, drawValueAxis, extent, formatNumbers, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesKey, ticks, unbroken, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { byRow, CHART_PLOT, commaList, customElement, drawTooltip, drawValueAxis, extent, formatNumbers, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, MixinChartStatus, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesKey, ticks, unbroken, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing, svg } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import '../../tooltip/index.js';
|
|
11
|
-
let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSource(HelixElement) {
|
|
11
|
+
let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
14
|
this.area = false;
|
|
@@ -59,7 +59,7 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
|
|
|
59
59
|
const series = this.series;
|
|
60
60
|
const readings = series.length === 0 ? [] : this.sourceRows.map((row) => row.label);
|
|
61
61
|
if (readings.length === 0) {
|
|
62
|
-
return html `<div id=${CHART_PLOT} part="plot"
|
|
62
|
+
return html `<div id=${CHART_PLOT} part="plot">${this.renderStatus()}</div>`;
|
|
63
63
|
}
|
|
64
64
|
const domain = this.range(series);
|
|
65
65
|
const y = linear(domain, [PLOT_HEIGHT, 0]);
|
|
@@ -7,6 +7,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
7
7
|
* Draws two concentric circles: a track and a moving arc. The track shows the
|
|
8
8
|
* arc as a proportion of a whole, and costs one element.
|
|
9
9
|
*
|
|
10
|
+
* **For work whose length is known, use `Progress` with `shape="ring"`.** It
|
|
11
|
+
* draws the same ring filled to the value, says the value as a progress bar
|
|
12
|
+
* does, and says when the work is complete. A loader claims no proportion, so
|
|
13
|
+
* it takes no value.
|
|
14
|
+
*
|
|
10
15
|
* **The center is a slot, empty by default.** Put your logo there once, where
|
|
11
16
|
* your app wraps the loader. When nothing is slotted, nothing extra is
|
|
12
17
|
* rendered.
|
|
@@ -15,6 +15,15 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
15
15
|
*
|
|
16
16
|
* The fill is a fraction of the track, written as a custom property.
|
|
17
17
|
*
|
|
18
|
+
* **The reading is said in the words the bar would show**: a whole percentage
|
|
19
|
+
* of the range in `locale`, the reading `format-options` writes, or
|
|
20
|
+
* `value-text`, as `aria-valuetext` beside the number.
|
|
21
|
+
*
|
|
22
|
+
* **Words set in the default slot are drawn on the bar**, or in the middle of a
|
|
23
|
+
* ring, such as `62%` or `3 of 8`, and are what the bar says as its reading
|
|
24
|
+
* where `value-text` is not set. While there are any, the bar carries
|
|
25
|
+
* `:state(content)`, so a sheet can make room for them.
|
|
26
|
+
*
|
|
18
27
|
* **Reaching `max` is said and sent.** When the value arrives at `max` from
|
|
19
28
|
* below, the bar says `complete-label`, "Complete" by default, in a polite
|
|
20
29
|
* `live` region and sends `helix-complete`, so a reader hears that the work is
|
|
@@ -28,10 +37,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
28
37
|
* @aliases Progress bar, Loading bar
|
|
29
38
|
* @documentation https://fusion.dev/helix/components/progress
|
|
30
39
|
*
|
|
40
|
+
* @slot - Words drawn on the bar, or in the middle of a ring, such as the reading. Said as the reading where `value-text` is not set.
|
|
31
41
|
* @slot label - A visible label. Its words name the progress bar in place of `label`.
|
|
32
42
|
*
|
|
33
43
|
* @event {HelixCompleteEvent} helix-complete - The value reached `max` from below. Its `value` is `max`. Not sent for a bar that starts full.
|
|
34
44
|
*
|
|
45
|
+
* @csspart content - The box holding the words of the default slot, over the track or in the middle of the ring. Carries `--progress-fraction`.
|
|
35
46
|
* @csspart groove - The unfilled circle under the ring's filled arc.
|
|
36
47
|
* @csspart indicator - The filled part that shows the work done.
|
|
37
48
|
* @csspart live - A polite region that says `complete-label` when the work is done, rendered from that moment. Hide it visually.
|
|
@@ -41,6 +52,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
41
52
|
* @cssproperty --progress-fraction - The value as a fraction of the range, 0 to 1. Pinned to 1 while indeterminate.
|
|
42
53
|
*
|
|
43
54
|
* @cssstate complete - The value is at `max` and the bar is not indeterminate.
|
|
55
|
+
* @cssstate content - Words are set in the default slot.
|
|
44
56
|
*/
|
|
45
57
|
export declare class HelixHeadlessProgress extends HelixElement {
|
|
46
58
|
/**
|
|
@@ -67,7 +79,8 @@ export declare class HelixHeadlessProgress extends HelixElement {
|
|
|
67
79
|
*
|
|
68
80
|
* A `percent` style writes the share of the range, and any other the value
|
|
69
81
|
* itself, so the example says "18 GB". Absent is a whole percentage. The
|
|
70
|
-
* words are announced
|
|
82
|
+
* words are announced whether or not `show-value` shows them. `value-text`
|
|
83
|
+
* still wins.
|
|
71
84
|
*/
|
|
72
85
|
formatOptions?: Intl.NumberFormatOptions;
|
|
73
86
|
/**
|
|
@@ -173,6 +186,8 @@ export declare class HelixHeadlessProgress extends HelixElement {
|
|
|
173
186
|
private get text();
|
|
174
187
|
/** A label's words changed, so the name is written again. */
|
|
175
188
|
private readonly renamed;
|
|
189
|
+
/** The words on the bar changed, so the reading is said again and the state set. */
|
|
190
|
+
private readonly reworded;
|
|
176
191
|
protected render(): TemplateResult;
|
|
177
192
|
}
|
|
178
193
|
declare global {
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, drawStatus, HelixCompleteEvent, HelixElement, hostAria, readingText, StatusController, slottedWords, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, drawStatus, HelixCompleteEvent, HelixElement, hostAria, readingText, StatusController, slotFilled, slotNamed, slottedWords, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
@@ -23,6 +23,10 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
23
23
|
this.renamed = () => {
|
|
24
24
|
this.requestUpdate();
|
|
25
25
|
};
|
|
26
|
+
this.reworded = () => {
|
|
27
|
+
this.toggleState('content', slotFilled(slotNamed(this.renderRoot, '')));
|
|
28
|
+
this.requestUpdate();
|
|
29
|
+
};
|
|
26
30
|
}
|
|
27
31
|
static { this.words = 'helix-progress'; }
|
|
28
32
|
get complete() {
|
|
@@ -48,8 +52,8 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
48
52
|
this.internals.ariaValueNow = this.indeterminate
|
|
49
53
|
? null
|
|
50
54
|
: String(Math.min(Math.max(this.value || min, min), max));
|
|
51
|
-
this.
|
|
52
|
-
|
|
55
|
+
const shown = this.valueText || slottedWords(this, ['']);
|
|
56
|
+
this.internals.ariaValueText = this.indeterminate ? shown || null : shown || this.text;
|
|
53
57
|
this.toggleState('complete', this.complete);
|
|
54
58
|
}
|
|
55
59
|
updated(changed) {
|
|
@@ -75,10 +79,14 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
75
79
|
<circle part="groove" />
|
|
76
80
|
<circle part="indicator" pathLength="100" style="--progress-fraction: ${fraction}" />
|
|
77
81
|
</svg>`;
|
|
82
|
+
const content = html `<span part="content" aria-hidden="true" style="--progress-fraction: ${fraction}"
|
|
83
|
+
><slot @slotchange=${this.reworded}></slot
|
|
84
|
+
></span>`;
|
|
78
85
|
return html `<slot name="label" @slotchange=${this.renamed}></slot>${this.shape === 'ring'
|
|
79
|
-
? ring
|
|
86
|
+
? html `${ring}${content}`
|
|
80
87
|
: html `<div part="track">
|
|
81
88
|
<div part="indicator" style="--progress-fraction: ${fraction}"></div>
|
|
89
|
+
${content}
|
|
82
90
|
</div>`}
|
|
83
91
|
${this.showValue && !this.indeterminate
|
|
84
92
|
? html `<span part="value" aria-hidden="true"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
/**
|
|
4
4
|
* @summary A short quotation, set inline with the text around it.
|
|
5
5
|
*
|
|
@@ -12,6 +12,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
12
12
|
* element, and its marks follow it: `lang="de"` sets „…“ and `lang="fr"` «…»
|
|
13
13
|
* inside an English page.
|
|
14
14
|
*
|
|
15
|
+
* **`no-marks` leaves the marks off**, for a quotation set where the page draws
|
|
16
|
+
* its own, such as a pull quote with a large decorative mark beside it or a
|
|
17
|
+
* table of sayings. The quotation still counts toward nesting, so a quote
|
|
18
|
+
* inside it takes the inner marks it would have had. Text with marks typed
|
|
19
|
+
* into it raises no warning under `no-marks`.
|
|
20
|
+
*
|
|
15
21
|
* For a passage set off from the surrounding text, use a blockquote.
|
|
16
22
|
*
|
|
17
23
|
* @since 0.1.0
|
|
@@ -25,10 +31,20 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
25
31
|
* @csspart quote - The quotation.
|
|
26
32
|
*/
|
|
27
33
|
export declare class HelixHeadlessQuote extends HelixElement {
|
|
34
|
+
static styles: CSSResultGroup;
|
|
28
35
|
/**
|
|
29
36
|
* URL identifying where the quotation came from.
|
|
30
37
|
*/
|
|
31
38
|
cite: string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether to leave the quotation marks off, for a quotation set where the
|
|
41
|
+
* page draws its own.
|
|
42
|
+
*
|
|
43
|
+
* Negative, because the marks are on by default and a boolean attribute is
|
|
44
|
+
* true whenever it is present. Reflected, because a stylesheet takes the
|
|
45
|
+
* marks off on it.
|
|
46
|
+
*/
|
|
47
|
+
noMarks: boolean;
|
|
32
48
|
/** Whether this quote has said its text carries typed marks, so it says so once. */
|
|
33
49
|
private warned;
|
|
34
50
|
/**
|
|
@@ -5,20 +5,21 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { customElement, HelixElement, reportRuntime } from '@fusion.dev/helix-core';
|
|
8
|
-
import { html, nothing } from 'lit';
|
|
8
|
+
import { css, html, nothing, } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { typedMarks } from '../marks.js';
|
|
11
11
|
let HelixHeadlessQuote = class HelixHeadlessQuote extends HelixElement {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
14
|
this.cite = '';
|
|
15
|
+
this.noMarks = false;
|
|
15
16
|
this.warned = false;
|
|
16
17
|
this.readText = () => {
|
|
17
18
|
const text = [...this.childNodes]
|
|
18
19
|
.filter((node) => !(node instanceof Element) || !node.slot)
|
|
19
20
|
.map((node) => node.textContent ?? '')
|
|
20
21
|
.join('');
|
|
21
|
-
if (this.warned || !typedMarks(text)) {
|
|
22
|
+
if (this.warned || this.noMarks || !typedMarks(text)) {
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
25
|
this.warned = true;
|
|
@@ -29,6 +30,15 @@ let HelixHeadlessQuote = class HelixHeadlessQuote extends HelixElement {
|
|
|
29
30
|
});
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
static { this.styles = css `
|
|
34
|
+
:host([no-marks]) [part='quote']::before {
|
|
35
|
+
content: no-open-quote;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:host([no-marks]) [part='quote']::after {
|
|
39
|
+
content: no-close-quote;
|
|
40
|
+
}
|
|
41
|
+
`; }
|
|
32
42
|
firstUpdated(changed) {
|
|
33
43
|
super.firstUpdated(changed);
|
|
34
44
|
this.readText();
|
|
@@ -41,6 +51,9 @@ let HelixHeadlessQuote = class HelixHeadlessQuote extends HelixElement {
|
|
|
41
51
|
__decorate([
|
|
42
52
|
property()
|
|
43
53
|
], HelixHeadlessQuote.prototype, "cite", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
property({ attribute: 'no-marks', reflect: true, type: Boolean })
|
|
56
|
+
], HelixHeadlessQuote.prototype, "noMarks", void 0);
|
|
44
57
|
HelixHeadlessQuote = __decorate([
|
|
45
58
|
customElement('helix-headless-quote')
|
|
46
59
|
], HelixHeadlessQuote);
|