@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
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
/** A single discrete-preset option for the `presets` property. */
|
|
4
|
+
export interface TimeRangePreset {
|
|
5
|
+
label: string;
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* `<lyra-time-range>` — a two-handle brush/scrubber over a numeric domain.
|
|
5
11
|
* Callers map their own time axis to `[min, max]`; no date logic lives here
|
|
6
12
|
* (matches the no-date-library constraint used elsewhere in this library).
|
|
7
13
|
*
|
|
14
|
+
* Optionally paired with a row of discrete presets (`presets`) — e.g. "Last
|
|
15
|
+
* 7 days" / "Last 30 days" — rendered above the track; picking one is just a
|
|
16
|
+
* shortcut that sets both handles at once, the continuous brush underneath
|
|
17
|
+
* is unaffected and both interaction modes coexist.
|
|
18
|
+
*
|
|
8
19
|
* @customElement lyra-time-range
|
|
9
|
-
* @event lyra-input - Fired continuously while dragging. `detail: { start, end }`.
|
|
10
|
-
* @event lyra-change - Fired on release / keyup-commit. `detail: { start, end }`.
|
|
11
|
-
* @csspart base, track, range, handle-start, handle-end
|
|
20
|
+
* @event lyra-input - Fired continuously while dragging or on each arrow-key press. `detail: { start, end }`.
|
|
21
|
+
* @event lyra-change - Fired on release / keyup-commit, or when a preset button is clicked. `detail: { start, end }`.
|
|
22
|
+
* @csspart base, track, range, handle-start, handle-end, presets, preset-button
|
|
12
23
|
*/
|
|
13
24
|
export declare class LyraTimeRange extends LyraElement {
|
|
14
25
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -18,17 +29,35 @@ export declare class LyraTimeRange extends LyraElement {
|
|
|
18
29
|
end: number;
|
|
19
30
|
step: number;
|
|
20
31
|
disabled: boolean;
|
|
21
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Optional discrete presets rendered as a `[part="presets"]` button row
|
|
34
|
+
* above the track. Purely additive: leaving this empty (the default)
|
|
35
|
+
* renders nothing extra and leaves the continuous brush untouched.
|
|
36
|
+
*/
|
|
37
|
+
presets: TimeRangePreset[];
|
|
38
|
+
private drags;
|
|
22
39
|
disconnectedCallback(): void;
|
|
40
|
+
private domain;
|
|
23
41
|
private percentOf;
|
|
42
|
+
/** Each handle's actual reachable sub-range, bounded by its sibling
|
|
43
|
+
* handle's current value the same way clamp() already enforces it — used
|
|
44
|
+
* both for aria-valuemin/aria-valuemax and for Home/End's jump targets,
|
|
45
|
+
* instead of reporting/jumping to the component's full [min, max] domain
|
|
46
|
+
* which the sibling handle may make partially unreachable. */
|
|
47
|
+
private reachableBounds;
|
|
24
48
|
private clamp;
|
|
25
49
|
private setValue;
|
|
50
|
+
/**
|
|
51
|
+
* Apply a discrete preset: sets both handles and emits the same
|
|
52
|
+
* lyra-input/lyra-change pair a committed drag or keyboard step would.
|
|
53
|
+
*/
|
|
54
|
+
private applyPreset;
|
|
26
55
|
private onKeyDown;
|
|
27
56
|
private onKeyUp;
|
|
28
57
|
private onPointerDown;
|
|
29
58
|
private onPointerMove;
|
|
30
59
|
private onPointerUp;
|
|
31
|
-
/** Stop
|
|
60
|
+
/** Stop the drag owned by `pointerId`, optionally committing a final lyra-change. */
|
|
32
61
|
private endDrag;
|
|
33
62
|
protected willUpdate(changed: PropertyValues): void;
|
|
34
63
|
render(): TemplateResult;
|
|
@@ -4,23 +4,47 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { html } from 'lit';
|
|
7
|
+
import { html, nothing } from 'lit';
|
|
8
8
|
import { property } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { isRtl } from '../../internal/rtl.js';
|
|
11
12
|
import { styles } from './time-range.styles.js';
|
|
12
13
|
function isArrowKey(key) {
|
|
13
14
|
return key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowLeft' || key === 'ArrowDown';
|
|
14
15
|
}
|
|
16
|
+
/** Keys that onKeyDown acts on and onKeyUp commits after — arrow keys plus
|
|
17
|
+
* the WAI-ARIA APG slider pattern's Home/End/PageUp/PageDown shortcuts. */
|
|
18
|
+
function isSliderKey(key) {
|
|
19
|
+
return isArrowKey(key) || key === 'Home' || key === 'End' || key === 'PageUp' || key === 'PageDown';
|
|
20
|
+
}
|
|
21
|
+
/** PageUp/PageDown move by a larger increment than a single ArrowUp/Down
|
|
22
|
+
* step, matching the WAI-ARIA APG slider pattern's expected keyboard
|
|
23
|
+
* interactions (and native `<input type=range>`). */
|
|
24
|
+
const PAGE_STEP_MULTIPLIER = 10;
|
|
25
|
+
/** Number of decimal digits in `n`'s shortest string representation, e.g.
|
|
26
|
+
* `0.1` -> 1, `5` -> 0. Used to round a stepped value back to the precision
|
|
27
|
+
* the caller's `step` itself implies, instead of leaving it at whatever
|
|
28
|
+
* binary floating-point noise `value / step` happened to produce. */
|
|
29
|
+
function decimalPlaces(n) {
|
|
30
|
+
const str = n.toString();
|
|
31
|
+
const dot = str.indexOf('.');
|
|
32
|
+
return dot === -1 ? 0 : str.length - dot - 1;
|
|
33
|
+
}
|
|
15
34
|
/**
|
|
16
35
|
* `<lyra-time-range>` — a two-handle brush/scrubber over a numeric domain.
|
|
17
36
|
* Callers map their own time axis to `[min, max]`; no date logic lives here
|
|
18
37
|
* (matches the no-date-library constraint used elsewhere in this library).
|
|
19
38
|
*
|
|
39
|
+
* Optionally paired with a row of discrete presets (`presets`) — e.g. "Last
|
|
40
|
+
* 7 days" / "Last 30 days" — rendered above the track; picking one is just a
|
|
41
|
+
* shortcut that sets both handles at once, the continuous brush underneath
|
|
42
|
+
* is unaffected and both interaction modes coexist.
|
|
43
|
+
*
|
|
20
44
|
* @customElement lyra-time-range
|
|
21
|
-
* @event lyra-input - Fired continuously while dragging. `detail: { start, end }`.
|
|
22
|
-
* @event lyra-change - Fired on release / keyup-commit. `detail: { start, end }`.
|
|
23
|
-
* @csspart base, track, range, handle-start, handle-end
|
|
45
|
+
* @event lyra-input - Fired continuously while dragging or on each arrow-key press. `detail: { start, end }`.
|
|
46
|
+
* @event lyra-change - Fired on release / keyup-commit, or when a preset button is clicked. `detail: { start, end }`.
|
|
47
|
+
* @csspart base, track, range, handle-start, handle-end, presets, preset-button
|
|
24
48
|
*/
|
|
25
49
|
export class LyraTimeRange extends LyraElement {
|
|
26
50
|
constructor() {
|
|
@@ -31,7 +55,17 @@ export class LyraTimeRange extends LyraElement {
|
|
|
31
55
|
this.end = 100;
|
|
32
56
|
this.step = 1;
|
|
33
57
|
this.disabled = false;
|
|
34
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Optional discrete presets rendered as a `[part="presets"]` button row
|
|
60
|
+
* above the track. Purely additive: leaving this empty (the default)
|
|
61
|
+
* renders nothing extra and leaves the continuous brush untouched.
|
|
62
|
+
*/
|
|
63
|
+
this.presets = [];
|
|
64
|
+
// Keyed by pointerId rather than a single scalar so two concurrent drags
|
|
65
|
+
// (e.g. a two-finger touch, one per handle) each keep tracking their own
|
|
66
|
+
// handle instead of the second pointerdown hijacking which handle the
|
|
67
|
+
// first pointer's subsequent moves apply to.
|
|
68
|
+
this.drags = new Map();
|
|
35
69
|
this.onKeyDown = (handle, e) => {
|
|
36
70
|
// A handle that already has focus when the component becomes disabled
|
|
37
71
|
// must not still respond to arrow keys (new pointerdowns are already
|
|
@@ -41,33 +75,64 @@ export class LyraTimeRange extends LyraElement {
|
|
|
41
75
|
if (this.disabled)
|
|
42
76
|
return;
|
|
43
77
|
const current = handle === 'start' ? this.start : this.end;
|
|
44
|
-
|
|
78
|
+
// Mirror the same left/right swap as onPointerMove: under RTL, physical
|
|
79
|
+
// ArrowRight moves toward inset-inline-start, i.e. a lower value.
|
|
80
|
+
const rtl = isRtl(this);
|
|
81
|
+
const forwardKey = rtl ? 'ArrowLeft' : 'ArrowRight';
|
|
82
|
+
const backwardKey = rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
83
|
+
if (e.key === forwardKey || e.key === 'ArrowUp') {
|
|
45
84
|
e.preventDefault();
|
|
46
85
|
this.setValue(handle, current + this.step, false);
|
|
47
86
|
}
|
|
48
|
-
else if (e.key ===
|
|
87
|
+
else if (e.key === backwardKey || e.key === 'ArrowDown') {
|
|
49
88
|
e.preventDefault();
|
|
50
89
|
this.setValue(handle, current - this.step, false);
|
|
51
90
|
}
|
|
91
|
+
else if (e.key === 'PageUp') {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
this.setValue(handle, current + this.step * PAGE_STEP_MULTIPLIER, false);
|
|
94
|
+
}
|
|
95
|
+
else if (e.key === 'PageDown') {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
this.setValue(handle, current - this.step * PAGE_STEP_MULTIPLIER, false);
|
|
98
|
+
}
|
|
99
|
+
else if (e.key === 'Home') {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
this.setValue(handle, this.reachableBounds(handle).min, false);
|
|
102
|
+
}
|
|
103
|
+
else if (e.key === 'End') {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
this.setValue(handle, this.reachableBounds(handle).max, false);
|
|
106
|
+
}
|
|
52
107
|
};
|
|
53
108
|
this.onKeyUp = (e) => {
|
|
54
|
-
// Only commit on release of the
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
if (this.disabled || !
|
|
109
|
+
// Only commit on release of the keys that onKeyDown acts on — releasing
|
|
110
|
+
// an unrelated key (Tab, Shift, ...) while a handle happens to be
|
|
111
|
+
// focused must not emit a spurious lyra-change.
|
|
112
|
+
if (this.disabled || !isSliderKey(e.key))
|
|
58
113
|
return;
|
|
59
114
|
this.emit('lyra-change', { start: this.start, end: this.end });
|
|
60
115
|
};
|
|
61
116
|
this.onPointerDown = (handle, e) => {
|
|
62
117
|
if (this.disabled)
|
|
63
118
|
return;
|
|
64
|
-
this.
|
|
119
|
+
const base = this.renderRoot.querySelector('[part="base"]');
|
|
120
|
+
this.drags.set(e.pointerId, { handle, base });
|
|
65
121
|
e.target.setPointerCapture(e.pointerId);
|
|
66
122
|
window.addEventListener('pointermove', this.onPointerMove);
|
|
67
123
|
window.addEventListener('pointerup', this.onPointerUp);
|
|
124
|
+
// A drag can end without a pointerup: a system gesture / palm rejection
|
|
125
|
+
// can fire `pointercancel`, and losing capture (e.g. element removed)
|
|
126
|
+
// fires `lostpointercapture` — both need the same teardown as pointerup
|
|
127
|
+
// or `this.drags` keeps a permanently-stale entry and these window
|
|
128
|
+
// listeners (and the closure keeping this instance alive) never get
|
|
129
|
+
// removed. Mirrors lyra-split's identical fix.
|
|
130
|
+
window.addEventListener('pointercancel', this.onPointerUp);
|
|
131
|
+
window.addEventListener('lostpointercapture', this.onPointerUp);
|
|
68
132
|
};
|
|
69
133
|
this.onPointerMove = (e) => {
|
|
70
|
-
|
|
134
|
+
const drag = this.drags.get(e.pointerId);
|
|
135
|
+
if (drag === undefined)
|
|
71
136
|
return;
|
|
72
137
|
if (this.disabled) {
|
|
73
138
|
// These are window-level listeners driven by setPointerCapture, so
|
|
@@ -78,17 +143,20 @@ export class LyraTimeRange extends LyraElement {
|
|
|
78
143
|
// would otherwise keep mutating start/end (and emitting lyra-input)
|
|
79
144
|
// after `disabled` flips true mid-drag. Abort the drag instead of
|
|
80
145
|
// continuing to process it.
|
|
81
|
-
this.endDrag(false);
|
|
146
|
+
this.endDrag(e.pointerId, false);
|
|
82
147
|
return;
|
|
83
148
|
}
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
149
|
+
const rect = drag.base.getBoundingClientRect();
|
|
150
|
+
const raw = (e.clientX - rect.left) / rect.width;
|
|
151
|
+
// The track is positioned with inset-inline-start (0% at the visual
|
|
152
|
+
// right edge under RTL), so the pointer ratio has to mirror that or a
|
|
153
|
+
// rightward drag would move the handle the wrong way.
|
|
154
|
+
const ratio = Math.min(1, Math.max(0, isRtl(this) ? 1 - raw : raw));
|
|
87
155
|
const value = this.min + ratio * (this.max - this.min);
|
|
88
|
-
this.setValue(
|
|
156
|
+
this.setValue(drag.handle, value, false);
|
|
89
157
|
};
|
|
90
|
-
this.onPointerUp = () => {
|
|
91
|
-
this.endDrag(true);
|
|
158
|
+
this.onPointerUp = (e) => {
|
|
159
|
+
this.endDrag(e.pointerId, true);
|
|
92
160
|
};
|
|
93
161
|
}
|
|
94
162
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
@@ -98,16 +166,76 @@ export class LyraTimeRange extends LyraElement {
|
|
|
98
166
|
// pointercancel/alt-tab means `pointerup` never reaches `window`), these
|
|
99
167
|
// window-level listeners — and the closure keeping this instance alive —
|
|
100
168
|
// would otherwise leak indefinitely.
|
|
169
|
+
this.drags.clear();
|
|
101
170
|
window.removeEventListener('pointermove', this.onPointerMove);
|
|
102
171
|
window.removeEventListener('pointerup', this.onPointerUp);
|
|
172
|
+
window.removeEventListener('pointercancel', this.onPointerUp);
|
|
173
|
+
window.removeEventListener('lostpointercapture', this.onPointerUp);
|
|
174
|
+
}
|
|
175
|
+
// Both percentOf() and clamp() key off this same lo/hi pair — willUpdate()
|
|
176
|
+
// already normalizes a caller-supplied `min > max` domain this way, so
|
|
177
|
+
// rendering and interactive dragging must agree with it instead of
|
|
178
|
+
// indexing off this.min/this.max directly (which inverts the visuals and
|
|
179
|
+
// locks dragging to a fixed value when min > max).
|
|
180
|
+
domain() {
|
|
181
|
+
// A caller-supplied min/max that fails Number attribute conversion (e.g.
|
|
182
|
+
// `min="not-a-number"`) arrives here as NaN; Math.min/Math.max would
|
|
183
|
+
// otherwise propagate that NaN into `lo`/`hi` and poison every
|
|
184
|
+
// percentOf()/clamp() caller. Mirror lyra-gauge's `ratio` getter, which
|
|
185
|
+
// short-circuits on isNaN(...) instead, by falling back to this
|
|
186
|
+
// property's own default.
|
|
187
|
+
const min = isNaN(this.min) ? 0 : this.min;
|
|
188
|
+
const max = isNaN(this.max) ? 100 : this.max;
|
|
189
|
+
return { lo: Math.min(min, max), hi: Math.max(min, max) };
|
|
103
190
|
}
|
|
104
191
|
percentOf(value) {
|
|
105
|
-
|
|
106
|
-
|
|
192
|
+
// A NaN `start`/`end` (e.g. an invalid `start` attribute) must not reach
|
|
193
|
+
// the rendered `inset-inline-start:NaN%` — that's an invalid CSS value
|
|
194
|
+
// the browser silently drops, leaving the handle stuck with no visible
|
|
195
|
+
// recovery. 0% is at least a stable, well-defined position.
|
|
196
|
+
if (isNaN(value))
|
|
197
|
+
return 0;
|
|
198
|
+
const { lo, hi } = this.domain();
|
|
199
|
+
const span = hi - lo || 1;
|
|
200
|
+
return ((value - lo) / span) * 100;
|
|
201
|
+
}
|
|
202
|
+
/** Each handle's actual reachable sub-range, bounded by its sibling
|
|
203
|
+
* handle's current value the same way clamp() already enforces it — used
|
|
204
|
+
* both for aria-valuemin/aria-valuemax and for Home/End's jump targets,
|
|
205
|
+
* instead of reporting/jumping to the component's full [min, max] domain
|
|
206
|
+
* which the sibling handle may make partially unreachable. */
|
|
207
|
+
reachableBounds(handle) {
|
|
208
|
+
const { lo, hi } = this.domain();
|
|
209
|
+
// A NaN sibling value (e.g. an invalid `end` attribute) must not leak
|
|
210
|
+
// into the *other* handle's aria-valuemin/aria-valuemax as a literal
|
|
211
|
+
// "NaN" — fall back to the domain bound it would otherwise report.
|
|
212
|
+
const start = isNaN(this.start) ? lo : this.start;
|
|
213
|
+
const end = isNaN(this.end) ? hi : this.end;
|
|
214
|
+
return handle === 'start' ? { min: lo, max: end } : { min: start, max: hi };
|
|
107
215
|
}
|
|
108
216
|
clamp(handle, value) {
|
|
109
|
-
const
|
|
110
|
-
|
|
217
|
+
const { lo, hi } = this.domain();
|
|
218
|
+
// A non-positive or non-finite step (e.g. a transient `step={0}` while a
|
|
219
|
+
// caller derives it as `(max-min)/tickCount`) would otherwise divide by
|
|
220
|
+
// zero/NaN below and permanently poison start/end with NaN, since the
|
|
221
|
+
// other handle's clamp cross-references this one's (already-NaN) value.
|
|
222
|
+
// Treat it as "unstepped" instead of propagating NaN.
|
|
223
|
+
const hasStep = Number.isFinite(this.step) && this.step > 0;
|
|
224
|
+
// Anchor the step grid at the domain's own `lo` (matching native
|
|
225
|
+
// `<input type=range>`) instead of absolute 0 — otherwise a `min` that
|
|
226
|
+
// isn't itself a multiple of `step` makes the very first nudge off `min`
|
|
227
|
+
// jump to the nearest multiple-of-step-from-zero instead of moving by
|
|
228
|
+
// one `step`. Round the result back to `step`'s own decimal precision
|
|
229
|
+
// (rather than leaving raw `value / step` binary-float noise in place)
|
|
230
|
+
// so repeated steps land on exact values like 20.1 instead of
|
|
231
|
+
// 20.200000000000003.
|
|
232
|
+
let stepped = value;
|
|
233
|
+
if (hasStep) {
|
|
234
|
+
const stepsFromLo = Math.round((value - lo) / this.step);
|
|
235
|
+
const factor = 10 ** decimalPlaces(this.step);
|
|
236
|
+
stepped = Math.round((lo + stepsFromLo * this.step) * factor) / factor;
|
|
237
|
+
}
|
|
238
|
+
const bounded = Math.min(hi, Math.max(lo, stepped));
|
|
111
239
|
if (handle === 'start')
|
|
112
240
|
return Math.min(bounded, this.end);
|
|
113
241
|
return Math.max(bounded, this.start);
|
|
@@ -122,13 +250,42 @@ export class LyraTimeRange extends LyraElement {
|
|
|
122
250
|
if (commit)
|
|
123
251
|
this.emit('lyra-change', { start: this.start, end: this.end });
|
|
124
252
|
}
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Apply a discrete preset: sets both handles and emits the same
|
|
255
|
+
* lyra-input/lyra-change pair a committed drag or keyboard step would.
|
|
256
|
+
*/
|
|
257
|
+
applyPreset(preset) {
|
|
258
|
+
if (this.disabled)
|
|
259
|
+
return;
|
|
260
|
+
// clamp('start', v) pins to <= this.end and clamp('end', v) pins to >=
|
|
261
|
+
// this.start, cross-referencing the *other* handle's current value. If
|
|
262
|
+
// the preset shifts the whole range past the old end (or before the old
|
|
263
|
+
// start), applying setValue() in either order straight away would let
|
|
264
|
+
// that cross-reference clip the first handle against the stale sibling
|
|
265
|
+
// before the second call updates it. Widen the working range first —
|
|
266
|
+
// never narrows anything, just guarantees both setValue() calls below
|
|
267
|
+
// land on the preset's exact values regardless of direction.
|
|
268
|
+
this.start = Math.min(this.start, preset.start);
|
|
269
|
+
this.end = Math.max(this.end, preset.end);
|
|
270
|
+
this.setValue('start', preset.start, false);
|
|
271
|
+
this.setValue('end', preset.end, true);
|
|
272
|
+
}
|
|
273
|
+
/** Stop the drag owned by `pointerId`, optionally committing a final lyra-change. */
|
|
274
|
+
endDrag(pointerId, commit) {
|
|
275
|
+
if (!this.drags.has(pointerId))
|
|
276
|
+
return;
|
|
277
|
+
this.drags.delete(pointerId);
|
|
278
|
+
if (commit)
|
|
128
279
|
this.emit('lyra-change', { start: this.start, end: this.end });
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
280
|
+
// Only the last concurrent drag to end tears down the shared window
|
|
281
|
+
// listeners — another pointer (e.g. the other finger of a two-finger
|
|
282
|
+
// drag) may still be down.
|
|
283
|
+
if (this.drags.size === 0) {
|
|
284
|
+
window.removeEventListener('pointermove', this.onPointerMove);
|
|
285
|
+
window.removeEventListener('pointerup', this.onPointerUp);
|
|
286
|
+
window.removeEventListener('pointercancel', this.onPointerUp);
|
|
287
|
+
window.removeEventListener('lostpointercapture', this.onPointerUp);
|
|
288
|
+
}
|
|
132
289
|
}
|
|
133
290
|
willUpdate(changed) {
|
|
134
291
|
if (changed.has('start') ||
|
|
@@ -161,7 +318,26 @@ export class LyraTimeRange extends LyraElement {
|
|
|
161
318
|
render() {
|
|
162
319
|
const startPct = this.percentOf(this.start);
|
|
163
320
|
const endPct = this.percentOf(this.end);
|
|
321
|
+
const startBounds = this.reachableBounds('start');
|
|
322
|
+
const endBounds = this.reachableBounds('end');
|
|
164
323
|
return html `
|
|
324
|
+
${this.presets.length > 0
|
|
325
|
+
? html `<div part="presets">
|
|
326
|
+
${this.presets.map((preset) => {
|
|
327
|
+
const active = preset.start === this.start && preset.end === this.end;
|
|
328
|
+
return html `<button
|
|
329
|
+
part="preset-button"
|
|
330
|
+
type="button"
|
|
331
|
+
?disabled=${this.disabled}
|
|
332
|
+
aria-pressed=${active ? 'true' : 'false'}
|
|
333
|
+
?data-active=${active}
|
|
334
|
+
@click=${() => this.applyPreset(preset)}
|
|
335
|
+
>
|
|
336
|
+
${preset.label}
|
|
337
|
+
</button>`;
|
|
338
|
+
})}
|
|
339
|
+
</div>`
|
|
340
|
+
: nothing}
|
|
165
341
|
<div part="base">
|
|
166
342
|
<div part="track"></div>
|
|
167
343
|
<div
|
|
@@ -173,9 +349,10 @@ export class LyraTimeRange extends LyraElement {
|
|
|
173
349
|
role="slider"
|
|
174
350
|
tabindex=${this.disabled ? '-1' : '0'}
|
|
175
351
|
aria-label="Range start"
|
|
176
|
-
aria-
|
|
177
|
-
aria-
|
|
178
|
-
aria-
|
|
352
|
+
aria-disabled=${this.disabled ? 'true' : nothing}
|
|
353
|
+
aria-valuemin=${startBounds.min}
|
|
354
|
+
aria-valuemax=${startBounds.max}
|
|
355
|
+
aria-valuenow=${isNaN(this.start) ? nothing : this.start}
|
|
179
356
|
style=${`inset-inline-start:${startPct}%`}
|
|
180
357
|
@pointerdown=${(e) => this.onPointerDown('start', e)}
|
|
181
358
|
@keydown=${(e) => this.onKeyDown('start', e)}
|
|
@@ -186,9 +363,10 @@ export class LyraTimeRange extends LyraElement {
|
|
|
186
363
|
role="slider"
|
|
187
364
|
tabindex=${this.disabled ? '-1' : '0'}
|
|
188
365
|
aria-label="Range end"
|
|
189
|
-
aria-
|
|
190
|
-
aria-
|
|
191
|
-
aria-
|
|
366
|
+
aria-disabled=${this.disabled ? 'true' : nothing}
|
|
367
|
+
aria-valuemin=${endBounds.min}
|
|
368
|
+
aria-valuemax=${endBounds.max}
|
|
369
|
+
aria-valuenow=${isNaN(this.end) ? nothing : this.end}
|
|
192
370
|
style=${`inset-inline-start:${endPct}%`}
|
|
193
371
|
@pointerdown=${(e) => this.onPointerDown('end', e)}
|
|
194
372
|
@keydown=${(e) => this.onKeyDown('end', e)}
|
|
@@ -216,4 +394,7 @@ __decorate([
|
|
|
216
394
|
__decorate([
|
|
217
395
|
property({ type: Boolean, reflect: true })
|
|
218
396
|
], LyraTimeRange.prototype, "disabled", void 0);
|
|
397
|
+
__decorate([
|
|
398
|
+
property({ attribute: false })
|
|
399
|
+
], LyraTimeRange.prototype, "presets", void 0);
|
|
219
400
|
defineElement('time-range', LyraTimeRange);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { html } from 'lit';
|
|
2
|
+
const presets = [
|
|
3
|
+
{ label: 'Last 7 days', start: 0, end: 7 },
|
|
4
|
+
{ label: 'Last 30 days', start: 0, end: 30 },
|
|
5
|
+
{ label: 'Last 90 days', start: 0, end: 90 },
|
|
6
|
+
];
|
|
2
7
|
const meta = {
|
|
3
8
|
title: 'TimeRange',
|
|
4
9
|
component: 'lyra-time-range',
|
|
@@ -11,3 +16,11 @@ export const Default = {
|
|
|
11
16
|
export const Disabled = {
|
|
12
17
|
render: () => html `<lyra-time-range min="0" max="100" start="20" end="80" disabled></lyra-time-range>`,
|
|
13
18
|
};
|
|
19
|
+
export const CoarseStep = {
|
|
20
|
+
render: () => html `<lyra-time-range min="0" max="100" start="20" end="80" step="10"></lyra-time-range>`,
|
|
21
|
+
};
|
|
22
|
+
export const DiscretePresets = {
|
|
23
|
+
render: () => html `
|
|
24
|
+
<lyra-time-range min="0" max="90" start="0" end="30" .presets=${presets}></lyra-time-range>
|
|
25
|
+
`,
|
|
26
|
+
};
|
|
@@ -3,12 +3,54 @@ export const styles = css `
|
|
|
3
3
|
:host {
|
|
4
4
|
display: block;
|
|
5
5
|
inline-size: 100%;
|
|
6
|
-
block-size:
|
|
6
|
+
/* No fixed block-size here (unlike before presets existed): [part="base"]
|
|
7
|
+
now carries its own 1.5rem below and the host's block box is just the
|
|
8
|
+
natural stack height of its children. With presets empty this still
|
|
9
|
+
computes to exactly 1.5rem (the one [part="base"] child), so the
|
|
10
|
+
brush-only case renders byte-for-byte the same as before. */
|
|
11
|
+
}
|
|
12
|
+
[part='presets'] {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
gap: var(--lyra-space-xs);
|
|
16
|
+
margin-block-end: var(--lyra-space-s);
|
|
17
|
+
}
|
|
18
|
+
[part='preset-button'] {
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: var(--lyra-space-xs) var(--lyra-space-s);
|
|
22
|
+
border: 1px solid var(--lyra-color-border);
|
|
23
|
+
border-radius: var(--lyra-radius);
|
|
24
|
+
background: var(--lyra-color-surface);
|
|
25
|
+
color: var(--lyra-color-text);
|
|
26
|
+
font: inherit;
|
|
27
|
+
font-size: 0.8125rem;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
transition: var(--lyra-transition-fast);
|
|
30
|
+
}
|
|
31
|
+
[part='preset-button']:hover:not(:disabled) {
|
|
32
|
+
border-color: var(--lyra-color-brand);
|
|
33
|
+
}
|
|
34
|
+
[part='preset-button']:focus-visible {
|
|
35
|
+
outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
|
|
36
|
+
outline-offset: var(--lyra-focus-ring-offset);
|
|
37
|
+
}
|
|
38
|
+
[part='preset-button'][data-active] {
|
|
39
|
+
background: var(--lyra-color-brand);
|
|
40
|
+
border-color: var(--lyra-color-brand);
|
|
41
|
+
color: var(--lyra-color-on-brand);
|
|
42
|
+
}
|
|
43
|
+
[part='preset-button']:disabled {
|
|
44
|
+
/* Dimming already comes from :host([disabled])'s opacity below (applies
|
|
45
|
+
to the whole host, presets row included) — stacking a second opacity
|
|
46
|
+
here would compound multiplicatively and over-dim relative to the
|
|
47
|
+
handles, which only restate the cursor for the same reason. */
|
|
48
|
+
cursor: not-allowed;
|
|
7
49
|
}
|
|
8
50
|
[part='base'] {
|
|
9
51
|
position: relative;
|
|
10
52
|
inline-size: 100%;
|
|
11
|
-
block-size:
|
|
53
|
+
block-size: 1.5rem;
|
|
12
54
|
display: flex;
|
|
13
55
|
align-items: center;
|
|
14
56
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TemplateResult } from 'lit';
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
3
|
export type ToastVariant = 'brand' | 'success' | 'warning' | 'danger' | 'neutral';
|
|
4
4
|
export type ToastSize = 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
@@ -30,15 +30,27 @@ export declare class LyraToastItem extends LyraElement {
|
|
|
30
30
|
/** Show the icon slot. */
|
|
31
31
|
withIcon: boolean;
|
|
32
32
|
private timer?;
|
|
33
|
-
private
|
|
33
|
+
private elapsedMs;
|
|
34
34
|
private startedAt;
|
|
35
|
+
private timerStarted;
|
|
35
36
|
private showRafId?;
|
|
37
|
+
private showAnimTimer?;
|
|
38
|
+
private hideAnimTimer?;
|
|
39
|
+
private hovering;
|
|
40
|
+
private focused;
|
|
41
|
+
private hiding;
|
|
42
|
+
protected updated(changed: PropertyValues): void;
|
|
36
43
|
firstUpdated(): void;
|
|
37
44
|
disconnectedCallback(): void;
|
|
38
45
|
private startTimer;
|
|
39
46
|
private resumeTimer;
|
|
40
47
|
private pauseTimer;
|
|
41
48
|
private clearTimer;
|
|
49
|
+
private onPointerEnter;
|
|
50
|
+
private onPointerLeave;
|
|
51
|
+
private onFocusIn;
|
|
52
|
+
private onFocusOut;
|
|
53
|
+
private get closeLabel();
|
|
42
54
|
/** Hide with animation, then remove from the DOM. */
|
|
43
55
|
hide(): Promise<void>;
|
|
44
56
|
render(): TemplateResult;
|