@aceshooting/lyra-ui 0.1.1 → 0.1.2
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 +77 -6
- package/custom-elements.json +12015 -0
- package/dist/components/chart/bar-chart.d.ts +14 -0
- package/dist/components/chart/bar-chart.js +14 -0
- package/dist/components/chart/box-plot.d.ts +46 -0
- package/dist/components/chart/box-plot.js +145 -0
- package/dist/components/chart/box-plot.styles.d.ts +1 -0
- package/dist/components/chart/box-plot.styles.js +22 -0
- package/dist/components/chart/bubble-chart.d.ts +19 -0
- package/dist/components/chart/bubble-chart.js +18 -0
- package/dist/components/chart/chart-loader.d.ts +8 -0
- package/dist/components/chart/chart-loader.js +26 -0
- package/dist/components/chart/chart.d.ts +91 -0
- package/dist/components/chart/chart.js +275 -0
- package/dist/components/chart/chart.styles.d.ts +1 -0
- package/dist/components/chart/chart.styles.js +33 -0
- package/dist/components/chart/doughnut-chart.d.ts +15 -0
- package/dist/components/chart/doughnut-chart.js +14 -0
- package/dist/components/chart/histogram-bin.d.ts +6 -0
- package/dist/components/chart/histogram-bin.js +19 -0
- package/dist/components/chart/histogram.d.ts +20 -0
- package/dist/components/chart/histogram.js +70 -0
- package/dist/components/chart/histogram.styles.d.ts +1 -0
- package/dist/components/chart/histogram.styles.js +1 -0
- package/dist/components/chart/line-chart.d.ts +14 -0
- package/dist/components/chart/line-chart.js +14 -0
- package/dist/components/chart/pie-chart.d.ts +16 -0
- package/dist/components/chart/pie-chart.js +15 -0
- package/dist/components/chart/polar-area-chart.d.ts +15 -0
- package/dist/components/chart/polar-area-chart.js +14 -0
- package/dist/components/chart/radar-chart.d.ts +15 -0
- package/dist/components/chart/radar-chart.js +14 -0
- package/dist/components/chart/scatter-chart.d.ts +15 -0
- package/dist/components/chart/scatter-chart.js +15 -0
- package/dist/components/combobox/combobox.d.ts +16 -0
- package/dist/components/combobox/combobox.js +113 -6
- package/dist/components/combobox/combobox.styles.js +68 -7
- package/dist/components/combobox/option.d.ts +2 -2
- package/dist/components/combobox/option.js +11 -7
- package/dist/components/date-picker/date-input.d.ts +12 -1
- package/dist/components/date-picker/date-input.js +68 -6
- package/dist/components/date-picker/date-input.styles.js +58 -6
- package/dist/components/date-picker/date-picker.js +5 -2
- package/dist/components/date-picker/date-picker.styles.js +9 -2
- package/dist/components/empty/empty.d.ts +1 -0
- package/dist/components/empty/empty.js +14 -2
- package/dist/components/export-button/csv.d.ts +10 -0
- package/dist/components/export-button/csv.js +28 -0
- package/dist/components/export-button/export-button.d.ts +41 -0
- package/dist/components/export-button/export-button.js +151 -0
- package/dist/components/export-button/export-button.styles.d.ts +1 -0
- package/dist/components/export-button/export-button.styles.js +79 -0
- package/dist/components/file-input/accept.d.ts +14 -0
- package/dist/components/file-input/accept.js +30 -0
- package/dist/components/file-input/file-input.d.ts +47 -0
- package/dist/components/file-input/file-input.js +182 -0
- package/dist/components/file-input/file-input.styles.d.ts +1 -0
- package/dist/components/file-input/file-input.styles.js +44 -0
- package/dist/components/flag/flag.d.ts +4 -0
- package/dist/components/flag/flag.js +24 -1
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/gauge/gauge.d.ts +29 -0
- package/dist/components/gauge/gauge.js +133 -0
- package/dist/components/gauge/gauge.styles.d.ts +1 -0
- package/dist/components/gauge/gauge.styles.js +60 -0
- package/dist/components/graph/graph.d.ts +68 -0
- package/dist/components/graph/graph.js +276 -0
- package/dist/components/graph/graph.styles.d.ts +1 -0
- package/dist/components/graph/graph.styles.js +34 -0
- package/dist/components/heatmap/heatmap-scale.d.ts +8 -0
- package/dist/components/heatmap/heatmap-scale.js +17 -0
- package/dist/components/heatmap/heatmap.d.ts +66 -0
- package/dist/components/heatmap/heatmap.js +252 -0
- package/dist/components/heatmap/heatmap.styles.d.ts +1 -0
- package/dist/components/heatmap/heatmap.styles.js +39 -0
- package/dist/components/map/map-loader.d.ts +8 -0
- package/dist/components/map/map-loader.js +17 -0
- package/dist/components/map/map.d.ts +49 -0
- package/dist/components/map/map.js +189 -0
- package/dist/components/map/map.styles.d.ts +1 -0
- package/dist/components/map/map.styles.js +48 -0
- package/dist/components/playback/playback.d.ts +51 -0
- package/dist/components/playback/playback.js +146 -0
- package/dist/components/playback/playback.styles.d.ts +1 -0
- package/dist/components/playback/playback.styles.js +40 -0
- package/dist/components/split/split.d.ts +41 -0
- package/dist/components/split/split.js +171 -0
- package/dist/components/split/split.styles.d.ts +1 -0
- package/dist/components/split/split.styles.js +51 -0
- package/dist/components/stat/stat.d.ts +9 -0
- package/dist/components/stat/stat.js +52 -7
- package/dist/components/stat/stat.styles.js +15 -5
- package/dist/components/table/table.d.ts +18 -6
- package/dist/components/table/table.js +76 -24
- package/dist/components/table/table.styles.js +32 -0
- package/dist/components/time-range/time-range.d.ts +40 -0
- package/dist/components/time-range/time-range.js +219 -0
- package/dist/components/time-range/time-range.styles.d.ts +1 -0
- package/dist/components/time-range/time-range.styles.js +78 -0
- package/dist/components/toast/toast-item.js +10 -2
- package/dist/components/toast/toast-item.styles.js +13 -4
- package/dist/components/toast/toast.styles.js +0 -1
- package/dist/components/tree/tree-node.d.ts +50 -0
- package/dist/components/tree/tree-node.js +196 -0
- package/dist/components/tree/tree.d.ts +65 -0
- package/dist/components/tree/tree.js +227 -0
- package/dist/components/tree/tree.styles.d.ts +1 -0
- package/dist/components/tree/tree.styles.js +13 -0
- package/dist/internal/form-associated.js +49 -1
- package/dist/internal/icons.d.ts +11 -0
- package/dist/internal/icons.js +64 -0
- package/dist/internal/tokens.styles.js +41 -1
- package/dist/lyra.d.ts +67 -0
- package/dist/lyra.js +55 -0
- package/llms-full.txt +1425 -0
- package/llms.txt +47 -0
- package/package.json +53 -4
|
@@ -14,6 +14,7 @@ export type OptionFilter = (option: LyraOption, query: string) => boolean;
|
|
|
14
14
|
* @slot - `<lyra-option>` elements.
|
|
15
15
|
* @slot label - Custom label content.
|
|
16
16
|
* @slot hint - Custom hint content.
|
|
17
|
+
* @slot error - Custom error content.
|
|
17
18
|
* @event change - The selection changed.
|
|
18
19
|
* @event input - The user typed in the filter or changed the selection.
|
|
19
20
|
* @event lyra-show - The listbox opened.
|
|
@@ -27,6 +28,7 @@ export type OptionFilter = (option: LyraOption, query: string) => boolean;
|
|
|
27
28
|
* @csspart tag - An individual selected tag.
|
|
28
29
|
* @csspart clear-button - The clear button.
|
|
29
30
|
* @csspart expand-icon - The dropdown indicator.
|
|
31
|
+
* @csspart error - The error message.
|
|
30
32
|
*/
|
|
31
33
|
export declare class LyraCombobox extends LyraElement {
|
|
32
34
|
static formAssociated: boolean;
|
|
@@ -43,6 +45,7 @@ export declare class LyraCombobox extends LyraElement {
|
|
|
43
45
|
name: string;
|
|
44
46
|
label: string;
|
|
45
47
|
hint: string;
|
|
48
|
+
errorText: string;
|
|
46
49
|
open: boolean;
|
|
47
50
|
withClear: boolean;
|
|
48
51
|
maxOptionsVisible: number;
|
|
@@ -51,14 +54,23 @@ export declare class LyraCombobox extends LyraElement {
|
|
|
51
54
|
private query;
|
|
52
55
|
private activeIndex;
|
|
53
56
|
private options;
|
|
57
|
+
private touched;
|
|
58
|
+
private hasHintSlot;
|
|
59
|
+
private hasErrorSlot;
|
|
60
|
+
private hasLabelSlot;
|
|
54
61
|
private internals;
|
|
55
62
|
private listId;
|
|
56
63
|
private cleanup?;
|
|
57
64
|
private _selected;
|
|
65
|
+
private _defaultSelected;
|
|
66
|
+
private _defaultCaptured;
|
|
58
67
|
constructor();
|
|
68
|
+
connectedCallback(): void;
|
|
69
|
+
protected willUpdate(): void;
|
|
59
70
|
/** The selected value(s): a string in single mode, a string[] in `multiple` mode. */
|
|
60
71
|
get value(): string | string[];
|
|
61
72
|
set value(next: string | string[]);
|
|
73
|
+
private updateValidity;
|
|
62
74
|
private syncFormValue;
|
|
63
75
|
formResetCallback(): void;
|
|
64
76
|
formDisabledCallback(disabled: boolean): void;
|
|
@@ -78,6 +90,10 @@ export declare class LyraCombobox extends LyraElement {
|
|
|
78
90
|
private removeValue;
|
|
79
91
|
private clear;
|
|
80
92
|
private onInput;
|
|
93
|
+
private onInputBlur;
|
|
94
|
+
private onHintSlotChange;
|
|
95
|
+
private onErrorSlotChange;
|
|
96
|
+
private onLabelSlotChange;
|
|
81
97
|
private onKeyDown;
|
|
82
98
|
private onComboMouseDown;
|
|
83
99
|
private renderRows;
|
|
@@ -10,6 +10,7 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
11
|
import { place } from '../../internal/positioner.js';
|
|
12
12
|
import { nextId } from '../../internal/a11y.js';
|
|
13
|
+
import { chevronIcon, closeIcon } from '../../internal/icons.js';
|
|
13
14
|
import { styles } from './combobox.styles.js';
|
|
14
15
|
import { LyraOption } from './option.js';
|
|
15
16
|
import './option.js';
|
|
@@ -24,6 +25,7 @@ import './option.js';
|
|
|
24
25
|
* @slot - `<lyra-option>` elements.
|
|
25
26
|
* @slot label - Custom label content.
|
|
26
27
|
* @slot hint - Custom hint content.
|
|
28
|
+
* @slot error - Custom error content.
|
|
27
29
|
* @event change - The selection changed.
|
|
28
30
|
* @event input - The user typed in the filter or changed the selection.
|
|
29
31
|
* @event lyra-show - The listbox opened.
|
|
@@ -37,6 +39,7 @@ import './option.js';
|
|
|
37
39
|
* @csspart tag - An individual selected tag.
|
|
38
40
|
* @csspart clear-button - The clear button.
|
|
39
41
|
* @csspart expand-icon - The dropdown indicator.
|
|
42
|
+
* @csspart error - The error message.
|
|
40
43
|
*/
|
|
41
44
|
export class LyraCombobox extends LyraElement {
|
|
42
45
|
static { this.formAssociated = true; }
|
|
@@ -53,6 +56,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
53
56
|
this.name = '';
|
|
54
57
|
this.label = '';
|
|
55
58
|
this.hint = '';
|
|
59
|
+
this.errorText = '';
|
|
56
60
|
this.open = false;
|
|
57
61
|
this.withClear = false;
|
|
58
62
|
this.maxOptionsVisible = 3;
|
|
@@ -61,13 +65,53 @@ export class LyraCombobox extends LyraElement {
|
|
|
61
65
|
this.query = '';
|
|
62
66
|
this.activeIndex = -1;
|
|
63
67
|
this.options = [];
|
|
68
|
+
// Set on the combobox input's first `blur`; gates the `data-invalid`
|
|
69
|
+
// reflection below so validity styling never flashes on first render.
|
|
70
|
+
this.touched = false;
|
|
71
|
+
// `[part]:empty` never matches — the part always contains a literal
|
|
72
|
+
// `<slot>` child element regardless of assigned content — so real
|
|
73
|
+
// emptiness is tracked in JS instead (same fix as lyra-stat's
|
|
74
|
+
// icon/caption, commit 6c1004c) and reflected via `hidden`. Applies to
|
|
75
|
+
// `form-control-label` too: the required-asterisk `::after` attaches to
|
|
76
|
+
// that box, so leaving it always-visible orphans a stray ' *' when no
|
|
77
|
+
// `label` is set.
|
|
78
|
+
this.hasHintSlot = false;
|
|
79
|
+
this.hasErrorSlot = false;
|
|
80
|
+
this.hasLabelSlot = false;
|
|
64
81
|
this.listId = nextId('combobox-list');
|
|
65
82
|
this._selected = [];
|
|
83
|
+
// What `form.reset()` restores to. Captured exactly once, from whatever
|
|
84
|
+
// `<lyra-option selected>` markup was present the first time slotted
|
|
85
|
+
// options are collected (mirrors native `<select><option selected>`) —
|
|
86
|
+
// never from the `value` setter, so a user picking an option (even the
|
|
87
|
+
// very first pick on an initially-unselected combobox) can't itself
|
|
88
|
+
// become the reset default (native `defaultValue`/`defaultSelected`
|
|
89
|
+
// semantics: only declarative/attribute state feeds the default, plain
|
|
90
|
+
// property assignment never does).
|
|
91
|
+
this._defaultSelected = [];
|
|
92
|
+
this._defaultCaptured = false;
|
|
66
93
|
this.collectOptions = (e) => {
|
|
67
94
|
const slot = e.target;
|
|
68
95
|
this.options = slot
|
|
69
96
|
.assignedElements({ flatten: true })
|
|
70
97
|
.filter((el) => el instanceof LyraOption);
|
|
98
|
+
if (!this._defaultCaptured) {
|
|
99
|
+
this._defaultCaptured = true;
|
|
100
|
+
// Seed the initial selection — and the reset default — from
|
|
101
|
+
// declarative `<lyra-option selected>` markup — mirrors native
|
|
102
|
+
// `<select><option selected>` — the exact case the audit found
|
|
103
|
+
// silently ignored (forms-core §combobox, Medium). This is the only
|
|
104
|
+
// place `_defaultSelected` is set; picking an option later (the
|
|
105
|
+
// `value` setter) never redefines the reset default, so an
|
|
106
|
+
// initially-unselected combobox always resets back to empty even
|
|
107
|
+
// after the user has picked something.
|
|
108
|
+
const declared = this.options.filter((o) => o.selected).map((o) => o.value);
|
|
109
|
+
this._defaultSelected = [...declared];
|
|
110
|
+
if (declared.length) {
|
|
111
|
+
this.value = this.multiple ? declared : declared[0];
|
|
112
|
+
return; // `value=`'s setter already called reflectSelected()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
71
115
|
this.reflectSelected();
|
|
72
116
|
};
|
|
73
117
|
this.onDocPointer = (e) => {
|
|
@@ -80,6 +124,18 @@ export class LyraCombobox extends LyraElement {
|
|
|
80
124
|
this.show();
|
|
81
125
|
this.emit('input');
|
|
82
126
|
};
|
|
127
|
+
this.onInputBlur = () => {
|
|
128
|
+
this.touched = true;
|
|
129
|
+
};
|
|
130
|
+
this.onHintSlotChange = (e) => {
|
|
131
|
+
this.hasHintSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
132
|
+
};
|
|
133
|
+
this.onErrorSlotChange = (e) => {
|
|
134
|
+
this.hasErrorSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
135
|
+
};
|
|
136
|
+
this.onLabelSlotChange = (e) => {
|
|
137
|
+
this.hasLabelSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
138
|
+
};
|
|
83
139
|
this.onKeyDown = (e) => {
|
|
84
140
|
const opts = this.filtered.filter((o) => !o.disabled);
|
|
85
141
|
switch (e.key) {
|
|
@@ -135,6 +191,17 @@ export class LyraCombobox extends LyraElement {
|
|
|
135
191
|
};
|
|
136
192
|
this.internals = this.attachInternals();
|
|
137
193
|
}
|
|
194
|
+
connectedCallback() {
|
|
195
|
+
super.connectedCallback();
|
|
196
|
+
this.updateValidity();
|
|
197
|
+
}
|
|
198
|
+
willUpdate() {
|
|
199
|
+
if (!this.hasUpdated) {
|
|
200
|
+
this.hasHintSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'hint');
|
|
201
|
+
this.hasErrorSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'error');
|
|
202
|
+
this.hasLabelSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'label');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
138
205
|
/** The selected value(s): a string in single mode, a string[] in `multiple` mode. */
|
|
139
206
|
get value() {
|
|
140
207
|
return this.multiple ? [...this._selected] : (this._selected[0] ?? '');
|
|
@@ -144,8 +211,17 @@ export class LyraCombobox extends LyraElement {
|
|
|
144
211
|
this._selected = Array.isArray(next) ? [...next] : next ? [next] : [];
|
|
145
212
|
this.syncFormValue();
|
|
146
213
|
this.reflectSelected();
|
|
214
|
+
this.updateValidity();
|
|
147
215
|
this.requestUpdate('value', old);
|
|
148
216
|
}
|
|
217
|
+
updateValidity() {
|
|
218
|
+
if (this.required && this._selected.length === 0) {
|
|
219
|
+
this.internals.setValidity({ valueMissing: true }, 'Please select an option.');
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.internals.setValidity({});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
149
225
|
syncFormValue() {
|
|
150
226
|
if (this.multiple) {
|
|
151
227
|
const fd = new FormData();
|
|
@@ -159,7 +235,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
159
235
|
}
|
|
160
236
|
}
|
|
161
237
|
formResetCallback() {
|
|
162
|
-
this.value = [];
|
|
238
|
+
this.value = [...this._defaultSelected];
|
|
163
239
|
this.query = '';
|
|
164
240
|
}
|
|
165
241
|
formDisabledCallback(disabled) {
|
|
@@ -225,6 +301,11 @@ export class LyraCombobox extends LyraElement {
|
|
|
225
301
|
this.cleanup = place(anchor, listbox);
|
|
226
302
|
}
|
|
227
303
|
}
|
|
304
|
+
if (changed.has('required'))
|
|
305
|
+
this.updateValidity();
|
|
306
|
+
if (changed.has('touched') || changed.has('required') || changed.has('value')) {
|
|
307
|
+
this.toggleAttribute('data-invalid', this.touched && !this.internals.validity.valid);
|
|
308
|
+
}
|
|
228
309
|
}
|
|
229
310
|
pick(option) {
|
|
230
311
|
if (option.disabled)
|
|
@@ -288,9 +369,14 @@ export class LyraCombobox extends LyraElement {
|
|
|
288
369
|
const shownTags = this.multiple ? this._selected.slice(0, this.maxOptionsVisible) : [];
|
|
289
370
|
const extra = this.multiple ? this._selected.length - shownTags.length : 0;
|
|
290
371
|
const hasValue = this._selected.length > 0;
|
|
372
|
+
const hasHint = this.hasHintSlot || this.hint.length > 0;
|
|
373
|
+
const hasError = this.hasErrorSlot || this.errorText.length > 0;
|
|
374
|
+
const hasLabel = this.hasLabelSlot || this.label.length > 0;
|
|
291
375
|
return html `
|
|
292
376
|
<div part="form-control">
|
|
293
|
-
<label part="form-control-label"
|
|
377
|
+
<label part="form-control-label" ?hidden=${!hasLabel}>
|
|
378
|
+
${this.label}<slot name="label" @slotchange=${this.onLabelSlotChange}></slot>
|
|
379
|
+
</label>
|
|
294
380
|
<div part="combobox" @mousedown=${this.onComboMouseDown}>
|
|
295
381
|
<div part="tags" style="display:contents">
|
|
296
382
|
${shownTags.map((v) => html `<span part="tag"
|
|
@@ -303,7 +389,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
303
389
|
this.removeValue(v);
|
|
304
390
|
}}
|
|
305
391
|
>
|
|
306
|
-
|
|
392
|
+
${closeIcon()}</button
|
|
307
393
|
></span
|
|
308
394
|
>`)}
|
|
309
395
|
${extra > 0 ? html `<span part="tag">+${extra}</span>` : ''}
|
|
@@ -323,6 +409,7 @@ export class LyraCombobox extends LyraElement {
|
|
|
323
409
|
@input=${this.onInput}
|
|
324
410
|
@keydown=${this.onKeyDown}
|
|
325
411
|
@focus=${() => this.show()}
|
|
412
|
+
@blur=${this.onInputBlur}
|
|
326
413
|
/>
|
|
327
414
|
${this.withClear && hasValue
|
|
328
415
|
? html `<button
|
|
@@ -334,17 +421,22 @@ export class LyraCombobox extends LyraElement {
|
|
|
334
421
|
this.clear();
|
|
335
422
|
}}
|
|
336
423
|
>
|
|
337
|
-
|
|
424
|
+
${closeIcon()}
|
|
338
425
|
</button>`
|
|
339
426
|
: ''}
|
|
340
|
-
<span part="expand-icon" aria-hidden="true"
|
|
427
|
+
<span part="expand-icon" aria-hidden="true">${chevronIcon()}</span>
|
|
341
428
|
</div>
|
|
342
429
|
<div part="listbox" id=${this.listId} role="listbox" aria-multiselectable=${this.multiple ? 'true' : 'false'}>
|
|
343
430
|
${filtered.length === 0
|
|
344
431
|
? html `<div class="empty">${this.emptyText}</div>`
|
|
345
432
|
: this.renderRows(filtered, activeId)}
|
|
346
433
|
</div>
|
|
347
|
-
<div part="
|
|
434
|
+
<div part="error" ?hidden=${!hasError}>
|
|
435
|
+
${this.errorText}<slot name="error" @slotchange=${this.onErrorSlotChange}></slot>
|
|
436
|
+
</div>
|
|
437
|
+
<div part="hint" ?hidden=${!hasHint}>
|
|
438
|
+
${this.hint}<slot name="hint" @slotchange=${this.onHintSlotChange}></slot>
|
|
439
|
+
</div>
|
|
348
440
|
</div>
|
|
349
441
|
<slot @slotchange=${this.collectOptions} hidden></slot>
|
|
350
442
|
`;
|
|
@@ -371,6 +463,9 @@ __decorate([
|
|
|
371
463
|
__decorate([
|
|
372
464
|
property()
|
|
373
465
|
], LyraCombobox.prototype, "hint", void 0);
|
|
466
|
+
__decorate([
|
|
467
|
+
property({ attribute: 'error-text' })
|
|
468
|
+
], LyraCombobox.prototype, "errorText", void 0);
|
|
374
469
|
__decorate([
|
|
375
470
|
property({ type: Boolean, reflect: true })
|
|
376
471
|
], LyraCombobox.prototype, "open", void 0);
|
|
@@ -395,4 +490,16 @@ __decorate([
|
|
|
395
490
|
__decorate([
|
|
396
491
|
state()
|
|
397
492
|
], LyraCombobox.prototype, "options", void 0);
|
|
493
|
+
__decorate([
|
|
494
|
+
state()
|
|
495
|
+
], LyraCombobox.prototype, "touched", void 0);
|
|
496
|
+
__decorate([
|
|
497
|
+
state()
|
|
498
|
+
], LyraCombobox.prototype, "hasHintSlot", void 0);
|
|
499
|
+
__decorate([
|
|
500
|
+
state()
|
|
501
|
+
], LyraCombobox.prototype, "hasErrorSlot", void 0);
|
|
502
|
+
__decorate([
|
|
503
|
+
state()
|
|
504
|
+
], LyraCombobox.prototype, "hasLabelSlot", void 0);
|
|
398
505
|
defineElement('combobox', LyraCombobox);
|
|
@@ -9,9 +9,19 @@ export const styles = css `
|
|
|
9
9
|
font-size: 0.875rem;
|
|
10
10
|
font-weight: 600;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
/* :empty never matches here -- the part always contains a literal slot
|
|
13
|
+
child element regardless of assigned/text content -- so real emptiness
|
|
14
|
+
is tracked in JS (hasLabelSlot) and reflected via the hidden attribute
|
|
15
|
+
instead (same fix as [part='hint']/[part='error'] below). Without this,
|
|
16
|
+
the required-asterisk ::after below (which attaches to this box)
|
|
17
|
+
renders a stray ' *' with nothing before it whenever label is unset. */
|
|
18
|
+
[part='form-control-label'][hidden] {
|
|
13
19
|
display: none;
|
|
14
20
|
}
|
|
21
|
+
:host([required]) [part='form-control-label']::after {
|
|
22
|
+
content: ' *';
|
|
23
|
+
color: var(--lyra-color-danger);
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
[part='combobox'] {
|
|
17
27
|
display: flex;
|
|
@@ -31,7 +41,9 @@ export const styles = css `
|
|
|
31
41
|
outline: 2px solid transparent;
|
|
32
42
|
}
|
|
33
43
|
:host([disabled]) [part='combobox'] {
|
|
34
|
-
|
|
44
|
+
/* was a literal 0.5; now the shared library-wide disabled-state token
|
|
45
|
+
(still 0.5 by default fallback, so no visual change here). */
|
|
46
|
+
opacity: var(--lyra-opacity-disabled);
|
|
35
47
|
cursor: not-allowed;
|
|
36
48
|
}
|
|
37
49
|
|
|
@@ -68,16 +80,34 @@ export const styles = css `
|
|
|
68
80
|
[part='clear-button'],
|
|
69
81
|
[part='expand-icon'] {
|
|
70
82
|
flex: 0 0 auto;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
71
86
|
border: none;
|
|
72
87
|
background: none;
|
|
73
88
|
cursor: pointer;
|
|
74
89
|
color: var(--lyra-color-text-quiet);
|
|
75
|
-
padding:
|
|
90
|
+
padding: var(--lyra-space-xs);
|
|
91
|
+
/* Real touch target in *both* dimensions (WCAG 2.2 SC 2.5.8 needs
|
|
92
|
+
24x24 CSS px, not just height — a design-review fix that only set
|
|
93
|
+
min-block-size left these buttons ~21px wide). icon-button-size is
|
|
94
|
+
the ceiling, but never grow past what the [part=combobox] row's own
|
|
95
|
+
2.5rem min-block-size has room for once its own block padding is
|
|
96
|
+
subtracted. */
|
|
97
|
+
min-inline-size: min(var(--lyra-icon-button-size), 1.75rem);
|
|
98
|
+
min-block-size: min(var(--lyra-icon-button-size), 1.75rem);
|
|
76
99
|
line-height: 1;
|
|
77
100
|
}
|
|
101
|
+
[part='expand-icon'] svg {
|
|
102
|
+
transform: rotate(90deg);
|
|
103
|
+
}
|
|
104
|
+
[part='clear-button']:focus-visible,
|
|
105
|
+
[part='tag__remove-button']:focus-visible {
|
|
106
|
+
outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
|
|
107
|
+
outline-offset: var(--lyra-focus-ring-offset);
|
|
108
|
+
}
|
|
78
109
|
|
|
79
110
|
[part='listbox'] {
|
|
80
|
-
display: none;
|
|
81
111
|
position: fixed;
|
|
82
112
|
z-index: 900;
|
|
83
113
|
box-sizing: border-box;
|
|
@@ -91,9 +121,26 @@ export const styles = css `
|
|
|
91
121
|
border: 1px solid var(--lyra-color-border);
|
|
92
122
|
border-radius: var(--lyra-radius);
|
|
93
123
|
box-shadow: var(--lyra-shadow);
|
|
124
|
+
/* Closed state: invisible + slightly raised. visibility (not
|
|
125
|
+
display:none) so opacity/transform can actually transition; hit-testing
|
|
126
|
+
and a11y exposure stay off since this part is already position:fixed. */
|
|
127
|
+
visibility: hidden;
|
|
128
|
+
opacity: 0;
|
|
129
|
+
transform: translateY(-0.25rem);
|
|
130
|
+
transition:
|
|
131
|
+
opacity var(--lyra-transition-fast),
|
|
132
|
+
transform var(--lyra-transition-fast),
|
|
133
|
+
visibility var(--lyra-transition-fast);
|
|
94
134
|
}
|
|
95
135
|
:host([open]) [part='listbox'] {
|
|
96
|
-
|
|
136
|
+
visibility: visible;
|
|
137
|
+
opacity: 1;
|
|
138
|
+
transform: translateY(0);
|
|
139
|
+
}
|
|
140
|
+
@media (prefers-reduced-motion: reduce) {
|
|
141
|
+
[part='listbox'] {
|
|
142
|
+
transition: none !important;
|
|
143
|
+
}
|
|
97
144
|
}
|
|
98
145
|
|
|
99
146
|
[part='option'] {
|
|
@@ -120,7 +167,9 @@ export const styles = css `
|
|
|
120
167
|
font-weight: 600;
|
|
121
168
|
}
|
|
122
169
|
[part='option'][aria-disabled='true'] {
|
|
123
|
-
|
|
170
|
+
/* was a literal 0.4; unified with the rest of the library's single
|
|
171
|
+
disabled-state opacity token (intentionally changes 0.4 -> 0.5). */
|
|
172
|
+
opacity: var(--lyra-opacity-disabled);
|
|
124
173
|
cursor: not-allowed;
|
|
125
174
|
}
|
|
126
175
|
|
|
@@ -142,7 +191,19 @@ export const styles = css `
|
|
|
142
191
|
font-size: 0.8125rem;
|
|
143
192
|
color: var(--lyra-color-text-quiet);
|
|
144
193
|
}
|
|
145
|
-
|
|
194
|
+
/* :empty never matches here -- the part always contains a literal
|
|
195
|
+
slot child element regardless of assigned/text content -- so real
|
|
196
|
+
emptiness is tracked in JS (hasHintSlot/hasErrorSlot) and reflected via
|
|
197
|
+
the hidden attribute instead (same fix as lyra-stat's icon/caption). */
|
|
198
|
+
[part='hint'][hidden] {
|
|
199
|
+
display: none;
|
|
200
|
+
}
|
|
201
|
+
[part='error'] {
|
|
202
|
+
margin-block-start: var(--lyra-space-xs);
|
|
203
|
+
font-size: 0.8125rem;
|
|
204
|
+
color: var(--lyra-color-danger);
|
|
205
|
+
}
|
|
206
|
+
[part='error'][hidden] {
|
|
146
207
|
display: none;
|
|
147
208
|
}
|
|
148
209
|
`;
|
|
@@ -10,7 +10,7 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
10
10
|
* @customElement lyra-option
|
|
11
11
|
*/
|
|
12
12
|
export declare class LyraOption extends LyraElement {
|
|
13
|
-
static styles: import("lit").
|
|
13
|
+
static styles: import("lit").CSSResultGroup[];
|
|
14
14
|
/** The selection key submitted with the form. */
|
|
15
15
|
value: string;
|
|
16
16
|
/** Disable selecting this option. */
|
|
@@ -21,7 +21,7 @@ export declare class LyraOption extends LyraElement {
|
|
|
21
21
|
group: string;
|
|
22
22
|
/** Extra text the filter should match beyond the label. */
|
|
23
23
|
searchText: string;
|
|
24
|
-
/** Resolved label: explicit `label` attribute wins, else trimmed text content. */
|
|
24
|
+
/** Resolved label: explicit non-empty `label` attribute wins, else trimmed text content. */
|
|
25
25
|
get label(): string;
|
|
26
26
|
render(): TemplateResult;
|
|
27
27
|
}
|
|
@@ -31,14 +31,18 @@ export class LyraOption extends LyraElement {
|
|
|
31
31
|
/** Extra text the filter should match beyond the label. */
|
|
32
32
|
this.searchText = '';
|
|
33
33
|
}
|
|
34
|
-
static { this.styles =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
static { this.styles = [
|
|
35
|
+
LyraElement.styles,
|
|
36
|
+
css `
|
|
37
|
+
:host {
|
|
38
|
+
display: none;
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
]; }
|
|
42
|
+
/** Resolved label: explicit non-empty `label` attribute wins, else trimmed text content. */
|
|
40
43
|
get label() {
|
|
41
|
-
|
|
44
|
+
const attr = this.getAttribute('label');
|
|
45
|
+
return (attr ? attr : (this.textContent ?? '')).trim();
|
|
42
46
|
}
|
|
43
47
|
render() {
|
|
44
48
|
return html `<slot></slot>`;
|
|
@@ -16,7 +16,8 @@ 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
|
|
19
|
+
* @csspart form-control, form-control-label, input-wrapper, input, clear-button, expand-button, popup, date-picker, hint, error
|
|
20
|
+
* @slot error - Custom error content.
|
|
20
21
|
*/
|
|
21
22
|
export declare class LyraDateInput extends LyraDateInput_base {
|
|
22
23
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -28,13 +29,19 @@ export declare class LyraDateInput extends LyraDateInput_base {
|
|
|
28
29
|
withClear: boolean;
|
|
29
30
|
label: string;
|
|
30
31
|
hint: string;
|
|
32
|
+
errorText: string;
|
|
31
33
|
placeholder: string;
|
|
32
34
|
locale: string;
|
|
33
35
|
months: 1 | 2;
|
|
34
36
|
firstDayOfWeek: string;
|
|
35
37
|
weekdayFormat: WeekdayFormat;
|
|
36
38
|
private cleanupFn?;
|
|
39
|
+
private touched;
|
|
40
|
+
private hasHintSlot;
|
|
41
|
+
private hasErrorSlot;
|
|
42
|
+
private hasLabelSlot;
|
|
37
43
|
private get displayText();
|
|
44
|
+
protected willUpdate(): void;
|
|
38
45
|
/** Open the calendar popover. */
|
|
39
46
|
show(): void;
|
|
40
47
|
/** Close the calendar popover. */
|
|
@@ -46,6 +53,10 @@ export declare class LyraDateInput extends LyraDateInput_base {
|
|
|
46
53
|
protected updated(changed: PropertyValues): void;
|
|
47
54
|
private onInputChange;
|
|
48
55
|
private onInputKey;
|
|
56
|
+
private onInputBlur;
|
|
57
|
+
private onHintSlotChange;
|
|
58
|
+
private onErrorSlotChange;
|
|
59
|
+
private onLabelSlotChange;
|
|
49
60
|
private onPickerChange;
|
|
50
61
|
render(): TemplateResult;
|
|
51
62
|
}
|
|
@@ -10,6 +10,7 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
10
10
|
import { FormAssociated } from '../../internal/form-associated.js';
|
|
11
11
|
import { defineElement } from '../../internal/prefix.js';
|
|
12
12
|
import { place } from '../../internal/positioner.js';
|
|
13
|
+
import { closeIcon, calendarIcon } from '../../internal/icons.js';
|
|
13
14
|
import { parseISO, formatISO } from './calendar-core.js';
|
|
14
15
|
import { styles } from './date-input.styles.js';
|
|
15
16
|
import { LyraDatePicker } from './date-picker.js';
|
|
@@ -27,7 +28,8 @@ import './date-picker.js';
|
|
|
27
28
|
* @event change - Fired on committed date transitions.
|
|
28
29
|
* @event lyra-show / lyra-hide - Calendar popover lifecycle.
|
|
29
30
|
* @event lyra-clear - The clear button was used.
|
|
30
|
-
* @csspart form-control, form-control-label, input-wrapper, input, clear-button, expand-button, popup, date-picker, hint
|
|
31
|
+
* @csspart form-control, form-control-label, input-wrapper, input, clear-button, expand-button, popup, date-picker, hint, error
|
|
32
|
+
* @slot error - Custom error content.
|
|
31
33
|
*/
|
|
32
34
|
export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
33
35
|
constructor() {
|
|
@@ -40,11 +42,25 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
40
42
|
this.withClear = false;
|
|
41
43
|
this.label = '';
|
|
42
44
|
this.hint = '';
|
|
45
|
+
this.errorText = '';
|
|
43
46
|
this.placeholder = '';
|
|
44
47
|
this.locale = '';
|
|
45
48
|
this.months = 1;
|
|
46
49
|
this.firstDayOfWeek = 'auto';
|
|
47
50
|
this.weekdayFormat = 'short';
|
|
51
|
+
// Set on the date input's first `blur`; gates the `data-invalid`
|
|
52
|
+
// reflection below so validity styling never flashes on first render.
|
|
53
|
+
this.touched = false;
|
|
54
|
+
// `[part]:empty` never matches — the part always contains a literal
|
|
55
|
+
// `<slot>` child element regardless of assigned content — so real
|
|
56
|
+
// emptiness is tracked in JS instead (same fix as lyra-stat's
|
|
57
|
+
// icon/caption, commit 6c1004c) and reflected via `hidden`. Applies to
|
|
58
|
+
// `form-control-label` too: the required-asterisk `::after` attaches to
|
|
59
|
+
// that box, so leaving it always-visible orphans a stray ' *' when no
|
|
60
|
+
// `label` is set.
|
|
61
|
+
this.hasHintSlot = false;
|
|
62
|
+
this.hasErrorSlot = false;
|
|
63
|
+
this.hasLabelSlot = false;
|
|
48
64
|
this.onDocPointer = (e) => {
|
|
49
65
|
if (!e.composedPath().includes(this))
|
|
50
66
|
this.hide();
|
|
@@ -70,6 +86,18 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
70
86
|
this.show();
|
|
71
87
|
}
|
|
72
88
|
};
|
|
89
|
+
this.onInputBlur = () => {
|
|
90
|
+
this.touched = true;
|
|
91
|
+
};
|
|
92
|
+
this.onHintSlotChange = (e) => {
|
|
93
|
+
this.hasHintSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
94
|
+
};
|
|
95
|
+
this.onErrorSlotChange = (e) => {
|
|
96
|
+
this.hasErrorSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
97
|
+
};
|
|
98
|
+
this.onLabelSlotChange = (e) => {
|
|
99
|
+
this.hasLabelSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
100
|
+
};
|
|
73
101
|
this.onPickerChange = (e) => {
|
|
74
102
|
e.stopPropagation();
|
|
75
103
|
const picker = e.target;
|
|
@@ -90,6 +118,13 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
90
118
|
const fmt = (d) => d.toLocaleDateString(this.locale || undefined);
|
|
91
119
|
return this.mode === 'range' && to ? `${fmt(from)} – ${fmt(to)}` : fmt(from);
|
|
92
120
|
}
|
|
121
|
+
willUpdate() {
|
|
122
|
+
if (!this.hasUpdated) {
|
|
123
|
+
this.hasHintSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'hint');
|
|
124
|
+
this.hasErrorSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'error');
|
|
125
|
+
this.hasLabelSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'label');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
93
128
|
/** Open the calendar popover. */
|
|
94
129
|
show() {
|
|
95
130
|
if (this.open || this.disabled || this.readonly)
|
|
@@ -119,6 +154,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
119
154
|
document.removeEventListener('pointerdown', this.onDocPointer);
|
|
120
155
|
}
|
|
121
156
|
updated(changed) {
|
|
157
|
+
super.updated(changed);
|
|
122
158
|
if (changed.has('open')) {
|
|
123
159
|
this.cleanupFn?.();
|
|
124
160
|
this.cleanupFn = undefined;
|
|
@@ -129,12 +165,20 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
129
165
|
this.cleanupFn = place(anchor, popup);
|
|
130
166
|
}
|
|
131
167
|
}
|
|
168
|
+
if (changed.has('touched') || changed.has('required') || changed.has('value')) {
|
|
169
|
+
this.toggleAttribute('data-invalid', this.touched && !this.internals.validity.valid);
|
|
170
|
+
}
|
|
132
171
|
}
|
|
133
172
|
render() {
|
|
134
173
|
const hasValue = this.value.length > 0;
|
|
174
|
+
const hasHint = this.hasHintSlot || this.hint.length > 0;
|
|
175
|
+
const hasError = this.hasErrorSlot || this.errorText.length > 0;
|
|
176
|
+
const hasLabel = this.hasLabelSlot || this.label.length > 0;
|
|
135
177
|
return html `
|
|
136
178
|
<div part="form-control">
|
|
137
|
-
<label part="form-control-label"
|
|
179
|
+
<label part="form-control-label" ?hidden=${!hasLabel}>
|
|
180
|
+
${this.label}<slot name="label" @slotchange=${this.onLabelSlotChange}></slot>
|
|
181
|
+
</label>
|
|
138
182
|
<div part="input-wrapper">
|
|
139
183
|
<input
|
|
140
184
|
part="input"
|
|
@@ -146,6 +190,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
146
190
|
?readonly=${this.readonly}
|
|
147
191
|
@change=${this.onInputChange}
|
|
148
192
|
@keydown=${this.onInputKey}
|
|
193
|
+
@blur=${this.onInputBlur}
|
|
149
194
|
/>
|
|
150
195
|
${this.withClear && hasValue
|
|
151
196
|
? html `<button
|
|
@@ -154,7 +199,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
154
199
|
aria-label="Clear"
|
|
155
200
|
@click=${() => this.clear()}
|
|
156
201
|
>
|
|
157
|
-
|
|
202
|
+
${closeIcon()}
|
|
158
203
|
</button>`
|
|
159
204
|
: ''}
|
|
160
205
|
<button
|
|
@@ -166,7 +211,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
166
211
|
?disabled=${this.disabled || this.readonly}
|
|
167
212
|
@click=${() => (this.open ? this.hide() : this.show())}
|
|
168
213
|
>
|
|
169
|
-
<span part="expand-icon" aria-hidden="true"
|
|
214
|
+
<span part="expand-icon" aria-hidden="true">${calendarIcon()}</span>
|
|
170
215
|
</button>
|
|
171
216
|
</div>
|
|
172
217
|
<div part="popup" role="dialog" aria-label="Choose date">
|
|
@@ -183,7 +228,12 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
183
228
|
@change=${this.onPickerChange}
|
|
184
229
|
></lyra-date-picker>
|
|
185
230
|
</div>
|
|
186
|
-
<div part="
|
|
231
|
+
<div part="error" ?hidden=${!hasError}>
|
|
232
|
+
${this.errorText}<slot name="error" @slotchange=${this.onErrorSlotChange}></slot>
|
|
233
|
+
</div>
|
|
234
|
+
<div part="hint" ?hidden=${!hasHint}>
|
|
235
|
+
${this.hint}<slot name="hint" @slotchange=${this.onHintSlotChange}></slot>
|
|
236
|
+
</div>
|
|
187
237
|
</div>
|
|
188
238
|
`;
|
|
189
239
|
}
|
|
@@ -212,6 +262,9 @@ __decorate([
|
|
|
212
262
|
__decorate([
|
|
213
263
|
property()
|
|
214
264
|
], LyraDateInput.prototype, "hint", void 0);
|
|
265
|
+
__decorate([
|
|
266
|
+
property({ attribute: 'error-text' })
|
|
267
|
+
], LyraDateInput.prototype, "errorText", void 0);
|
|
215
268
|
__decorate([
|
|
216
269
|
property()
|
|
217
270
|
], LyraDateInput.prototype, "placeholder", void 0);
|
|
@@ -229,5 +282,14 @@ __decorate([
|
|
|
229
282
|
], LyraDateInput.prototype, "weekdayFormat", void 0);
|
|
230
283
|
__decorate([
|
|
231
284
|
state()
|
|
232
|
-
], LyraDateInput.prototype, "
|
|
285
|
+
], LyraDateInput.prototype, "touched", void 0);
|
|
286
|
+
__decorate([
|
|
287
|
+
state()
|
|
288
|
+
], LyraDateInput.prototype, "hasHintSlot", void 0);
|
|
289
|
+
__decorate([
|
|
290
|
+
state()
|
|
291
|
+
], LyraDateInput.prototype, "hasErrorSlot", void 0);
|
|
292
|
+
__decorate([
|
|
293
|
+
state()
|
|
294
|
+
], LyraDateInput.prototype, "hasLabelSlot", void 0);
|
|
233
295
|
defineElement('date-input', LyraDateInput);
|