@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
|
@@ -11,7 +11,9 @@ export type ExportFormat = 'csv' | 'json';
|
|
|
11
11
|
* @event lyra-export - `detail: { format }`, cancelable — call `preventDefault()`
|
|
12
12
|
* to substitute the built-in client-side download with a server-generated one.
|
|
13
13
|
* @event lyra-export-complete - Fired after a non-cancelled download completes.
|
|
14
|
-
* @csspart trigger
|
|
14
|
+
* @csspart trigger - The button that triggers the export (or opens the format menu).
|
|
15
|
+
* @csspart menu - The format-choice menu, shown when more than one format is configured.
|
|
16
|
+
* @csspart menu-item - A single format option inside the menu.
|
|
15
17
|
*/
|
|
16
18
|
export declare class LyraExportButton extends LyraElement {
|
|
17
19
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -22,14 +24,26 @@ export declare class LyraExportButton extends LyraElement {
|
|
|
22
24
|
disabled: boolean;
|
|
23
25
|
label: string;
|
|
24
26
|
open: boolean;
|
|
27
|
+
private triggerEl?;
|
|
28
|
+
private menuEl?;
|
|
29
|
+
private readonly menuId;
|
|
25
30
|
private cleanup?;
|
|
31
|
+
/** Which menu item to focus the next time `open` flips true; reset after use. */
|
|
32
|
+
private pendingMenuFocusIndex;
|
|
26
33
|
disconnectedCallback(): void;
|
|
27
34
|
private onDocPointer;
|
|
28
35
|
private openMenu;
|
|
29
36
|
private closeMenu;
|
|
37
|
+
private menuItemEls;
|
|
38
|
+
/** Focuses the menu item at `index` (clamped), if the menu is already open and rendered. */
|
|
39
|
+
private focusMenuItem;
|
|
40
|
+
/** Opens the menu (if closed) and focuses `index`, or moves focus there directly if already open. */
|
|
41
|
+
private focusMenuItemOnOpen;
|
|
30
42
|
private onKeyDown;
|
|
31
43
|
protected firstUpdated(): void;
|
|
32
44
|
protected updated(changed: PropertyValues): void;
|
|
45
|
+
/** Applies the same `columns` allow-list CSV exports use, so JSON can't leak fields CSV hides. */
|
|
46
|
+
private rowsForExport;
|
|
33
47
|
private doExport;
|
|
34
48
|
private onTriggerClick;
|
|
35
49
|
render(): TemplateResult;
|
|
@@ -5,10 +5,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { html, nothing } from 'lit';
|
|
8
|
-
import { property } from 'lit/decorators.js';
|
|
8
|
+
import { property, query } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
11
|
import { place } from '../../internal/positioner.js';
|
|
12
|
+
import { nextId } from '../../internal/a11y.js';
|
|
12
13
|
import { buildCsv, downloadBlob } from './csv.js';
|
|
13
14
|
import { styles } from './export-button.styles.js';
|
|
14
15
|
/**
|
|
@@ -20,7 +21,9 @@ import { styles } from './export-button.styles.js';
|
|
|
20
21
|
* @event lyra-export - `detail: { format }`, cancelable — call `preventDefault()`
|
|
21
22
|
* to substitute the built-in client-side download with a server-generated one.
|
|
22
23
|
* @event lyra-export-complete - Fired after a non-cancelled download completes.
|
|
23
|
-
* @csspart trigger
|
|
24
|
+
* @csspart trigger - The button that triggers the export (or opens the format menu).
|
|
25
|
+
* @csspart menu - The format-choice menu, shown when more than one format is configured.
|
|
26
|
+
* @csspart menu-item - A single format option inside the menu.
|
|
24
27
|
*/
|
|
25
28
|
export class LyraExportButton extends LyraElement {
|
|
26
29
|
constructor() {
|
|
@@ -32,15 +35,47 @@ export class LyraExportButton extends LyraElement {
|
|
|
32
35
|
this.disabled = false;
|
|
33
36
|
this.label = 'Export';
|
|
34
37
|
this.open = false;
|
|
38
|
+
this.menuId = nextId('export-menu');
|
|
39
|
+
/** Which menu item to focus the next time `open` flips true; reset after use. */
|
|
40
|
+
this.pendingMenuFocusIndex = 0;
|
|
35
41
|
this.onDocPointer = (e) => {
|
|
36
42
|
if (!e.composedPath().includes(this))
|
|
37
43
|
this.closeMenu();
|
|
38
44
|
};
|
|
39
45
|
this.onKeyDown = (e) => {
|
|
40
|
-
if (e.key === 'Escape'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
if (e.key === 'Escape') {
|
|
47
|
+
if (this.open) {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
this.closeMenu();
|
|
50
|
+
this.triggerEl?.focus();
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.formats.length <= 1 || this.disabled)
|
|
55
|
+
return;
|
|
56
|
+
const items = this.menuItemEls();
|
|
57
|
+
const currentIndex = items.indexOf(e.target);
|
|
58
|
+
switch (e.key) {
|
|
59
|
+
case 'ArrowDown':
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
this.focusMenuItemOnOpen(currentIndex === -1 ? 0 : currentIndex + 1);
|
|
62
|
+
break;
|
|
63
|
+
case 'ArrowUp':
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
this.focusMenuItemOnOpen(currentIndex === -1 ? items.length - 1 : currentIndex - 1);
|
|
66
|
+
break;
|
|
67
|
+
case 'Home':
|
|
68
|
+
if (this.open) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
this.focusMenuItem(0);
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case 'End':
|
|
74
|
+
if (this.open) {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
this.focusMenuItem(items.length - 1);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
44
79
|
}
|
|
45
80
|
};
|
|
46
81
|
}
|
|
@@ -60,9 +95,29 @@ export class LyraExportButton extends LyraElement {
|
|
|
60
95
|
return;
|
|
61
96
|
this.open = false;
|
|
62
97
|
}
|
|
98
|
+
menuItemEls() {
|
|
99
|
+
return Array.from(this.renderRoot.querySelectorAll('[part="menu-item"]'));
|
|
100
|
+
}
|
|
101
|
+
/** Focuses the menu item at `index` (clamped), if the menu is already open and rendered. */
|
|
102
|
+
focusMenuItem(index) {
|
|
103
|
+
const items = this.menuItemEls();
|
|
104
|
+
if (items.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
items[Math.max(0, Math.min(items.length - 1, index))]?.focus();
|
|
107
|
+
}
|
|
108
|
+
/** Opens the menu (if closed) and focuses `index`, or moves focus there directly if already open. */
|
|
109
|
+
focusMenuItemOnOpen(index) {
|
|
110
|
+
if (this.open) {
|
|
111
|
+
this.focusMenuItem(index);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.pendingMenuFocusIndex = index;
|
|
115
|
+
this.openMenu();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
63
118
|
firstUpdated() {
|
|
64
|
-
// Single delegated listener catches Escape from the
|
|
65
|
-
// any menu-item inside this shadow root.
|
|
119
|
+
// Single delegated listener catches Escape/Arrow/Home/End from the
|
|
120
|
+
// trigger button or any menu-item inside this shadow root.
|
|
66
121
|
this.renderRoot.addEventListener('keydown', this.onKeyDown);
|
|
67
122
|
}
|
|
68
123
|
updated(changed) {
|
|
@@ -76,16 +131,31 @@ export class LyraExportButton extends LyraElement {
|
|
|
76
131
|
// property), which bypasses openMenu() entirely.
|
|
77
132
|
document.removeEventListener('pointerdown', this.onDocPointer);
|
|
78
133
|
if (this.open) {
|
|
79
|
-
const anchor = this.
|
|
80
|
-
const menu = this.
|
|
134
|
+
const anchor = this.triggerEl;
|
|
135
|
+
const menu = this.menuEl;
|
|
81
136
|
if (anchor && menu)
|
|
82
137
|
this.cleanup = place(anchor, menu);
|
|
83
138
|
document.addEventListener('pointerdown', this.onDocPointer);
|
|
139
|
+
this.focusMenuItem(this.pendingMenuFocusIndex);
|
|
140
|
+
this.pendingMenuFocusIndex = 0;
|
|
84
141
|
}
|
|
85
142
|
}
|
|
86
143
|
}
|
|
144
|
+
/** Applies the same `columns` allow-list CSV exports use, so JSON can't leak fields CSV hides. */
|
|
145
|
+
rowsForExport() {
|
|
146
|
+
if (this.columns.length === 0)
|
|
147
|
+
return this.rows;
|
|
148
|
+
const keys = this.columns.map((c) => c.key);
|
|
149
|
+
return this.rows.map((row) => {
|
|
150
|
+
const picked = {};
|
|
151
|
+
for (const key of keys)
|
|
152
|
+
picked[key] = row[key];
|
|
153
|
+
return picked;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
87
156
|
doExport(format) {
|
|
88
157
|
this.closeMenu();
|
|
158
|
+
this.triggerEl?.focus();
|
|
89
159
|
const ev = this.emit('lyra-export', { format });
|
|
90
160
|
if (ev.defaultPrevented)
|
|
91
161
|
return;
|
|
@@ -93,7 +163,7 @@ export class LyraExportButton extends LyraElement {
|
|
|
93
163
|
downloadBlob(buildCsv(this.rows, this.columns), `${this.filename}.csv`, 'text/csv;charset=utf-8;');
|
|
94
164
|
}
|
|
95
165
|
else {
|
|
96
|
-
downloadBlob(JSON.stringify(this.
|
|
166
|
+
downloadBlob(JSON.stringify(this.rowsForExport(), null, 2), `${this.filename}.json`, 'application/json');
|
|
97
167
|
}
|
|
98
168
|
this.emit('lyra-export-complete', { format });
|
|
99
169
|
}
|
|
@@ -113,12 +183,13 @@ export class LyraExportButton extends LyraElement {
|
|
|
113
183
|
?disabled=${this.disabled}
|
|
114
184
|
aria-haspopup=${this.formats.length > 1 ? 'menu' : nothing}
|
|
115
185
|
aria-expanded=${this.formats.length > 1 ? (this.open ? 'true' : 'false') : nothing}
|
|
186
|
+
aria-controls=${this.formats.length > 1 ? this.menuId : nothing}
|
|
116
187
|
@click=${() => this.onTriggerClick()}
|
|
117
188
|
>
|
|
118
189
|
${this.label}
|
|
119
190
|
</button>
|
|
120
191
|
${this.formats.length > 1
|
|
121
|
-
? html `<div part="menu" role="menu">
|
|
192
|
+
? html `<div id=${this.menuId} part="menu" role="menu">
|
|
122
193
|
${this.formats.map((f) => html `<button
|
|
123
194
|
part="menu-item"
|
|
124
195
|
role="menuitem"
|
|
@@ -153,4 +224,10 @@ __decorate([
|
|
|
153
224
|
__decorate([
|
|
154
225
|
property({ type: Boolean, reflect: true })
|
|
155
226
|
], LyraExportButton.prototype, "open", void 0);
|
|
227
|
+
__decorate([
|
|
228
|
+
query('[part="trigger"]')
|
|
229
|
+
], LyraExportButton.prototype, "triggerEl", void 0);
|
|
230
|
+
__decorate([
|
|
231
|
+
query('[part="menu"]')
|
|
232
|
+
], LyraExportButton.prototype, "menuEl", void 0);
|
|
156
233
|
defineElement('export-button', LyraExportButton);
|
|
@@ -30,7 +30,16 @@ export const styles = css `
|
|
|
30
30
|
[part='menu'] {
|
|
31
31
|
/* Closed state: invisible + slightly raised. visibility (not
|
|
32
32
|
display:none) so opacity/transform can actually transition; hit-testing
|
|
33
|
-
and a11y exposure stay off since this part is already position:fixed.
|
|
33
|
+
and a11y exposure stay off since this part is already position:fixed.
|
|
34
|
+
visibility deliberately isn't in the transition list below: a
|
|
35
|
+
transitioned property's computed value only settles to its new
|
|
36
|
+
target after the UA has run an actual style-change/rendering pass,
|
|
37
|
+
which lags behind a same-tick attribute write (e.g. updated()
|
|
38
|
+
synchronously focusing the first menu item right after flipping
|
|
39
|
+
open) -- that item would still resolve as visibility: hidden (and
|
|
40
|
+
so silently fail to focus) at the moment .focus() is called. Leaving
|
|
41
|
+
visibility untransitioned makes it apply immediately, in the same
|
|
42
|
+
synchronous style pass as the open attribute write. */
|
|
34
43
|
visibility: hidden;
|
|
35
44
|
position: fixed;
|
|
36
45
|
z-index: 900;
|
|
@@ -44,8 +53,7 @@ export const styles = css `
|
|
|
44
53
|
transform: translateY(-4px);
|
|
45
54
|
transition:
|
|
46
55
|
opacity var(--lyra-transition-fast),
|
|
47
|
-
transform var(--lyra-transition-fast)
|
|
48
|
-
visibility var(--lyra-transition-fast);
|
|
56
|
+
transform var(--lyra-transition-fast);
|
|
49
57
|
}
|
|
50
58
|
:host([open]) [part='menu'] {
|
|
51
59
|
visibility: visible;
|
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
* `file` may be a `DataTransferItem` cast as `File` (the dragenter-preview
|
|
9
9
|
* call site, before the drop payload has real `File` objects) - unlike
|
|
10
10
|
* `File`, `DataTransferItem` has no `.name`, only `.type`, so extension
|
|
11
|
-
* patterns (`.csv`) can't be evaluated pre-drop
|
|
11
|
+
* patterns (`.csv`) can't be evaluated pre-drop. By default an unresolved
|
|
12
|
+
* extension pattern counts as no match, since that's the correct behavior
|
|
13
|
+
* once real `File` objects are available at drop time. Pass
|
|
14
|
+
* `assumeExtensionMatch: true` to instead treat an unresolved extension
|
|
15
|
+
* pattern as a possible match — this is what dragenter preview uses so it
|
|
16
|
+
* doesn't render a false "reject" state for accept lists that are
|
|
17
|
+
* extension-only, when the file may well be accepted once dropped.
|
|
12
18
|
*/
|
|
13
|
-
export declare function matchesAccept(file: File, accept: string): boolean;
|
|
19
|
+
export declare function matchesAccept(file: File, accept: string, assumeExtensionMatch?: boolean): boolean;
|
|
@@ -8,9 +8,15 @@
|
|
|
8
8
|
* `file` may be a `DataTransferItem` cast as `File` (the dragenter-preview
|
|
9
9
|
* call site, before the drop payload has real `File` objects) - unlike
|
|
10
10
|
* `File`, `DataTransferItem` has no `.name`, only `.type`, so extension
|
|
11
|
-
* patterns (`.csv`) can't be evaluated pre-drop
|
|
11
|
+
* patterns (`.csv`) can't be evaluated pre-drop. By default an unresolved
|
|
12
|
+
* extension pattern counts as no match, since that's the correct behavior
|
|
13
|
+
* once real `File` objects are available at drop time. Pass
|
|
14
|
+
* `assumeExtensionMatch: true` to instead treat an unresolved extension
|
|
15
|
+
* pattern as a possible match — this is what dragenter preview uses so it
|
|
16
|
+
* doesn't render a false "reject" state for accept lists that are
|
|
17
|
+
* extension-only, when the file may well be accepted once dropped.
|
|
12
18
|
*/
|
|
13
|
-
export function matchesAccept(file, accept) {
|
|
19
|
+
export function matchesAccept(file, accept, assumeExtensionMatch = false) {
|
|
14
20
|
const parts = accept
|
|
15
21
|
.split(',')
|
|
16
22
|
.map((s) => s.trim().toLowerCase())
|
|
@@ -21,7 +27,7 @@ export function matchesAccept(file, accept) {
|
|
|
21
27
|
const type = (file.type || '').toLowerCase();
|
|
22
28
|
return parts.some((p) => {
|
|
23
29
|
if (p.startsWith('.'))
|
|
24
|
-
return name
|
|
30
|
+
return name === undefined ? assumeExtensionMatch : name.endsWith(p);
|
|
25
31
|
if (p.endsWith('/*'))
|
|
26
32
|
return type.startsWith(p.slice(0, -1));
|
|
27
33
|
return type === p;
|
|
@@ -10,9 +10,11 @@ export interface RejectedFile {
|
|
|
10
10
|
* where files ultimately get uploaded and processed anyway.
|
|
11
11
|
*
|
|
12
12
|
* @customElement lyra-file-input
|
|
13
|
-
* @slot - Custom drop-zone content, overrides the `label` attribute.
|
|
13
|
+
* @slot - Custom drop-zone content, overrides the `label` attribute. The
|
|
14
|
+
* accessible name always comes from `label`, so icon-only slot content
|
|
15
|
+
* remains announced correctly.
|
|
14
16
|
* @event lyra-files - `detail: { files, rejected }`, fired on drop and manual selection.
|
|
15
|
-
* @csspart base, input
|
|
17
|
+
* @csspart base, input, status
|
|
16
18
|
*/
|
|
17
19
|
export declare class LyraFileInput extends LyraElement {
|
|
18
20
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -38,6 +40,7 @@ export declare class LyraFileInput extends LyraElement {
|
|
|
38
40
|
private onDrop;
|
|
39
41
|
private onInputChange;
|
|
40
42
|
private onKeyDown;
|
|
43
|
+
private statusText;
|
|
41
44
|
render(): TemplateResult;
|
|
42
45
|
}
|
|
43
46
|
declare global {
|
|
@@ -8,6 +8,7 @@ import { html, nothing } from 'lit';
|
|
|
8
8
|
import { property, state, query } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { srOnly } from '../../internal/a11y.js';
|
|
11
12
|
import { styles } from './file-input.styles.js';
|
|
12
13
|
import { matchesAccept } from './accept.js';
|
|
13
14
|
/**
|
|
@@ -16,9 +17,11 @@ import { matchesAccept } from './accept.js';
|
|
|
16
17
|
* where files ultimately get uploaded and processed anyway.
|
|
17
18
|
*
|
|
18
19
|
* @customElement lyra-file-input
|
|
19
|
-
* @slot - Custom drop-zone content, overrides the `label` attribute.
|
|
20
|
+
* @slot - Custom drop-zone content, overrides the `label` attribute. The
|
|
21
|
+
* accessible name always comes from `label`, so icon-only slot content
|
|
22
|
+
* remains announced correctly.
|
|
20
23
|
* @event lyra-files - `detail: { files, rejected }`, fired on drop and manual selection.
|
|
21
|
-
* @csspart base, input
|
|
24
|
+
* @csspart base, input, status
|
|
22
25
|
*/
|
|
23
26
|
export class LyraFileInput extends LyraElement {
|
|
24
27
|
constructor() {
|
|
@@ -80,13 +83,16 @@ export class LyraFileInput extends LyraElement {
|
|
|
80
83
|
}
|
|
81
84
|
};
|
|
82
85
|
}
|
|
83
|
-
static { this.styles = [LyraElement.styles, styles]; }
|
|
84
|
-
isAllowed(file) {
|
|
86
|
+
static { this.styles = [LyraElement.styles, styles, srOnly]; }
|
|
87
|
+
isAllowed(file, isPreview = false) {
|
|
85
88
|
if (this.forbiddenMimeTypes.includes(file.type))
|
|
86
89
|
return 'type';
|
|
87
90
|
if (this.allowedMimeTypes.length > 0 && !this.allowedMimeTypes.includes(file.type))
|
|
88
91
|
return 'type';
|
|
89
|
-
|
|
92
|
+
// During dragenter preview, `accept` extension patterns can't be evaluated (no
|
|
93
|
+
// `.name` yet) — treat them as a possible match rather than a guaranteed reject,
|
|
94
|
+
// so the preview doesn't flag a file that will in fact be accepted on drop.
|
|
95
|
+
if (this.accept && !matchesAccept(file, this.accept, isPreview))
|
|
90
96
|
return 'type';
|
|
91
97
|
// `file.size` is `undefined` on the synthetic `DataTransferItem`-cast objects
|
|
92
98
|
// used during dragenter preview (real sizes aren't available until drop),
|
|
@@ -95,14 +101,14 @@ export class LyraFileInput extends LyraElement {
|
|
|
95
101
|
return 'size';
|
|
96
102
|
return 'ok';
|
|
97
103
|
}
|
|
98
|
-
classify(fileList) {
|
|
104
|
+
classify(fileList, isPreview = false) {
|
|
99
105
|
if (!this.multiple && fileList.length > 1) {
|
|
100
106
|
return { files: [], rejected: fileList.map((file) => ({ file, reason: 'count' })) };
|
|
101
107
|
}
|
|
102
108
|
const files = [];
|
|
103
109
|
const rejected = [];
|
|
104
110
|
for (const f of fileList) {
|
|
105
|
-
const reason = this.isAllowed(f);
|
|
111
|
+
const reason = this.isAllowed(f, isPreview);
|
|
106
112
|
if (reason === 'ok')
|
|
107
113
|
files.push(f);
|
|
108
114
|
else
|
|
@@ -119,9 +125,16 @@ export class LyraFileInput extends LyraElement {
|
|
|
119
125
|
this.inputEl?.click();
|
|
120
126
|
}
|
|
121
127
|
previewState(fileList) {
|
|
122
|
-
const { rejected } = this.classify(fileList);
|
|
128
|
+
const { rejected } = this.classify(fileList, true);
|
|
123
129
|
return rejected.length > 0 ? 'reject' : 'accept';
|
|
124
130
|
}
|
|
131
|
+
statusText() {
|
|
132
|
+
if (this.dragState === 'accept')
|
|
133
|
+
return 'Release to add the file.';
|
|
134
|
+
if (this.dragState === 'reject')
|
|
135
|
+
return 'This file type is not accepted.';
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
125
138
|
render() {
|
|
126
139
|
return html `
|
|
127
140
|
<div
|
|
@@ -129,6 +142,7 @@ export class LyraFileInput extends LyraElement {
|
|
|
129
142
|
role="button"
|
|
130
143
|
tabindex=${this.disabled ? '-1' : '0'}
|
|
131
144
|
aria-disabled=${this.disabled ? 'true' : nothing}
|
|
145
|
+
aria-label=${this.label}
|
|
132
146
|
data-drag-state=${this.dragState}
|
|
133
147
|
@dragenter=${this.onDragEnter}
|
|
134
148
|
@dragover=${this.onDragOver}
|
|
@@ -139,6 +153,7 @@ export class LyraFileInput extends LyraElement {
|
|
|
139
153
|
>
|
|
140
154
|
<slot>${this.label}</slot>
|
|
141
155
|
</div>
|
|
156
|
+
<div part="status" class="sr-only" role="status" aria-live="polite">${this.statusText()}</div>
|
|
142
157
|
<input
|
|
143
158
|
part="input"
|
|
144
159
|
type="file"
|
|
@@ -8,3 +8,12 @@ export default meta;
|
|
|
8
8
|
export const Default = {
|
|
9
9
|
render: () => html `<lyra-file-input multiple accept=".csv,.xlsx"></lyra-file-input>`,
|
|
10
10
|
};
|
|
11
|
+
export const CustomSlotContent = {
|
|
12
|
+
render: () => html `<lyra-file-input multiple accept=".csv,.xlsx" label="Upload spreadsheets">
|
|
13
|
+
<strong>Drag spreadsheets here</strong>
|
|
14
|
+
<span>or click to browse (.csv, .xlsx)</span>
|
|
15
|
+
</lyra-file-input>`,
|
|
16
|
+
};
|
|
17
|
+
export const Disabled = {
|
|
18
|
+
render: () => html `<lyra-file-input disabled accept=".csv,.xlsx"></lyra-file-input>`,
|
|
19
|
+
};
|
|
@@ -1,17 +1,48 @@
|
|
|
1
1
|
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
3
|
import '../skeleton/skeleton.js';
|
|
4
|
+
type FlagUrlResolver = (code: string, options?: {
|
|
5
|
+
variant?: 'detailed';
|
|
6
|
+
}) => Promise<string | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the optional peer dependency `@aceshooting/lyra-flags`'s `flagUrl`
|
|
9
|
+
* via the given importer (a real dynamic import by default). Uncached and
|
|
10
|
+
* dependency-injectable — unlike `loadFlagUrlResolver()` below — so the
|
|
11
|
+
* caught-error warning path is directly testable without needing to
|
|
12
|
+
* actually uninstall the package.
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadFlagUrl(importFlags?: () => Promise<{
|
|
15
|
+
flagUrl: FlagUrlResolver;
|
|
16
|
+
}>): Promise<FlagUrlResolver | null>;
|
|
4
17
|
/**
|
|
5
18
|
* `<lyra-flag>` — a country/language flag.
|
|
6
19
|
*
|
|
7
20
|
* Flag images are shipped by the optional peer package `@aceshooting/lyra-flags`,
|
|
8
21
|
* not bundled into lyra-ui itself, so importing the core library pulls zero flag
|
|
9
22
|
* weight. Give it a `country` (ISO 3166-1 alpha-2) or a `language` tag (mapped to
|
|
10
|
-
* a representative country).
|
|
23
|
+
* a representative country). While that peer package's `flagUrl()` resolves (or
|
|
24
|
+
* if it isn't installed), the host carries `aria-busy="true"` and a skeleton
|
|
25
|
+
* placeholder renders in its place.
|
|
26
|
+
*
|
|
27
|
+
* **Bundle-size note:** `country`/`language` resolve through the peer package's
|
|
28
|
+
* `flagUrl(code)`, which is itself genuinely code-split per flag (see that
|
|
29
|
+
* package's docs) — using `<lyra-flag country="fr">` anywhere in an app ships
|
|
30
|
+
* only the flags actually requested at runtime, not all 249. If you already
|
|
31
|
+
* have a flag's URL at build time (e.g. from your own literal
|
|
32
|
+
* `import frUrl from '@aceshooting/lyra-flags/flags/fr.svg?url'`), pass it as
|
|
33
|
+
* `src` instead to skip the peer-package round trip (and its loading-skeleton
|
|
34
|
+
* flash) entirely.
|
|
35
|
+
*
|
|
36
|
+
* A handful of flags (e.g. `es`, `pt` — any whose design includes a detailed coat of
|
|
37
|
+
* arms/seal/emblem) ship a second, full-detail source SVG alongside the default icon-optimized
|
|
38
|
+
* one; set `detailed` to request it (e.g. for a hero-scale display where the extra illustrative
|
|
39
|
+
* detail is actually visible). A no-op for every other code — see `detailed`'s own doc.
|
|
11
40
|
*
|
|
12
41
|
* @customElement lyra-flag
|
|
13
42
|
* @example <lyra-flag country="fr"></lyra-flag>
|
|
14
43
|
* @example <lyra-flag language="en" label="English"></lyra-flag>
|
|
44
|
+
* @example <lyra-flag src=${frUrl} label="French"></lyra-flag>
|
|
45
|
+
* @example <lyra-flag country="es" detailed></lyra-flag>
|
|
15
46
|
* @csspart image - The underlying <img>.
|
|
16
47
|
*/
|
|
17
48
|
export declare class LyraFlag extends LyraElement {
|
|
@@ -20,13 +51,47 @@ export declare class LyraFlag extends LyraElement {
|
|
|
20
51
|
country?: string;
|
|
21
52
|
/** BCP-47-ish language tag (e.g. `en`, `en-US`) resolved to a country flag. */
|
|
22
53
|
language?: string;
|
|
23
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* A pre-resolved flag image URL — takes precedence over `country`/`language`
|
|
56
|
+
* and skips the `@aceshooting/lyra-flags` peer-package lookup (and its
|
|
57
|
+
* loading-skeleton round trip) entirely. See the class doc: mainly useful to
|
|
58
|
+
* avoid even the small per-flag async hop when you already have the URL at
|
|
59
|
+
* build time. `label` is effectively required alongside `src` — there's no
|
|
60
|
+
* `country`/`language` to derive a fallback `alt` from.
|
|
61
|
+
*/
|
|
62
|
+
src?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Accessible label / `alt` text. Defaults to a localized, human-readable
|
|
65
|
+
* region name derived from the *resolved country code* via
|
|
66
|
+
* `Intl.DisplayNames` (e.g. `"United Kingdom"`) — for a `language`-only
|
|
67
|
+
* element (e.g. `language="en"`) that's the mapped country's display name,
|
|
68
|
+
* not the language tag itself. Falls back to the bare uppercase code if
|
|
69
|
+
* `Intl.DisplayNames` can't resolve it. Has no default when only `src` is
|
|
70
|
+
* given (no country/language to derive one from).
|
|
71
|
+
*/
|
|
24
72
|
label?: string;
|
|
25
73
|
/** Render as a circular flag. */
|
|
26
74
|
round: boolean;
|
|
27
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Requests the pristine, full-detail source SVG instead of the default icon-optimized one —
|
|
77
|
+
* only meaningful for the minority of `country`/`language` codes whose source art was large
|
|
78
|
+
* enough to need optimizing (e.g. `es`, `pt`, a national coat of arms/seal/emblem); for every
|
|
79
|
+
* other code this is a safe no-op (the default and detailed variants are the same file). Has no
|
|
80
|
+
* effect when `src` is set — a pre-resolved URL is used as-is regardless. Intended for rendering
|
|
81
|
+
* a flag larger than icon scale (e.g. a hero display) where the extra illustrative detail is
|
|
82
|
+
* actually visible.
|
|
83
|
+
*/
|
|
84
|
+
detailed: boolean;
|
|
85
|
+
private resolvedSrc?;
|
|
28
86
|
/** True while the lazy-loaded `@aceshooting/lyra-flags` peer resolver is in flight. */
|
|
29
87
|
private loading;
|
|
88
|
+
/**
|
|
89
|
+
* Bumped on every `willUpdate` pass; captured by each in-flight resolver
|
|
90
|
+
* `.then()` so a resolution for a `country`/`language` that's since changed
|
|
91
|
+
* (or been cleared) can recognize itself as stale and no-op instead of
|
|
92
|
+
* overwriting newer state.
|
|
93
|
+
*/
|
|
94
|
+
private resolveToken;
|
|
30
95
|
private get code();
|
|
31
96
|
protected willUpdate(changed: PropertyValues<this>): void;
|
|
32
97
|
protected updated(): void;
|
|
@@ -37,3 +102,4 @@ declare global {
|
|
|
37
102
|
'lyra-flag': LyraFlag;
|
|
38
103
|
}
|
|
39
104
|
}
|
|
105
|
+
export {};
|