@aceshooting/lyra-ui 2.0.0 → 2.1.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 +87 -14
- package/custom-elements.json +11555 -5539
- package/dist/components/app-rail/app-rail-item.d.ts +30 -0
- package/dist/components/app-rail/app-rail-item.js +68 -0
- package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
- package/dist/components/app-rail/app-rail-item.styles.js +65 -0
- package/dist/components/app-rail/app-rail.d.ts +21 -24
- package/dist/components/app-rail/app-rail.js +68 -132
- package/dist/components/app-rail/app-rail.styles.js +5 -2
- package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
- package/dist/components/attachment-chip/attachment-chip.js +73 -29
- package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
- package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
- package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
- package/dist/components/chart/bar-chart.js +2 -18
- package/dist/components/chart/box-plot.d.ts +25 -1
- package/dist/components/chart/box-plot.js +136 -6
- package/dist/components/chart/box-plot.styles.js +13 -0
- package/dist/components/chart/bubble-chart.js +2 -18
- package/dist/components/chart/chart.d.ts +54 -10
- package/dist/components/chart/chart.js +182 -39
- package/dist/components/chart/doughnut-chart.js +2 -18
- package/dist/components/chart/histogram-bin.d.ts +5 -2
- package/dist/components/chart/histogram-bin.js +12 -4
- package/dist/components/chart/histogram.js +8 -16
- package/dist/components/chart/line-chart.js +2 -18
- package/dist/components/chart/lite-chart.d.ts +80 -3
- package/dist/components/chart/lite-chart.js +250 -53
- package/dist/components/chart/pie-chart.js +2 -18
- package/dist/components/chart/polar-area-chart.js +2 -18
- package/dist/components/chart/radar-chart.js +2 -18
- package/dist/components/chart/scatter-chart.js +2 -18
- package/dist/components/chat-composer/chat-composer.d.ts +42 -1
- package/dist/components/chat-composer/chat-composer.js +84 -3
- package/dist/components/chat-composer/chat-composer.styles.js +11 -1
- package/dist/components/chat-message/chat-message.d.ts +32 -4
- package/dist/components/chat-message/chat-message.js +57 -17
- package/dist/components/checkbox/checkbox.d.ts +66 -8
- package/dist/components/checkbox/checkbox.js +173 -50
- package/dist/components/checkbox/checkbox.styles.js +9 -2
- package/dist/components/chip/chip-group.js +8 -2
- package/dist/components/chip/chip.js +9 -2
- package/dist/components/citation-badge/citation-badge.js +21 -3
- package/dist/components/code-block/code-block.d.ts +38 -0
- package/dist/components/code-block/code-block.js +72 -7
- package/dist/components/code-block/code-loader.d.ts +44 -1
- package/dist/components/code-block/code-loader.js +63 -0
- package/dist/components/combobox/combobox.d.ts +46 -5
- package/dist/components/combobox/combobox.js +175 -35
- package/dist/components/combobox/combobox.styles.js +3 -3
- package/dist/components/context-meter/context-meter.js +17 -8
- package/dist/components/context-meter/context-meter.styles.js +0 -6
- package/dist/components/conversation-item/conversation-item.d.ts +13 -16
- package/dist/components/conversation-item/conversation-item.js +29 -20
- package/dist/components/date-picker/calendar-core.d.ts +24 -0
- package/dist/components/date-picker/calendar-core.js +51 -4
- package/dist/components/date-picker/date-input.d.ts +77 -10
- package/dist/components/date-picker/date-input.js +278 -73
- package/dist/components/date-picker/date-picker.d.ts +28 -4
- package/dist/components/date-picker/date-picker.js +95 -37
- package/dist/components/dialog/confirm.js +3 -6
- package/dist/components/dialog/dialog.d.ts +75 -31
- package/dist/components/dialog/dialog.js +147 -131
- package/dist/components/dialog/dialog.styles.js +46 -2
- package/dist/components/dock-panel/dock-panel.d.ts +8 -0
- package/dist/components/dock-panel/dock-panel.js +15 -1
- package/dist/components/dock-panel/dock-panel.styles.js +15 -1
- package/dist/components/document-preview/document-preview.d.ts +15 -7
- package/dist/components/document-preview/document-preview.js +29 -13
- package/dist/components/empty/empty.d.ts +1 -2
- package/dist/components/empty/empty.js +18 -12
- package/dist/components/empty/empty.styles.js +0 -3
- package/dist/components/export-button/export-button.d.ts +12 -0
- package/dist/components/export-button/export-button.js +33 -5
- package/dist/components/export-button/export-button.styles.js +5 -1
- package/dist/components/file-input/accept.js +2 -0
- package/dist/components/file-input/file-input.d.ts +10 -1
- package/dist/components/file-input/file-input.js +36 -5
- package/dist/components/flag/flag.d.ts +1 -1
- package/dist/components/flag/flag.js +15 -1
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +4 -1
- package/dist/components/flag/language-map.js +6 -3
- package/dist/components/gauge/gauge.d.ts +10 -1
- package/dist/components/gauge/gauge.js +28 -9
- package/dist/components/generation-status/generation-status.d.ts +13 -5
- package/dist/components/generation-status/generation-status.js +59 -5
- package/dist/components/graph/graph.d.ts +37 -3
- package/dist/components/graph/graph.js +132 -20
- package/dist/components/heatmap/calendar-grid.d.ts +15 -6
- package/dist/components/heatmap/calendar-grid.js +17 -6
- package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
- package/dist/components/heatmap/heatmap-scale.js +8 -2
- package/dist/components/heatmap/heatmap.d.ts +166 -25
- package/dist/components/heatmap/heatmap.js +372 -89
- package/dist/components/json-viewer/json-viewer.d.ts +22 -5
- package/dist/components/json-viewer/json-viewer.js +115 -37
- package/dist/components/json-viewer/json-viewer.styles.js +2 -1
- package/dist/components/kbd/kbd.js +24 -7
- package/dist/components/live-region/live-region.d.ts +10 -1
- package/dist/components/live-region/live-region.js +42 -6
- package/dist/components/map/map.d.ts +24 -1
- package/dist/components/map/map.js +89 -24
- package/dist/components/markdown/markdown-loader.d.ts +24 -0
- package/dist/components/markdown/markdown-loader.js +35 -1
- package/dist/components/markdown/markdown.d.ts +46 -8
- package/dist/components/markdown/markdown.js +136 -31
- package/dist/components/markdown/markdown.styles.js +7 -1
- package/dist/components/media-card/media-card.d.ts +25 -17
- package/dist/components/media-card/media-card.js +43 -65
- package/dist/components/mention-popover/mention-popover.d.ts +4 -1
- package/dist/components/mention-popover/mention-popover.js +23 -5
- package/dist/components/menu/menu-item.d.ts +32 -3
- package/dist/components/menu/menu-item.js +94 -7
- package/dist/components/menu/menu-item.styles.js +8 -0
- package/dist/components/menu/menu.d.ts +25 -1
- package/dist/components/menu/menu.js +99 -3
- package/dist/components/menu/menu.styles.js +3 -2
- package/dist/components/model-select/model-select.d.ts +48 -3
- package/dist/components/model-select/model-select.js +120 -22
- package/dist/components/model-select/model-select.styles.js +17 -3
- package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
- package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +72 -29
- package/dist/components/playback/playback.styles.js +6 -1
- package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
- package/dist/components/responsive-panel/responsive-panel.js +130 -132
- package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
- package/dist/components/result-card/result-card.d.ts +8 -2
- package/dist/components/result-card/result-card.js +9 -3
- package/dist/components/result-card/result-card.styles.js +0 -1
- package/dist/components/select/select.d.ts +59 -20
- package/dist/components/select/select.js +125 -43
- package/dist/components/skeleton/skeleton.d.ts +3 -0
- package/dist/components/skeleton/skeleton.js +7 -1
- package/dist/components/skeleton/skeleton.styles.js +5 -4
- package/dist/components/slider/slider.d.ts +57 -5
- package/dist/components/slider/slider.js +195 -80
- package/dist/components/source-card/source-card.d.ts +17 -5
- package/dist/components/source-card/source-card.js +55 -8
- package/dist/components/source-card/source-card.styles.js +1 -1
- package/dist/components/source-list/source-list.js +9 -1
- package/dist/components/sparkline/sparkline.js +34 -10
- package/dist/components/split/split.d.ts +141 -14
- package/dist/components/split/split.js +357 -52
- package/dist/components/split/split.styles.js +18 -1
- package/dist/components/stat/stat.d.ts +10 -4
- package/dist/components/stat/stat.js +46 -11
- package/dist/components/stream-status/stream-status.d.ts +27 -11
- package/dist/components/stream-status/stream-status.js +95 -13
- package/dist/components/streaming-text/streaming-text.d.ts +9 -3
- package/dist/components/streaming-text/streaming-text.js +25 -10
- package/dist/components/streaming-text/streaming-text.styles.js +3 -2
- package/dist/components/switch/switch.d.ts +52 -7
- package/dist/components/switch/switch.js +111 -40
- package/dist/components/switch/switch.styles.js +1 -1
- package/dist/components/table/table.d.ts +18 -3
- package/dist/components/table/table.js +88 -20
- package/dist/components/tabs/tabs.d.ts +23 -0
- package/dist/components/tabs/tabs.js +76 -12
- package/dist/components/tabs/tabs.styles.js +11 -0
- package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
- package/dist/components/thinking-panel/thinking-panel.js +34 -9
- package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
- package/dist/components/time-range/time-range.d.ts +59 -2
- package/dist/components/time-range/time-range.js +163 -67
- package/dist/components/toast/toast-item.d.ts +12 -3
- package/dist/components/toast/toast-item.js +90 -53
- package/dist/components/toast/toast-item.styles.js +59 -28
- package/dist/components/toast/toast.js +17 -5
- package/dist/components/toast/toaster.js +10 -1
- package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
- package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
- package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
- package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
- package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
- package/dist/components/tool-param-form/tool-param-form.js +350 -106
- package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
- package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
- package/dist/components/tool-result-view/registry.d.ts +0 -8
- package/dist/components/tool-result-view/registry.js +7 -2
- package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
- package/dist/components/tool-result-view/tool-result-view.js +18 -4
- package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
- package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
- package/dist/components/tree/tree-node.d.ts +4 -1
- package/dist/components/tree/tree-node.js +4 -1
- package/dist/components/tree/tree.d.ts +17 -8
- package/dist/components/tree/tree.js +21 -20
- package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
- package/dist/components/typing-indicator/typing-indicator.js +16 -4
- package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
- package/dist/components/virtual-list/virtual-list.d.ts +56 -19
- package/dist/components/virtual-list/virtual-list.js +184 -38
- package/dist/components/virtual-list/virtual-list.styles.js +12 -0
- package/dist/components/widget/widget.d.ts +5 -4
- package/dist/components/widget/widget.js +54 -120
- package/dist/components/widget/widget.styles.js +8 -2
- package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
- package/dist/components/word-cloud/word-cloud-layout.js +20 -6
- package/dist/components/word-cloud/word-cloud.d.ts +22 -2
- package/dist/components/word-cloud/word-cloud.js +45 -6
- package/dist/components/word-cloud/word-cloud.styles.js +13 -1
- package/dist/internal/anchored-validity.d.ts +1 -0
- package/dist/internal/anchored-validity.js +86 -0
- package/dist/internal/form-associated.d.ts +7 -0
- package/dist/internal/form-associated.js +43 -4
- package/dist/internal/numbers.d.ts +12 -0
- package/dist/internal/numbers.js +26 -0
- package/dist/internal/overlay-manager.d.ts +51 -0
- package/dist/internal/overlay-manager.js +523 -0
- package/dist/internal/positioner.js +27 -3
- package/dist/internal/safe-url.d.ts +12 -0
- package/dist/internal/safe-url.js +41 -0
- package/dist/internal/tokens.styles.js +17 -11
- package/dist/lyra.d.ts +5 -2
- package/dist/lyra.js +3 -1
- package/llms-full.txt +970 -426
- package/llms.txt +5 -4
- package/package.json +19 -2
|
@@ -10,6 +10,7 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
10
10
|
import { FormAssociated } from '../../internal/form-associated.js';
|
|
11
11
|
import { defineElement } from '../../internal/prefix.js';
|
|
12
12
|
import { isRtl } from '../../internal/rtl.js';
|
|
13
|
+
import { finiteNumber, finiteRange } from '../../internal/numbers.js';
|
|
13
14
|
import { styles } from './slider.styles.js';
|
|
14
15
|
/** PageUp/PageDown move by a larger increment than a single Arrow step,
|
|
15
16
|
* matching the WAI-ARIA APG slider pattern's expected keyboard interactions
|
|
@@ -23,15 +24,16 @@ function isArrowKey(key) {
|
|
|
23
24
|
function isSliderKey(key) {
|
|
24
25
|
return isArrowKey(key) || key === 'Home' || key === 'End' || key === 'PageUp' || key === 'PageDown';
|
|
25
26
|
}
|
|
26
|
-
/** Number of decimal
|
|
27
|
-
* `0.1` -> 1, `5` -> 0. Used to round a stepped value back
|
|
28
|
-
* `step` itself implies, instead of leaving binary
|
|
29
|
-
*
|
|
30
|
-
* helper. */
|
|
27
|
+
/** Number of decimal places implied by `n`, including exponent notation.
|
|
28
|
+
* `0.1` -> 1, `5` -> 0, and `1e-7` -> 7. Used to round a stepped value back
|
|
29
|
+
* to the precision `step` itself implies, instead of leaving binary
|
|
30
|
+
* floating-point noise in place. */
|
|
31
31
|
function decimalPlaces(n) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (!Number.isFinite(n) || n === 0)
|
|
33
|
+
return 0;
|
|
34
|
+
const [mantissa, exponentText] = n.toExponential().split('e');
|
|
35
|
+
const mantissaPlaces = mantissa.includes('.') ? mantissa.length - mantissa.indexOf('.') - 1 : 0;
|
|
36
|
+
return Math.max(0, mantissaPlaces - Number(exponentText));
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* `<lyra-slider>` — a numeric range control (e.g. an LLM "temperature"
|
|
@@ -49,6 +51,10 @@ function decimalPlaces(n) {
|
|
|
49
51
|
* to block submission before that default lands, matching how `required`
|
|
50
52
|
* isn't a meaningful constraint on a native range input either.
|
|
51
53
|
*
|
|
54
|
+
* Clicking anywhere on the track (not just the 16px thumb) jumps the thumb
|
|
55
|
+
* to that point and continues the same gesture as a drag, matching native
|
|
56
|
+
* `<input type=range>` click-to-seek.
|
|
57
|
+
*
|
|
52
58
|
* @customElement lyra-slider
|
|
53
59
|
* @event lyra-input - Fired continuously during an active drag or a
|
|
54
60
|
* keyboard step (including OS key-repeat while a key is held), mirroring
|
|
@@ -67,9 +73,9 @@ function decimalPlaces(n) {
|
|
|
67
73
|
export class LyraSlider extends FormAssociated(LyraElement) {
|
|
68
74
|
constructor() {
|
|
69
75
|
super(...arguments);
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
72
|
-
this.
|
|
76
|
+
this._min = 0;
|
|
77
|
+
this._max = 100;
|
|
78
|
+
this._step = 1;
|
|
73
79
|
/** Accessible name for the slider, used when no visible label context
|
|
74
80
|
* exists around it (e.g. no wrapping `<label>` or adjacent heading). Set
|
|
75
81
|
* as `aria-label` on the interactive `role="slider"` element. A plain
|
|
@@ -87,6 +93,8 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
87
93
|
// multi-touch/robustness parity with lyra-split and lyra-time-range, even
|
|
88
94
|
// though a single thumb has no per-pointer state of its own to track.
|
|
89
95
|
this.activePointers = new Set();
|
|
96
|
+
this.changedPointers = new Set();
|
|
97
|
+
this.keyboardChanged = false;
|
|
90
98
|
this.onKeyDown = (e) => {
|
|
91
99
|
if (this.effectiveDisabled)
|
|
92
100
|
return;
|
|
@@ -101,27 +109,29 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
101
109
|
const backwardKey = rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
102
110
|
if (e.key === forwardKey || e.key === 'ArrowUp') {
|
|
103
111
|
e.preventDefault();
|
|
104
|
-
this.setValue(current + this.step, false);
|
|
112
|
+
this.keyboardChanged = this.setValue(current + this.step, false) || this.keyboardChanged;
|
|
105
113
|
}
|
|
106
114
|
else if (e.key === backwardKey || e.key === 'ArrowDown') {
|
|
107
115
|
e.preventDefault();
|
|
108
|
-
this.setValue(current - this.step, false);
|
|
116
|
+
this.keyboardChanged = this.setValue(current - this.step, false) || this.keyboardChanged;
|
|
109
117
|
}
|
|
110
118
|
else if (e.key === 'PageUp') {
|
|
111
119
|
e.preventDefault();
|
|
112
|
-
this.
|
|
120
|
+
this.keyboardChanged =
|
|
121
|
+
this.setValue(current + this.step * PAGE_STEP_MULTIPLIER, false) || this.keyboardChanged;
|
|
113
122
|
}
|
|
114
123
|
else if (e.key === 'PageDown') {
|
|
115
124
|
e.preventDefault();
|
|
116
|
-
this.
|
|
125
|
+
this.keyboardChanged =
|
|
126
|
+
this.setValue(current - this.step * PAGE_STEP_MULTIPLIER, false) || this.keyboardChanged;
|
|
117
127
|
}
|
|
118
128
|
else if (e.key === 'Home') {
|
|
119
129
|
e.preventDefault();
|
|
120
|
-
this.setValue(this.domain().lo, false);
|
|
130
|
+
this.keyboardChanged = this.setValue(this.domain().lo, false) || this.keyboardChanged;
|
|
121
131
|
}
|
|
122
132
|
else if (e.key === 'End') {
|
|
123
133
|
e.preventDefault();
|
|
124
|
-
this.setValue(this.domain().hi, false);
|
|
134
|
+
this.keyboardChanged = this.setValue(this.domain().hi, false) || this.keyboardChanged;
|
|
125
135
|
}
|
|
126
136
|
};
|
|
127
137
|
this.onKeyUp = (e) => {
|
|
@@ -135,20 +145,46 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
135
145
|
// shape a pointer drag has.
|
|
136
146
|
if (this.effectiveDisabled || !isSliderKey(e.key))
|
|
137
147
|
return;
|
|
138
|
-
this.
|
|
148
|
+
if (this.keyboardChanged) {
|
|
149
|
+
this.emit('lyra-change', { value: this.valueAsNumber });
|
|
150
|
+
this.keyboardChanged = false;
|
|
151
|
+
}
|
|
139
152
|
};
|
|
140
153
|
this.onPointerDown = (e) => {
|
|
141
154
|
if (this.effectiveDisabled)
|
|
142
155
|
return;
|
|
143
|
-
this.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
this.beginDrag(e.pointerId, e.target);
|
|
157
|
+
};
|
|
158
|
+
/** A pointerdown anywhere on `[part="base"]` other than the thumb itself
|
|
159
|
+
* (the vast majority of the control's clickable area) jumps the thumb to
|
|
160
|
+
* that point and continues the same gesture as a drag, mirroring native
|
|
161
|
+
* `<input type=range>` click-to-seek. A pointerdown that started on the
|
|
162
|
+
* thumb bubbles up to this same listener too — already fully handled by
|
|
163
|
+
* `onPointerDown` above, so it's ignored here via the `e.target === thumb`
|
|
164
|
+
* check. Unlike the two-handle `lyra-time-range` (where a track click is
|
|
165
|
+
* ambiguous about which handle should move), a single thumb has no such
|
|
166
|
+
* ambiguity. */
|
|
167
|
+
this.onBasePointerDown = (e) => {
|
|
168
|
+
if (this.effectiveDisabled)
|
|
169
|
+
return;
|
|
170
|
+
const thumb = this.renderRoot.querySelector('[part="thumb"]');
|
|
171
|
+
if (!thumb || e.target === thumb)
|
|
172
|
+
return;
|
|
173
|
+
const track = this.renderRoot.querySelector('[part="track"]');
|
|
174
|
+
if (!track)
|
|
175
|
+
return;
|
|
176
|
+
const rect = track.getBoundingClientRect();
|
|
177
|
+
const raw = rect.width === 0 ? 0 : (e.clientX - rect.left) / rect.width;
|
|
178
|
+
// Mirrors onPointerMove's own RTL handling below.
|
|
179
|
+
const ratio = Math.min(1, Math.max(0, isRtl(this) ? 1 - raw : raw));
|
|
180
|
+
const { lo, hi } = this.domain();
|
|
181
|
+
if (this.setValue(lo + ratio * (hi - lo), false))
|
|
182
|
+
this.changedPointers.add(e.pointerId);
|
|
183
|
+
this.beginDrag(e.pointerId, thumb);
|
|
184
|
+
// Keyboard interaction (arrow keys, Home/End, ...) can continue
|
|
185
|
+
// seamlessly right after the click, exactly as if the user had tabbed to
|
|
186
|
+
// the thumb and started dragging it directly.
|
|
187
|
+
thumb.focus();
|
|
152
188
|
};
|
|
153
189
|
this.onPointerMove = (e) => {
|
|
154
190
|
if (!this.activePointers.has(e.pointerId))
|
|
@@ -172,16 +208,63 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
172
208
|
// rightward drag would move the thumb the wrong way.
|
|
173
209
|
const ratio = Math.min(1, Math.max(0, isRtl(this) ? 1 - raw : raw));
|
|
174
210
|
const { lo, hi } = this.domain();
|
|
175
|
-
this.setValue(lo + ratio * (hi - lo), false)
|
|
211
|
+
if (this.setValue(lo + ratio * (hi - lo), false))
|
|
212
|
+
this.changedPointers.add(e.pointerId);
|
|
176
213
|
};
|
|
177
214
|
this.onPointerUp = (e) => {
|
|
178
215
|
this.endDrag(e.pointerId, true);
|
|
179
216
|
};
|
|
180
217
|
}
|
|
181
218
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
219
|
+
// These accessors sanitize the live value synchronously when a range
|
|
220
|
+
// setting changes. Keeping the properties `noAccessor` prevents Lit's
|
|
221
|
+
// default async field setter from leaving `.value`, `.valueAsNumber`, and
|
|
222
|
+
// ElementInternals' form value disagreeing until the next update flush.
|
|
223
|
+
static { this.properties = {
|
|
224
|
+
min: { type: Number, noAccessor: true },
|
|
225
|
+
max: { type: Number, noAccessor: true },
|
|
226
|
+
step: { type: Number, noAccessor: true },
|
|
227
|
+
}; }
|
|
228
|
+
get min() {
|
|
229
|
+
return this._min;
|
|
230
|
+
}
|
|
231
|
+
set min(next) {
|
|
232
|
+
const old = this._min;
|
|
233
|
+
this._min = finiteNumber(next, 0);
|
|
234
|
+
this.requestUpdate('min', old);
|
|
235
|
+
this.sanitizeCurrentValue();
|
|
236
|
+
}
|
|
237
|
+
get max() {
|
|
238
|
+
return this._max;
|
|
239
|
+
}
|
|
240
|
+
set max(next) {
|
|
241
|
+
const old = this._max;
|
|
242
|
+
this._max = finiteNumber(next, 100);
|
|
243
|
+
this.requestUpdate('max', old);
|
|
244
|
+
this.sanitizeCurrentValue();
|
|
245
|
+
}
|
|
246
|
+
get step() {
|
|
247
|
+
return this._step;
|
|
248
|
+
}
|
|
249
|
+
set step(next) {
|
|
250
|
+
const old = this._step;
|
|
251
|
+
// A zero/negative step is retained as an explicit "unstepped" mode;
|
|
252
|
+
// invalid/non-finite input follows the same safe path without poisoning
|
|
253
|
+
// the current value with NaN.
|
|
254
|
+
this._step = finiteRange(next, 0, 0);
|
|
255
|
+
this.requestUpdate('step', old);
|
|
256
|
+
this.sanitizeCurrentValue();
|
|
257
|
+
}
|
|
182
258
|
connectedCallback() {
|
|
183
259
|
super.connectedCallback();
|
|
184
|
-
this.
|
|
260
|
+
if (this.pendingValue !== undefined) {
|
|
261
|
+
const pending = this.pendingValue;
|
|
262
|
+
this.pendingValue = undefined;
|
|
263
|
+
this.value = pending;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
this.ensureValue();
|
|
267
|
+
}
|
|
185
268
|
}
|
|
186
269
|
disconnectedCallback() {
|
|
187
270
|
super.disconnectedCallback();
|
|
@@ -189,6 +272,8 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
189
272
|
// mid-drag (or a pointercancel/alt-tab means pointerup never reaches
|
|
190
273
|
// `window`), these window-level listeners would otherwise leak.
|
|
191
274
|
this.activePointers.clear();
|
|
275
|
+
this.changedPointers.clear();
|
|
276
|
+
this.keyboardChanged = false;
|
|
192
277
|
window.removeEventListener('pointermove', this.onPointerMove);
|
|
193
278
|
window.removeEventListener('pointerup', this.onPointerUp);
|
|
194
279
|
window.removeEventListener('pointercancel', this.onPointerUp);
|
|
@@ -201,13 +286,36 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
201
286
|
* to the midpoint of `[min, max]`. Writing stringifies the clamped result
|
|
202
287
|
* back into `value`. */
|
|
203
288
|
get valueAsNumber() {
|
|
204
|
-
if (
|
|
289
|
+
if (super.value === '')
|
|
205
290
|
return this.defaultNumericValue();
|
|
206
|
-
const n = Number(
|
|
291
|
+
const n = Number(super.value);
|
|
207
292
|
return Number.isFinite(n) ? this.clampValue(n) : this.defaultNumericValue();
|
|
208
293
|
}
|
|
209
294
|
set valueAsNumber(next) {
|
|
210
|
-
this.value = String(this.clampValue(next));
|
|
295
|
+
this.value = String(Number.isFinite(next) ? this.clampValue(next) : this.defaultNumericValue());
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* `FormAssociated` provides the form plumbing; this override adds the
|
|
299
|
+
* slider's native-range sanitization at the IDL boundary so invalid direct
|
|
300
|
+
* assignments cannot briefly submit a literal `NaN`/`Infinity`.
|
|
301
|
+
*/
|
|
302
|
+
get value() {
|
|
303
|
+
return super.value;
|
|
304
|
+
}
|
|
305
|
+
set value(next) {
|
|
306
|
+
const raw = next ?? '';
|
|
307
|
+
if (!this.isConnected) {
|
|
308
|
+
this.pendingValue = raw;
|
|
309
|
+
super.value = raw;
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const numeric = Number(raw);
|
|
313
|
+
const sanitized = raw === '' || !Number.isFinite(numeric) ? this.defaultNumericValue() : this.clampValue(numeric);
|
|
314
|
+
super.value = String(sanitized);
|
|
315
|
+
}
|
|
316
|
+
formResetCallback() {
|
|
317
|
+
super.formResetCallback();
|
|
318
|
+
this.value = String(this.valueAsNumber);
|
|
211
319
|
}
|
|
212
320
|
/** If `value` is still unset, seed it with the sanitized default — the
|
|
213
321
|
* midpoint of `[min, max]`, snapped to `step` — so `value`/`valueAsNumber`
|
|
@@ -216,12 +324,26 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
216
324
|
if (this.value === '')
|
|
217
325
|
this.value = String(this.defaultNumericValue());
|
|
218
326
|
}
|
|
327
|
+
/** Re-sanitize an already assigned value immediately after range settings change. */
|
|
328
|
+
sanitizeCurrentValue() {
|
|
329
|
+
if (!this.isConnected)
|
|
330
|
+
return;
|
|
331
|
+
const current = super.value;
|
|
332
|
+
if (current === '')
|
|
333
|
+
return;
|
|
334
|
+
const sanitized = String(this.clampValue(Number(current)));
|
|
335
|
+
if (sanitized !== current)
|
|
336
|
+
this.value = sanitized;
|
|
337
|
+
}
|
|
219
338
|
domain() {
|
|
220
339
|
// A caller-supplied min/max that fails Number attribute conversion
|
|
221
|
-
// arrives here as NaN
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
340
|
+
// arrives here as NaN, and a literal `min="Infinity"`/`max="Infinity"`
|
|
341
|
+
// arrives as +-Infinity; `isNaN(...)` alone only catches the former, so
|
|
342
|
+
// test finiteness instead -- otherwise Infinity propagates into every
|
|
343
|
+
// clampValue()/percentOf() caller (e.g. the midpoint default computing
|
|
344
|
+
// `0 + Infinity / 2`).
|
|
345
|
+
const min = finiteNumber(this.min, 0);
|
|
346
|
+
const max = finiteNumber(this.max, 100);
|
|
225
347
|
return { lo: Math.min(min, max), hi: Math.max(min, max) };
|
|
226
348
|
}
|
|
227
349
|
defaultNumericValue() {
|
|
@@ -229,26 +351,32 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
229
351
|
return this.clampValue(lo + (hi - lo) / 2);
|
|
230
352
|
}
|
|
231
353
|
percentOf(value) {
|
|
232
|
-
if (isNaN(value))
|
|
233
|
-
return 0;
|
|
234
354
|
const { lo, hi } = this.domain();
|
|
355
|
+
const safeValue = finiteRange(value, lo, lo, hi);
|
|
235
356
|
const span = hi - lo || 1;
|
|
236
|
-
return ((
|
|
357
|
+
return ((safeValue - lo) / span) * 100;
|
|
237
358
|
}
|
|
238
359
|
clampValue(raw) {
|
|
239
360
|
const { lo, hi } = this.domain();
|
|
361
|
+
// A NaN/Infinity `raw` (e.g. `valueAsNumber = NaN`, or a `value` string
|
|
362
|
+
// that fails Number conversion) would otherwise propagate straight
|
|
363
|
+
// through the Math.round/Math.max/Math.min calls below and poison the
|
|
364
|
+
// submitted FormAssociated value with the literal "NaN"/"Infinity" —
|
|
365
|
+
// resolve it to a real, finite, in-domain number instead.
|
|
366
|
+
raw = finiteNumber(raw, lo);
|
|
240
367
|
// A non-positive or non-finite step would otherwise divide by zero/NaN
|
|
241
368
|
// below; treat it as "unstepped" instead of propagating NaN.
|
|
242
|
-
const
|
|
369
|
+
const step = finiteRange(this.step, 0, 0);
|
|
370
|
+
const hasStep = step > 0;
|
|
243
371
|
let stepped = raw;
|
|
244
372
|
if (hasStep) {
|
|
245
373
|
// Anchor the step grid at the domain's own `lo` (matching native
|
|
246
374
|
// `<input type=range>`) instead of absolute 0, and round back to
|
|
247
375
|
// `step`'s own decimal precision so repeated steps land on exact
|
|
248
376
|
// values like 0.7 instead of 0.7000000000000001.
|
|
249
|
-
const stepsFromLo = Math.round((raw - lo) /
|
|
250
|
-
const factor = 10 ** decimalPlaces(
|
|
251
|
-
stepped = Math.round((lo + stepsFromLo *
|
|
377
|
+
const stepsFromLo = Math.round((raw - lo) / step);
|
|
378
|
+
const factor = 10 ** decimalPlaces(step);
|
|
379
|
+
stepped = Math.round((lo + stepsFromLo * step) * factor) / factor;
|
|
252
380
|
}
|
|
253
381
|
return Math.min(hi, Math.max(lo, stepped));
|
|
254
382
|
}
|
|
@@ -256,18 +384,40 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
256
384
|
return String(v);
|
|
257
385
|
}
|
|
258
386
|
setValue(raw, commit) {
|
|
387
|
+
const previous = this.valueAsNumber;
|
|
259
388
|
const clamped = this.clampValue(raw);
|
|
389
|
+
if (clamped === previous)
|
|
390
|
+
return false;
|
|
260
391
|
this.value = String(clamped);
|
|
261
392
|
this.emit('lyra-input', { value: clamped });
|
|
262
393
|
if (commit)
|
|
263
394
|
this.emit('lyra-change', { value: clamped });
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
/** Start tracking `pointerId` as an active drag, transferring pointer
|
|
398
|
+
* capture to `captureTarget` and wiring the shared window-level move/end
|
|
399
|
+
* listeners. Shared by a pointerdown that starts on the thumb itself and
|
|
400
|
+
* one that starts elsewhere on the track (see `onBasePointerDown`), so
|
|
401
|
+
* both gestures continue identically from here on. */
|
|
402
|
+
beginDrag(pointerId, captureTarget) {
|
|
403
|
+
this.activePointers.add(pointerId);
|
|
404
|
+
this.changedPointers.delete(pointerId);
|
|
405
|
+
captureTarget.setPointerCapture(pointerId);
|
|
406
|
+
window.addEventListener('pointermove', this.onPointerMove);
|
|
407
|
+
window.addEventListener('pointerup', this.onPointerUp);
|
|
408
|
+
// A drag can end without a pointerup: a system gesture / palm rejection
|
|
409
|
+
// can fire `pointercancel`, and losing capture (e.g. element removed)
|
|
410
|
+
// fires `lostpointercapture` — both need the same teardown as pointerup.
|
|
411
|
+
window.addEventListener('pointercancel', this.onPointerUp);
|
|
412
|
+
window.addEventListener('lostpointercapture', this.onPointerUp);
|
|
264
413
|
}
|
|
265
414
|
/** Stop the drag owned by `pointerId`, optionally committing a final lyra-change. */
|
|
266
415
|
endDrag(pointerId, commit) {
|
|
267
416
|
if (!this.activePointers.has(pointerId))
|
|
268
417
|
return;
|
|
269
418
|
this.activePointers.delete(pointerId);
|
|
270
|
-
|
|
419
|
+
const changed = this.changedPointers.delete(pointerId);
|
|
420
|
+
if (commit && changed)
|
|
271
421
|
this.emit('lyra-change', { value: this.valueAsNumber });
|
|
272
422
|
// Only the last concurrent drag to end tears down the shared window
|
|
273
423
|
// listeners — an overlapping second pointer may still be down.
|
|
@@ -278,32 +428,6 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
278
428
|
window.removeEventListener('lostpointercapture', this.onPointerUp);
|
|
279
429
|
}
|
|
280
430
|
}
|
|
281
|
-
willUpdate(changed) {
|
|
282
|
-
// `formResetCallback` (inherited from `FormAssociated`, restoring
|
|
283
|
-
// `value` to whatever default the mixin captured — "" when no `value`
|
|
284
|
-
// attribute was ever declared) isn't part of the mixin's exported
|
|
285
|
-
// `FormAssociatedInterface` type, so it can't be overridden here with a
|
|
286
|
-
// `super` call the way `connectedCallback`/`disconnectedCallback` above
|
|
287
|
-
// are. Catching the resulting empty `value` here instead, in the same
|
|
288
|
-
// pre-render pass that already re-clamps a changed min/max/step below,
|
|
289
|
-
// covers `form.reset()` the same way `connectedCallback` covers first
|
|
290
|
-
// mount — and doing it in `willUpdate` (not `updated`) folds the fix-up
|
|
291
|
-
// into this same update cycle instead of scheduling a second one.
|
|
292
|
-
if (changed.has('value') && this.value === '') {
|
|
293
|
-
this.ensureValue();
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
if ((changed.has('min') || changed.has('max') || changed.has('step')) && this.value !== '') {
|
|
297
|
-
// A narrowed/shifted domain (or a changed step grid) after mount must
|
|
298
|
-
// not leave `value` — and the rendered thumb position it drives —
|
|
299
|
-
// outside the current bounds or off the step grid. `min`/`max`/`step`
|
|
300
|
-
// already hold their new values by this point, so the getter itself
|
|
301
|
-
// (which clamps against the live domain) computes the corrected value.
|
|
302
|
-
const next = String(this.valueAsNumber);
|
|
303
|
-
if (next !== this.value)
|
|
304
|
-
this.value = next;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
431
|
render() {
|
|
308
432
|
const num = this.valueAsNumber;
|
|
309
433
|
const pct = this.percentOf(num);
|
|
@@ -311,7 +435,7 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
311
435
|
const { lo, hi } = this.domain();
|
|
312
436
|
const ariaLabel = this.label || this.getAttribute('aria-label') || nothing;
|
|
313
437
|
return html `
|
|
314
|
-
<div part="base">
|
|
438
|
+
<div part="base" @pointerdown=${this.onBasePointerDown}>
|
|
315
439
|
<div part="track"></div>
|
|
316
440
|
<div part="fill" style=${`inline-size:${pct}%`}></div>
|
|
317
441
|
<div
|
|
@@ -334,15 +458,6 @@ export class LyraSlider extends FormAssociated(LyraElement) {
|
|
|
334
458
|
`;
|
|
335
459
|
}
|
|
336
460
|
}
|
|
337
|
-
__decorate([
|
|
338
|
-
property({ type: Number })
|
|
339
|
-
], LyraSlider.prototype, "min", void 0);
|
|
340
|
-
__decorate([
|
|
341
|
-
property({ type: Number })
|
|
342
|
-
], LyraSlider.prototype, "max", void 0);
|
|
343
|
-
__decorate([
|
|
344
|
-
property({ type: Number })
|
|
345
|
-
], LyraSlider.prototype, "step", void 0);
|
|
346
461
|
__decorate([
|
|
347
462
|
property()
|
|
348
463
|
], LyraSlider.prototype, "label", void 0);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TemplateResult } from 'lit';
|
|
1
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
3
|
export interface SourceCardExpandDetail {
|
|
4
4
|
sourceId: string;
|
|
@@ -24,10 +24,12 @@ export interface SourceCardOpenDetail {
|
|
|
24
24
|
* card's `id`/`source-id`. See the `@example` below.
|
|
25
25
|
*
|
|
26
26
|
* @customElement lyra-source-card
|
|
27
|
-
* @slot excerpt - A short preview,
|
|
27
|
+
* @slot excerpt - A short preview. When left empty, the `excerpt` part
|
|
28
|
+
* collapses away entirely rather than leaving an empty gap in the card.
|
|
28
29
|
* @slot full - The complete source text/chunk, hidden behind the "Show
|
|
29
30
|
* more"/"Show less" toggle. When left empty, no toggle renders at all — a
|
|
30
|
-
* card with no `full` content simply has no expand affordance.
|
|
31
|
+
* card with no `full` content simply has no expand affordance. Removing all
|
|
32
|
+
* `full`-slotted content while expanded automatically collapses it back.
|
|
31
33
|
* @event lyra-expand - The per-card "Show more"/"Show less" toggle was
|
|
32
34
|
* activated. `detail: { sourceId, expanded }`.
|
|
33
35
|
* @event lyra-open - The title was activated. `detail: { sourceId, href }` —
|
|
@@ -38,7 +40,7 @@ export interface SourceCardOpenDetail {
|
|
|
38
40
|
* viewer, etc).
|
|
39
41
|
* @csspart base - The outer container.
|
|
40
42
|
* @csspart title - The clickable title/page heading (`<button>`).
|
|
41
|
-
* @csspart excerpt - The wrapper around the `excerpt` slot.
|
|
43
|
+
* @csspart excerpt - The wrapper around the `excerpt` slot, `hidden` when the slot has no assigned content.
|
|
42
44
|
* @csspart full - The wrapper around the `full` slot, `hidden` while collapsed.
|
|
43
45
|
* @csspart toggle - The "Show more"/"Show less" button. Only rendered when the `full` slot has content.
|
|
44
46
|
*
|
|
@@ -67,7 +69,12 @@ export declare class LyraSourceCard extends LyraElement {
|
|
|
67
69
|
static styles: import("lit").CSSResultGroup[];
|
|
68
70
|
/** Stable identifier matching a `<lyra-citation-badge>` elsewhere on the page. */
|
|
69
71
|
sourceId: string;
|
|
70
|
-
/** The source's display title, e.g. a filename.
|
|
72
|
+
/** The source's display title, e.g. a filename. Rendered only as the
|
|
73
|
+
* title button's own text -- a bare host-level `title` attribute (the
|
|
74
|
+
* browser's global tooltip attribute) is actively stripped once Lit has
|
|
75
|
+
* synced it into this property, so the card never grows an unsolicited
|
|
76
|
+
* native tooltip repeating the same text. See `attributeChangedCallback`
|
|
77
|
+
* and `updated` below. */
|
|
71
78
|
title: string;
|
|
72
79
|
/** Optional page reference, e.g. `12` or `"iv"` — rendered as-is (never
|
|
73
80
|
* parsed/validated as a number), so a non-numeric page label works too. */
|
|
@@ -75,11 +82,16 @@ export declare class LyraSourceCard extends LyraElement {
|
|
|
75
82
|
/** Optional URL, echoed back (unopened) in `lyra-open`'s detail. */
|
|
76
83
|
href?: string;
|
|
77
84
|
private hasFullSlot;
|
|
85
|
+
private hasExcerptSlot;
|
|
78
86
|
private fullExpanded;
|
|
79
87
|
private readonly fullId;
|
|
88
|
+
private stripHostTitleAttr;
|
|
80
89
|
protected willUpdate(): void;
|
|
81
90
|
firstUpdated(): void;
|
|
91
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
92
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
82
93
|
private onFullSlotChange;
|
|
94
|
+
private onExcerptSlotChange;
|
|
83
95
|
private get titleText();
|
|
84
96
|
private onTitleClick;
|
|
85
97
|
private toggleFull;
|
|
@@ -26,10 +26,12 @@ import { styles } from './source-card.styles.js';
|
|
|
26
26
|
* card's `id`/`source-id`. See the `@example` below.
|
|
27
27
|
*
|
|
28
28
|
* @customElement lyra-source-card
|
|
29
|
-
* @slot excerpt - A short preview,
|
|
29
|
+
* @slot excerpt - A short preview. When left empty, the `excerpt` part
|
|
30
|
+
* collapses away entirely rather than leaving an empty gap in the card.
|
|
30
31
|
* @slot full - The complete source text/chunk, hidden behind the "Show
|
|
31
32
|
* more"/"Show less" toggle. When left empty, no toggle renders at all — a
|
|
32
|
-
* card with no `full` content simply has no expand affordance.
|
|
33
|
+
* card with no `full` content simply has no expand affordance. Removing all
|
|
34
|
+
* `full`-slotted content while expanded automatically collapses it back.
|
|
33
35
|
* @event lyra-expand - The per-card "Show more"/"Show less" toggle was
|
|
34
36
|
* activated. `detail: { sourceId, expanded }`.
|
|
35
37
|
* @event lyra-open - The title was activated. `detail: { sourceId, href }` —
|
|
@@ -40,7 +42,7 @@ import { styles } from './source-card.styles.js';
|
|
|
40
42
|
* viewer, etc).
|
|
41
43
|
* @csspart base - The outer container.
|
|
42
44
|
* @csspart title - The clickable title/page heading (`<button>`).
|
|
43
|
-
* @csspart excerpt - The wrapper around the `excerpt` slot.
|
|
45
|
+
* @csspart excerpt - The wrapper around the `excerpt` slot, `hidden` when the slot has no assigned content.
|
|
44
46
|
* @csspart full - The wrapper around the `full` slot, `hidden` while collapsed.
|
|
45
47
|
* @csspart toggle - The "Show more"/"Show less" button. Only rendered when the `full` slot has content.
|
|
46
48
|
*
|
|
@@ -70,17 +72,35 @@ export class LyraSourceCard extends LyraElement {
|
|
|
70
72
|
super(...arguments);
|
|
71
73
|
/** Stable identifier matching a `<lyra-citation-badge>` elsewhere on the page. */
|
|
72
74
|
this.sourceId = '';
|
|
73
|
-
/** The source's display title, e.g. a filename.
|
|
75
|
+
/** The source's display title, e.g. a filename. Rendered only as the
|
|
76
|
+
* title button's own text -- a bare host-level `title` attribute (the
|
|
77
|
+
* browser's global tooltip attribute) is actively stripped once Lit has
|
|
78
|
+
* synced it into this property, so the card never grows an unsolicited
|
|
79
|
+
* native tooltip repeating the same text. See `attributeChangedCallback`
|
|
80
|
+
* and `updated` below. */
|
|
74
81
|
this.title = '';
|
|
75
82
|
// See `<lyra-widget>`'s `hasActionsSlot` for the identical
|
|
76
83
|
// presence-tracking convention -- a `[part]` always contains a literal
|
|
77
84
|
// `<slot>` child regardless of assigned content, so CSS `:empty` never
|
|
78
85
|
// matches; real emptiness is tracked in JS instead.
|
|
79
86
|
this.hasFullSlot = false;
|
|
87
|
+
this.hasExcerptSlot = false;
|
|
80
88
|
this.fullExpanded = false;
|
|
81
89
|
this.fullId = nextId('source-card-full');
|
|
90
|
+
// Guards the `removeAttribute('title')` call in `updated()` below: removing
|
|
91
|
+
// an observed attribute fires `attributeChangedCallback` synchronously just
|
|
92
|
+
// like setting one does, and without this flag Lit would treat the removal
|
|
93
|
+
// as a fresh (empty) attribute value and reset the `title` property right
|
|
94
|
+
// back to `null`, losing the value it just finished syncing in.
|
|
95
|
+
this.stripHostTitleAttr = false;
|
|
82
96
|
this.onFullSlotChange = (e) => {
|
|
83
|
-
|
|
97
|
+
const count = e.target.assignedElements({ flatten: true }).length;
|
|
98
|
+
this.hasFullSlot = count > 0;
|
|
99
|
+
if (count === 0)
|
|
100
|
+
this.fullExpanded = false;
|
|
101
|
+
};
|
|
102
|
+
this.onExcerptSlotChange = (e) => {
|
|
103
|
+
this.hasExcerptSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
84
104
|
};
|
|
85
105
|
this.onTitleClick = () => {
|
|
86
106
|
this.emit('lyra-open', { sourceId: this.sourceId, href: this.href });
|
|
@@ -94,11 +114,33 @@ export class LyraSourceCard extends LyraElement {
|
|
|
94
114
|
willUpdate() {
|
|
95
115
|
if (!this.hasUpdated) {
|
|
96
116
|
this.hasFullSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'full');
|
|
117
|
+
this.hasExcerptSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'excerpt');
|
|
97
118
|
}
|
|
98
119
|
}
|
|
99
120
|
firstUpdated() {
|
|
100
|
-
const
|
|
101
|
-
|
|
121
|
+
const fullSlot = this.shadowRoot.querySelector('slot[name="full"]');
|
|
122
|
+
const fullCount = fullSlot.assignedElements({ flatten: true }).length;
|
|
123
|
+
this.hasFullSlot = fullCount > 0;
|
|
124
|
+
if (fullCount === 0)
|
|
125
|
+
this.fullExpanded = false;
|
|
126
|
+
const excerptSlot = this.shadowRoot.querySelector('slot[name="excerpt"]');
|
|
127
|
+
this.hasExcerptSlot = excerptSlot.assignedElements({ flatten: true }).length > 0;
|
|
128
|
+
}
|
|
129
|
+
attributeChangedCallback(name, old, value) {
|
|
130
|
+
if (name === 'title' && this.stripHostTitleAttr)
|
|
131
|
+
return;
|
|
132
|
+
super.attributeChangedCallback(name, old, value);
|
|
133
|
+
}
|
|
134
|
+
updated(changed) {
|
|
135
|
+
if (changed.has('title') && this.hasAttribute('title')) {
|
|
136
|
+
// The attribute has already been converted into the `title` property
|
|
137
|
+
// by this point (that's how it got here), so the DOM attribute itself
|
|
138
|
+
// is now redundant -- and, left in place, would make the whole card
|
|
139
|
+
// show a native tooltip repeating the title text on hover.
|
|
140
|
+
this.stripHostTitleAttr = true;
|
|
141
|
+
this.removeAttribute('title');
|
|
142
|
+
this.stripHostTitleAttr = false;
|
|
143
|
+
}
|
|
102
144
|
}
|
|
103
145
|
get titleText() {
|
|
104
146
|
const base = this.title || 'Untitled source';
|
|
@@ -108,7 +150,9 @@ export class LyraSourceCard extends LyraElement {
|
|
|
108
150
|
return html `
|
|
109
151
|
<div part="base">
|
|
110
152
|
<button part="title" type="button" @click=${this.onTitleClick}>${this.titleText}</button>
|
|
111
|
-
<div part="excerpt"
|
|
153
|
+
<div part="excerpt" ?hidden=${!this.hasExcerptSlot}>
|
|
154
|
+
<slot name="excerpt" @slotchange=${this.onExcerptSlotChange}></slot>
|
|
155
|
+
</div>
|
|
112
156
|
${this.hasFullSlot
|
|
113
157
|
? html `<button
|
|
114
158
|
part="toggle"
|
|
@@ -142,6 +186,9 @@ __decorate([
|
|
|
142
186
|
__decorate([
|
|
143
187
|
state()
|
|
144
188
|
], LyraSourceCard.prototype, "hasFullSlot", void 0);
|
|
189
|
+
__decorate([
|
|
190
|
+
state()
|
|
191
|
+
], LyraSourceCard.prototype, "hasExcerptSlot", void 0);
|
|
145
192
|
__decorate([
|
|
146
193
|
state()
|
|
147
194
|
], LyraSourceCard.prototype, "fullExpanded", void 0);
|
|
@@ -69,7 +69,15 @@ export class LyraSourceList extends LyraElement {
|
|
|
69
69
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
70
70
|
willUpdate() {
|
|
71
71
|
if (!this.hasUpdated) {
|
|
72
|
-
|
|
72
|
+
// Must count only children assigned to the *default* slot -- same rule
|
|
73
|
+
// `firstUpdated`/`onSlotChange` apply via `assignedElements()` below --
|
|
74
|
+
// otherwise a direct child carrying a foreign `slot` attribute makes
|
|
75
|
+
// this pre-count disagree with the authoritative one, which schedules
|
|
76
|
+
// a wasted second update (and Lit's dev-mode change-in-update warning).
|
|
77
|
+
// An explicit `slot=""` still assigns to the default slot per the HTML
|
|
78
|
+
// slot algorithm, so check the attribute's value rather than its mere
|
|
79
|
+
// presence.
|
|
80
|
+
this.slottedCount = Array.from(this.children).filter((el) => !el.getAttribute('slot')).length;
|
|
73
81
|
}
|
|
74
82
|
}
|
|
75
83
|
firstUpdated() {
|