@aceshooting/lyra-ui 0.1.1 → 0.1.3
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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { html, nothing } from 'lit';
|
|
8
|
+
import { property, state, query } from 'lit/decorators.js';
|
|
9
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
|
+
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { styles } from './file-input.styles.js';
|
|
12
|
+
import { matchesAccept } from './accept.js';
|
|
13
|
+
/**
|
|
14
|
+
* `<lyra-file-input>` — a drag-drop + click-to-browse file dropzone. Emits
|
|
15
|
+
* raw `File[]`; parsing (CSV/XLSX/etc.) is left to the host, matching every
|
|
16
|
+
* surveyed upload seed (all of which upload raw files to a backend).
|
|
17
|
+
*
|
|
18
|
+
* @customElement lyra-file-input
|
|
19
|
+
* @slot - Custom drop-zone content, overrides the `label` attribute.
|
|
20
|
+
* @event lyra-files - `detail: { files, rejected }`, fired on drop and manual selection.
|
|
21
|
+
* @csspart base, input
|
|
22
|
+
*/
|
|
23
|
+
export class LyraFileInput extends LyraElement {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
this.multiple = false;
|
|
27
|
+
this.disabled = false;
|
|
28
|
+
this.accept = '';
|
|
29
|
+
this.allowedMimeTypes = [];
|
|
30
|
+
this.forbiddenMimeTypes = [];
|
|
31
|
+
this.maxFileSize = 0;
|
|
32
|
+
this.label = 'Drop files here or click to browse';
|
|
33
|
+
this.dragState = 'default';
|
|
34
|
+
this.dragCounter = 0;
|
|
35
|
+
this.onDragEnter = (e) => {
|
|
36
|
+
if (this.disabled)
|
|
37
|
+
return;
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
this.dragCounter++;
|
|
40
|
+
const items = e.dataTransfer ? [...e.dataTransfer.items].filter((i) => i.kind === 'file') : [];
|
|
41
|
+
this.dragState = items.length ? this.previewState(items) : 'default';
|
|
42
|
+
};
|
|
43
|
+
this.onDragOver = (e) => {
|
|
44
|
+
if (this.disabled)
|
|
45
|
+
return;
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
};
|
|
48
|
+
this.onDragLeave = (e) => {
|
|
49
|
+
if (this.disabled)
|
|
50
|
+
return;
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
this.dragCounter = Math.max(0, this.dragCounter - 1);
|
|
53
|
+
if (this.dragCounter === 0)
|
|
54
|
+
this.dragState = 'default';
|
|
55
|
+
};
|
|
56
|
+
this.onDrop = (e) => {
|
|
57
|
+
if (this.disabled)
|
|
58
|
+
return;
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
this.dragCounter = 0;
|
|
61
|
+
this.dragState = 'default';
|
|
62
|
+
const files = [...(e.dataTransfer?.files ?? [])];
|
|
63
|
+
if (files.length)
|
|
64
|
+
this.emitFiles(files);
|
|
65
|
+
};
|
|
66
|
+
this.onInputChange = (e) => {
|
|
67
|
+
const files = [...(e.target.files ?? [])];
|
|
68
|
+
e.target.value = '';
|
|
69
|
+
if (files.length)
|
|
70
|
+
this.emitFiles(files);
|
|
71
|
+
};
|
|
72
|
+
this.onKeyDown = (e) => {
|
|
73
|
+
if (this.disabled)
|
|
74
|
+
return;
|
|
75
|
+
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
|
|
76
|
+
// Prevent Space from scrolling the page, matching the `table.ts`
|
|
77
|
+
// sortable-header/row convention for role-based clickable elements.
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
this.openPicker();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
static { this.styles = [LyraElement.styles, styles]; }
|
|
84
|
+
isAllowed(file) {
|
|
85
|
+
if (this.forbiddenMimeTypes.includes(file.type))
|
|
86
|
+
return 'type';
|
|
87
|
+
if (this.allowedMimeTypes.length > 0 && !this.allowedMimeTypes.includes(file.type))
|
|
88
|
+
return 'type';
|
|
89
|
+
if (this.accept && !matchesAccept(file, this.accept))
|
|
90
|
+
return 'type';
|
|
91
|
+
// `file.size` is `undefined` on the synthetic `DataTransferItem`-cast objects
|
|
92
|
+
// used during dragenter preview (real sizes aren't available until drop),
|
|
93
|
+
// so this naturally only takes effect for the real `classify()` call at drop time.
|
|
94
|
+
if (this.maxFileSize > 0 && file.size > this.maxFileSize)
|
|
95
|
+
return 'size';
|
|
96
|
+
return 'ok';
|
|
97
|
+
}
|
|
98
|
+
classify(fileList) {
|
|
99
|
+
if (!this.multiple && fileList.length > 1) {
|
|
100
|
+
return { files: [], rejected: fileList.map((file) => ({ file, reason: 'count' })) };
|
|
101
|
+
}
|
|
102
|
+
const files = [];
|
|
103
|
+
const rejected = [];
|
|
104
|
+
for (const f of fileList) {
|
|
105
|
+
const reason = this.isAllowed(f);
|
|
106
|
+
if (reason === 'ok')
|
|
107
|
+
files.push(f);
|
|
108
|
+
else
|
|
109
|
+
rejected.push({ file: f, reason });
|
|
110
|
+
}
|
|
111
|
+
return { files, rejected };
|
|
112
|
+
}
|
|
113
|
+
emitFiles(fileList) {
|
|
114
|
+
const { files, rejected } = this.classify(fileList);
|
|
115
|
+
this.emit('lyra-files', { files, rejected });
|
|
116
|
+
}
|
|
117
|
+
/** Programmatically open the native file picker. */
|
|
118
|
+
openPicker() {
|
|
119
|
+
this.inputEl?.click();
|
|
120
|
+
}
|
|
121
|
+
previewState(fileList) {
|
|
122
|
+
const { rejected } = this.classify(fileList);
|
|
123
|
+
return rejected.length > 0 ? 'reject' : 'accept';
|
|
124
|
+
}
|
|
125
|
+
render() {
|
|
126
|
+
return html `
|
|
127
|
+
<div
|
|
128
|
+
part="base"
|
|
129
|
+
role="button"
|
|
130
|
+
tabindex=${this.disabled ? '-1' : '0'}
|
|
131
|
+
aria-disabled=${this.disabled ? 'true' : nothing}
|
|
132
|
+
data-drag-state=${this.dragState}
|
|
133
|
+
@dragenter=${this.onDragEnter}
|
|
134
|
+
@dragover=${this.onDragOver}
|
|
135
|
+
@dragleave=${this.onDragLeave}
|
|
136
|
+
@drop=${this.onDrop}
|
|
137
|
+
@click=${() => !this.disabled && this.openPicker()}
|
|
138
|
+
@keydown=${this.onKeyDown}
|
|
139
|
+
>
|
|
140
|
+
<slot>${this.label}</slot>
|
|
141
|
+
</div>
|
|
142
|
+
<input
|
|
143
|
+
part="input"
|
|
144
|
+
type="file"
|
|
145
|
+
tabindex="-1"
|
|
146
|
+
aria-hidden="true"
|
|
147
|
+
accept=${this.accept}
|
|
148
|
+
?multiple=${this.multiple}
|
|
149
|
+
?disabled=${this.disabled}
|
|
150
|
+
@change=${this.onInputChange}
|
|
151
|
+
/>
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
__decorate([
|
|
156
|
+
property({ type: Boolean, reflect: true })
|
|
157
|
+
], LyraFileInput.prototype, "multiple", void 0);
|
|
158
|
+
__decorate([
|
|
159
|
+
property({ type: Boolean, reflect: true })
|
|
160
|
+
], LyraFileInput.prototype, "disabled", void 0);
|
|
161
|
+
__decorate([
|
|
162
|
+
property()
|
|
163
|
+
], LyraFileInput.prototype, "accept", void 0);
|
|
164
|
+
__decorate([
|
|
165
|
+
property({ attribute: false })
|
|
166
|
+
], LyraFileInput.prototype, "allowedMimeTypes", void 0);
|
|
167
|
+
__decorate([
|
|
168
|
+
property({ attribute: false })
|
|
169
|
+
], LyraFileInput.prototype, "forbiddenMimeTypes", void 0);
|
|
170
|
+
__decorate([
|
|
171
|
+
property({ type: Number, attribute: 'max-file-size' })
|
|
172
|
+
], LyraFileInput.prototype, "maxFileSize", void 0);
|
|
173
|
+
__decorate([
|
|
174
|
+
property()
|
|
175
|
+
], LyraFileInput.prototype, "label", void 0);
|
|
176
|
+
__decorate([
|
|
177
|
+
state()
|
|
178
|
+
], LyraFileInput.prototype, "dragState", void 0);
|
|
179
|
+
__decorate([
|
|
180
|
+
query('input[type="file"]')
|
|
181
|
+
], LyraFileInput.prototype, "inputEl", void 0);
|
|
182
|
+
defineElement('file-input', LyraFileInput);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
[part='base'] {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
gap: var(--lyra-space-xs);
|
|
12
|
+
padding: var(--lyra-space-l);
|
|
13
|
+
border: 2px dashed var(--lyra-color-border);
|
|
14
|
+
border-radius: var(--lyra-radius);
|
|
15
|
+
background: var(--lyra-color-surface);
|
|
16
|
+
color: var(--lyra-color-text-quiet);
|
|
17
|
+
text-align: center;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
font-size: 0.875rem;
|
|
20
|
+
}
|
|
21
|
+
[part='base'][data-drag-state='accept'] {
|
|
22
|
+
border-color: var(--lyra-color-success);
|
|
23
|
+
background: color-mix(in srgb, var(--lyra-color-success) 8%, transparent);
|
|
24
|
+
}
|
|
25
|
+
[part='base'][data-drag-state='reject'] {
|
|
26
|
+
border-color: var(--lyra-color-danger);
|
|
27
|
+
background: color-mix(in srgb, var(--lyra-color-danger) 8%, transparent);
|
|
28
|
+
}
|
|
29
|
+
[part='base']:focus-visible {
|
|
30
|
+
outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
|
|
31
|
+
outline-offset: var(--lyra-focus-ring-offset);
|
|
32
|
+
}
|
|
33
|
+
:host([disabled]) [part='base'] {
|
|
34
|
+
opacity: var(--lyra-opacity-disabled);
|
|
35
|
+
cursor: not-allowed;
|
|
36
|
+
}
|
|
37
|
+
[part='input'] {
|
|
38
|
+
position: absolute;
|
|
39
|
+
inline-size: 1px;
|
|
40
|
+
block-size: 1px;
|
|
41
|
+
opacity: 0;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
import '../skeleton/skeleton.js';
|
|
3
4
|
/**
|
|
4
5
|
* `<lyra-flag>` — a country/language flag.
|
|
5
6
|
*
|
|
@@ -24,8 +25,11 @@ export declare class LyraFlag extends LyraElement {
|
|
|
24
25
|
/** Render as a circular flag. */
|
|
25
26
|
round: boolean;
|
|
26
27
|
private src?;
|
|
28
|
+
/** True while the lazy-loaded `@aceshooting/lyra-flags` peer resolver is in flight. */
|
|
29
|
+
private loading;
|
|
27
30
|
private get code();
|
|
28
31
|
protected willUpdate(changed: PropertyValues<this>): void;
|
|
32
|
+
protected updated(): void;
|
|
29
33
|
render(): TemplateResult;
|
|
30
34
|
}
|
|
31
35
|
declare global {
|
|
@@ -10,6 +10,7 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
11
|
import { styles } from './flag.styles.js';
|
|
12
12
|
import { languageToCountry } from './language-map.js';
|
|
13
|
+
import '../skeleton/skeleton.js';
|
|
13
14
|
let flagUrlResolver;
|
|
14
15
|
/**
|
|
15
16
|
* Lazily loads the optional peer dependency '@aceshooting/lyra-flags' once per
|
|
@@ -45,6 +46,8 @@ export class LyraFlag extends LyraElement {
|
|
|
45
46
|
super(...arguments);
|
|
46
47
|
/** Render as a circular flag. */
|
|
47
48
|
this.round = false;
|
|
49
|
+
/** True while the lazy-loaded `@aceshooting/lyra-flags` peer resolver is in flight. */
|
|
50
|
+
this.loading = true;
|
|
48
51
|
}
|
|
49
52
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
50
53
|
get code() {
|
|
@@ -55,18 +58,35 @@ export class LyraFlag extends LyraElement {
|
|
|
55
58
|
return undefined;
|
|
56
59
|
}
|
|
57
60
|
willUpdate(changed) {
|
|
58
|
-
|
|
61
|
+
// `this.hasUpdated` (not just `changed`) forces this body to run once on
|
|
62
|
+
// the very first update even when neither `country` nor `language` is
|
|
63
|
+
// ever set: an unset optional property's first "assignment" is
|
|
64
|
+
// undefined -> undefined (no-op to Lit), so `changed.has('country')`
|
|
65
|
+
// would otherwise never become true for a `<lyra-flag>` that never
|
|
66
|
+
// receives a code, leaving `loading` stuck at its initial `true` forever.
|
|
67
|
+
if (this.hasUpdated && !changed.has('country') && !changed.has('language'))
|
|
59
68
|
return;
|
|
60
69
|
const code = this.code;
|
|
61
70
|
if (!code) {
|
|
62
71
|
this.src = undefined;
|
|
72
|
+
this.loading = false;
|
|
63
73
|
return;
|
|
64
74
|
}
|
|
75
|
+
this.loading = true;
|
|
65
76
|
void loadFlagUrlResolver().then((resolve) => {
|
|
66
77
|
this.src = resolve?.(code);
|
|
78
|
+
this.loading = false;
|
|
67
79
|
});
|
|
68
80
|
}
|
|
81
|
+
updated() {
|
|
82
|
+
if (this.loading)
|
|
83
|
+
this.setAttribute('aria-busy', 'true');
|
|
84
|
+
else
|
|
85
|
+
this.removeAttribute('aria-busy');
|
|
86
|
+
}
|
|
69
87
|
render() {
|
|
88
|
+
if (this.loading)
|
|
89
|
+
return html `<lyra-skeleton variant="rect"></lyra-skeleton>`;
|
|
70
90
|
if (!this.src)
|
|
71
91
|
return html ``;
|
|
72
92
|
const alt = this.label ?? (this.code ?? '').toUpperCase();
|
|
@@ -88,4 +108,7 @@ __decorate([
|
|
|
88
108
|
__decorate([
|
|
89
109
|
state()
|
|
90
110
|
], LyraFlag.prototype, "src", void 0);
|
|
111
|
+
__decorate([
|
|
112
|
+
state()
|
|
113
|
+
], LyraFlag.prototype, "loading", void 0);
|
|
91
114
|
defineElement('flag', LyraFlag);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
export type GaugeType = 'radial' | 'linear';
|
|
4
|
+
/**
|
|
5
|
+
* `<lyra-gauge>` — a radial or linear meter. First-party invention; no
|
|
6
|
+
* generic gauge widget was found across the surveyed repos.
|
|
7
|
+
*
|
|
8
|
+
* @customElement lyra-gauge
|
|
9
|
+
* @csspart base, track, fill, value, label
|
|
10
|
+
*/
|
|
11
|
+
export declare class LyraGauge extends LyraElement {
|
|
12
|
+
static styles: import("lit").CSSResultGroup[];
|
|
13
|
+
value: number;
|
|
14
|
+
min: number;
|
|
15
|
+
max: number;
|
|
16
|
+
type: GaugeType;
|
|
17
|
+
label: string;
|
|
18
|
+
valueLabel?: string;
|
|
19
|
+
private get ratio();
|
|
20
|
+
protected willUpdate(): void;
|
|
21
|
+
private renderRadial;
|
|
22
|
+
private renderLinear;
|
|
23
|
+
render(): TemplateResult;
|
|
24
|
+
}
|
|
25
|
+
declare global {
|
|
26
|
+
interface HTMLElementTagNameMap {
|
|
27
|
+
'lyra-gauge': LyraGauge;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { html, svg } from 'lit';
|
|
8
|
+
import { property } from 'lit/decorators.js';
|
|
9
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
|
+
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { styles } from './gauge.styles.js';
|
|
12
|
+
// Radial gauge sweeps 270° (like a speedometer), leaving a 90° gap at the bottom.
|
|
13
|
+
const SWEEP_DEG = 270;
|
|
14
|
+
const START_DEG = 135; // rotate so the gap is centered at the bottom
|
|
15
|
+
const RADIUS = 40;
|
|
16
|
+
const CENTER = 50;
|
|
17
|
+
const STROKE = 10;
|
|
18
|
+
// Linear gauge: keep the bar thin and low in the 0..20 viewBox so the top
|
|
19
|
+
// ~12 units are free for a value/label text row above it.
|
|
20
|
+
const LINEAR_STROKE = 6;
|
|
21
|
+
const LINEAR_BAR_Y = 15;
|
|
22
|
+
const LINEAR_TEXT_Y = 8;
|
|
23
|
+
// Length of the x1=0 -> x2=100 horizontal fill/track line (Euclidean, y1==y2).
|
|
24
|
+
const LINEAR_LENGTH = 100;
|
|
25
|
+
function polarToCartesian(angleDeg) {
|
|
26
|
+
const rad = (angleDeg * Math.PI) / 180;
|
|
27
|
+
return [CENTER + RADIUS * Math.cos(rad), CENTER + RADIUS * Math.sin(rad)];
|
|
28
|
+
}
|
|
29
|
+
function arcPath(startDeg, endDeg) {
|
|
30
|
+
const [sx, sy] = polarToCartesian(startDeg);
|
|
31
|
+
const [ex, ey] = polarToCartesian(endDeg);
|
|
32
|
+
const largeArc = endDeg - startDeg > 180 ? 1 : 0;
|
|
33
|
+
return `M ${sx} ${sy} A ${RADIUS} ${RADIUS} 0 ${largeArc} 1 ${ex} ${ey}`;
|
|
34
|
+
}
|
|
35
|
+
// The radial fill's `d` is fixed geometry (the full 270° sweep), built once —
|
|
36
|
+
// a `d` rebuilt from `ratio` on every render is not reliably CSS-transitionable
|
|
37
|
+
// across browsers. Animated progress instead rides stroke-dasharray/
|
|
38
|
+
// stroke-dashoffset (always transitionable) over this constant path, the same
|
|
39
|
+
// technique used for the linear variant's fill line below.
|
|
40
|
+
const RADIAL_ARC_D = arcPath(START_DEG, START_DEG + SWEEP_DEG);
|
|
41
|
+
const RADIAL_ARC_LENGTH = (SWEEP_DEG / 360) * 2 * Math.PI * RADIUS;
|
|
42
|
+
/**
|
|
43
|
+
* `<lyra-gauge>` — a radial or linear meter. First-party invention; no
|
|
44
|
+
* generic gauge widget was found across the surveyed repos.
|
|
45
|
+
*
|
|
46
|
+
* @customElement lyra-gauge
|
|
47
|
+
* @csspart base, track, fill, value, label
|
|
48
|
+
*/
|
|
49
|
+
export class LyraGauge extends LyraElement {
|
|
50
|
+
constructor() {
|
|
51
|
+
super(...arguments);
|
|
52
|
+
this.value = 0;
|
|
53
|
+
this.min = 0;
|
|
54
|
+
this.max = 100;
|
|
55
|
+
this.type = 'radial';
|
|
56
|
+
this.label = '';
|
|
57
|
+
}
|
|
58
|
+
static { this.styles = [LyraElement.styles, styles]; }
|
|
59
|
+
get ratio() {
|
|
60
|
+
const span = this.max - this.min || 1;
|
|
61
|
+
return Math.min(1, Math.max(0, (this.value - this.min) / span));
|
|
62
|
+
}
|
|
63
|
+
willUpdate() {
|
|
64
|
+
this.setAttribute('role', 'meter');
|
|
65
|
+
this.setAttribute('aria-valuenow', String(this.value));
|
|
66
|
+
this.setAttribute('aria-valuemin', String(this.min));
|
|
67
|
+
this.setAttribute('aria-valuemax', String(this.max));
|
|
68
|
+
if (this.label)
|
|
69
|
+
this.setAttribute('aria-label', this.label);
|
|
70
|
+
else
|
|
71
|
+
this.removeAttribute('aria-label');
|
|
72
|
+
}
|
|
73
|
+
renderRadial() {
|
|
74
|
+
const text = this.valueLabel ?? String(this.value);
|
|
75
|
+
// Dashoffset counts down from the full arc length (nothing revealed) to 0
|
|
76
|
+
// (whole sweep revealed) as ratio goes 0 -> 1 — the classic "draw an SVG
|
|
77
|
+
// path" technique, which transitions smoothly via plain CSS.
|
|
78
|
+
const dashoffset = RADIAL_ARC_LENGTH * (1 - this.ratio);
|
|
79
|
+
return html `<svg part="base" viewBox="0 0 100 100">
|
|
80
|
+
<path part="track" stroke-width=${STROKE} d=${RADIAL_ARC_D}></path>
|
|
81
|
+
<path
|
|
82
|
+
part="fill"
|
|
83
|
+
stroke-width=${STROKE}
|
|
84
|
+
d=${RADIAL_ARC_D}
|
|
85
|
+
stroke-dasharray=${RADIAL_ARC_LENGTH}
|
|
86
|
+
stroke-dashoffset=${dashoffset}
|
|
87
|
+
></path>
|
|
88
|
+
<text part="value" x="50" y="52">${text}</text>
|
|
89
|
+
${this.label ? svg `<text part="label" x="50" y="68">${this.label}</text>` : ''}
|
|
90
|
+
</svg>`;
|
|
91
|
+
}
|
|
92
|
+
renderLinear() {
|
|
93
|
+
const text = this.valueLabel ?? String(this.value);
|
|
94
|
+
const dashoffset = LINEAR_LENGTH * (1 - this.ratio);
|
|
95
|
+
return html `<svg part="base" viewBox="0 0 100 20" preserveAspectRatio="none">
|
|
96
|
+
<line part="track" x1="0" y1=${LINEAR_BAR_Y} x2="100" y2=${LINEAR_BAR_Y} stroke-width=${LINEAR_STROKE}></line>
|
|
97
|
+
<line
|
|
98
|
+
part="fill"
|
|
99
|
+
x1="0"
|
|
100
|
+
y1=${LINEAR_BAR_Y}
|
|
101
|
+
x2="100"
|
|
102
|
+
y2=${LINEAR_BAR_Y}
|
|
103
|
+
stroke-width=${LINEAR_STROKE}
|
|
104
|
+
stroke-dasharray=${LINEAR_LENGTH}
|
|
105
|
+
stroke-dashoffset=${dashoffset}
|
|
106
|
+
></line>
|
|
107
|
+
${this.label ? svg `<text part="label" x="0" y=${LINEAR_TEXT_Y}>${this.label}</text>` : ''}
|
|
108
|
+
<text part="value" x="100" y=${LINEAR_TEXT_Y}>${text}</text>
|
|
109
|
+
</svg>`;
|
|
110
|
+
}
|
|
111
|
+
render() {
|
|
112
|
+
return this.type === 'linear' ? this.renderLinear() : this.renderRadial();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
__decorate([
|
|
116
|
+
property({ type: Number })
|
|
117
|
+
], LyraGauge.prototype, "value", void 0);
|
|
118
|
+
__decorate([
|
|
119
|
+
property({ type: Number })
|
|
120
|
+
], LyraGauge.prototype, "min", void 0);
|
|
121
|
+
__decorate([
|
|
122
|
+
property({ type: Number })
|
|
123
|
+
], LyraGauge.prototype, "max", void 0);
|
|
124
|
+
__decorate([
|
|
125
|
+
property({ reflect: true })
|
|
126
|
+
], LyraGauge.prototype, "type", void 0);
|
|
127
|
+
__decorate([
|
|
128
|
+
property()
|
|
129
|
+
], LyraGauge.prototype, "label", void 0);
|
|
130
|
+
__decorate([
|
|
131
|
+
property({ attribute: false })
|
|
132
|
+
], LyraGauge.prototype, "valueLabel", void 0);
|
|
133
|
+
defineElement('gauge', LyraGauge);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: inline-block;
|
|
5
|
+
inline-size: 8em;
|
|
6
|
+
block-size: 8em;
|
|
7
|
+
}
|
|
8
|
+
:host([type='linear']) {
|
|
9
|
+
inline-size: 12em;
|
|
10
|
+
block-size: 1.5em;
|
|
11
|
+
}
|
|
12
|
+
svg {
|
|
13
|
+
display: block;
|
|
14
|
+
inline-size: 100%;
|
|
15
|
+
block-size: 100%;
|
|
16
|
+
overflow: visible;
|
|
17
|
+
}
|
|
18
|
+
[part='track'] {
|
|
19
|
+
fill: none;
|
|
20
|
+
stroke: var(--lyra-color-border);
|
|
21
|
+
}
|
|
22
|
+
[part='fill'] {
|
|
23
|
+
fill: none;
|
|
24
|
+
stroke: var(--lyra-color-brand);
|
|
25
|
+
stroke-linecap: round;
|
|
26
|
+
transition: stroke-dashoffset var(--lyra-transition-base);
|
|
27
|
+
}
|
|
28
|
+
[part='value'] {
|
|
29
|
+
font-size: 1rem;
|
|
30
|
+
font-weight: 700;
|
|
31
|
+
text-anchor: middle;
|
|
32
|
+
fill: var(--lyra-color-text);
|
|
33
|
+
font-family: var(--lyra-font);
|
|
34
|
+
}
|
|
35
|
+
[part='label'] {
|
|
36
|
+
font-size: 0.625rem;
|
|
37
|
+
text-anchor: middle;
|
|
38
|
+
fill: var(--lyra-color-text-quiet);
|
|
39
|
+
font-family: var(--lyra-font);
|
|
40
|
+
text-transform: uppercase;
|
|
41
|
+
}
|
|
42
|
+
:host([type='linear']) [part='fill'] {
|
|
43
|
+
stroke-linecap: butt;
|
|
44
|
+
}
|
|
45
|
+
:host([type='linear']) [part='value'],
|
|
46
|
+
:host([type='linear']) [part='label'] {
|
|
47
|
+
font-size: 0.5rem;
|
|
48
|
+
}
|
|
49
|
+
:host([type='linear']) [part='value'] {
|
|
50
|
+
text-anchor: end;
|
|
51
|
+
}
|
|
52
|
+
:host([type='linear']) [part='label'] {
|
|
53
|
+
text-anchor: start;
|
|
54
|
+
}
|
|
55
|
+
@media (prefers-reduced-motion: reduce) {
|
|
56
|
+
[part='fill'] {
|
|
57
|
+
transition: none !important;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
import '../skeleton/skeleton.js';
|
|
4
|
+
export interface GraphNode {
|
|
5
|
+
id: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
radius?: number;
|
|
8
|
+
color?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GraphLink {
|
|
11
|
+
source: string;
|
|
12
|
+
target: string;
|
|
13
|
+
width?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `<lyra-graph>` — a force-directed node-link diagram with pan/zoom/drag.
|
|
17
|
+
* Requires the optional peer deps `d3-force`/`d3-drag`/`d3-zoom`/`d3-selection`
|
|
18
|
+
* (lazy-loaded; a consumer who never uses this component pays zero d3 cost).
|
|
19
|
+
*
|
|
20
|
+
* @customElement lyra-graph
|
|
21
|
+
* @event lyra-node-click - `detail: { id }`.
|
|
22
|
+
* @event lyra-link-click - `detail: { source, target }`.
|
|
23
|
+
* @csspart base, svg, node, link, label
|
|
24
|
+
*/
|
|
25
|
+
export declare class LyraGraph extends LyraElement {
|
|
26
|
+
static styles: import("lit").CSSResultGroup[];
|
|
27
|
+
nodes: GraphNode[];
|
|
28
|
+
links: GraphLink[];
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
chargeStrength: number;
|
|
32
|
+
linkDistance: number;
|
|
33
|
+
/** True until the lazy-loaded d3 peer dependencies have settled (success or failure). */
|
|
34
|
+
private loading;
|
|
35
|
+
private simNodes;
|
|
36
|
+
private simLinks;
|
|
37
|
+
/** Current pan/zoom transform (SVG `transform` attribute syntax), driven by d3-zoom. */
|
|
38
|
+
private transform;
|
|
39
|
+
private simulation?;
|
|
40
|
+
private d3?;
|
|
41
|
+
/** The `<svg>` currently wired up with d3-zoom (guards a one-time bind). */
|
|
42
|
+
private zoomedEl?;
|
|
43
|
+
/** Node `<circle>`s already wired up with d3-drag; cleared on every simulation rebuild
|
|
44
|
+
* so DOM elements Lit reuses across a rebuild get rebound to their fresh datum. */
|
|
45
|
+
private boundNodeEls;
|
|
46
|
+
connectedCallback(): void;
|
|
47
|
+
disconnectedCallback(): void;
|
|
48
|
+
private nodeRadius;
|
|
49
|
+
protected updated(changed: PropertyValues): void;
|
|
50
|
+
/**
|
|
51
|
+
* Imperatively wires up d3-zoom (pan/zoom on the `<svg>`) and d3-drag
|
|
52
|
+
* (per-node drag) against the just-rendered DOM. Runs after every render:
|
|
53
|
+
* binding the `<svg>` is a one-time no-op guard (`zoomedEl`), and node
|
|
54
|
+
* `<circle>`s are skipped once bound (`boundNodeEls`) — a WeakSet reset on
|
|
55
|
+
* every `rebuildSimulation()` so DOM nodes Lit reuses across a rebuild get
|
|
56
|
+
* rebound against their new datum instead of a stale one.
|
|
57
|
+
*/
|
|
58
|
+
private applyInteractions;
|
|
59
|
+
private rebuildSimulation;
|
|
60
|
+
private onNodeClick;
|
|
61
|
+
private onLinkClick;
|
|
62
|
+
render(): TemplateResult;
|
|
63
|
+
}
|
|
64
|
+
declare global {
|
|
65
|
+
interface HTMLElementTagNameMap {
|
|
66
|
+
'lyra-graph': LyraGraph;
|
|
67
|
+
}
|
|
68
|
+
}
|