@aceshooting/lyra-ui 0.1.4 → 1.0.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 +17 -8
- package/custom-elements.json +4460 -2940
- package/dist/components/chart/bar-chart.stories.d.ts +5 -0
- package/dist/components/chart/bar-chart.stories.js +20 -0
- package/dist/components/chart/box-plot.stories.d.ts +5 -0
- package/dist/components/chart/box-plot.stories.js +29 -0
- package/dist/components/chart/bubble-chart.stories.d.ts +5 -0
- package/dist/components/chart/bubble-chart.stories.js +26 -0
- package/dist/components/chart/doughnut-chart.stories.d.ts +5 -0
- package/dist/components/chart/doughnut-chart.stories.js +26 -0
- package/dist/components/chart/histogram.stories.d.ts +5 -0
- package/dist/components/chart/histogram.stories.js +17 -0
- package/dist/components/chart/line-chart.stories.d.ts +5 -0
- package/dist/components/chart/line-chart.stories.js +24 -0
- package/dist/components/chart/pie-chart.stories.d.ts +5 -0
- package/dist/components/chart/pie-chart.stories.js +26 -0
- package/dist/components/chart/polar-area-chart.stories.d.ts +5 -0
- package/dist/components/chart/polar-area-chart.stories.js +20 -0
- package/dist/components/chart/radar-chart.stories.d.ts +5 -0
- package/dist/components/chart/radar-chart.stories.js +20 -0
- package/dist/components/chart/scatter-chart.stories.d.ts +5 -0
- package/dist/components/chart/scatter-chart.stories.js +31 -0
- package/dist/components/combobox/combobox.d.ts +26 -1
- package/dist/components/combobox/combobox.js +147 -31
- package/dist/components/combobox/combobox.styles.js +21 -0
- package/dist/components/combobox/option.d.ts +4 -0
- package/dist/components/combobox/option.js +10 -0
- package/dist/components/date-picker/calendar-core.d.ts +6 -2
- package/dist/components/date-picker/calendar-core.js +23 -4
- package/dist/components/date-picker/date-input.d.ts +1 -0
- package/dist/components/date-picker/date-input.js +19 -2
- package/dist/components/date-picker/date-picker.js +4 -2
- package/dist/components/date-picker/date-picker.styles.js +5 -1
- package/dist/components/empty/empty.stories.d.ts +5 -0
- package/dist/components/empty/empty.stories.js +14 -0
- package/dist/components/export-button/export-button.js +7 -2
- package/dist/components/export-button/export-button.stories.d.ts +6 -0
- package/dist/components/export-button/export-button.stories.js +32 -0
- package/dist/components/file-input/file-input.stories.d.ts +5 -0
- package/dist/components/file-input/file-input.stories.js +10 -0
- package/dist/components/flag/flag.js +12 -2
- package/dist/components/gauge/gauge.js +8 -4
- package/dist/components/gauge/gauge.stories.d.ts +6 -0
- package/dist/components/gauge/gauge.stories.js +13 -0
- package/dist/components/graph/graph.stories.d.ts +5 -0
- package/dist/components/graph/graph.stories.js +30 -0
- package/dist/components/heatmap/heatmap.stories.d.ts +5 -0
- package/dist/components/heatmap/heatmap.stories.js +24 -0
- package/dist/components/map/map.d.ts +6 -0
- package/dist/components/map/map.js +69 -9
- package/dist/components/map/map.stories.d.ts +5 -0
- package/dist/components/map/map.stories.js +40 -0
- package/dist/components/playback/playback.js +6 -0
- package/dist/components/playback/playback.stories.d.ts +5 -0
- package/dist/components/playback/playback.stories.js +10 -0
- package/dist/components/skeleton/skeleton.stories.d.ts +5 -0
- package/dist/components/skeleton/skeleton.stories.js +16 -0
- package/dist/components/split/split.d.ts +1 -0
- package/dist/components/split/split.js +25 -4
- package/dist/components/split/split.stories.d.ts +5 -0
- package/dist/components/split/split.stories.js +16 -0
- package/dist/components/stat/stat.stories.d.ts +5 -0
- package/dist/components/stat/stat.stories.js +16 -0
- package/dist/components/table/table.stories.d.ts +6 -0
- package/dist/components/table/table.stories.js +22 -0
- package/dist/components/time-range/time-range.stories.d.ts +6 -0
- package/dist/components/time-range/time-range.stories.js +13 -0
- package/dist/components/toast/toast-item.d.ts +1 -0
- package/dist/components/toast/toast-item.js +16 -1
- package/dist/components/tree/tree.stories.d.ts +5 -0
- package/dist/components/tree/tree.stories.js +22 -0
- package/dist/components/widget/widget.d.ts +40 -0
- package/dist/components/widget/widget.js +159 -0
- package/dist/components/widget/widget.stories.d.ts +6 -0
- package/dist/components/widget/widget.stories.js +31 -0
- package/dist/components/widget/widget.styles.d.ts +1 -0
- package/dist/components/widget/widget.styles.js +90 -0
- package/dist/internal/icons.d.ts +2 -0
- package/dist/internal/icons.js +9 -0
- package/dist/internal/scroll-lock.d.ts +7 -0
- package/dist/internal/scroll-lock.js +25 -0
- package/dist/internal/tokens.styles.js +5 -0
- package/dist/lyra.d.ts +5 -0
- package/dist/lyra.js +2 -0
- package/llms.txt +3 -0
- package/package.json +3 -3
|
@@ -11,6 +11,15 @@ import { defineElement } from '../../internal/prefix.js';
|
|
|
11
11
|
import { styles } from './flag.styles.js';
|
|
12
12
|
import { languageToCountry } from './language-map.js';
|
|
13
13
|
import '../skeleton/skeleton.js';
|
|
14
|
+
/**
|
|
15
|
+
* ISO 3166-1 alpha-2 country code: exactly two letters, case-insensitive.
|
|
16
|
+
* The peer package `@aceshooting/lyra-flags` naively interpolates its `code`
|
|
17
|
+
* argument into a `new URL('./flags/${code}.svg', ...)` with no validation
|
|
18
|
+
* of its own — an unvalidated `country` containing `../` segments (e.g.
|
|
19
|
+
* `../../etc`) can escape the intended flags/ directory. Anything that
|
|
20
|
+
* doesn't match this shape is treated the same as an unknown/missing flag.
|
|
21
|
+
*/
|
|
22
|
+
const COUNTRY_CODE_RE = /^[a-z]{2}$/i;
|
|
14
23
|
let flagUrlResolver;
|
|
15
24
|
/**
|
|
16
25
|
* Lazily loads the optional peer dependency '@aceshooting/lyra-flags' once per
|
|
@@ -51,8 +60,9 @@ export class LyraFlag extends LyraElement {
|
|
|
51
60
|
}
|
|
52
61
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
53
62
|
get code() {
|
|
54
|
-
if (this.country)
|
|
55
|
-
return this.country.toLowerCase();
|
|
63
|
+
if (this.country) {
|
|
64
|
+
return COUNTRY_CODE_RE.test(this.country) ? this.country.toLowerCase() : undefined;
|
|
65
|
+
}
|
|
56
66
|
if (this.language)
|
|
57
67
|
return languageToCountry(this.language);
|
|
58
68
|
return undefined;
|
|
@@ -69,6 +69,10 @@ export class LyraGauge extends LyraElement {
|
|
|
69
69
|
this.setAttribute('aria-label', this.label);
|
|
70
70
|
else
|
|
71
71
|
this.removeAttribute('aria-label');
|
|
72
|
+
if (this.valueLabel)
|
|
73
|
+
this.setAttribute('aria-valuetext', this.valueLabel);
|
|
74
|
+
else
|
|
75
|
+
this.removeAttribute('aria-valuetext');
|
|
72
76
|
}
|
|
73
77
|
renderRadial() {
|
|
74
78
|
const text = this.valueLabel ?? String(this.value);
|
|
@@ -85,8 +89,8 @@ export class LyraGauge extends LyraElement {
|
|
|
85
89
|
stroke-dasharray=${RADIAL_ARC_LENGTH}
|
|
86
90
|
stroke-dashoffset=${dashoffset}
|
|
87
91
|
></path>
|
|
88
|
-
<text part="value" x="50" y="52">${text}</text>
|
|
89
|
-
${this.label ? svg `<text part="label" x="50" y="68">${this.label}</text>` : ''}
|
|
92
|
+
<text part="value" x="50" y="52" aria-hidden="true">${text}</text>
|
|
93
|
+
${this.label ? svg `<text part="label" x="50" y="68" aria-hidden="true">${this.label}</text>` : ''}
|
|
90
94
|
</svg>`;
|
|
91
95
|
}
|
|
92
96
|
renderLinear() {
|
|
@@ -104,8 +108,8 @@ export class LyraGauge extends LyraElement {
|
|
|
104
108
|
stroke-dasharray=${LINEAR_LENGTH}
|
|
105
109
|
stroke-dashoffset=${dashoffset}
|
|
106
110
|
></line>
|
|
107
|
-
${this.label ? svg `<text part="label" x="0" y=${LINEAR_TEXT_Y}>${this.label}</text>` : ''}
|
|
108
|
-
<text part="value" x="100" y=${LINEAR_TEXT_Y}>${text}</text>
|
|
111
|
+
${this.label ? svg `<text part="label" x="0" y=${LINEAR_TEXT_Y} aria-hidden="true">${this.label}</text>` : ''}
|
|
112
|
+
<text part="value" x="100" y=${LINEAR_TEXT_Y} aria-hidden="true">${text}</text>
|
|
109
113
|
</svg>`;
|
|
110
114
|
}
|
|
111
115
|
render() {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Gauge',
|
|
4
|
+
component: 'lyra-gauge',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Radial = {
|
|
9
|
+
render: () => html `<lyra-gauge value="72" max="100" label="CPU"></lyra-gauge>`,
|
|
10
|
+
};
|
|
11
|
+
export const Linear = {
|
|
12
|
+
render: () => html `<lyra-gauge type="linear" value="40" max="100" label="Disk"></lyra-gauge>`,
|
|
13
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const nodes = [
|
|
3
|
+
{ id: 'a', label: 'A' },
|
|
4
|
+
{ id: 'b', label: 'B' },
|
|
5
|
+
{ id: 'c', label: 'C' },
|
|
6
|
+
{ id: 'd', label: 'D' },
|
|
7
|
+
];
|
|
8
|
+
const links = [
|
|
9
|
+
{ source: 'a', target: 'b' },
|
|
10
|
+
{ source: 'a', target: 'c' },
|
|
11
|
+
{ source: 'b', target: 'd' },
|
|
12
|
+
{ source: 'c', target: 'd' },
|
|
13
|
+
];
|
|
14
|
+
const meta = {
|
|
15
|
+
title: 'Graph',
|
|
16
|
+
component: 'lyra-graph',
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
};
|
|
19
|
+
export default meta;
|
|
20
|
+
export const Default = {
|
|
21
|
+
render: () => html `
|
|
22
|
+
<lyra-graph
|
|
23
|
+
width="480"
|
|
24
|
+
height="320"
|
|
25
|
+
style="height: 20rem"
|
|
26
|
+
.nodes=${nodes}
|
|
27
|
+
.links=${links}
|
|
28
|
+
></lyra-graph>
|
|
29
|
+
`,
|
|
30
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Heatmap',
|
|
4
|
+
component: 'lyra-heatmap',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Default = {
|
|
9
|
+
render: () => html `
|
|
10
|
+
<lyra-heatmap
|
|
11
|
+
cell-size="24"
|
|
12
|
+
value-label="events"
|
|
13
|
+
.rowLabels=${['Mon', 'Tue', 'Wed', 'Thu', 'Fri']}
|
|
14
|
+
.colLabels=${['0h', '6h', '12h', '18h']}
|
|
15
|
+
.values=${[
|
|
16
|
+
[1, 4, 9, 2],
|
|
17
|
+
[0, 2, 6, 3],
|
|
18
|
+
[5, 8, 3, 1],
|
|
19
|
+
[-1, 1, 4, 7],
|
|
20
|
+
[2, 3, 5, 6],
|
|
21
|
+
]}
|
|
22
|
+
></lyra-heatmap>
|
|
23
|
+
`,
|
|
24
|
+
};
|
|
@@ -28,6 +28,11 @@ export interface MapMarker {
|
|
|
28
28
|
* @event lyra-map-load - Fired once the underlying maplibregl.Map loads.
|
|
29
29
|
* @event lyra-map-click - `detail: { lngLat, feature? }`.
|
|
30
30
|
* @csspart base, container, legend, legend-swatch
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ The default `mapStyle` (when unset) uses OpenStreetMap's demo tile
|
|
33
|
+
* server, which is not suitable for production traffic — see the
|
|
34
|
+
* `DEFAULT_STYLE` doc comment in `map.ts`. Always pass an explicit
|
|
35
|
+
* `mapStyle` in production.
|
|
31
36
|
*/
|
|
32
37
|
export declare class LyraMap extends LyraElement {
|
|
33
38
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -46,6 +51,7 @@ export declare class LyraMap extends LyraElement {
|
|
|
46
51
|
private _appliedFillLayerId?;
|
|
47
52
|
private _maplibreModule;
|
|
48
53
|
private _markerInstances;
|
|
54
|
+
private _connectGeneration;
|
|
49
55
|
/** The raw `maplibregl.Map` instance — escape hatch for anything this wrapper doesn't expose. */
|
|
50
56
|
get map(): MaplibreMap | undefined;
|
|
51
57
|
connectedCallback(): void;
|
|
@@ -7,11 +7,24 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
/// <reference types="geojson" />
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property, query, state } from 'lit/decorators.js';
|
|
10
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
10
11
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
11
12
|
import { defineElement } from '../../internal/prefix.js';
|
|
12
13
|
import { loadMaplibre } from './map-loader.js';
|
|
13
14
|
import { styles } from './map.styles.js';
|
|
14
15
|
import '../skeleton/skeleton.js';
|
|
16
|
+
/**
|
|
17
|
+
* Fallback `mapStyle` used only when a consumer never sets one. Points at
|
|
18
|
+
* OpenStreetMap's shared **demo** tile server (`tile.openstreetmap.org`),
|
|
19
|
+
* which is explicitly NOT for production/bulk use: it has no capacity
|
|
20
|
+
* guarantees, requires an identifying `User-Agent`/`Referer`, and will
|
|
21
|
+
* rate-limit or IP-block non-compliant or high-volume clients per the OSM
|
|
22
|
+
* Foundation's tile usage policy
|
|
23
|
+
* (https://operations.osmfoundation.org/policies/tiles/). Convenient for
|
|
24
|
+
* local development and quick prototypes only — production apps MUST supply
|
|
25
|
+
* their own `mapStyle` (a hosted vector/raster style from a tile provider
|
|
26
|
+
* you have a plan with, or your own tile server).
|
|
27
|
+
*/
|
|
15
28
|
const DEFAULT_STYLE = {
|
|
16
29
|
version: 8,
|
|
17
30
|
sources: {
|
|
@@ -24,6 +37,19 @@ const DEFAULT_STYLE = {
|
|
|
24
37
|
},
|
|
25
38
|
layers: [{ id: 'lyra-osm', type: 'raster', source: 'lyra-osm' }],
|
|
26
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Rejects a `LegendEntry.color` that could break out of the single
|
|
42
|
+
* `background` declaration it's assigned to — `;`, `{`, and `}` are all a
|
|
43
|
+
* value needs to terminate that declaration and start another. This matters
|
|
44
|
+
* even though the swatch's color is set via Lit's `styleMap` directive (not
|
|
45
|
+
* raw string interpolation): `styleMap`'s first commit for a given
|
|
46
|
+
* attribute part serializes the whole `style` value as a single string
|
|
47
|
+
* (only later updates go through the safe `CSSStyleDeclaration.setProperty()`
|
|
48
|
+
* path), so an unsanitized value could still inject on that first render.
|
|
49
|
+
*/
|
|
50
|
+
function sanitizeSwatchColor(color) {
|
|
51
|
+
return /[;{}]/.test(color) ? undefined : color;
|
|
52
|
+
}
|
|
27
53
|
/**
|
|
28
54
|
* `<lyra-map>` — a maplibre-gl wrapper with a declarative legend and
|
|
29
55
|
* choropleth GeoJSON layer, plus a raw `map` escape hatch. Requires the
|
|
@@ -33,6 +59,11 @@ const DEFAULT_STYLE = {
|
|
|
33
59
|
* @event lyra-map-load - Fired once the underlying maplibregl.Map loads.
|
|
34
60
|
* @event lyra-map-click - `detail: { lngLat, feature? }`.
|
|
35
61
|
* @csspart base, container, legend, legend-swatch
|
|
62
|
+
*
|
|
63
|
+
* ⚠️ The default `mapStyle` (when unset) uses OpenStreetMap's demo tile
|
|
64
|
+
* server, which is not suitable for production traffic — see the
|
|
65
|
+
* `DEFAULT_STYLE` doc comment in `map.ts`. Always pass an explicit
|
|
66
|
+
* `mapStyle` in production.
|
|
36
67
|
*/
|
|
37
68
|
export class LyraMap extends LyraElement {
|
|
38
69
|
constructor() {
|
|
@@ -52,6 +83,17 @@ export class LyraMap extends LyraElement {
|
|
|
52
83
|
// choropleth updates from ever re-running.
|
|
53
84
|
this._styleLoaded = false;
|
|
54
85
|
this._markerInstances = new Map();
|
|
86
|
+
// Bumped on every connectedCallback and captured by value in its
|
|
87
|
+
// loadMaplibre().then() closure below. A disconnect immediately followed
|
|
88
|
+
// by a reconnect (fast remounts, route/tab switches, etc.) before that
|
|
89
|
+
// cached promise settles would otherwise let *every* connect attempt's
|
|
90
|
+
// closure construct its own maplibregl.Map against the same container —
|
|
91
|
+
// only the last one written to `this._map` survives, and the earlier
|
|
92
|
+
// ones leak their WebGL context/canvas/event listeners forever. Each
|
|
93
|
+
// closure compares its captured generation against the current value
|
|
94
|
+
// before doing anything observable, and bails if a newer
|
|
95
|
+
// connectedCallback has since superseded it.
|
|
96
|
+
this._connectGeneration = 0;
|
|
55
97
|
}
|
|
56
98
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
57
99
|
/** The raw `maplibregl.Map` instance — escape hatch for anything this wrapper doesn't expose. */
|
|
@@ -60,7 +102,15 @@ export class LyraMap extends LyraElement {
|
|
|
60
102
|
}
|
|
61
103
|
connectedCallback() {
|
|
62
104
|
super.connectedCallback();
|
|
105
|
+
const generation = ++this._connectGeneration;
|
|
63
106
|
void loadMaplibre().then(async (mod) => {
|
|
107
|
+
// A newer connectedCallback (disconnect + reconnect) already
|
|
108
|
+
// superseded this attempt while loadMaplibre()'s cached promise was
|
|
109
|
+
// in flight — bail before touching any state, let the newer attempt's
|
|
110
|
+
// own closure (already queued behind this one on the same promise)
|
|
111
|
+
// take over instead.
|
|
112
|
+
if (generation !== this._connectGeneration)
|
|
113
|
+
return;
|
|
64
114
|
this.loading = false;
|
|
65
115
|
if (!mod)
|
|
66
116
|
return;
|
|
@@ -68,11 +118,13 @@ export class LyraMap extends LyraElement {
|
|
|
68
118
|
// `[part="container"]` only exists once `loading` flips to `false` and
|
|
69
119
|
// Lit re-renders — wait for that render to land before querying it.
|
|
70
120
|
await this.updateComplete;
|
|
71
|
-
//
|
|
72
|
-
// loadMaplibre()/updateComplete window
|
|
73
|
-
// a maplibre Map (WebGL context + event
|
|
74
|
-
// instance (disconnectedCallback's cleanup
|
|
75
|
-
|
|
121
|
+
// Re-check after the await: the element may have been removed from
|
|
122
|
+
// the DOM entirely while this loadMaplibre()/updateComplete window
|
|
123
|
+
// was in flight — don't spin up a maplibre Map (WebGL context + event
|
|
124
|
+
// listeners) for a detached instance (disconnectedCallback's cleanup
|
|
125
|
+
// already ran) — or superseded by yet another disconnect+reconnect
|
|
126
|
+
// cycle that happened during the `await` above.
|
|
127
|
+
if (generation !== this._connectGeneration || !this.containerEl || !this.isConnected)
|
|
76
128
|
return;
|
|
77
129
|
this._map = new mod.Map({
|
|
78
130
|
container: this.containerEl,
|
|
@@ -122,11 +174,16 @@ export class LyraMap extends LyraElement {
|
|
|
122
174
|
this._styleLoaded = false;
|
|
123
175
|
this._appliedChoroplethSourceId = undefined;
|
|
124
176
|
this._appliedFillLayerId = undefined;
|
|
125
|
-
|
|
177
|
+
// Register the listener *before* calling setStyle(): maplibre-gl's
|
|
178
|
+
// diff-based style update path (small, incremental style changes) can
|
|
179
|
+
// fire 'style.load' synchronously from inside setStyle() itself —
|
|
180
|
+
// registering the listener afterwards would miss that emission and
|
|
181
|
+
// leave the choropleth (and `_styleLoaded`) never re-applied.
|
|
126
182
|
this._map.once('style.load', () => {
|
|
127
183
|
this._styleLoaded = true;
|
|
128
184
|
this.applyChoropleth();
|
|
129
185
|
});
|
|
186
|
+
this._map.setStyle(this.mapStyle);
|
|
130
187
|
}
|
|
131
188
|
else if (changed.has('choropleth') && this._styleLoaded) {
|
|
132
189
|
this.applyChoropleth();
|
|
@@ -234,10 +291,13 @@ export class LyraMap extends LyraElement {
|
|
|
234
291
|
: html `<div part="container"></div>`}
|
|
235
292
|
${this.legend.length
|
|
236
293
|
? html `<div part="legend">
|
|
237
|
-
${this.legend.map((entry) =>
|
|
238
|
-
|
|
294
|
+
${this.legend.map((entry) => {
|
|
295
|
+
const bg = sanitizeSwatchColor(entry.color);
|
|
296
|
+
return html `<div class="legend-row">
|
|
297
|
+
<span part="legend-swatch" style=${styleMap(bg ? { background: bg } : {})}></span>
|
|
239
298
|
<span>${entry.label}</span>
|
|
240
|
-
</div
|
|
299
|
+
</div>`;
|
|
300
|
+
})}
|
|
241
301
|
</div>`
|
|
242
302
|
: nothing}
|
|
243
303
|
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const legend = [
|
|
3
|
+
{ color: '#5b8def', label: 'Low' },
|
|
4
|
+
{ color: '#e5484d', label: 'High' },
|
|
5
|
+
];
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Map',
|
|
8
|
+
component: 'lyra-map',
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: 'Demoed with a raster OpenStreetMap tile style since a vector style needs an API key.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export default meta;
|
|
19
|
+
export const Default = {
|
|
20
|
+
render: () => html `
|
|
21
|
+
<lyra-map
|
|
22
|
+
style="height: 20rem"
|
|
23
|
+
center="[2.3522, 48.8566]"
|
|
24
|
+
zoom="4"
|
|
25
|
+
.legend=${legend}
|
|
26
|
+
.mapStyle=${{
|
|
27
|
+
version: 8,
|
|
28
|
+
sources: {
|
|
29
|
+
demo: {
|
|
30
|
+
type: 'raster',
|
|
31
|
+
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
|
|
32
|
+
tileSize: 256,
|
|
33
|
+
attribution: '© OpenStreetMap contributors',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
layers: [{ id: 'demo', type: 'raster', source: 'demo' }],
|
|
37
|
+
}}
|
|
38
|
+
></lyra-map>
|
|
39
|
+
`,
|
|
40
|
+
};
|
|
@@ -46,6 +46,12 @@ export class LyraPlayback extends LyraElement {
|
|
|
46
46
|
willUpdate(changed) {
|
|
47
47
|
if (changed.has('hidden') && this.hidden)
|
|
48
48
|
this.pause();
|
|
49
|
+
// If length is externally reduced to <= 1 while playing, the timer would
|
|
50
|
+
// otherwise keep firing forever — and the play button (the only control
|
|
51
|
+
// that could stop it) becomes ?disabled at that point, leaving no way to
|
|
52
|
+
// stop it from the rendered UI.
|
|
53
|
+
if (changed.has('length') && this.length <= 1)
|
|
54
|
+
this.pause();
|
|
49
55
|
}
|
|
50
56
|
/** Start playback; no-op if there's nothing to advance through. */
|
|
51
57
|
play() {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Playback',
|
|
4
|
+
component: 'lyra-playback',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Default = {
|
|
9
|
+
render: () => html `<lyra-playback length="10" interval-ms="500" loop></lyra-playback>`,
|
|
10
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Skeleton',
|
|
4
|
+
component: 'lyra-skeleton',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Gallery = {
|
|
9
|
+
render: () => html `
|
|
10
|
+
<div style="display:flex; gap:1rem; align-items:center;">
|
|
11
|
+
<lyra-skeleton width="10rem" height="1rem"></lyra-skeleton>
|
|
12
|
+
<lyra-skeleton variant="circle" width="3rem" height="3rem"></lyra-skeleton>
|
|
13
|
+
<lyra-skeleton variant="rect" effect="sheen" width="6rem" height="3rem"></lyra-skeleton>
|
|
14
|
+
</div>
|
|
15
|
+
`,
|
|
16
|
+
};
|
|
@@ -29,6 +29,10 @@ export class LyraSplit extends LyraElement {
|
|
|
29
29
|
this.dragIndex = -1;
|
|
30
30
|
this.dragStartPos = 0;
|
|
31
31
|
this.dragStartSizes = [];
|
|
32
|
+
this.onSlotChange = () => {
|
|
33
|
+
this.panelCount = this.children.length;
|
|
34
|
+
this.ensureSizes();
|
|
35
|
+
};
|
|
32
36
|
this.onPointerDown = (e, index) => {
|
|
33
37
|
this.dragIndex = index;
|
|
34
38
|
this.dragStartPos = this.orientation === 'vertical' ? e.clientY : e.clientX;
|
|
@@ -86,7 +90,14 @@ export class LyraSplit extends LyraElement {
|
|
|
86
90
|
const key = this.storageFullKey;
|
|
87
91
|
if (!key)
|
|
88
92
|
return;
|
|
89
|
-
|
|
93
|
+
let raw;
|
|
94
|
+
try {
|
|
95
|
+
raw = localStorage.getItem(key);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* localStorage unavailable (private browsing, sandboxed iframe, etc.) */
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
90
101
|
if (!raw)
|
|
91
102
|
return;
|
|
92
103
|
try {
|
|
@@ -100,8 +111,14 @@ export class LyraSplit extends LyraElement {
|
|
|
100
111
|
}
|
|
101
112
|
persist() {
|
|
102
113
|
const key = this.storageFullKey;
|
|
103
|
-
if (key)
|
|
114
|
+
if (!key)
|
|
115
|
+
return;
|
|
116
|
+
try {
|
|
104
117
|
localStorage.setItem(key, JSON.stringify(this.sizes));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* ignore persistence failures (e.g. quota exceeded, private browsing) */
|
|
121
|
+
}
|
|
105
122
|
}
|
|
106
123
|
ensureSizes() {
|
|
107
124
|
if (this.sizes.length === this.panelCount && this.panelCount > 0)
|
|
@@ -137,20 +154,24 @@ export class LyraSplit extends LyraElement {
|
|
|
137
154
|
render() {
|
|
138
155
|
const dividers = [];
|
|
139
156
|
for (let i = 0; i < this.panelCount - 1; i++) {
|
|
157
|
+
// The achievable max for this divider is bounded by its two adjacent
|
|
158
|
+
// panels, not the whole track — pushing past it would starve a panel
|
|
159
|
+
// further down the line even though this pair still has room.
|
|
160
|
+
const valueMax = (this.sizes[i] ?? 0) + (this.sizes[i + 1] ?? 0) - this.min;
|
|
140
161
|
dividers.push(html `<div
|
|
141
162
|
part="divider"
|
|
142
163
|
role="separator"
|
|
143
164
|
aria-orientation=${this.orientation === 'vertical' ? 'horizontal' : 'vertical'}
|
|
144
165
|
aria-valuenow=${Math.round(this.sizes[i] ?? 0)}
|
|
145
166
|
aria-valuemin=${this.min}
|
|
146
|
-
aria-valuemax=${
|
|
167
|
+
aria-valuemax=${Math.round(valueMax)}
|
|
147
168
|
tabindex="0"
|
|
148
169
|
style=${`order:${i * 2 + 1}`}
|
|
149
170
|
@pointerdown=${(e) => this.onPointerDown(e, i)}
|
|
150
171
|
@keydown=${(e) => this.onDividerKeyDown(e, i)}
|
|
151
172
|
></div>`);
|
|
152
173
|
}
|
|
153
|
-
return html `<div part="base"><slot></slot>${dividers}</div>`;
|
|
174
|
+
return html `<div part="base"><slot @slotchange=${this.onSlotChange}></slot>${dividers}</div>`;
|
|
154
175
|
}
|
|
155
176
|
}
|
|
156
177
|
__decorate([
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Split',
|
|
4
|
+
component: 'lyra-split',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Default = {
|
|
9
|
+
render: () => html `
|
|
10
|
+
<lyra-split style="height: 8rem; border: 1px solid #ddd">
|
|
11
|
+
<div style="padding: 0.5rem">Panel A</div>
|
|
12
|
+
<div style="padding: 0.5rem">Panel B</div>
|
|
13
|
+
<div style="padding: 0.5rem">Panel C</div>
|
|
14
|
+
</lyra-split>
|
|
15
|
+
`,
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Stat',
|
|
4
|
+
component: 'lyra-stat',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Gallery = {
|
|
9
|
+
render: () => html `
|
|
10
|
+
<div style="display:flex; gap:1rem; flex-wrap:wrap;">
|
|
11
|
+
<lyra-stat label="Revenue" value="12.4" unit="k€" trend="3.2" variant="success"></lyra-stat>
|
|
12
|
+
<lyra-stat label="Errors" value="128" trend="-5.1" variant="danger"></lyra-stat>
|
|
13
|
+
<lyra-stat label="Sessions" value="9,204"></lyra-stat>
|
|
14
|
+
</div>
|
|
15
|
+
`,
|
|
16
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const rows = [
|
|
3
|
+
{ id: 'a', name: 'Alpha', score: 92 },
|
|
4
|
+
{ id: 'b', name: 'Beta', score: 81 },
|
|
5
|
+
{ id: 'c', name: 'Gamma', score: 76 },
|
|
6
|
+
];
|
|
7
|
+
const columns = [
|
|
8
|
+
{ key: 'name', label: 'Name', sortable: true, cell: (r) => r.name },
|
|
9
|
+
{ key: 'score', label: 'Score', sortable: true, align: 'end', cell: (r) => r.score },
|
|
10
|
+
];
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'Table',
|
|
13
|
+
component: 'lyra-table',
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
};
|
|
16
|
+
export default meta;
|
|
17
|
+
export const Default = {
|
|
18
|
+
render: () => html `<lyra-table .columns=${columns} .rows=${rows}></lyra-table>`,
|
|
19
|
+
};
|
|
20
|
+
export const Empty = {
|
|
21
|
+
render: () => html `<lyra-table .columns=${columns} .rows=${[]}></lyra-table>`,
|
|
22
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'TimeRange',
|
|
4
|
+
component: 'lyra-time-range',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Default = {
|
|
9
|
+
render: () => html `<lyra-time-range min="0" max="100" start="20" end="80"></lyra-time-range>`,
|
|
10
|
+
};
|
|
11
|
+
export const Disabled = {
|
|
12
|
+
render: () => html `<lyra-time-range min="0" max="100" start="20" end="80" disabled></lyra-time-range>`,
|
|
13
|
+
};
|
|
@@ -49,6 +49,16 @@ export class LyraToastItem extends LyraElement {
|
|
|
49
49
|
this.remaining = 0;
|
|
50
50
|
this.startedAt = 0;
|
|
51
51
|
this.resumeTimer = () => {
|
|
52
|
+
// Guard against an interleaved pointer+focus pause/resume sequence
|
|
53
|
+
// calling resumeTimer() twice without a pauseTimer() in between --
|
|
54
|
+
// without clearing here, the earlier setTimeout is orphaned (not
|
|
55
|
+
// tracked by `this.timer` anymore) and still fires on its own
|
|
56
|
+
// schedule, auto-dismissing the toast early even after it's paused
|
|
57
|
+
// again.
|
|
58
|
+
if (this.timer !== undefined) {
|
|
59
|
+
clearTimeout(this.timer);
|
|
60
|
+
this.timer = undefined;
|
|
61
|
+
}
|
|
52
62
|
if (!isFinite(this.duration) || this.duration <= 0 || this.remaining <= 0)
|
|
53
63
|
return;
|
|
54
64
|
this.startedAt = performance.now();
|
|
@@ -66,7 +76,8 @@ export class LyraToastItem extends LyraElement {
|
|
|
66
76
|
firstUpdated() {
|
|
67
77
|
// Assertive for actionable severities, polite otherwise.
|
|
68
78
|
this.setAttribute('role', this.variant === 'danger' || this.variant === 'warning' ? 'alert' : 'status');
|
|
69
|
-
requestAnimationFrame(() => {
|
|
79
|
+
this.showRafId = requestAnimationFrame(() => {
|
|
80
|
+
this.showRafId = undefined;
|
|
70
81
|
this.setAttribute('data-visible', '');
|
|
71
82
|
this.emit('lyra-show');
|
|
72
83
|
window.setTimeout(() => this.emit('lyra-after-show'), ANIM_MS);
|
|
@@ -75,6 +86,10 @@ export class LyraToastItem extends LyraElement {
|
|
|
75
86
|
}
|
|
76
87
|
disconnectedCallback() {
|
|
77
88
|
super.disconnectedCallback();
|
|
89
|
+
if (this.showRafId !== undefined) {
|
|
90
|
+
cancelAnimationFrame(this.showRafId);
|
|
91
|
+
this.showRafId = undefined;
|
|
92
|
+
}
|
|
78
93
|
this.clearTimer();
|
|
79
94
|
}
|
|
80
95
|
startTimer() {
|