@fusion.dev/helix-headless 0.153.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 +459 -53
- package/dist/badge/root/badge-root.d.ts +15 -2
- package/dist/badge/root/badge-root.js +20 -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/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/quote/root/quote-root.d.ts +17 -1
- package/dist/quote/root/quote-root.js +15 -2
- 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 +428 -56
- package/types/vue.d.ts +73 -16
- package/types/words.d.ts +13 -0
|
@@ -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,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);
|
|
@@ -61,6 +61,13 @@ export declare class HelixHeadlessSkeleton extends HelixElement {
|
|
|
61
61
|
protected willUpdate(changed: PropertyValues): void;
|
|
62
62
|
protected firstUpdated(changed: PropertyValues): void;
|
|
63
63
|
protected render(): TemplateResult;
|
|
64
|
+
/**
|
|
65
|
+
* The placeholder shape and the sheen inside it.
|
|
66
|
+
*
|
|
67
|
+
* A method, so a tier above can draw the placeholder as several shapes, as
|
|
68
|
+
* the styled tier draws a paragraph of lines.
|
|
69
|
+
*/
|
|
70
|
+
protected renderShape(): TemplateResult;
|
|
64
71
|
}
|
|
65
72
|
declare global {
|
|
66
73
|
interface HTMLElementTagNameMap {
|
|
@@ -38,7 +38,10 @@ let HelixHeadlessSkeleton = class HelixHeadlessSkeleton extends HelixElement {
|
|
|
38
38
|
render() {
|
|
39
39
|
return html `<span part="content" ?inert=${this.loading}
|
|
40
40
|
><slot @slotchange=${() => this.requestUpdate()}></slot></span
|
|
41
|
-
|
|
41
|
+
>${this.renderShape()}`;
|
|
42
|
+
}
|
|
43
|
+
renderShape() {
|
|
44
|
+
return html `<span part="skeleton"><span part="sheen"></span></span>`;
|
|
42
45
|
}
|
|
43
46
|
};
|
|
44
47
|
__decorate([
|
|
@@ -4,9 +4,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
4
4
|
* @summary A row of related measurements.
|
|
5
5
|
*
|
|
6
6
|
* @description
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* **A named group is a group**, so a screen reader announces the stats
|
|
8
|
+
* together under its name, such as "Usage, group". Name it with `label`,
|
|
9
|
+
* `aria-label` or `aria-labelledby`. An unnamed one has no role, since a group
|
|
10
|
+
* with no name only adds "group" to what a reader says, as an unnamed Card
|
|
11
|
+
* has none. The figures are read together and their order has no meaning, so
|
|
12
|
+
* the group does not announce positions such as "1 of 4".
|
|
10
13
|
*
|
|
11
14
|
* @since 0.1.0
|
|
12
15
|
* @status stable
|
|
@@ -17,9 +20,25 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
17
20
|
*/
|
|
18
21
|
export declare class HelixHeadlessStatGroup extends HelixElement {
|
|
19
22
|
/**
|
|
20
|
-
* Accessible name for the group.
|
|
23
|
+
* Accessible name for the group, which makes it a group.
|
|
24
|
+
*
|
|
25
|
+
* Absent leaves the stats with no group around them, unless `aria-label` or
|
|
26
|
+
* `aria-labelledby` names it.
|
|
21
27
|
*/
|
|
22
28
|
label: string;
|
|
29
|
+
/**
|
|
30
|
+
* Hears a name written on the element, which no property mirrors.
|
|
31
|
+
*
|
|
32
|
+
* Lit observes the attributes of its own properties only, and `ariaLabel`
|
|
33
|
+
* and `ariaLabelledByElements` belong to the platform.
|
|
34
|
+
*/
|
|
35
|
+
static get observedAttributes(): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Called by the browser when an observed attribute changes. A name written
|
|
38
|
+
* or taken off asks for an update, because it decides whether the stats are
|
|
39
|
+
* a group.
|
|
40
|
+
*/
|
|
41
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
23
42
|
protected willUpdate(changed: PropertyValues): void;
|
|
24
43
|
protected render(): TemplateResult;
|
|
25
44
|
}
|
|
@@ -12,9 +12,21 @@ let HelixHeadlessStatGroup = class HelixHeadlessStatGroup extends HelixElement {
|
|
|
12
12
|
super(...arguments);
|
|
13
13
|
this.label = '';
|
|
14
14
|
}
|
|
15
|
+
static get observedAttributes() {
|
|
16
|
+
return [...super.observedAttributes, 'aria-label', 'aria-labelledby'];
|
|
17
|
+
}
|
|
18
|
+
attributeChangedCallback(name, old, value) {
|
|
19
|
+
super.attributeChangedCallback(name, old, value);
|
|
20
|
+
if (name === 'aria-label' || name === 'aria-labelledby') {
|
|
21
|
+
this.requestUpdate();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
15
24
|
willUpdate(changed) {
|
|
16
25
|
super.willUpdate(changed);
|
|
17
|
-
|
|
26
|
+
const named = this.label.trim() !== '' ||
|
|
27
|
+
Boolean(this.getAttribute?.('aria-label')?.trim()) ||
|
|
28
|
+
Boolean(this.getAttribute?.('aria-labelledby')?.trim());
|
|
29
|
+
hostAria(this.internals, { label: this.label, role: named ? 'group' : '' });
|
|
18
30
|
}
|
|
19
31
|
render() {
|
|
20
32
|
return html `<slot></slot>`;
|
|
@@ -19,6 +19,19 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
19
19
|
* attribute; a stat with no label has no role. Put a `Trend` in the `trend`
|
|
20
20
|
* slot to have it read inside the group, after the figure it qualifies.
|
|
21
21
|
*
|
|
22
|
+
* **Or give the figure as a number.** `value` is written with
|
|
23
|
+
* `Intl.NumberFormat`, in `locale` and with `format-options`, so
|
|
24
|
+
* `value="1234567" format-options='{"notation":"compact"}'` shows `1.2M`, and
|
|
25
|
+
* a server writes it as the browser does. A number in `value` takes the place
|
|
26
|
+
* of a slotted figure.
|
|
27
|
+
*
|
|
28
|
+
* **Each slot is a part of the same name**, and `value` for the figure, so a
|
|
29
|
+
* page reaches the label, the unit and the trend from outside. A slot draws no
|
|
30
|
+
* box, so a part passes inherited properties to what is slotted. **The stat
|
|
31
|
+
* says which pieces it holds**, as `:state(has-label)`, `:state(has-unit)` and
|
|
32
|
+
* `:state(has-trend)`, read in the browser, so a served stat carries none
|
|
33
|
+
* until the script runs.
|
|
34
|
+
*
|
|
22
35
|
* @since 0.1.0
|
|
23
36
|
* @status stable
|
|
24
37
|
* @category data
|
|
@@ -30,7 +43,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
30
43
|
* @slot trend - How the value moved, usually a `Trend`, read after the value inside the stat's group.
|
|
31
44
|
* @slot unit - The unit, shown beside the value.
|
|
32
45
|
*
|
|
33
|
-
* @csspart
|
|
46
|
+
* @csspart label - The slot the label arrives through, drawing no box, as `value` does.
|
|
47
|
+
* @csspart trend - The slot the trend arrives through, drawing no box, as `value` does.
|
|
48
|
+
* @csspart unit - The slot the unit arrives through, drawing no box, as `value` does.
|
|
49
|
+
* @csspart value - The slot the value is written in, or the figure `value` writes. A slot draws no box, so it takes the inherited properties, such as color and font, and passes them to the value.
|
|
50
|
+
*
|
|
51
|
+
* @cssstate has-label - Something is slotted into `label`.
|
|
52
|
+
* @cssstate has-trend - Something is slotted into `trend`.
|
|
53
|
+
* @cssstate has-unit - Something is slotted into `unit`.
|
|
34
54
|
*/
|
|
35
55
|
export declare class HelixHeadlessStat extends HelixElement {
|
|
36
56
|
/**
|
|
@@ -61,9 +81,34 @@ export declare class HelixHeadlessStat extends HelixElement {
|
|
|
61
81
|
* carries nothing nobody wrote.
|
|
62
82
|
*/
|
|
63
83
|
direction?: 'column-reverse' | 'row' | 'row-reverse';
|
|
84
|
+
/**
|
|
85
|
+
* `Intl.NumberFormat` options `value` is written with, such as
|
|
86
|
+
* `{"notation":"compact"}` for `1.2M` or `{"style":"percent"}` for `98%`,
|
|
87
|
+
* written as JSON in the attribute.
|
|
88
|
+
*
|
|
89
|
+
* Absent writes the number as the locale writes any number.
|
|
90
|
+
*/
|
|
91
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
92
|
+
/**
|
|
93
|
+
* The locale `value` is written in, such as `de` for `1.234.567`.
|
|
94
|
+
*
|
|
95
|
+
* Absent reads the nearest `lang`, then the runtime's own. Set it on a page
|
|
96
|
+
* rendered on a server, so the server and the browser write the figure
|
|
97
|
+
* alike.
|
|
98
|
+
*/
|
|
99
|
+
locale: string;
|
|
100
|
+
/**
|
|
101
|
+
* The figure as a number, written with `format-options` in `locale` in
|
|
102
|
+
* place of a slotted value.
|
|
103
|
+
*
|
|
104
|
+
* Absent leaves the figure to the slot.
|
|
105
|
+
*/
|
|
106
|
+
value?: number;
|
|
64
107
|
protected willUpdate(changed: PropertyValues): void;
|
|
65
108
|
protected firstUpdated(changed: PropertyValues): void;
|
|
66
109
|
protected updated(changed: PropertyValues): void;
|
|
110
|
+
/** Says which pieces the stat holds, one call apiece so each pairs with its `@cssstate`. */
|
|
111
|
+
private readonly readPieces;
|
|
67
112
|
protected render(): TemplateResult;
|
|
68
113
|
}
|
|
69
114
|
declare global {
|
|
@@ -4,10 +4,20 @@ 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, HelixElement, hostAria, reportRuntime, slottedWords, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, formatNumber, HelixElement, hostAria, reportRuntime, slotFilled, slottedWords, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.locale = '';
|
|
14
|
+
this.readPieces = () => {
|
|
15
|
+
const filled = (name) => slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`));
|
|
16
|
+
this.toggleState('has-label', filled('label'));
|
|
17
|
+
this.toggleState('has-unit', filled('unit'));
|
|
18
|
+
this.toggleState('has-trend', filled('trend'));
|
|
19
|
+
};
|
|
20
|
+
}
|
|
11
21
|
willUpdate(changed) {
|
|
12
22
|
super.willUpdate(changed);
|
|
13
23
|
const labels = this.knownChildren.filter((child) => child.slot === 'label');
|
|
@@ -25,6 +35,7 @@ let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
|
|
|
25
35
|
}
|
|
26
36
|
updated(changed) {
|
|
27
37
|
super.updated(changed);
|
|
38
|
+
this.readPieces();
|
|
28
39
|
const given = this.direction;
|
|
29
40
|
if (changed.has('direction') && (given === 'up' || given === 'down' || given === 'neutral')) {
|
|
30
41
|
reportRuntime({
|
|
@@ -37,15 +48,28 @@ let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
|
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
50
|
render() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
const value = this.value;
|
|
52
|
+
const figure = value === undefined || !Number.isFinite(value)
|
|
53
|
+
? ''
|
|
54
|
+
: formatNumber(value, wordsLocale(this), this.formatOptions);
|
|
55
|
+
return html `<slot name="label" part="label" @slotchange=${() => this.requestUpdate()}></slot>
|
|
56
|
+
${figure === '' ? html `<slot part="value"></slot>` : html `<span part="value">${figure}</span>`}
|
|
57
|
+
<slot name="unit" part="unit" @slotchange=${this.readPieces}></slot>
|
|
58
|
+
<slot name="trend" part="trend" @slotchange=${this.readPieces}></slot>`;
|
|
44
59
|
}
|
|
45
60
|
};
|
|
46
61
|
__decorate([
|
|
47
62
|
property({ reflect: true })
|
|
48
63
|
], HelixHeadlessStat.prototype, "direction", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
property({ attribute: 'format-options', type: Object })
|
|
66
|
+
], HelixHeadlessStat.prototype, "formatOptions", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
property()
|
|
69
|
+
], HelixHeadlessStat.prototype, "locale", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
property({ type: Number })
|
|
72
|
+
], HelixHeadlessStat.prototype, "value", void 0);
|
|
49
73
|
HelixHeadlessStat = __decorate([
|
|
50
74
|
customElement('helix-headless-stat')
|
|
51
75
|
], HelixHeadlessStat);
|