@aceshooting/lyra-ui 1.1.0 → 1.2.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/README.md +1 -1
- package/custom-elements.json +9289 -8457
- package/dist/components/chart/chart.d.ts +11 -0
- package/dist/components/chart/chart.js +54 -2
- package/dist/components/chart/lite-chart.d.ts +9 -0
- package/dist/components/chart/lite-chart.js +41 -1
- package/dist/components/chart/lite-chart.stories.d.ts +2 -0
- package/dist/components/chart/lite-chart.stories.js +16 -0
- package/dist/components/combobox/combobox.js +1 -1
- package/dist/components/flag/flag.d.ts +26 -5
- package/dist/components/flag/flag.js +44 -3
- package/dist/components/flag/flag.stories.d.ts +1 -0
- package/dist/components/flag/flag.stories.js +14 -0
- package/dist/components/heatmap/heatmap.d.ts +15 -3
- package/dist/components/heatmap/heatmap.js +24 -7
- package/dist/components/heatmap/heatmap.stories.d.ts +5 -0
- package/dist/components/heatmap/heatmap.stories.js +25 -0
- package/dist/components/select/select.d.ts +3 -0
- package/dist/components/select/select.js +6 -42
- package/dist/components/select/select.stories.d.ts +2 -0
- package/dist/components/select/select.stories.js +16 -0
- package/dist/components/select/select.styles.js +26 -2
- package/dist/components/stat/stat.d.ts +16 -0
- package/dist/components/stat/stat.js +41 -1
- package/dist/components/stat/stat.stories.d.ts +1 -0
- package/dist/components/stat/stat.stories.js +17 -0
- package/dist/components/stat/stat.styles.js +20 -0
- package/dist/components/widget/widget.d.ts +10 -0
- package/dist/components/widget/widget.js +24 -1
- package/dist/components/widget/widget.stories.d.ts +2 -0
- package/dist/components/widget/widget.stories.js +33 -0
- package/dist/components/widget/widget.styles.js +9 -2
- package/dist/components/word-cloud/word-cloud.styles.js +1 -0
- package/llms-full.txt +90 -20
- package/package.json +3 -3
|
@@ -77,6 +77,9 @@ export declare class LyraChart extends LyraElement {
|
|
|
77
77
|
/** True until the lazy-loaded `chart.js` peer dependency has settled (success or failure). */
|
|
78
78
|
private loading;
|
|
79
79
|
private zoomed;
|
|
80
|
+
private visible;
|
|
81
|
+
private intersectionObserver?;
|
|
82
|
+
private lastSignature;
|
|
80
83
|
private canvasEl?;
|
|
81
84
|
private chart?;
|
|
82
85
|
private chartJsModule?;
|
|
@@ -95,6 +98,14 @@ export declare class LyraChart extends LyraElement {
|
|
|
95
98
|
* is called fresh from `buildConfig()` on every draw rather than cached.
|
|
96
99
|
*/
|
|
97
100
|
private themeColors;
|
|
101
|
+
/**
|
|
102
|
+
* A content-affecting-properties fingerprint used by `updated()` to skip a
|
|
103
|
+
* redundant `draw()` when neither visibility nor any of these properties
|
|
104
|
+
* actually changed since the last draw (e.g. an unrelated property/state
|
|
105
|
+
* update, or `requestUpdate()` with nothing changed). Mirrors
|
|
106
|
+
* `lite-chart.ts`'s `computeSignature()`.
|
|
107
|
+
*/
|
|
108
|
+
private computeSignature;
|
|
98
109
|
/**
|
|
99
110
|
* Builds `options.scales` for the effective chart type: no scale at all for
|
|
100
111
|
* pie/doughnut (a proportional-area chart has no axis), the single radial
|
|
@@ -99,6 +99,8 @@ export class LyraChart extends LyraElement {
|
|
|
99
99
|
/** True until the lazy-loaded `chart.js` peer dependency has settled (success or failure). */
|
|
100
100
|
this.loading = true;
|
|
101
101
|
this.zoomed = false;
|
|
102
|
+
this.visible = true;
|
|
103
|
+
this.lastSignature = '';
|
|
102
104
|
// `chartjs-plugin-zoom`'s own `resetZoom()` synchronously re-invokes the
|
|
103
105
|
// `onZoomComplete` callback below as part of its reset, which would emit a
|
|
104
106
|
// stale `{zoomed: true}` right before `resetZoom()` emits the real
|
|
@@ -116,11 +118,21 @@ export class LyraChart extends LyraElement {
|
|
|
116
118
|
this.chartJsModule = mod;
|
|
117
119
|
this.draw();
|
|
118
120
|
});
|
|
121
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
|
122
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
123
|
+
const wasVisible = this.visible;
|
|
124
|
+
this.visible = entries[0]?.isIntersecting ?? true;
|
|
125
|
+
if (this.visible && !wasVisible)
|
|
126
|
+
this.draw();
|
|
127
|
+
});
|
|
128
|
+
this.intersectionObserver.observe(this);
|
|
129
|
+
}
|
|
119
130
|
}
|
|
120
131
|
disconnectedCallback() {
|
|
121
132
|
super.disconnectedCallback();
|
|
122
133
|
this.chart?.destroy();
|
|
123
134
|
this.chart = undefined;
|
|
135
|
+
this.intersectionObserver?.disconnect();
|
|
124
136
|
}
|
|
125
137
|
updated(changed) {
|
|
126
138
|
if (this.loading)
|
|
@@ -135,9 +147,23 @@ export class LyraChart extends LyraElement {
|
|
|
135
147
|
if (changed.has('height')) {
|
|
136
148
|
this.style.setProperty('--lyra-chart-height', this.height);
|
|
137
149
|
}
|
|
150
|
+
// While `chart.js` is still loading, `draw()` would no-op anyway (no
|
|
151
|
+
// `chartJsModule`/`canvasEl` yet) — bail before touching `lastSignature`
|
|
152
|
+
// so that phantom "no-op" update doesn't get cached as the baseline and
|
|
153
|
+
// silently swallow the real first draw once loading finishes with no
|
|
154
|
+
// other property having changed in the meantime.
|
|
155
|
+
if (this.loading)
|
|
156
|
+
return;
|
|
138
157
|
const onlyZoomChanged = changed.size === 1 && changed.has('zoomed');
|
|
139
|
-
if (
|
|
140
|
-
|
|
158
|
+
if (onlyZoomChanged)
|
|
159
|
+
return;
|
|
160
|
+
if (!this.visible)
|
|
161
|
+
return; // becoming visible again triggers its own draw() via the observer above
|
|
162
|
+
const signature = this.computeSignature();
|
|
163
|
+
if (signature === this.lastSignature)
|
|
164
|
+
return;
|
|
165
|
+
this.lastSignature = signature;
|
|
166
|
+
this.draw();
|
|
141
167
|
}
|
|
142
168
|
seriesToDataset(s) {
|
|
143
169
|
const colors = Array.isArray(s.color) ? s.color : s.color ? [s.color] : undefined;
|
|
@@ -174,6 +200,29 @@ export class LyraChart extends LyraElement {
|
|
|
174
200
|
tooltipText: cs.getPropertyValue('--lyra-chart-tooltip-text').trim() || FALLBACK_TOOLTIP_TEXT,
|
|
175
201
|
};
|
|
176
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* A content-affecting-properties fingerprint used by `updated()` to skip a
|
|
205
|
+
* redundant `draw()` when neither visibility nor any of these properties
|
|
206
|
+
* actually changed since the last draw (e.g. an unrelated property/state
|
|
207
|
+
* update, or `requestUpdate()` with nothing changed). Mirrors
|
|
208
|
+
* `lite-chart.ts`'s `computeSignature()`.
|
|
209
|
+
*/
|
|
210
|
+
computeSignature() {
|
|
211
|
+
return JSON.stringify([
|
|
212
|
+
this.type,
|
|
213
|
+
this.labels,
|
|
214
|
+
this.datasets,
|
|
215
|
+
this.legend,
|
|
216
|
+
this.area,
|
|
217
|
+
this.xLabel,
|
|
218
|
+
this.yLabel,
|
|
219
|
+
this.y2Label,
|
|
220
|
+
this.beginAtZero,
|
|
221
|
+
this.horizontal,
|
|
222
|
+
this.stacked,
|
|
223
|
+
this.config,
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
177
226
|
/**
|
|
178
227
|
* Builds `options.scales` for the effective chart type: no scale at all for
|
|
179
228
|
* pie/doughnut (a proportional-area chart has no axis), the single radial
|
|
@@ -428,6 +477,9 @@ __decorate([
|
|
|
428
477
|
__decorate([
|
|
429
478
|
state()
|
|
430
479
|
], LyraChart.prototype, "zoomed", void 0);
|
|
480
|
+
__decorate([
|
|
481
|
+
state()
|
|
482
|
+
], LyraChart.prototype, "visible", void 0);
|
|
431
483
|
__decorate([
|
|
432
484
|
query('canvas')
|
|
433
485
|
], LyraChart.prototype, "canvasEl", void 0);
|
|
@@ -50,22 +50,31 @@ export declare class LyraLiteChart extends LyraElement {
|
|
|
50
50
|
beginAtZero: boolean;
|
|
51
51
|
/** Stacks each category's bars into one segmented bar. Ignored for `type="line"`. */
|
|
52
52
|
stacked: boolean;
|
|
53
|
+
/** Formats a y-axis tick value for display (e.g. `(v) => \`$${v.toFixed(2)}\``). Falls back to the
|
|
54
|
+
* built-in nice-number formatter when unset. */
|
|
55
|
+
tickFormat?: (value: number) => string;
|
|
53
56
|
private plotWidth;
|
|
54
57
|
private plotHeight;
|
|
58
|
+
private visible;
|
|
55
59
|
private svgEl?;
|
|
56
60
|
private resizeObserver?;
|
|
61
|
+
private intersectionObserver?;
|
|
62
|
+
private lastSignature;
|
|
63
|
+
private lastResult?;
|
|
57
64
|
connectedCallback(): void;
|
|
58
65
|
disconnectedCallback(): void;
|
|
59
66
|
protected firstUpdated(): void;
|
|
60
67
|
protected updated(changed: PropertyValues): void;
|
|
61
68
|
private colorFor;
|
|
62
69
|
private domain;
|
|
70
|
+
private computeSignature;
|
|
63
71
|
private emitPoint;
|
|
64
72
|
private onPointKeyDown;
|
|
65
73
|
private renderGrid;
|
|
66
74
|
private renderBars;
|
|
67
75
|
private renderLines;
|
|
68
76
|
render(): TemplateResult;
|
|
77
|
+
private renderChart;
|
|
69
78
|
}
|
|
70
79
|
declare global {
|
|
71
80
|
interface HTMLElementTagNameMap {
|
|
@@ -106,6 +106,8 @@ export class LyraLiteChart extends LyraElement {
|
|
|
106
106
|
this.stacked = false;
|
|
107
107
|
this.plotWidth = 0;
|
|
108
108
|
this.plotHeight = 0;
|
|
109
|
+
this.visible = true;
|
|
110
|
+
this.lastSignature = '';
|
|
109
111
|
}
|
|
110
112
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
111
113
|
connectedCallback() {
|
|
@@ -124,10 +126,17 @@ export class LyraLiteChart extends LyraElement {
|
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
});
|
|
129
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
|
130
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
131
|
+
this.visible = entries[0]?.isIntersecting ?? true;
|
|
132
|
+
});
|
|
133
|
+
this.intersectionObserver.observe(this);
|
|
134
|
+
}
|
|
127
135
|
}
|
|
128
136
|
disconnectedCallback() {
|
|
129
137
|
super.disconnectedCallback();
|
|
130
138
|
this.resizeObserver?.disconnect();
|
|
139
|
+
this.intersectionObserver?.disconnect();
|
|
131
140
|
}
|
|
132
141
|
firstUpdated() {
|
|
133
142
|
if (this.svgEl)
|
|
@@ -179,6 +188,20 @@ export class LyraLiteChart extends LyraElement {
|
|
|
179
188
|
}
|
|
180
189
|
return niceDomain(lo, hi, this.beginAtZero, TICK_COUNT);
|
|
181
190
|
}
|
|
191
|
+
computeSignature() {
|
|
192
|
+
return JSON.stringify([
|
|
193
|
+
this.type,
|
|
194
|
+
this.labels,
|
|
195
|
+
this.datasets,
|
|
196
|
+
this.legend,
|
|
197
|
+
this.xLabel,
|
|
198
|
+
this.yLabel,
|
|
199
|
+
this.beginAtZero,
|
|
200
|
+
this.stacked,
|
|
201
|
+
this.plotWidth,
|
|
202
|
+
this.plotHeight,
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
182
205
|
emitPoint(datasetIndex, index) {
|
|
183
206
|
const label = this.labels[index];
|
|
184
207
|
const value = this.datasets[datasetIndex]?.data[index] ?? null;
|
|
@@ -196,7 +219,7 @@ export class LyraLiteChart extends LyraElement {
|
|
|
196
219
|
const y = plotY + plotH - ((t - lo) / span) * plotH;
|
|
197
220
|
return svg `
|
|
198
221
|
<line part="grid-line" x1=${plotX} y1=${y} x2=${plotX + plotW} y2=${y}></line>
|
|
199
|
-
<text part="axis-label" x=${plotX - 6} y=${y} text-anchor="end" dominant-baseline="middle">${formatTick(t)}</text>
|
|
222
|
+
<text part="axis-label" x=${plotX - 6} y=${y} text-anchor="end" dominant-baseline="middle">${this.tickFormat ? this.tickFormat(t) : formatTick(t)}</text>
|
|
200
223
|
`;
|
|
201
224
|
});
|
|
202
225
|
}
|
|
@@ -294,6 +317,17 @@ export class LyraLiteChart extends LyraElement {
|
|
|
294
317
|
});
|
|
295
318
|
}
|
|
296
319
|
render() {
|
|
320
|
+
if (!this.visible && this.lastResult)
|
|
321
|
+
return this.lastResult;
|
|
322
|
+
const signature = this.computeSignature();
|
|
323
|
+
if (signature === this.lastSignature && this.lastResult)
|
|
324
|
+
return this.lastResult;
|
|
325
|
+
this.lastSignature = signature;
|
|
326
|
+
const result = this.renderChart();
|
|
327
|
+
this.lastResult = result;
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
renderChart() {
|
|
297
331
|
const w = this.plotWidth || 400;
|
|
298
332
|
const h = this.plotHeight || 200;
|
|
299
333
|
const padLeft = PAD_LEFT + (this.yLabel ? AXIS_TITLE_SPACE : 0);
|
|
@@ -367,12 +401,18 @@ __decorate([
|
|
|
367
401
|
__decorate([
|
|
368
402
|
property({ type: Boolean })
|
|
369
403
|
], LyraLiteChart.prototype, "stacked", void 0);
|
|
404
|
+
__decorate([
|
|
405
|
+
property({ attribute: false })
|
|
406
|
+
], LyraLiteChart.prototype, "tickFormat", void 0);
|
|
370
407
|
__decorate([
|
|
371
408
|
state()
|
|
372
409
|
], LyraLiteChart.prototype, "plotWidth", void 0);
|
|
373
410
|
__decorate([
|
|
374
411
|
state()
|
|
375
412
|
], LyraLiteChart.prototype, "plotHeight", void 0);
|
|
413
|
+
__decorate([
|
|
414
|
+
state()
|
|
415
|
+
], LyraLiteChart.prototype, "visible", void 0);
|
|
376
416
|
__decorate([
|
|
377
417
|
query('svg')
|
|
378
418
|
], LyraLiteChart.prototype, "svgEl", void 0);
|
|
@@ -10,3 +10,5 @@ export declare const StackedBars: Story;
|
|
|
10
10
|
export declare const Line: Story;
|
|
11
11
|
/** Clicking (or Enter/Space on a focused) bar/point fires `lyra-point-click`, same detail shape as `lyra-chart`'s. */
|
|
12
12
|
export declare const ClickToFilter: Story;
|
|
13
|
+
/** `tickFormat` customizes y-axis tick labels (e.g. currency) instead of the built-in nice-number formatter. */
|
|
14
|
+
export declare const CurrencyTickFormat: Story;
|
|
@@ -96,3 +96,19 @@ export const ClickToFilter = {
|
|
|
96
96
|
`;
|
|
97
97
|
},
|
|
98
98
|
};
|
|
99
|
+
/** `tickFormat` customizes y-axis tick labels (e.g. currency) instead of the built-in nice-number formatter. */
|
|
100
|
+
export const CurrencyTickFormat = {
|
|
101
|
+
render: () => {
|
|
102
|
+
const series = [{ label: 'Revenue', data: [1204.37, 1890.5, 1420.1, 2260.75] }];
|
|
103
|
+
return html `
|
|
104
|
+
<lyra-lite-chart
|
|
105
|
+
type="bar"
|
|
106
|
+
height="16rem"
|
|
107
|
+
style="width: 22rem"
|
|
108
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
109
|
+
.datasets=${series}
|
|
110
|
+
.tickFormat=${(v) => `$${v.toFixed(2)}`}
|
|
111
|
+
></lyra-lite-chart>
|
|
112
|
+
`;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
@@ -549,7 +549,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
549
549
|
id=${this.inputId}
|
|
550
550
|
part="combobox-input"
|
|
551
551
|
role="combobox"
|
|
552
|
-
aria-label=${this.label || this.placeholder || 'Combobox'}
|
|
552
|
+
aria-label=${this.getAttribute('aria-label') || this.label || this.placeholder || 'Combobox'}
|
|
553
553
|
aria-expanded=${this.open ? 'true' : 'false'}
|
|
554
554
|
aria-controls=${this.listId}
|
|
555
555
|
aria-activedescendant=${activeId}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
3
|
import '../skeleton/skeleton.js';
|
|
4
|
-
type FlagUrlResolver = (code: string
|
|
4
|
+
type FlagUrlResolver = (code: string, options?: {
|
|
5
|
+
variant?: 'detailed';
|
|
6
|
+
}) => Promise<string | undefined>;
|
|
5
7
|
/**
|
|
6
8
|
* Resolves the optional peer dependency `@aceshooting/lyra-flags`'s `flagUrl`
|
|
7
9
|
* via the given importer (a real dynamic import by default). Uncached and
|
|
@@ -31,10 +33,16 @@ export declare function loadFlagUrl(importFlags?: () => Promise<{
|
|
|
31
33
|
* `src` instead to skip the peer-package round trip (and its loading-skeleton
|
|
32
34
|
* flash) entirely.
|
|
33
35
|
*
|
|
36
|
+
* A handful of flags (e.g. `es`, `pt` — any whose design includes a detailed coat of
|
|
37
|
+
* arms/seal/emblem) ship a second, full-detail source SVG alongside the default icon-optimized
|
|
38
|
+
* one; set `detailed` to request it (e.g. for a hero-scale display where the extra illustrative
|
|
39
|
+
* detail is actually visible). A no-op for every other code — see `detailed`'s own doc.
|
|
40
|
+
*
|
|
34
41
|
* @customElement lyra-flag
|
|
35
42
|
* @example <lyra-flag country="fr"></lyra-flag>
|
|
36
43
|
* @example <lyra-flag language="en" label="English"></lyra-flag>
|
|
37
44
|
* @example <lyra-flag src=${frUrl} label="French"></lyra-flag>
|
|
45
|
+
* @example <lyra-flag country="es" detailed></lyra-flag>
|
|
38
46
|
* @csspart image - The underlying <img>.
|
|
39
47
|
*/
|
|
40
48
|
export declare class LyraFlag extends LyraElement {
|
|
@@ -53,14 +61,27 @@ export declare class LyraFlag extends LyraElement {
|
|
|
53
61
|
*/
|
|
54
62
|
src?: string;
|
|
55
63
|
/**
|
|
56
|
-
* Accessible label / `alt` text. Defaults to
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
64
|
+
* Accessible label / `alt` text. Defaults to a localized, human-readable
|
|
65
|
+
* region name derived from the *resolved country code* via
|
|
66
|
+
* `Intl.DisplayNames` (e.g. `"United Kingdom"`) — for a `language`-only
|
|
67
|
+
* element (e.g. `language="en"`) that's the mapped country's display name,
|
|
68
|
+
* not the language tag itself. Falls back to the bare uppercase code if
|
|
69
|
+
* `Intl.DisplayNames` can't resolve it. Has no default when only `src` is
|
|
70
|
+
* given (no country/language to derive one from).
|
|
60
71
|
*/
|
|
61
72
|
label?: string;
|
|
62
73
|
/** Render as a circular flag. */
|
|
63
74
|
round: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Requests the pristine, full-detail source SVG instead of the default icon-optimized one —
|
|
77
|
+
* only meaningful for the minority of `country`/`language` codes whose source art was large
|
|
78
|
+
* enough to need optimizing (e.g. `es`, `pt`, a national coat of arms/seal/emblem); for every
|
|
79
|
+
* other code this is a safe no-op (the default and detailed variants are the same file). Has no
|
|
80
|
+
* effect when `src` is set — a pre-resolved URL is used as-is regardless. Intended for rendering
|
|
81
|
+
* a flag larger than icon scale (e.g. a hero display) where the extra illustrative detail is
|
|
82
|
+
* actually visible.
|
|
83
|
+
*/
|
|
84
|
+
detailed: boolean;
|
|
64
85
|
private resolvedSrc?;
|
|
65
86
|
/** True while the lazy-loaded `@aceshooting/lyra-flags` peer resolver is in flight. */
|
|
66
87
|
private loading;
|
|
@@ -28,6 +28,22 @@ export async function loadFlagUrl(importFlags = () => import('@aceshooting/lyra-
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolves an ISO 3166-1 alpha-2 region code to a human-readable, localized
|
|
33
|
+
* display name (e.g. `'FR'` -> `'France'`) via `Intl.DisplayNames`, for use as
|
|
34
|
+
* the default accessible name (`alt`) instead of a bare code read
|
|
35
|
+
* letter-by-letter by most screen readers. Falls back to the uppercase code
|
|
36
|
+
* itself if `Intl.DisplayNames` throws (unrecognized region) or isn't
|
|
37
|
+
* available in the current runtime.
|
|
38
|
+
*/
|
|
39
|
+
function displayNameFor(code) {
|
|
40
|
+
try {
|
|
41
|
+
return new Intl.DisplayNames([navigator.language], { type: 'region' }).of(code.toUpperCase()) ?? code.toUpperCase();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return code.toUpperCase();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
31
47
|
let flagUrlResolver;
|
|
32
48
|
/**
|
|
33
49
|
* Lazily loads the optional peer dependency '@aceshooting/lyra-flags' once per
|
|
@@ -59,10 +75,16 @@ function loadFlagUrlResolver() {
|
|
|
59
75
|
* `src` instead to skip the peer-package round trip (and its loading-skeleton
|
|
60
76
|
* flash) entirely.
|
|
61
77
|
*
|
|
78
|
+
* A handful of flags (e.g. `es`, `pt` — any whose design includes a detailed coat of
|
|
79
|
+
* arms/seal/emblem) ship a second, full-detail source SVG alongside the default icon-optimized
|
|
80
|
+
* one; set `detailed` to request it (e.g. for a hero-scale display where the extra illustrative
|
|
81
|
+
* detail is actually visible). A no-op for every other code — see `detailed`'s own doc.
|
|
82
|
+
*
|
|
62
83
|
* @customElement lyra-flag
|
|
63
84
|
* @example <lyra-flag country="fr"></lyra-flag>
|
|
64
85
|
* @example <lyra-flag language="en" label="English"></lyra-flag>
|
|
65
86
|
* @example <lyra-flag src=${frUrl} label="French"></lyra-flag>
|
|
87
|
+
* @example <lyra-flag country="es" detailed></lyra-flag>
|
|
66
88
|
* @csspart image - The underlying <img>.
|
|
67
89
|
*/
|
|
68
90
|
export class LyraFlag extends LyraElement {
|
|
@@ -70,6 +92,16 @@ export class LyraFlag extends LyraElement {
|
|
|
70
92
|
super(...arguments);
|
|
71
93
|
/** Render as a circular flag. */
|
|
72
94
|
this.round = false;
|
|
95
|
+
/**
|
|
96
|
+
* Requests the pristine, full-detail source SVG instead of the default icon-optimized one —
|
|
97
|
+
* only meaningful for the minority of `country`/`language` codes whose source art was large
|
|
98
|
+
* enough to need optimizing (e.g. `es`, `pt`, a national coat of arms/seal/emblem); for every
|
|
99
|
+
* other code this is a safe no-op (the default and detailed variants are the same file). Has no
|
|
100
|
+
* effect when `src` is set — a pre-resolved URL is used as-is regardless. Intended for rendering
|
|
101
|
+
* a flag larger than icon scale (e.g. a hero display) where the extra illustrative detail is
|
|
102
|
+
* actually visible.
|
|
103
|
+
*/
|
|
104
|
+
this.detailed = false;
|
|
73
105
|
/** True while the lazy-loaded `@aceshooting/lyra-flags` peer resolver is in flight. */
|
|
74
106
|
this.loading = true;
|
|
75
107
|
/**
|
|
@@ -96,8 +128,13 @@ export class LyraFlag extends LyraElement {
|
|
|
96
128
|
// undefined -> undefined (no-op to Lit), so none of `changed.has(...)`
|
|
97
129
|
// would otherwise ever become true for a `<lyra-flag>` that never
|
|
98
130
|
// receives one, leaving `loading` stuck at its initial `true` forever.
|
|
99
|
-
if (this.hasUpdated &&
|
|
131
|
+
if (this.hasUpdated &&
|
|
132
|
+
!changed.has('country') &&
|
|
133
|
+
!changed.has('language') &&
|
|
134
|
+
!changed.has('src') &&
|
|
135
|
+
!changed.has('detailed')) {
|
|
100
136
|
return;
|
|
137
|
+
}
|
|
101
138
|
const token = ++this.resolveToken; // invalidates any in-flight peer resolution either way
|
|
102
139
|
if (this.src) {
|
|
103
140
|
this.resolvedSrc = undefined;
|
|
@@ -112,7 +149,7 @@ export class LyraFlag extends LyraElement {
|
|
|
112
149
|
}
|
|
113
150
|
this.loading = true;
|
|
114
151
|
void loadFlagUrlResolver()
|
|
115
|
-
.then((resolve) => resolve?.(code))
|
|
152
|
+
.then((resolve) => resolve?.(code, this.detailed ? { variant: 'detailed' } : undefined))
|
|
116
153
|
.then((url) => {
|
|
117
154
|
if (token !== this.resolveToken)
|
|
118
155
|
return; // superseded by a later country/language/src change
|
|
@@ -132,7 +169,8 @@ export class LyraFlag extends LyraElement {
|
|
|
132
169
|
const url = this.src ?? this.resolvedSrc;
|
|
133
170
|
if (!url)
|
|
134
171
|
return html ``;
|
|
135
|
-
const
|
|
172
|
+
const code = this.code;
|
|
173
|
+
const alt = this.label ?? (code ? displayNameFor(code) : '');
|
|
136
174
|
return html `<img part="image" src=${url} alt=${alt} loading="lazy" decoding="async" />`;
|
|
137
175
|
}
|
|
138
176
|
}
|
|
@@ -151,6 +189,9 @@ __decorate([
|
|
|
151
189
|
__decorate([
|
|
152
190
|
property({ type: Boolean, reflect: true })
|
|
153
191
|
], LyraFlag.prototype, "round", void 0);
|
|
192
|
+
__decorate([
|
|
193
|
+
property({ type: Boolean, reflect: true })
|
|
194
|
+
], LyraFlag.prototype, "detailed", void 0);
|
|
154
195
|
__decorate([
|
|
155
196
|
state()
|
|
156
197
|
], LyraFlag.prototype, "resolvedSrc", void 0);
|
|
@@ -15,3 +15,17 @@ export const Gallery = {
|
|
|
15
15
|
</div>
|
|
16
16
|
`,
|
|
17
17
|
};
|
|
18
|
+
export const DetailedVariant = {
|
|
19
|
+
render: () => html `
|
|
20
|
+
<div style="display:flex; gap:2rem; align-items:center;">
|
|
21
|
+
<div style="display:flex; flex-direction:column; align-items:center; gap:0.5rem;">
|
|
22
|
+
<lyra-flag country="es" label="Spain (default, icon-optimized)" style="height: 6rem"></lyra-flag>
|
|
23
|
+
<span>default</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div style="display:flex; flex-direction:column; align-items:center; gap:0.5rem;">
|
|
26
|
+
<lyra-flag country="es" detailed label="Spain (detailed)" style="height: 6rem"></lyra-flag>
|
|
27
|
+
<span>detailed</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
`,
|
|
31
|
+
};
|
|
@@ -82,7 +82,9 @@ export declare function mixColor(fromColor: string, toColor: string, t: number):
|
|
|
82
82
|
* @customElement lyra-heatmap
|
|
83
83
|
* @event lyra-cell-click - Fired on click, or Enter/Space on the
|
|
84
84
|
* focused/hovered cell. `detail: { row, col, value }` in matrix mode,
|
|
85
|
-
* `detail: { date, value }` in calendar mode.
|
|
85
|
+
* `detail: { date, value }` in calendar mode. `cellText` overrides the
|
|
86
|
+
* built-in English "Row X, Col Y: value" / "Mon DD: value" template used for
|
|
87
|
+
* both the hover tooltip and the keyboard live-region announcement.
|
|
86
88
|
* @csspart base, canvas, tooltip, live-region, legend, legend-lo, legend-hi, legend-annotation
|
|
87
89
|
*/
|
|
88
90
|
export declare class LyraHeatmap extends LyraElement {
|
|
@@ -107,6 +109,10 @@ export declare class LyraHeatmap extends LyraElement {
|
|
|
107
109
|
bucketCount: number;
|
|
108
110
|
/** Cells to ring-highlight — `row`/`col` in matrix mode, `date` in calendar mode. See `HeatmapAnnotation`. */
|
|
109
111
|
annotations: HeatmapAnnotation[];
|
|
112
|
+
/** Formats the per-cell tooltip and keyboard live-region text — receives the cell position
|
|
113
|
+
* (`MatrixCellPos` in matrix mode, `CalendarCellPos` in calendar mode) and its value. Falls back to
|
|
114
|
+
* the built-in English "Row X, Col Y: value" / "Mon DD: value" template when unset. */
|
|
115
|
+
cellText?: (pos: MatrixCellPos | CalendarCellPos, value: number) => string;
|
|
110
116
|
private canvas?;
|
|
111
117
|
private resizeObserver?;
|
|
112
118
|
private dprQuery?;
|
|
@@ -185,11 +191,17 @@ export declare class LyraHeatmap extends LyraElement {
|
|
|
185
191
|
/**
|
|
186
192
|
* Human-readable "<label>: <value>" text for a cell — shared by the hover
|
|
187
193
|
* tooltip and the keyboard-focus live-region announcement, so both always
|
|
188
|
-
* describe a cell the same way.
|
|
194
|
+
* describe a cell the same way. This is the built-in English fallback used
|
|
195
|
+
* when `cellText` isn't set — see `resolveCellText()`.
|
|
189
196
|
*/
|
|
190
|
-
private
|
|
197
|
+
private defaultCellText;
|
|
191
198
|
private matrixCellText;
|
|
192
199
|
private calendarCellText;
|
|
200
|
+
/** The raw numeric value at a cell position, in either mode — shared by `resolveCellText()`
|
|
201
|
+
* so a custom `cellText` formatter receives the same value the built-in template would use. */
|
|
202
|
+
private valueAt;
|
|
203
|
+
/** Dispatches to the host-provided `cellText` formatter when set, otherwise the built-in template. */
|
|
204
|
+
private resolveCellText;
|
|
193
205
|
/** Refreshes the visually-hidden live-region text for a newly-focused cell. */
|
|
194
206
|
private announce;
|
|
195
207
|
private emitCellClick;
|
|
@@ -170,7 +170,9 @@ export function mixColor(fromColor, toColor, t) {
|
|
|
170
170
|
* @customElement lyra-heatmap
|
|
171
171
|
* @event lyra-cell-click - Fired on click, or Enter/Space on the
|
|
172
172
|
* focused/hovered cell. `detail: { row, col, value }` in matrix mode,
|
|
173
|
-
* `detail: { date, value }` in calendar mode.
|
|
173
|
+
* `detail: { date, value }` in calendar mode. `cellText` overrides the
|
|
174
|
+
* built-in English "Row X, Col Y: value" / "Mon DD: value" template used for
|
|
175
|
+
* both the hover tooltip and the keyboard live-region announcement.
|
|
174
176
|
* @csspart base, canvas, tooltip, live-region, legend, legend-lo, legend-hi, legend-annotation
|
|
175
177
|
*/
|
|
176
178
|
export class LyraHeatmap extends LyraElement {
|
|
@@ -270,7 +272,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
270
272
|
this.cachedValueRange = this.computeValueRange();
|
|
271
273
|
const bounds = this.cachedValueRange;
|
|
272
274
|
const range = bounds ? `${bounds[0]}–${bounds[1]}` : 'no data';
|
|
273
|
-
this.setAttribute('role', '
|
|
275
|
+
this.setAttribute('role', 'group');
|
|
274
276
|
if (this.mode === 'calendar') {
|
|
275
277
|
this.setAttribute('aria-label', `Calendar heatmap of ${this.days.length} days, ${this.valueLabel} range ${range}`);
|
|
276
278
|
}
|
|
@@ -579,9 +581,10 @@ export class LyraHeatmap extends LyraElement {
|
|
|
579
581
|
/**
|
|
580
582
|
* Human-readable "<label>: <value>" text for a cell — shared by the hover
|
|
581
583
|
* tooltip and the keyboard-focus live-region announcement, so both always
|
|
582
|
-
* describe a cell the same way.
|
|
584
|
+
* describe a cell the same way. This is the built-in English fallback used
|
|
585
|
+
* when `cellText` isn't set — see `resolveCellText()`.
|
|
583
586
|
*/
|
|
584
|
-
|
|
587
|
+
defaultCellText(pos) {
|
|
585
588
|
return 'week' in pos ? this.calendarCellText(pos) : this.matrixCellText(pos);
|
|
586
589
|
}
|
|
587
590
|
matrixCellText(pos) {
|
|
@@ -593,13 +596,24 @@ export class LyraHeatmap extends LyraElement {
|
|
|
593
596
|
}
|
|
594
597
|
calendarCellText(pos) {
|
|
595
598
|
const { date, value } = this.calendarCellAt(pos);
|
|
596
|
-
const label = parseIsoDate(date).toLocaleString(
|
|
599
|
+
const label = parseIsoDate(date).toLocaleString(undefined, { month: 'short', day: 'numeric', timeZone: 'UTC' });
|
|
597
600
|
const valueText = value < 0 || !Number.isFinite(value) ? 'no data' : String(value);
|
|
598
601
|
return `${label}: ${valueText}`;
|
|
599
602
|
}
|
|
603
|
+
/** The raw numeric value at a cell position, in either mode — shared by `resolveCellText()`
|
|
604
|
+
* so a custom `cellText` formatter receives the same value the built-in template would use. */
|
|
605
|
+
valueAt(pos) {
|
|
606
|
+
if ('week' in pos)
|
|
607
|
+
return this.calendarCellAt(pos).value;
|
|
608
|
+
return this.values[pos.row]?.[pos.col] ?? -1;
|
|
609
|
+
}
|
|
610
|
+
/** Dispatches to the host-provided `cellText` formatter when set, otherwise the built-in template. */
|
|
611
|
+
resolveCellText(pos) {
|
|
612
|
+
return this.cellText ? this.cellText(pos, this.valueAt(pos)) : this.defaultCellText(pos);
|
|
613
|
+
}
|
|
600
614
|
/** Refreshes the visually-hidden live-region text for a newly-focused cell. */
|
|
601
615
|
announce(pos) {
|
|
602
|
-
this.liveText = this.
|
|
616
|
+
this.liveText = this.resolveCellText(pos);
|
|
603
617
|
}
|
|
604
618
|
emitCellClick(pos) {
|
|
605
619
|
if ('week' in pos) {
|
|
@@ -703,7 +717,7 @@ export class LyraHeatmap extends LyraElement {
|
|
|
703
717
|
?hidden=${!this.hoverCell}
|
|
704
718
|
style=${styleMap(this.hoverCell ? this.tooltipStyle(this.hoverCell) : {})}
|
|
705
719
|
>
|
|
706
|
-
${this.hoverCell ? this.
|
|
720
|
+
${this.hoverCell ? this.resolveCellText(this.hoverCell) : ''}
|
|
707
721
|
</div>
|
|
708
722
|
<div part="live-region" class="sr-only" role="status" aria-live="polite">${this.liveText}</div>
|
|
709
723
|
<div part="legend">
|
|
@@ -750,6 +764,9 @@ __decorate([
|
|
|
750
764
|
__decorate([
|
|
751
765
|
property({ attribute: false })
|
|
752
766
|
], LyraHeatmap.prototype, "annotations", void 0);
|
|
767
|
+
__decorate([
|
|
768
|
+
property({ attribute: false })
|
|
769
|
+
], LyraHeatmap.prototype, "cellText", void 0);
|
|
753
770
|
__decorate([
|
|
754
771
|
query('canvas')
|
|
755
772
|
], LyraHeatmap.prototype, "canvas", void 0);
|
|
@@ -18,5 +18,10 @@ export declare const HoverFocusClick: Story;
|
|
|
18
18
|
* also surface a swatch + text entry in the legend.
|
|
19
19
|
*/
|
|
20
20
|
export declare const Annotations: Story;
|
|
21
|
+
/**
|
|
22
|
+
* `cellText` overrides the built-in English tooltip/live-region template —
|
|
23
|
+
* here with a French translation — for both matrix and calendar modes.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CustomCellText: Story;
|
|
21
26
|
/** `annotations` in calendar mode match by ISO `date` instead of `row`/`col`. */
|
|
22
27
|
export declare const CalendarAnnotations: Story;
|
|
@@ -131,6 +131,31 @@ export const Annotations = {
|
|
|
131
131
|
></lyra-heatmap>
|
|
132
132
|
`,
|
|
133
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* `cellText` overrides the built-in English tooltip/live-region template —
|
|
136
|
+
* here with a French translation — for both matrix and calendar modes.
|
|
137
|
+
*/
|
|
138
|
+
export const CustomCellText = {
|
|
139
|
+
render: () => html `
|
|
140
|
+
<lyra-heatmap
|
|
141
|
+
cell-size="28"
|
|
142
|
+
value-label="évènements"
|
|
143
|
+
.rowLabels=${['Lun', 'Mar', 'Mer']}
|
|
144
|
+
.colLabels=${['0h', '6h', '12h']}
|
|
145
|
+
.values=${[
|
|
146
|
+
[1, 4, 9],
|
|
147
|
+
[0, 2, 6],
|
|
148
|
+
[-1, 1, 4],
|
|
149
|
+
]}
|
|
150
|
+
.cellText=${(pos, value) => {
|
|
151
|
+
const rows = ['Lun', 'Mar', 'Mer'];
|
|
152
|
+
const cols = ['0h', '6h', '12h'];
|
|
153
|
+
const valueText = value < 0 ? 'aucune donnée' : String(value);
|
|
154
|
+
return `Ligne ${rows[pos.row]}, Col ${cols[pos.col]} : ${valueText}`;
|
|
155
|
+
}}
|
|
156
|
+
></lyra-heatmap>
|
|
157
|
+
`,
|
|
158
|
+
};
|
|
134
159
|
/** `annotations` in calendar mode match by ISO `date` instead of `row`/`col`. */
|
|
135
160
|
export const CalendarAnnotations = {
|
|
136
161
|
render: () => {
|
|
@@ -42,6 +42,7 @@ import '../combobox/option.js';
|
|
|
42
42
|
* @csspart error - The error message.
|
|
43
43
|
* @csspart hint - The hint message.
|
|
44
44
|
*/
|
|
45
|
+
export type LyraSelectSize = 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
45
46
|
export declare class LyraSelect extends LyraElement {
|
|
46
47
|
static formAssociated: boolean;
|
|
47
48
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -58,6 +59,8 @@ export declare class LyraSelect extends LyraElement {
|
|
|
58
59
|
hint: string;
|
|
59
60
|
errorText: string;
|
|
60
61
|
open: boolean;
|
|
62
|
+
/** Visual size — same `xs`–`xl` scale as `lyra-toast-item`'s `size`. */
|
|
63
|
+
size: LyraSelectSize;
|
|
61
64
|
private activeIndex;
|
|
62
65
|
private options;
|
|
63
66
|
private touched;
|