@aceshooting/lyra-ui 1.0.1 → 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 +9 -10
- package/custom-elements.json +15118 -8342
- package/dist/components/chart/chart.d.ts +57 -1
- package/dist/components/chart/chart.js +202 -12
- package/dist/components/chart/chart.stories.d.ts +32 -0
- package/dist/components/chart/chart.stories.js +141 -0
- package/dist/components/chart/chart.styles.js +16 -0
- package/dist/components/chart/histogram.d.ts +2 -0
- package/dist/components/chart/histogram.js +18 -2
- package/dist/components/chart/line-chart.stories.d.ts +17 -0
- package/dist/components/chart/line-chart.stories.js +75 -0
- package/dist/components/chart/lite-chart.d.ts +83 -0
- package/dist/components/chart/lite-chart.js +424 -0
- package/dist/components/chart/lite-chart.stories.d.ts +14 -0
- package/dist/components/chart/lite-chart.stories.js +114 -0
- package/dist/components/chart/lite-chart.styles.d.ts +1 -0
- package/dist/components/chart/lite-chart.styles.js +83 -0
- package/dist/components/combobox/combobox.d.ts +9 -0
- package/dist/components/combobox/combobox.js +76 -13
- package/dist/components/combobox/combobox.stories.d.ts +18 -0
- package/dist/components/combobox/combobox.stories.js +75 -0
- package/dist/components/date-picker/calendar-core.js +13 -2
- package/dist/components/date-picker/date-input.d.ts +9 -1
- package/dist/components/date-picker/date-input.js +55 -7
- package/dist/components/date-picker/date-input.stories.d.ts +5 -0
- package/dist/components/date-picker/date-input.stories.js +36 -0
- package/dist/components/date-picker/date-picker.d.ts +11 -3
- package/dist/components/date-picker/date-picker.js +87 -37
- package/dist/components/date-picker/date-picker.stories.d.ts +4 -0
- package/dist/components/date-picker/date-picker.stories.js +12 -0
- package/dist/components/date-picker/date-picker.styles.js +1 -1
- package/dist/components/empty/empty.d.ts +12 -1
- package/dist/components/empty/empty.js +20 -3
- package/dist/components/empty/empty.stories.d.ts +2 -0
- package/dist/components/empty/empty.stories.js +20 -0
- package/dist/components/empty/empty.styles.js +11 -0
- package/dist/components/export-button/csv.js +6 -2
- package/dist/components/export-button/export-button.d.ts +15 -1
- package/dist/components/export-button/export-button.js +89 -12
- package/dist/components/export-button/export-button.stories.d.ts +1 -0
- package/dist/components/export-button/export-button.stories.js +5 -0
- package/dist/components/export-button/export-button.styles.js +11 -3
- package/dist/components/file-input/accept.d.ts +8 -2
- package/dist/components/file-input/accept.js +9 -3
- package/dist/components/file-input/file-input.d.ts +5 -2
- package/dist/components/file-input/file-input.js +23 -8
- package/dist/components/file-input/file-input.stories.d.ts +2 -0
- package/dist/components/file-input/file-input.stories.js +9 -0
- package/dist/components/flag/flag.d.ts +69 -3
- package/dist/components/flag/flag.js +107 -30
- package/dist/components/flag/flag.stories.d.ts +1 -0
- package/dist/components/flag/flag.stories.js +14 -0
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +11 -0
- package/dist/components/flag/language-map.js +12 -1
- package/dist/components/gauge/gauge.d.ts +2 -0
- package/dist/components/gauge/gauge.js +19 -5
- package/dist/components/gauge/gauge.stories.d.ts +2 -0
- package/dist/components/gauge/gauge.stories.js +6 -0
- package/dist/components/graph/graph-loader.d.ts +28 -0
- package/dist/components/graph/graph-loader.js +44 -0
- package/dist/components/graph/graph.d.ts +41 -15
- package/dist/components/graph/graph.js +146 -71
- package/dist/components/graph/graph.stories.d.ts +3 -0
- package/dist/components/graph/graph.stories.js +38 -0
- package/dist/components/graph/graph.styles.js +6 -1
- package/dist/components/heatmap/calendar-grid.d.ts +7 -0
- package/dist/components/heatmap/calendar-grid.js +27 -10
- package/dist/components/heatmap/heatmap.d.ts +149 -12
- package/dist/components/heatmap/heatmap.js +445 -28
- package/dist/components/heatmap/heatmap.stories.d.ts +22 -0
- package/dist/components/heatmap/heatmap.stories.js +155 -0
- package/dist/components/heatmap/heatmap.styles.js +60 -2
- package/dist/components/map/map.d.ts +17 -1
- package/dist/components/map/map.js +68 -21
- package/dist/components/map/map.stories.d.ts +10 -0
- package/dist/components/map/map.stories.js +107 -12
- package/dist/components/map/map.styles.js +2 -2
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +36 -12
- package/dist/components/playback/playback.stories.d.ts +2 -0
- package/dist/components/playback/playback.stories.js +6 -0
- package/dist/components/playback/playback.styles.js +10 -2
- package/dist/components/select/select.d.ts +125 -0
- package/dist/components/select/select.js +480 -0
- package/dist/components/select/select.stories.d.ts +21 -0
- package/dist/components/select/select.stories.js +91 -0
- package/dist/components/select/select.styles.d.ts +1 -0
- package/dist/components/select/select.styles.js +212 -0
- package/dist/components/skeleton/skeleton.js +4 -7
- package/dist/components/sparkline/sparkline.d.ts +1 -1
- package/dist/components/sparkline/sparkline.js +40 -13
- package/dist/components/sparkline/sparkline.stories.d.ts +4 -0
- package/dist/components/sparkline/sparkline.stories.js +22 -0
- package/dist/components/split/split.d.ts +24 -3
- package/dist/components/split/split.js +155 -31
- package/dist/components/split/split.stories.d.ts +2 -0
- package/dist/components/split/split.stories.js +20 -0
- package/dist/components/stat/stat.d.ts +43 -1
- package/dist/components/stat/stat.js +97 -4
- package/dist/components/stat/stat.stories.d.ts +4 -0
- package/dist/components/stat/stat.stories.js +66 -1
- package/dist/components/stat/stat.styles.js +64 -0
- package/dist/components/table/table.d.ts +64 -2
- package/dist/components/table/table.js +213 -17
- package/dist/components/table/table.stories.d.ts +6 -0
- package/dist/components/table/table.stories.js +45 -0
- package/dist/components/table/table.styles.js +51 -4
- package/dist/components/time-range/time-range.d.ts +34 -5
- package/dist/components/time-range/time-range.js +217 -36
- package/dist/components/time-range/time-range.stories.d.ts +2 -0
- package/dist/components/time-range/time-range.stories.js +13 -0
- package/dist/components/time-range/time-range.styles.js +44 -2
- package/dist/components/toast/toast-item.d.ts +14 -2
- package/dist/components/toast/toast-item.js +134 -17
- package/dist/components/toast/toast-item.styles.js +28 -2
- package/dist/components/toast/toast.stories.d.ts +3 -0
- package/dist/components/toast/toast.stories.js +41 -1
- package/dist/components/toast/toast.styles.js +11 -11
- package/dist/components/toast/toaster.d.ts +1 -1
- package/dist/components/toast/toaster.js +13 -6
- package/dist/components/tree/tree-node.d.ts +13 -7
- package/dist/components/tree/tree-node.js +24 -10
- package/dist/components/tree/tree.d.ts +64 -15
- package/dist/components/tree/tree.js +113 -22
- package/dist/components/tree/tree.stories.d.ts +4 -0
- package/dist/components/tree/tree.stories.js +31 -1
- package/dist/components/tree/update-cascade.d.ts +13 -0
- package/dist/components/tree/update-cascade.js +13 -0
- package/dist/components/widget/widget.d.ts +25 -1
- package/dist/components/widget/widget.js +164 -6
- package/dist/components/widget/widget.stories.d.ts +3 -0
- package/dist/components/widget/widget.stories.js +43 -0
- package/dist/components/widget/widget.styles.js +20 -3
- package/dist/components/word-cloud/word-cloud-layout.d.ts +63 -0
- package/dist/components/word-cloud/word-cloud-layout.js +145 -0
- package/dist/components/word-cloud/word-cloud.d.ts +65 -0
- package/dist/components/word-cloud/word-cloud.js +255 -0
- package/dist/components/word-cloud/word-cloud.stories.d.ts +10 -0
- package/dist/components/word-cloud/word-cloud.stories.js +55 -0
- package/dist/components/word-cloud/word-cloud.styles.d.ts +1 -0
- package/dist/components/word-cloud/word-cloud.styles.js +55 -0
- package/dist/internal/a11y.js +2 -1
- package/dist/internal/rtl.d.ts +7 -0
- package/dist/internal/rtl.js +9 -0
- package/dist/lyra.d.ts +9 -1
- package/dist/lyra.js +6 -0
- package/llms-full.txt +962 -276
- package/llms.txt +12 -9
- package/package.json +6 -4
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Charts/LiteChart',
|
|
4
|
+
component: 'lyra-lite-chart',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
/** Zero-dependency alternative to `lyra-chart` — plain SVG/DOM, no `chart.js` peer dep. */
|
|
9
|
+
export const Default = {
|
|
10
|
+
render: () => {
|
|
11
|
+
const series = [{ label: 'Revenue', data: [12, 19, 14, 22] }];
|
|
12
|
+
return html `
|
|
13
|
+
<lyra-lite-chart
|
|
14
|
+
type="bar"
|
|
15
|
+
height="16rem"
|
|
16
|
+
style="width: 22rem"
|
|
17
|
+
legend
|
|
18
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
19
|
+
.datasets=${series}
|
|
20
|
+
></lyra-lite-chart>
|
|
21
|
+
`;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export const GroupedBars = {
|
|
25
|
+
render: () => {
|
|
26
|
+
const series = [
|
|
27
|
+
{ label: 'This year', data: [12, 19, 14, 22] },
|
|
28
|
+
{ label: 'Last year', data: [9, 15, 11, 18] },
|
|
29
|
+
];
|
|
30
|
+
return html `
|
|
31
|
+
<lyra-lite-chart
|
|
32
|
+
type="bar"
|
|
33
|
+
height="16rem"
|
|
34
|
+
style="width: 24rem"
|
|
35
|
+
legend
|
|
36
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
37
|
+
.datasets=${series}
|
|
38
|
+
></lyra-lite-chart>
|
|
39
|
+
`;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
/** `stacked` sums each category's bars into one segmented bar instead of grouping them side by side. */
|
|
43
|
+
export const StackedBars = {
|
|
44
|
+
render: () => {
|
|
45
|
+
const series = [
|
|
46
|
+
{ label: 'Docs', data: [4, 6, 3, 8] },
|
|
47
|
+
{ label: 'Bugs', data: [3, 2, 5, 4] },
|
|
48
|
+
{ label: 'Features', data: [5, 7, 6, 3] },
|
|
49
|
+
];
|
|
50
|
+
return html `
|
|
51
|
+
<lyra-lite-chart
|
|
52
|
+
type="bar"
|
|
53
|
+
stacked
|
|
54
|
+
height="16rem"
|
|
55
|
+
style="width: 24rem"
|
|
56
|
+
legend
|
|
57
|
+
x-label="Week"
|
|
58
|
+
y-label="Commits"
|
|
59
|
+
.labels=${['W1', 'W2', 'W3', 'W4']}
|
|
60
|
+
.datasets=${series}
|
|
61
|
+
></lyra-lite-chart>
|
|
62
|
+
`;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export const Line = {
|
|
66
|
+
render: () => {
|
|
67
|
+
const series = [
|
|
68
|
+
{ label: 'CPU', data: [20, 35, 42, 30, 55] },
|
|
69
|
+
{ label: 'Memory', data: [40, 38, 45, 60, 58] },
|
|
70
|
+
];
|
|
71
|
+
return html `
|
|
72
|
+
<lyra-lite-chart
|
|
73
|
+
type="line"
|
|
74
|
+
height="16rem"
|
|
75
|
+
style="width: 24rem"
|
|
76
|
+
legend
|
|
77
|
+
.labels=${['00:00', '00:05', '00:10', '00:15', '00:20']}
|
|
78
|
+
.datasets=${series}
|
|
79
|
+
></lyra-lite-chart>
|
|
80
|
+
`;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
/** Clicking (or Enter/Space on a focused) bar/point fires `lyra-point-click`, same detail shape as `lyra-chart`'s. */
|
|
84
|
+
export const ClickToFilter = {
|
|
85
|
+
render: () => {
|
|
86
|
+
const series = [{ label: 'Runs', data: [12, 19, 14, 22] }];
|
|
87
|
+
return html `
|
|
88
|
+
<lyra-lite-chart
|
|
89
|
+
type="bar"
|
|
90
|
+
height="16rem"
|
|
91
|
+
style="width: 22rem"
|
|
92
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
93
|
+
.datasets=${series}
|
|
94
|
+
@lyra-point-click=${(e) => alert(JSON.stringify(e.detail))}
|
|
95
|
+
></lyra-lite-chart>
|
|
96
|
+
`;
|
|
97
|
+
},
|
|
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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
position: relative;
|
|
6
|
+
block-size: var(--lyra-chart-height, 280px);
|
|
7
|
+
/* Same token names/fallback chain as chart.ts's --lyra-chart-* — a host
|
|
8
|
+
already theming lyra-chart gets lyra-lite-chart themed for free, and
|
|
9
|
+
vice versa. Unlike chart.ts (canvas-rendered, can't consume var()
|
|
10
|
+
directly), this component is plain SVG/DOM, so these are read natively
|
|
11
|
+
by the CSS below — no getComputedStyle()/JS-side resolution needed. */
|
|
12
|
+
--lyra-chart-grid-color: var(--lyra-color-border);
|
|
13
|
+
--lyra-chart-tick-color: var(--lyra-color-text-quiet);
|
|
14
|
+
--lyra-chart-legend-color: var(--lyra-color-text);
|
|
15
|
+
--lyra-chart-tooltip-bg: var(--lyra-color-surface);
|
|
16
|
+
--lyra-chart-tooltip-text: var(--lyra-color-text);
|
|
17
|
+
}
|
|
18
|
+
[part='base'] {
|
|
19
|
+
position: relative;
|
|
20
|
+
inline-size: 100%;
|
|
21
|
+
block-size: 100%;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: var(--lyra-space-xs);
|
|
25
|
+
}
|
|
26
|
+
svg {
|
|
27
|
+
flex: 1 1 auto;
|
|
28
|
+
inline-size: 100%;
|
|
29
|
+
block-size: 100%;
|
|
30
|
+
min-block-size: 0;
|
|
31
|
+
overflow: visible;
|
|
32
|
+
}
|
|
33
|
+
[part='grid-line'] {
|
|
34
|
+
stroke: var(--lyra-chart-grid-color);
|
|
35
|
+
stroke-width: 1;
|
|
36
|
+
}
|
|
37
|
+
[part='axis-label'] {
|
|
38
|
+
fill: var(--lyra-chart-tick-color);
|
|
39
|
+
font-size: 10px;
|
|
40
|
+
font-family: var(--lyra-font-family, inherit);
|
|
41
|
+
}
|
|
42
|
+
[part='axis-title'] {
|
|
43
|
+
fill: var(--lyra-chart-tick-color);
|
|
44
|
+
font-size: 11px;
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
font-family: var(--lyra-font-family, inherit);
|
|
47
|
+
}
|
|
48
|
+
[part='bar'] {
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
[part='bar']:focus-visible,
|
|
52
|
+
[part='point']:focus-visible {
|
|
53
|
+
outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
|
|
54
|
+
outline-offset: 1px;
|
|
55
|
+
}
|
|
56
|
+
[part='line'] {
|
|
57
|
+
fill: none;
|
|
58
|
+
stroke-width: 2;
|
|
59
|
+
}
|
|
60
|
+
[part='point'] {
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
[part='legend'] {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-wrap: wrap;
|
|
66
|
+
gap: var(--lyra-space-s);
|
|
67
|
+
justify-content: center;
|
|
68
|
+
flex: 0 0 auto;
|
|
69
|
+
}
|
|
70
|
+
[part='legend-item'] {
|
|
71
|
+
display: inline-flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 0.35em;
|
|
74
|
+
font-size: 0.8rem;
|
|
75
|
+
color: var(--lyra-chart-legend-color);
|
|
76
|
+
}
|
|
77
|
+
[part='legend-swatch'] {
|
|
78
|
+
inline-size: 0.7em;
|
|
79
|
+
block-size: 0.7em;
|
|
80
|
+
border-radius: 2px;
|
|
81
|
+
flex: 0 0 auto;
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
@@ -29,15 +29,23 @@ export type ComboboxSource = (query: string) => Promise<ComboboxSourceRow[]>;
|
|
|
29
29
|
* @event lyra-show - The listbox opened.
|
|
30
30
|
* @event lyra-hide - The listbox closed.
|
|
31
31
|
* @event lyra-clear - The value was cleared.
|
|
32
|
+
* @csspart form-control - The outer wrapper around label, combobox, listbox, error and hint.
|
|
33
|
+
* @csspart form-control-label - The `<label>` element.
|
|
32
34
|
* @csspart combobox - The input container (positioning anchor).
|
|
33
35
|
* @csspart combobox-input - The text input.
|
|
34
36
|
* @csspart listbox - The options popover.
|
|
35
37
|
* @csspart option - An option row.
|
|
38
|
+
* @csspart option-dot - An option row's leading status dot (when `dot-color` is set).
|
|
39
|
+
* @csspart option-label - An option row's label/sub wrapper.
|
|
40
|
+
* @csspart option-sub - An option row's secondary line (when `sub` is set).
|
|
41
|
+
* @csspart option-overflow - The "+N more" indicator shown when rows are capped by `maxRender`.
|
|
36
42
|
* @csspart tags - The multi-select tag container.
|
|
37
43
|
* @csspart tag - An individual selected tag.
|
|
44
|
+
* @csspart tag__remove-button - A tag's remove button.
|
|
38
45
|
* @csspart clear-button - The clear button.
|
|
39
46
|
* @csspart expand-icon - The dropdown indicator.
|
|
40
47
|
* @csspart error - The error message.
|
|
48
|
+
* @csspart hint - The hint message.
|
|
41
49
|
*/
|
|
42
50
|
export declare class LyraCombobox extends LyraElement {
|
|
43
51
|
static formAssociated: boolean;
|
|
@@ -79,6 +87,7 @@ export declare class LyraCombobox extends LyraElement {
|
|
|
79
87
|
private listId;
|
|
80
88
|
private inputId;
|
|
81
89
|
private cleanup?;
|
|
90
|
+
private _isFirstUpdate;
|
|
82
91
|
private _selected;
|
|
83
92
|
private _defaultSelected;
|
|
84
93
|
private _defaultCaptured;
|
|
@@ -31,15 +31,23 @@ import './option.js';
|
|
|
31
31
|
* @event lyra-show - The listbox opened.
|
|
32
32
|
* @event lyra-hide - The listbox closed.
|
|
33
33
|
* @event lyra-clear - The value was cleared.
|
|
34
|
+
* @csspart form-control - The outer wrapper around label, combobox, listbox, error and hint.
|
|
35
|
+
* @csspart form-control-label - The `<label>` element.
|
|
34
36
|
* @csspart combobox - The input container (positioning anchor).
|
|
35
37
|
* @csspart combobox-input - The text input.
|
|
36
38
|
* @csspart listbox - The options popover.
|
|
37
39
|
* @csspart option - An option row.
|
|
40
|
+
* @csspart option-dot - An option row's leading status dot (when `dot-color` is set).
|
|
41
|
+
* @csspart option-label - An option row's label/sub wrapper.
|
|
42
|
+
* @csspart option-sub - An option row's secondary line (when `sub` is set).
|
|
43
|
+
* @csspart option-overflow - The "+N more" indicator shown when rows are capped by `maxRender`.
|
|
38
44
|
* @csspart tags - The multi-select tag container.
|
|
39
45
|
* @csspart tag - An individual selected tag.
|
|
46
|
+
* @csspart tag__remove-button - A tag's remove button.
|
|
40
47
|
* @csspart clear-button - The clear button.
|
|
41
48
|
* @csspart expand-icon - The dropdown indicator.
|
|
42
49
|
* @csspart error - The error message.
|
|
50
|
+
* @csspart hint - The hint message.
|
|
43
51
|
*/
|
|
44
52
|
export class LyraCombobox extends LyraElement {
|
|
45
53
|
static { this.formAssociated = true; }
|
|
@@ -91,6 +99,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
91
99
|
this._rowsByValue = new Map();
|
|
92
100
|
this.listId = nextId('combobox-list');
|
|
93
101
|
this.inputId = nextId('combobox-input');
|
|
102
|
+
this._isFirstUpdate = true;
|
|
94
103
|
this._selected = [];
|
|
95
104
|
// What `form.reset()` restores to. Captured exactly once, from whatever
|
|
96
105
|
// `<lyra-option selected>` markup was present the first time slotted
|
|
@@ -104,6 +113,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
104
113
|
this._defaultCaptured = false;
|
|
105
114
|
this.collectOptions = (e) => {
|
|
106
115
|
const slot = e.target;
|
|
116
|
+
const previous = new Set(this.options);
|
|
107
117
|
this.options = slot
|
|
108
118
|
.assignedElements({ flatten: true })
|
|
109
119
|
.filter((el) => el instanceof LyraOption);
|
|
@@ -123,6 +133,20 @@ export class LyraCombobox extends LyraElement {
|
|
|
123
133
|
return; // `value=`'s setter already called reflectSelected()
|
|
124
134
|
}
|
|
125
135
|
}
|
|
136
|
+
else {
|
|
137
|
+
// Options slotted in after the first pass (e.g. a lazily-populated
|
|
138
|
+
// list appended post-connect) still declare selection the same way a
|
|
139
|
+
// native `<select><option selected>` would -- seed newly-arrived ones
|
|
140
|
+
// into the live selection instead of letting reflectSelected() below
|
|
141
|
+
// strip their `selected` attribute back off.
|
|
142
|
+
const newlySelected = this.options.filter((o) => !previous.has(o) && o.selected).map((o) => o.value);
|
|
143
|
+
if (newlySelected.length) {
|
|
144
|
+
this.value = this.multiple
|
|
145
|
+
? [...new Set([...this._selected, ...newlySelected])]
|
|
146
|
+
: newlySelected[newlySelected.length - 1];
|
|
147
|
+
return; // `value=`'s setter already called reflectSelected()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
126
150
|
this.reflectSelected();
|
|
127
151
|
};
|
|
128
152
|
this.onDocPointer = (e) => {
|
|
@@ -139,6 +163,12 @@ export class LyraCombobox extends LyraElement {
|
|
|
139
163
|
};
|
|
140
164
|
this.onInputBlur = () => {
|
|
141
165
|
this.touched = true;
|
|
166
|
+
// A mouse click outside the element is already handled by
|
|
167
|
+
// onDocPointer/hide(), but that leaves keyboard users with no way to
|
|
168
|
+
// dismiss the listbox short of Escape -- tabbing focus away from the
|
|
169
|
+
// input should close it too, the same as it would for a native
|
|
170
|
+
// `<select>`'s popup.
|
|
171
|
+
this.hide();
|
|
142
172
|
};
|
|
143
173
|
this.onHintSlotChange = (e) => {
|
|
144
174
|
this.hasHintSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
@@ -227,6 +257,11 @@ export class LyraCombobox extends LyraElement {
|
|
|
227
257
|
this.updateValidity();
|
|
228
258
|
}
|
|
229
259
|
willUpdate() {
|
|
260
|
+
// `hasUpdated` flips to `true` before `updated()` even sees its first
|
|
261
|
+
// call, so it can't distinguish "just mounted" from "just changed" there
|
|
262
|
+
// -- capture that distinction here, while it's still reliable, for
|
|
263
|
+
// `updated()`'s `open`-handling below to consult.
|
|
264
|
+
this._isFirstUpdate = !this.hasUpdated;
|
|
230
265
|
if (!this.hasUpdated) {
|
|
231
266
|
this.hasHintSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'hint');
|
|
232
267
|
this.hasErrorSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'error');
|
|
@@ -255,10 +290,19 @@ export class LyraCombobox extends LyraElement {
|
|
|
255
290
|
}
|
|
256
291
|
syncFormValue() {
|
|
257
292
|
if (this.multiple) {
|
|
293
|
+
// A FormData form value submits under the keys baked into the FormData
|
|
294
|
+
// itself, bypassing the element's own `name` the way a plain string
|
|
295
|
+
// value would use it -- so an unnamed multi-select must contribute
|
|
296
|
+
// nothing (matching a nameless native `<select multiple>`) rather than
|
|
297
|
+
// inventing a shared key that would merge with any other unnamed
|
|
298
|
+
// combobox in the same form.
|
|
299
|
+
if (!this.name) {
|
|
300
|
+
this.internals.setFormValue(null);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
258
303
|
const fd = new FormData();
|
|
259
|
-
const key = this.name || 'value';
|
|
260
304
|
for (const v of this._selected)
|
|
261
|
-
fd.append(
|
|
305
|
+
fd.append(this.name, v);
|
|
262
306
|
this.internals.setFormValue(fd);
|
|
263
307
|
}
|
|
264
308
|
else {
|
|
@@ -281,6 +325,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
281
325
|
disconnectedCallback() {
|
|
282
326
|
super.disconnectedCallback();
|
|
283
327
|
this.cleanup?.();
|
|
328
|
+
clearTimeout(this.sourceTimer);
|
|
284
329
|
document.removeEventListener('pointerdown', this.onDocPointer);
|
|
285
330
|
}
|
|
286
331
|
reflectSelected() {
|
|
@@ -339,33 +384,41 @@ export class LyraCombobox extends LyraElement {
|
|
|
339
384
|
if (this.open || this.disabled)
|
|
340
385
|
return;
|
|
341
386
|
this.open = true;
|
|
342
|
-
this.emit('lyra-show');
|
|
343
|
-
document.addEventListener('pointerdown', this.onDocPointer);
|
|
344
387
|
}
|
|
345
388
|
hide() {
|
|
346
389
|
if (!this.open)
|
|
347
390
|
return;
|
|
348
391
|
this.open = false;
|
|
349
392
|
this.activeIndex = -1;
|
|
350
|
-
this.emit('lyra-hide');
|
|
351
|
-
document.removeEventListener('pointerdown', this.onDocPointer);
|
|
352
393
|
}
|
|
353
394
|
updated(changed) {
|
|
354
395
|
if (changed.has('open')) {
|
|
355
396
|
this.cleanup?.();
|
|
356
397
|
this.cleanup = undefined;
|
|
398
|
+
// All `open`-driven side effects (positioning, the click-outside
|
|
399
|
+
// listener, and the lyra-show/lyra-hide events) live here rather than
|
|
400
|
+
// in show()/hide() so they fire however `open` became true -- via
|
|
401
|
+
// show()/hide()'s own user-interaction paths, or a consumer/test
|
|
402
|
+
// setting `el.open` directly, which bypasses both entirely.
|
|
357
403
|
if (this.open) {
|
|
404
|
+
document.addEventListener('pointerdown', this.onDocPointer);
|
|
405
|
+
// Don't announce a "show" transition for markup that's simply
|
|
406
|
+
// rendering open for the first time (e.g. `<lyra-combobox open>`) --
|
|
407
|
+
// only for an actual closed-to-open transition.
|
|
408
|
+
if (!this._isFirstUpdate)
|
|
409
|
+
this.emit('lyra-show');
|
|
358
410
|
const anchor = this.renderRoot.querySelector('[part="combobox"]');
|
|
359
411
|
const listbox = this.renderRoot.querySelector('[part="listbox"]');
|
|
360
412
|
if (anchor && listbox)
|
|
361
413
|
this.cleanup = place(anchor, listbox);
|
|
362
|
-
// Reacting to the `open` property itself (not just inside show())
|
|
363
|
-
// means this fires however `open` became true — via show()'s own
|
|
364
|
-
// user-interaction paths, or a consumer/test setting `el.open = true`
|
|
365
|
-
// directly, which bypasses show() entirely.
|
|
366
414
|
if (this.source && this.asyncRows.length === 0)
|
|
367
415
|
this.runSource(this.query);
|
|
368
416
|
}
|
|
417
|
+
else {
|
|
418
|
+
document.removeEventListener('pointerdown', this.onDocPointer);
|
|
419
|
+
if (!this._isFirstUpdate)
|
|
420
|
+
this.emit('lyra-hide');
|
|
421
|
+
}
|
|
369
422
|
}
|
|
370
423
|
if (changed.has('required'))
|
|
371
424
|
this.updateValidity();
|
|
@@ -388,6 +441,12 @@ export class LyraCombobox extends LyraElement {
|
|
|
388
441
|
this.query = '';
|
|
389
442
|
this.hide();
|
|
390
443
|
}
|
|
444
|
+
// The query resets to '' above but `asyncRows` doesn't refresh on its
|
|
445
|
+
// own -- re-run `source` so the listbox (still open in multiple mode,
|
|
446
|
+
// and whatever `asyncRows` holds for the next time it reopens in single
|
|
447
|
+
// mode) matches the now-empty input instead of the stale prior query.
|
|
448
|
+
if (this.source)
|
|
449
|
+
this.runSource(this.query);
|
|
391
450
|
this.emit('input');
|
|
392
451
|
this.emit('change');
|
|
393
452
|
}
|
|
@@ -398,6 +457,8 @@ export class LyraCombobox extends LyraElement {
|
|
|
398
457
|
clear() {
|
|
399
458
|
this.value = [];
|
|
400
459
|
this.query = '';
|
|
460
|
+
if (this.source)
|
|
461
|
+
this.runSource(this.query);
|
|
401
462
|
this.emit('lyra-clear');
|
|
402
463
|
this.emit('change');
|
|
403
464
|
}
|
|
@@ -488,11 +549,13 @@ export class LyraCombobox extends LyraElement {
|
|
|
488
549
|
id=${this.inputId}
|
|
489
550
|
part="combobox-input"
|
|
490
551
|
role="combobox"
|
|
491
|
-
aria-label=${this.label || this.placeholder || 'Combobox'}
|
|
552
|
+
aria-label=${this.getAttribute('aria-label') || this.label || this.placeholder || 'Combobox'}
|
|
492
553
|
aria-expanded=${this.open ? 'true' : 'false'}
|
|
493
554
|
aria-controls=${this.listId}
|
|
494
555
|
aria-activedescendant=${activeId}
|
|
495
556
|
aria-autocomplete="list"
|
|
557
|
+
aria-required=${this.required ? 'true' : 'false'}
|
|
558
|
+
aria-invalid=${this.touched && !this.internals.validity.valid ? 'true' : 'false'}
|
|
496
559
|
autocomplete="off"
|
|
497
560
|
.value=${this.displayValue}
|
|
498
561
|
placeholder=${hasValue && !this.multiple ? '' : this.placeholder}
|
|
@@ -526,9 +589,9 @@ export class LyraCombobox extends LyraElement {
|
|
|
526
589
|
@click=${this.onListboxClick}
|
|
527
590
|
>
|
|
528
591
|
${this.loading
|
|
529
|
-
? html `<div class="loading">Loading…</div>`
|
|
592
|
+
? html `<div class="loading" role="option" aria-selected="false" aria-disabled="true">Loading…</div>`
|
|
530
593
|
: rows.length === 0
|
|
531
|
-
? html `<div class="empty">${this.emptyText}</div>`
|
|
594
|
+
? html `<div class="empty" role="option" aria-selected="false" aria-disabled="true">${this.emptyText}</div>`
|
|
532
595
|
: html `${this.renderRows(rows, activeId)}
|
|
533
596
|
${overflow > 0
|
|
534
597
|
? html `<div part="option-overflow">+${overflow} more — refine your search</div>`
|
|
@@ -4,3 +4,21 @@ export default meta;
|
|
|
4
4
|
type Story = StoryObj;
|
|
5
5
|
export declare const Default: Story;
|
|
6
6
|
export declare const Multiple: Story;
|
|
7
|
+
/**
|
|
8
|
+
* Rows come from `<lyra-option>` children plus `group` (section headers),
|
|
9
|
+
* `sub` (a secondary line), and `dot-color` (a leading status dot) — useful
|
|
10
|
+
* for richer pickers like a device or status list.
|
|
11
|
+
*/
|
|
12
|
+
export declare const RichRows: Story;
|
|
13
|
+
/**
|
|
14
|
+
* `source` replaces the light-DOM `<lyra-option>` list with an async
|
|
15
|
+
* `(query) => Promise<ComboboxSourceRow[]>` lookup, debounced ~200ms after
|
|
16
|
+
* each keystroke. A "Loading…" row is shown while a call is in flight.
|
|
17
|
+
*/
|
|
18
|
+
export declare const AsyncSource: Story;
|
|
19
|
+
/**
|
|
20
|
+
* `filter` overrides the default label/searchText matcher entirely, e.g. to
|
|
21
|
+
* match only from the start of the label instead of anywhere within it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CustomFilter: Story;
|
|
24
|
+
export declare const States: Story;
|
|
@@ -25,3 +25,78 @@ export const Multiple = {
|
|
|
25
25
|
</lyra-combobox>
|
|
26
26
|
`,
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Rows come from `<lyra-option>` children plus `group` (section headers),
|
|
30
|
+
* `sub` (a secondary line), and `dot-color` (a leading status dot) — useful
|
|
31
|
+
* for richer pickers like a device or status list.
|
|
32
|
+
*/
|
|
33
|
+
export const RichRows = {
|
|
34
|
+
render: () => html `
|
|
35
|
+
<lyra-combobox label="Inverter" placeholder="Pick one…" style="max-width: 22rem">
|
|
36
|
+
<lyra-option value="inv-1" group="Building A" sub="Running" dot-color="var(--lyra-color-success)">
|
|
37
|
+
Inverter 1
|
|
38
|
+
</lyra-option>
|
|
39
|
+
<lyra-option value="inv-2" group="Building A" sub="Idle" dot-color="var(--lyra-color-text-quiet)">
|
|
40
|
+
Inverter 2
|
|
41
|
+
</lyra-option>
|
|
42
|
+
<lyra-option value="inv-3" group="Building B" sub="Fault" dot-color="var(--lyra-color-danger)">
|
|
43
|
+
Inverter 3
|
|
44
|
+
</lyra-option>
|
|
45
|
+
</lyra-combobox>
|
|
46
|
+
`,
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* `source` replaces the light-DOM `<lyra-option>` list with an async
|
|
50
|
+
* `(query) => Promise<ComboboxSourceRow[]>` lookup, debounced ~200ms after
|
|
51
|
+
* each keystroke. A "Loading…" row is shown while a call is in flight.
|
|
52
|
+
*/
|
|
53
|
+
export const AsyncSource = {
|
|
54
|
+
render: () => {
|
|
55
|
+
const all = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape'];
|
|
56
|
+
const source = async (query) => {
|
|
57
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
58
|
+
return all
|
|
59
|
+
.filter((label) => label.toLowerCase().includes(query.toLowerCase()))
|
|
60
|
+
.map((label) => ({ value: label.toLowerCase(), label }));
|
|
61
|
+
};
|
|
62
|
+
return html `
|
|
63
|
+
<lyra-combobox label="Fruit (async)" placeholder="Type to search…" with-clear
|
|
64
|
+
style="max-width: 22rem" .source=${source}
|
|
65
|
+
></lyra-combobox>
|
|
66
|
+
`;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* `filter` overrides the default label/searchText matcher entirely, e.g. to
|
|
71
|
+
* match only from the start of the label instead of anywhere within it.
|
|
72
|
+
*/
|
|
73
|
+
export const CustomFilter = {
|
|
74
|
+
render: () => {
|
|
75
|
+
const startsWith = (option, query) => option.label.toLowerCase().startsWith(query.toLowerCase());
|
|
76
|
+
return html `
|
|
77
|
+
<lyra-combobox label="Fruit (starts with…)" placeholder="Try “an”…" style="max-width: 22rem"
|
|
78
|
+
.filter=${startsWith}
|
|
79
|
+
>
|
|
80
|
+
<lyra-option value="a">Apple</lyra-option>
|
|
81
|
+
<lyra-option value="b">Banana</lyra-option>
|
|
82
|
+
<lyra-option value="c">Cherry</lyra-option>
|
|
83
|
+
<lyra-option value="d">Date</lyra-option>
|
|
84
|
+
</lyra-combobox>
|
|
85
|
+
`;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
export const States = {
|
|
89
|
+
render: () => html `
|
|
90
|
+
<div style="display: flex; flex-direction: column; gap: 1.5rem; max-width: 22rem">
|
|
91
|
+
<lyra-combobox label="Disabled" disabled placeholder="Can't touch this">
|
|
92
|
+
<lyra-option value="a">Apple</lyra-option>
|
|
93
|
+
</lyra-combobox>
|
|
94
|
+
<lyra-combobox label="Required" required hint="Pick your favorite">
|
|
95
|
+
<lyra-option value="a">Apple</lyra-option>
|
|
96
|
+
</lyra-combobox>
|
|
97
|
+
<lyra-combobox label="Invalid" required error-text="Selection required">
|
|
98
|
+
<lyra-option value="a">Apple</lyra-option>
|
|
99
|
+
</lyra-combobox>
|
|
100
|
+
</div>
|
|
101
|
+
`,
|
|
102
|
+
};
|
|
@@ -5,8 +5,19 @@ export function parseISO(s) {
|
|
|
5
5
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s.trim());
|
|
6
6
|
if (!m)
|
|
7
7
|
return null;
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const year = Number(m[1]);
|
|
9
|
+
const month = Number(m[2]);
|
|
10
|
+
const day = Number(m[3]);
|
|
11
|
+
const d = new Date(year, month - 1, day);
|
|
12
|
+
if (isNaN(d.getTime()))
|
|
13
|
+
return null;
|
|
14
|
+
// The Date constructor silently rolls invalid components over into the
|
|
15
|
+
// next month/year (e.g. month 13 or Feb 30) instead of producing an
|
|
16
|
+
// invalid date, so a calendar-invalid input must be caught by comparing
|
|
17
|
+
// the constructed date back against what was actually typed.
|
|
18
|
+
if (d.getFullYear() !== year || d.getMonth() !== month - 1 || d.getDate() !== day)
|
|
19
|
+
return null;
|
|
20
|
+
return d;
|
|
10
21
|
}
|
|
11
22
|
/** Format a Date as local `YYYY-MM-DD`. */
|
|
12
23
|
export function formatISO(d) {
|
|
@@ -16,8 +16,10 @@ declare const LyraDateInput_base: typeof LyraElement & (new (...args: any[]) =>
|
|
|
16
16
|
* @event change - Fired on committed date transitions.
|
|
17
17
|
* @event lyra-show / lyra-hide - Calendar popover lifecycle.
|
|
18
18
|
* @event lyra-clear - The clear button was used.
|
|
19
|
-
* @csspart form-control, form-control-label, input-wrapper, input, clear-button, expand-button, popup, date-picker, hint, error
|
|
19
|
+
* @csspart form-control, form-control-label, input-wrapper, input, clear-button, expand-button, expand-icon, popup, date-picker, hint, error
|
|
20
|
+
* @slot label - Custom label content.
|
|
20
21
|
* @slot error - Custom error content.
|
|
22
|
+
* @slot hint - Custom hint content.
|
|
21
23
|
*/
|
|
22
24
|
export declare class LyraDateInput extends LyraDateInput_base {
|
|
23
25
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -35,8 +37,12 @@ export declare class LyraDateInput extends LyraDateInput_base {
|
|
|
35
37
|
months: 1 | 2;
|
|
36
38
|
firstDayOfWeek: string;
|
|
37
39
|
weekdayFormat: WeekdayFormat;
|
|
40
|
+
disablePast: boolean;
|
|
41
|
+
disableFuture: boolean;
|
|
42
|
+
withOutsideDays: boolean;
|
|
38
43
|
private cleanupFn?;
|
|
39
44
|
private inputId;
|
|
45
|
+
private popupId;
|
|
40
46
|
private touched;
|
|
41
47
|
private hasHintSlot;
|
|
42
48
|
private hasErrorSlot;
|
|
@@ -54,10 +60,12 @@ export declare class LyraDateInput extends LyraDateInput_base {
|
|
|
54
60
|
protected updated(changed: PropertyValues): void;
|
|
55
61
|
private onInputChange;
|
|
56
62
|
private onInputKey;
|
|
63
|
+
private onFormControlKey;
|
|
57
64
|
private onInputBlur;
|
|
58
65
|
private onHintSlotChange;
|
|
59
66
|
private onErrorSlotChange;
|
|
60
67
|
private onLabelSlotChange;
|
|
68
|
+
private onPickerInput;
|
|
61
69
|
private onPickerChange;
|
|
62
70
|
render(): TemplateResult;
|
|
63
71
|
}
|