@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.
Files changed (224) hide show
  1. package/README.md +87 -14
  2. package/custom-elements.json +11555 -5539
  3. package/dist/components/app-rail/app-rail-item.d.ts +30 -0
  4. package/dist/components/app-rail/app-rail-item.js +68 -0
  5. package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
  6. package/dist/components/app-rail/app-rail-item.styles.js +65 -0
  7. package/dist/components/app-rail/app-rail.d.ts +21 -24
  8. package/dist/components/app-rail/app-rail.js +68 -132
  9. package/dist/components/app-rail/app-rail.styles.js +5 -2
  10. package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
  11. package/dist/components/attachment-chip/attachment-chip.js +73 -29
  12. package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
  13. package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
  14. package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
  15. package/dist/components/chart/bar-chart.js +2 -18
  16. package/dist/components/chart/box-plot.d.ts +25 -1
  17. package/dist/components/chart/box-plot.js +136 -6
  18. package/dist/components/chart/box-plot.styles.js +13 -0
  19. package/dist/components/chart/bubble-chart.js +2 -18
  20. package/dist/components/chart/chart.d.ts +54 -10
  21. package/dist/components/chart/chart.js +182 -39
  22. package/dist/components/chart/doughnut-chart.js +2 -18
  23. package/dist/components/chart/histogram-bin.d.ts +5 -2
  24. package/dist/components/chart/histogram-bin.js +12 -4
  25. package/dist/components/chart/histogram.js +8 -16
  26. package/dist/components/chart/line-chart.js +2 -18
  27. package/dist/components/chart/lite-chart.d.ts +80 -3
  28. package/dist/components/chart/lite-chart.js +250 -53
  29. package/dist/components/chart/pie-chart.js +2 -18
  30. package/dist/components/chart/polar-area-chart.js +2 -18
  31. package/dist/components/chart/radar-chart.js +2 -18
  32. package/dist/components/chart/scatter-chart.js +2 -18
  33. package/dist/components/chat-composer/chat-composer.d.ts +42 -1
  34. package/dist/components/chat-composer/chat-composer.js +84 -3
  35. package/dist/components/chat-composer/chat-composer.styles.js +11 -1
  36. package/dist/components/chat-message/chat-message.d.ts +32 -4
  37. package/dist/components/chat-message/chat-message.js +57 -17
  38. package/dist/components/checkbox/checkbox.d.ts +66 -8
  39. package/dist/components/checkbox/checkbox.js +173 -50
  40. package/dist/components/checkbox/checkbox.styles.js +9 -2
  41. package/dist/components/chip/chip-group.js +8 -2
  42. package/dist/components/chip/chip.js +9 -2
  43. package/dist/components/citation-badge/citation-badge.js +21 -3
  44. package/dist/components/code-block/code-block.d.ts +38 -0
  45. package/dist/components/code-block/code-block.js +72 -7
  46. package/dist/components/code-block/code-loader.d.ts +44 -1
  47. package/dist/components/code-block/code-loader.js +63 -0
  48. package/dist/components/combobox/combobox.d.ts +46 -5
  49. package/dist/components/combobox/combobox.js +175 -35
  50. package/dist/components/combobox/combobox.styles.js +3 -3
  51. package/dist/components/context-meter/context-meter.js +17 -8
  52. package/dist/components/context-meter/context-meter.styles.js +0 -6
  53. package/dist/components/conversation-item/conversation-item.d.ts +13 -16
  54. package/dist/components/conversation-item/conversation-item.js +29 -20
  55. package/dist/components/date-picker/calendar-core.d.ts +24 -0
  56. package/dist/components/date-picker/calendar-core.js +51 -4
  57. package/dist/components/date-picker/date-input.d.ts +77 -10
  58. package/dist/components/date-picker/date-input.js +278 -73
  59. package/dist/components/date-picker/date-picker.d.ts +28 -4
  60. package/dist/components/date-picker/date-picker.js +95 -37
  61. package/dist/components/dialog/confirm.js +3 -6
  62. package/dist/components/dialog/dialog.d.ts +75 -31
  63. package/dist/components/dialog/dialog.js +147 -131
  64. package/dist/components/dialog/dialog.styles.js +46 -2
  65. package/dist/components/dock-panel/dock-panel.d.ts +8 -0
  66. package/dist/components/dock-panel/dock-panel.js +15 -1
  67. package/dist/components/dock-panel/dock-panel.styles.js +15 -1
  68. package/dist/components/document-preview/document-preview.d.ts +15 -7
  69. package/dist/components/document-preview/document-preview.js +29 -13
  70. package/dist/components/empty/empty.d.ts +1 -2
  71. package/dist/components/empty/empty.js +18 -12
  72. package/dist/components/empty/empty.styles.js +0 -3
  73. package/dist/components/export-button/export-button.d.ts +12 -0
  74. package/dist/components/export-button/export-button.js +33 -5
  75. package/dist/components/export-button/export-button.styles.js +5 -1
  76. package/dist/components/file-input/accept.js +2 -0
  77. package/dist/components/file-input/file-input.d.ts +10 -1
  78. package/dist/components/file-input/file-input.js +36 -5
  79. package/dist/components/flag/flag.d.ts +1 -1
  80. package/dist/components/flag/flag.js +15 -1
  81. package/dist/components/flag/flag.styles.js +1 -1
  82. package/dist/components/flag/language-map.d.ts +4 -1
  83. package/dist/components/flag/language-map.js +6 -3
  84. package/dist/components/gauge/gauge.d.ts +10 -1
  85. package/dist/components/gauge/gauge.js +28 -9
  86. package/dist/components/generation-status/generation-status.d.ts +13 -5
  87. package/dist/components/generation-status/generation-status.js +59 -5
  88. package/dist/components/graph/graph.d.ts +37 -3
  89. package/dist/components/graph/graph.js +132 -20
  90. package/dist/components/heatmap/calendar-grid.d.ts +15 -6
  91. package/dist/components/heatmap/calendar-grid.js +17 -6
  92. package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
  93. package/dist/components/heatmap/heatmap-scale.js +8 -2
  94. package/dist/components/heatmap/heatmap.d.ts +166 -25
  95. package/dist/components/heatmap/heatmap.js +372 -89
  96. package/dist/components/json-viewer/json-viewer.d.ts +22 -5
  97. package/dist/components/json-viewer/json-viewer.js +115 -37
  98. package/dist/components/json-viewer/json-viewer.styles.js +2 -1
  99. package/dist/components/kbd/kbd.js +24 -7
  100. package/dist/components/live-region/live-region.d.ts +10 -1
  101. package/dist/components/live-region/live-region.js +42 -6
  102. package/dist/components/map/map.d.ts +24 -1
  103. package/dist/components/map/map.js +89 -24
  104. package/dist/components/markdown/markdown-loader.d.ts +24 -0
  105. package/dist/components/markdown/markdown-loader.js +35 -1
  106. package/dist/components/markdown/markdown.d.ts +46 -8
  107. package/dist/components/markdown/markdown.js +136 -31
  108. package/dist/components/markdown/markdown.styles.js +7 -1
  109. package/dist/components/media-card/media-card.d.ts +25 -17
  110. package/dist/components/media-card/media-card.js +43 -65
  111. package/dist/components/mention-popover/mention-popover.d.ts +4 -1
  112. package/dist/components/mention-popover/mention-popover.js +23 -5
  113. package/dist/components/menu/menu-item.d.ts +32 -3
  114. package/dist/components/menu/menu-item.js +94 -7
  115. package/dist/components/menu/menu-item.styles.js +8 -0
  116. package/dist/components/menu/menu.d.ts +25 -1
  117. package/dist/components/menu/menu.js +99 -3
  118. package/dist/components/menu/menu.styles.js +3 -2
  119. package/dist/components/model-select/model-select.d.ts +48 -3
  120. package/dist/components/model-select/model-select.js +120 -22
  121. package/dist/components/model-select/model-select.styles.js +17 -3
  122. package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
  123. package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
  124. package/dist/components/playback/playback.d.ts +6 -2
  125. package/dist/components/playback/playback.js +72 -29
  126. package/dist/components/playback/playback.styles.js +6 -1
  127. package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
  128. package/dist/components/responsive-panel/responsive-panel.js +130 -132
  129. package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
  130. package/dist/components/result-card/result-card.d.ts +8 -2
  131. package/dist/components/result-card/result-card.js +9 -3
  132. package/dist/components/result-card/result-card.styles.js +0 -1
  133. package/dist/components/select/select.d.ts +59 -20
  134. package/dist/components/select/select.js +125 -43
  135. package/dist/components/skeleton/skeleton.d.ts +3 -0
  136. package/dist/components/skeleton/skeleton.js +7 -1
  137. package/dist/components/skeleton/skeleton.styles.js +5 -4
  138. package/dist/components/slider/slider.d.ts +57 -5
  139. package/dist/components/slider/slider.js +195 -80
  140. package/dist/components/source-card/source-card.d.ts +17 -5
  141. package/dist/components/source-card/source-card.js +55 -8
  142. package/dist/components/source-card/source-card.styles.js +1 -1
  143. package/dist/components/source-list/source-list.js +9 -1
  144. package/dist/components/sparkline/sparkline.js +34 -10
  145. package/dist/components/split/split.d.ts +141 -14
  146. package/dist/components/split/split.js +357 -52
  147. package/dist/components/split/split.styles.js +18 -1
  148. package/dist/components/stat/stat.d.ts +10 -4
  149. package/dist/components/stat/stat.js +46 -11
  150. package/dist/components/stream-status/stream-status.d.ts +27 -11
  151. package/dist/components/stream-status/stream-status.js +95 -13
  152. package/dist/components/streaming-text/streaming-text.d.ts +9 -3
  153. package/dist/components/streaming-text/streaming-text.js +25 -10
  154. package/dist/components/streaming-text/streaming-text.styles.js +3 -2
  155. package/dist/components/switch/switch.d.ts +52 -7
  156. package/dist/components/switch/switch.js +111 -40
  157. package/dist/components/switch/switch.styles.js +1 -1
  158. package/dist/components/table/table.d.ts +18 -3
  159. package/dist/components/table/table.js +88 -20
  160. package/dist/components/tabs/tabs.d.ts +23 -0
  161. package/dist/components/tabs/tabs.js +76 -12
  162. package/dist/components/tabs/tabs.styles.js +11 -0
  163. package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
  164. package/dist/components/thinking-panel/thinking-panel.js +34 -9
  165. package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
  166. package/dist/components/time-range/time-range.d.ts +59 -2
  167. package/dist/components/time-range/time-range.js +163 -67
  168. package/dist/components/toast/toast-item.d.ts +12 -3
  169. package/dist/components/toast/toast-item.js +90 -53
  170. package/dist/components/toast/toast-item.styles.js +59 -28
  171. package/dist/components/toast/toast.js +17 -5
  172. package/dist/components/toast/toaster.js +10 -1
  173. package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
  174. package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
  175. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
  176. package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
  177. package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
  178. package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
  179. package/dist/components/tool-param-form/tool-param-form.js +350 -106
  180. package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
  181. package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
  182. package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
  183. package/dist/components/tool-result-view/registry.d.ts +0 -8
  184. package/dist/components/tool-result-view/registry.js +7 -2
  185. package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
  186. package/dist/components/tool-result-view/tool-result-view.js +18 -4
  187. package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
  188. package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
  189. package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
  190. package/dist/components/tree/tree-node.d.ts +4 -1
  191. package/dist/components/tree/tree-node.js +4 -1
  192. package/dist/components/tree/tree.d.ts +17 -8
  193. package/dist/components/tree/tree.js +21 -20
  194. package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
  195. package/dist/components/typing-indicator/typing-indicator.js +16 -4
  196. package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
  197. package/dist/components/virtual-list/virtual-list.d.ts +56 -19
  198. package/dist/components/virtual-list/virtual-list.js +184 -38
  199. package/dist/components/virtual-list/virtual-list.styles.js +12 -0
  200. package/dist/components/widget/widget.d.ts +5 -4
  201. package/dist/components/widget/widget.js +54 -120
  202. package/dist/components/widget/widget.styles.js +8 -2
  203. package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
  204. package/dist/components/word-cloud/word-cloud-layout.js +20 -6
  205. package/dist/components/word-cloud/word-cloud.d.ts +22 -2
  206. package/dist/components/word-cloud/word-cloud.js +45 -6
  207. package/dist/components/word-cloud/word-cloud.styles.js +13 -1
  208. package/dist/internal/anchored-validity.d.ts +1 -0
  209. package/dist/internal/anchored-validity.js +86 -0
  210. package/dist/internal/form-associated.d.ts +7 -0
  211. package/dist/internal/form-associated.js +43 -4
  212. package/dist/internal/numbers.d.ts +12 -0
  213. package/dist/internal/numbers.js +26 -0
  214. package/dist/internal/overlay-manager.d.ts +51 -0
  215. package/dist/internal/overlay-manager.js +523 -0
  216. package/dist/internal/positioner.js +27 -3
  217. package/dist/internal/safe-url.d.ts +12 -0
  218. package/dist/internal/safe-url.js +41 -0
  219. package/dist/internal/tokens.styles.js +17 -11
  220. package/dist/lyra.d.ts +5 -2
  221. package/dist/lyra.js +3 -1
  222. package/llms-full.txt +970 -426
  223. package/llms.txt +5 -4
  224. package/package.json +19 -2
@@ -9,6 +9,7 @@ import { property } from 'lit/decorators.js';
9
9
  import { LyraElement } from '../../internal/lyra-element.js';
10
10
  import { defineElement } from '../../internal/prefix.js';
11
11
  import { isRtl } from '../../internal/rtl.js';
12
+ import { finiteNumber, finiteRange } from '../../internal/numbers.js';
12
13
  import { styles } from './time-range.styles.js';
13
14
  function isArrowKey(key) {
14
15
  return key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowLeft' || key === 'ArrowDown';
@@ -22,14 +23,17 @@ function isSliderKey(key) {
22
23
  * step, matching the WAI-ARIA APG slider pattern's expected keyboard
23
24
  * interactions (and native `<input type=range>`). */
24
25
  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. */
26
+ /** Number of decimal places implied by `n`, including exponent notation.
27
+ * `0.1` -> 1, `5` -> 0, and `1e-7` -> 7. Used to round a stepped value back
28
+ * to the precision the caller's `step` itself implies, instead of leaving
29
+ * it at whatever binary floating-point noise `value / step` happened to
30
+ * produce. */
29
31
  function decimalPlaces(n) {
30
- const str = n.toString();
31
- const dot = str.indexOf('.');
32
- return dot === -1 ? 0 : str.length - dot - 1;
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));
33
37
  }
34
38
  /**
35
39
  * `<lyra-time-range>` — a two-handle brush/scrubber over a numeric domain.
@@ -41,26 +45,72 @@ function decimalPlaces(n) {
41
45
  * shortcut that sets both handles at once, the continuous brush underneath
42
46
  * is unaffected and both interaction modes coexist.
43
47
  *
48
+ * Form-associated (via a directly-attached `ElementInternals`, mirroring
49
+ * `<lyra-combobox>`'s minimal pattern rather than the single-string-value
50
+ * `FormAssociated` mixin, which doesn't fit a two-handle range): an ancestor
51
+ * `<fieldset disabled>` disables both handles and every preset button
52
+ * through `effectiveDisabled`, the same way it would a native `<input>`,
53
+ * without touching the consumer-facing `disabled` property/attribute itself.
54
+ *
44
55
  * @customElement lyra-time-range
45
56
  * @event lyra-input - Fired continuously while dragging or on each arrow-key press. `detail: { start, end }`.
46
57
  * @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
58
+ * @csspart base - The time-range wrapper.
59
+ * @csspart track - The complete range track.
60
+ * @csspart range - The selected range.
61
+ * @csspart handle-start - The start handle.
62
+ * @csspart handle-end - The end handle.
63
+ * @csspart presets - The preset controls wrapper.
64
+ * @csspart preset-button - A preset button.
48
65
  */
49
66
  export class LyraTimeRange extends LyraElement {
67
+ static { this.formAssociated = true; }
68
+ static { this.styles = [LyraElement.styles, styles]; }
69
+ static { this.properties = {
70
+ // Hand-written accessor (see the `get`/`set disabled` pair below)
71
+ // instead of a plain `@property({ reflect: true })`: this element is
72
+ // form-associated, and the browser invokes `formDisabledCallback`
73
+ // synchronously for *this element's own* `disabled` attribute changes
74
+ // too, not only for an ancestor `<fieldset disabled>` toggling. A plain
75
+ // reflecting property defers its attribute write into the async Lit
76
+ // update cycle, so that browser callback would otherwise fire nested
77
+ // *inside* this same element's own in-progress update -- the
78
+ // `requestUpdate()` it triggers (to pick up `_fieldsetDisabled`) then
79
+ // races Lit's own scheduling and can resolve `updateComplete` before the
80
+ // corrected value actually renders. Reflecting the attribute
81
+ // synchronously, right in the setter (mirrors `<lyra-combobox>`'s and
82
+ // `FormAssociated`'s identical `disabled` accessor), makes the browser
83
+ // invoke that callback *before* any Lit update even starts, so it never
84
+ // interleaves with one.
85
+ disabled: { type: Boolean, reflect: true, noAccessor: true },
86
+ }; }
50
87
  constructor() {
51
- super(...arguments);
88
+ super();
52
89
  this.min = 0;
53
90
  this.max = 100;
54
91
  this.start = 0;
55
92
  this.end = 100;
56
93
  this.step = 1;
57
- this.disabled = false;
94
+ /** Accessible name for the start handle, used as its `aria-label`.
95
+ * Overridable for i18n/custom copy; defaults to the same literal text
96
+ * this component always rendered before the property existed. */
97
+ this.startLabel = 'Range start';
98
+ /** Accessible name for the end handle, used as its `aria-label`.
99
+ * Overridable for i18n/custom copy; defaults to the same literal text
100
+ * this component always rendered before the property existed. */
101
+ this.endLabel = 'Range end';
58
102
  /**
59
103
  * Optional discrete presets rendered as a `[part="presets"]` button row
60
104
  * above the track. Purely additive: leaving this empty (the default)
61
105
  * renders nothing extra and leaves the continuous brush untouched.
62
106
  */
63
107
  this.presets = [];
108
+ this._disabled = false;
109
+ // Tracked separately from the consumer's own `disabled` -- a fieldset
110
+ // cascade must never mutate that IDL property/attribute itself (mirrors
111
+ // combobox.ts's/slider.ts's identical `_fieldsetDisabled`/
112
+ // `effectiveDisabled` pattern), only the combined getter below.
113
+ this._fieldsetDisabled = false;
64
114
  // Keyed by pointerId rather than a single scalar so two concurrent drags
65
115
  // (e.g. a two-finger touch, one per handle) each keep tracking their own
66
116
  // handle instead of the second pointerdown hijacking which handle the
@@ -69,12 +119,14 @@ export class LyraTimeRange extends LyraElement {
69
119
  this.onKeyDown = (handle, e) => {
70
120
  // A handle that already has focus when the component becomes disabled
71
121
  // must not still respond to arrow keys (new pointerdowns are already
72
- // blocked by the `if (this.disabled) return;` guard in onPointerDown,
73
- // and disabled handles carry `tabindex="-1"`, but a pre-existing focus
74
- // can bypass both of those).
75
- if (this.disabled)
122
+ // blocked by the `if (this.effectiveDisabled) return;` guard in
123
+ // onPointerDown, and disabled handles carry `tabindex="-1"`, but a
124
+ // pre-existing focus can bypass both of those).
125
+ if (this.effectiveDisabled)
76
126
  return;
77
- const current = handle === 'start' ? this.start : this.end;
127
+ const current = handle === 'start'
128
+ ? finiteRange(this.start, this.domain().lo)
129
+ : finiteRange(this.end, this.domain().hi);
78
130
  // Mirror the same left/right swap as onPointerMove: under RTL, physical
79
131
  // ArrowRight moves toward inset-inline-start, i.e. a lower value.
80
132
  const rtl = isRtl(this);
@@ -109,12 +161,12 @@ export class LyraTimeRange extends LyraElement {
109
161
  // Only commit on release of the keys that onKeyDown acts on — releasing
110
162
  // an unrelated key (Tab, Shift, ...) while a handle happens to be
111
163
  // focused must not emit a spurious lyra-change.
112
- if (this.disabled || !isSliderKey(e.key))
164
+ if (this.effectiveDisabled || !isSliderKey(e.key))
113
165
  return;
114
166
  this.emit('lyra-change', { start: this.start, end: this.end });
115
167
  };
116
168
  this.onPointerDown = (handle, e) => {
117
- if (this.disabled)
169
+ if (this.effectiveDisabled)
118
170
  return;
119
171
  const base = this.renderRoot.querySelector('[part="base"]');
120
172
  this.drags.set(e.pointerId, { handle, base });
@@ -134,15 +186,15 @@ export class LyraTimeRange extends LyraElement {
134
186
  const drag = this.drags.get(e.pointerId);
135
187
  if (drag === undefined)
136
188
  return;
137
- if (this.disabled) {
189
+ if (this.effectiveDisabled) {
138
190
  // These are window-level listeners driven by setPointerCapture, so
139
191
  // they keep firing for this pointerId regardless of any CSS on the
140
192
  // host (this was already true even back when the host used
141
193
  // `pointer-events: none`, since that never governed an
142
194
  // already-captured window listener) — a drag already in progress
143
195
  // would otherwise keep mutating start/end (and emitting lyra-input)
144
- // after `disabled` flips true mid-drag. Abort the drag instead of
145
- // continuing to process it.
196
+ // after `disabled` (or an ancestor fieldset) flips true mid-drag.
197
+ // Abort the drag instead of continuing to process it.
146
198
  this.endDrag(e.pointerId, false);
147
199
  return;
148
200
  }
@@ -152,14 +204,41 @@ export class LyraTimeRange extends LyraElement {
152
204
  // right edge under RTL), so the pointer ratio has to mirror that or a
153
205
  // rightward drag would move the handle the wrong way.
154
206
  const ratio = Math.min(1, Math.max(0, isRtl(this) ? 1 - raw : raw));
155
- const value = this.min + ratio * (this.max - this.min);
207
+ const { lo, hi } = this.domain();
208
+ const value = lo + ratio * (hi - lo);
156
209
  this.setValue(drag.handle, value, false);
157
210
  };
158
211
  this.onPointerUp = (e) => {
159
212
  this.endDrag(e.pointerId, true);
160
213
  };
214
+ this.internals = this.attachInternals();
215
+ }
216
+ get disabled() {
217
+ return this._disabled;
218
+ }
219
+ set disabled(next) {
220
+ const old = this._disabled;
221
+ this._disabled = Boolean(next);
222
+ this.toggleAttribute('disabled', this._disabled);
223
+ this.requestUpdate('disabled', old);
224
+ }
225
+ /** Effective disabled state: this element's own `disabled` OR an ancestor
226
+ * `<fieldset disabled>`'s inherited state -- mirrors native `<input>`,
227
+ * whose own `disabled` IDL property/attribute is never mutated by a
228
+ * fieldset. */
229
+ get effectiveDisabled() {
230
+ return this.disabled || this._fieldsetDisabled;
231
+ }
232
+ /**
233
+ * Called by the browser when an ancestor `<fieldset disabled>` toggles.
234
+ * Tracked separately from the consumer's own `disabled` (see
235
+ * `effectiveDisabled`) so a consumer's explicit `disabled` survives the
236
+ * fieldset re-enabling instead of being permanently overwritten.
237
+ */
238
+ formDisabledCallback(disabled) {
239
+ this._fieldsetDisabled = disabled;
240
+ this.requestUpdate();
161
241
  }
162
- static { this.styles = [LyraElement.styles, styles]; }
163
242
  disconnectedCallback() {
164
243
  super.disconnectedCallback();
165
244
  // Mirror lyra-split's cleanup: if the element is removed mid-drag (or a
@@ -184,8 +263,8 @@ export class LyraTimeRange extends LyraElement {
184
263
  // percentOf()/clamp() caller. Mirror lyra-gauge's `ratio` getter, which
185
264
  // short-circuits on isNaN(...) instead, by falling back to this
186
265
  // property's own default.
187
- const min = isNaN(this.min) ? 0 : this.min;
188
- const max = isNaN(this.max) ? 100 : this.max;
266
+ const min = finiteNumber(this.min, 0);
267
+ const max = finiteNumber(this.max, 100);
189
268
  return { lo: Math.min(min, max), hi: Math.max(min, max) };
190
269
  }
191
270
  percentOf(value) {
@@ -193,11 +272,10 @@ export class LyraTimeRange extends LyraElement {
193
272
  // the rendered `inset-inline-start:NaN%` — that's an invalid CSS value
194
273
  // the browser silently drops, leaving the handle stuck with no visible
195
274
  // recovery. 0% is at least a stable, well-defined position.
196
- if (isNaN(value))
197
- return 0;
198
275
  const { lo, hi } = this.domain();
276
+ const safeValue = finiteRange(value, lo, lo, hi);
199
277
  const span = hi - lo || 1;
200
- return ((value - lo) / span) * 100;
278
+ return ((safeValue - lo) / span) * 100;
201
279
  }
202
280
  /** Each handle's actual reachable sub-range, bounded by its sibling
203
281
  * handle's current value the same way clamp() already enforces it — used
@@ -209,8 +287,8 @@ export class LyraTimeRange extends LyraElement {
209
287
  // A NaN sibling value (e.g. an invalid `end` attribute) must not leak
210
288
  // into the *other* handle's aria-valuemin/aria-valuemax as a literal
211
289
  // "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;
290
+ const start = finiteRange(this.start, lo, lo, hi);
291
+ const end = finiteRange(this.end, hi, lo, hi);
214
292
  return handle === 'start' ? { min: lo, max: end } : { min: start, max: hi };
215
293
  }
216
294
  clamp(handle, value) {
@@ -220,7 +298,6 @@ export class LyraTimeRange extends LyraElement {
220
298
  // zero/NaN below and permanently poison start/end with NaN, since the
221
299
  // other handle's clamp cross-references this one's (already-NaN) value.
222
300
  // Treat it as "unstepped" instead of propagating NaN.
223
- const hasStep = Number.isFinite(this.step) && this.step > 0;
224
301
  // Anchor the step grid at the domain's own `lo` (matching native
225
302
  // `<input type=range>`) instead of absolute 0 — otherwise a `min` that
226
303
  // isn't itself a multiple of `step` makes the very first nudge off `min`
@@ -229,16 +306,17 @@ export class LyraTimeRange extends LyraElement {
229
306
  // (rather than leaving raw `value / step` binary-float noise in place)
230
307
  // so repeated steps land on exact values like 20.1 instead of
231
308
  // 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;
309
+ let stepped = finiteNumber(value, lo);
310
+ const step = finiteRange(this.step, 0, 0);
311
+ if (Number.isFinite(step) && step > 0) {
312
+ const stepsFromLo = Math.round((stepped - lo) / step);
313
+ const factor = 10 ** decimalPlaces(step);
314
+ stepped = Math.round((lo + stepsFromLo * step) * factor) / factor;
237
315
  }
238
316
  const bounded = Math.min(hi, Math.max(lo, stepped));
239
317
  if (handle === 'start')
240
- return Math.min(bounded, this.end);
241
- return Math.max(bounded, this.start);
318
+ return Math.min(bounded, finiteRange(this.end, hi, lo, hi));
319
+ return Math.max(bounded, finiteRange(this.start, lo, lo, hi));
242
320
  }
243
321
  setValue(handle, value, commit) {
244
322
  const clamped = this.clamp(handle, value);
@@ -253,22 +331,28 @@ export class LyraTimeRange extends LyraElement {
253
331
  /**
254
332
  * Apply a discrete preset: sets both handles and emits the same
255
333
  * lyra-input/lyra-change pair a committed drag or keyboard step would.
334
+ *
335
+ * Deliberately bypasses `setValue()`/`clamp()` — a preset is an explicit
336
+ * discrete jump to the caller's own exact numbers, not a stepped nudge, so
337
+ * routing it through `clamp()`'s step-grid rounding would silently round
338
+ * a preset's values away from what the caller specified (and desync the
339
+ * active-button match in render(), which compares against these exact
340
+ * numbers). Only the domain bounds and the start<=end invariant apply
341
+ * here, not `step`. Both handles are also assigned before either event
342
+ * fires — routing them through two sequential `setValue()` calls instead
343
+ * would emit an extra lyra-input whose detail still held the stale
344
+ * pre-preset value for whichever handle hadn't been assigned yet.
256
345
  */
257
346
  applyPreset(preset) {
258
- if (this.disabled)
347
+ if (this.effectiveDisabled)
259
348
  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);
349
+ const { lo, hi } = this.domain();
350
+ const start = finiteRange(preset.start, lo, lo, hi);
351
+ const end = finiteRange(preset.end, hi, lo, hi);
352
+ this.start = Math.min(start, end);
353
+ this.end = Math.max(start, end);
354
+ this.emit('lyra-input', { start: this.start, end: this.end });
355
+ this.emit('lyra-change', { start: this.start, end: this.end });
272
356
  }
273
357
  /** Stop the drag owned by `pointerId`, optionally committing a final lyra-change. */
274
358
  endDrag(pointerId, commit) {
@@ -298,20 +382,25 @@ export class LyraTimeRange extends LyraElement {
298
382
  // the track, since percentOf() would otherwise produce <0%/>100%.
299
383
  // Guard against a caller passing min > max so the bounds themselves
300
384
  // stay well-formed.
301
- const lo = Math.min(this.min, this.max);
302
- const hi = Math.max(this.min, this.max);
303
- this.start = Math.min(hi, Math.max(lo, this.start));
304
- this.end = Math.min(hi, Math.max(lo, this.end));
385
+ const { lo, hi } = this.domain();
386
+ if (Number.isFinite(this.start))
387
+ this.start = finiteRange(this.start, lo, lo, hi);
388
+ if (Number.isFinite(this.end))
389
+ this.end = finiteRange(this.end, hi, lo, hi);
305
390
  }
306
391
  // Keep start <= end regardless of which side changed (a controlled
307
392
  // caller may set only `end`, e.g. two-way-binding an external store).
308
393
  // The handle that just moved wins; the other side is pulled to meet it,
309
394
  // mirroring the direction `clamp()` already uses during interactive
310
395
  // dragging.
311
- if (changed.has('start') || changed.has('min') || changed.has('max')) {
396
+ if ((changed.has('start') || changed.has('min') || changed.has('max')) &&
397
+ Number.isFinite(this.start) &&
398
+ Number.isFinite(this.end)) {
312
399
  this.start = Math.min(this.start, this.end);
313
400
  }
314
- if (changed.has('end') || changed.has('min') || changed.has('max')) {
401
+ if ((changed.has('end') || changed.has('min') || changed.has('max')) &&
402
+ Number.isFinite(this.start) &&
403
+ Number.isFinite(this.end)) {
315
404
  this.end = Math.max(this.end, this.start);
316
405
  }
317
406
  }
@@ -328,7 +417,7 @@ export class LyraTimeRange extends LyraElement {
328
417
  return html `<button
329
418
  part="preset-button"
330
419
  type="button"
331
- ?disabled=${this.disabled}
420
+ ?disabled=${this.effectiveDisabled}
332
421
  aria-pressed=${active ? 'true' : 'false'}
333
422
  ?data-active=${active}
334
423
  @click=${() => this.applyPreset(preset)}
@@ -347,12 +436,14 @@ export class LyraTimeRange extends LyraElement {
347
436
  <div
348
437
  part="handle-start"
349
438
  role="slider"
350
- tabindex=${this.disabled ? '-1' : '0'}
351
- aria-label="Range start"
352
- aria-disabled=${this.disabled ? 'true' : nothing}
439
+ tabindex=${this.effectiveDisabled ? '-1' : '0'}
440
+ aria-label=${this.startLabel}
441
+ aria-disabled=${this.effectiveDisabled ? 'true' : nothing}
353
442
  aria-valuemin=${startBounds.min}
354
443
  aria-valuemax=${startBounds.max}
355
- aria-valuenow=${isNaN(this.start) ? nothing : this.start}
444
+ aria-valuenow=${Number.isFinite(this.start)
445
+ ? finiteRange(this.start, startBounds.min, startBounds.min, startBounds.max)
446
+ : nothing}
356
447
  style=${`inset-inline-start:${startPct}%`}
357
448
  @pointerdown=${(e) => this.onPointerDown('start', e)}
358
449
  @keydown=${(e) => this.onKeyDown('start', e)}
@@ -361,12 +452,14 @@ export class LyraTimeRange extends LyraElement {
361
452
  <div
362
453
  part="handle-end"
363
454
  role="slider"
364
- tabindex=${this.disabled ? '-1' : '0'}
365
- aria-label="Range end"
366
- aria-disabled=${this.disabled ? 'true' : nothing}
455
+ tabindex=${this.effectiveDisabled ? '-1' : '0'}
456
+ aria-label=${this.endLabel}
457
+ aria-disabled=${this.effectiveDisabled ? 'true' : nothing}
367
458
  aria-valuemin=${endBounds.min}
368
459
  aria-valuemax=${endBounds.max}
369
- aria-valuenow=${isNaN(this.end) ? nothing : this.end}
460
+ aria-valuenow=${Number.isFinite(this.end)
461
+ ? finiteRange(this.end, endBounds.min, endBounds.min, endBounds.max)
462
+ : nothing}
370
463
  style=${`inset-inline-start:${endPct}%`}
371
464
  @pointerdown=${(e) => this.onPointerDown('end', e)}
372
465
  @keydown=${(e) => this.onKeyDown('end', e)}
@@ -392,8 +485,11 @@ __decorate([
392
485
  property({ type: Number })
393
486
  ], LyraTimeRange.prototype, "step", void 0);
394
487
  __decorate([
395
- property({ type: Boolean, reflect: true })
396
- ], LyraTimeRange.prototype, "disabled", void 0);
488
+ property({ attribute: 'start-label' })
489
+ ], LyraTimeRange.prototype, "startLabel", void 0);
490
+ __decorate([
491
+ property({ attribute: 'end-label' })
492
+ ], LyraTimeRange.prototype, "endLabel", void 0);
397
493
  __decorate([
398
494
  property({ attribute: false })
399
495
  ], LyraTimeRange.prototype, "presets", void 0);
@@ -34,12 +34,12 @@ export declare class LyraToastItem extends LyraElement {
34
34
  private startedAt;
35
35
  private timerStarted;
36
36
  private showRafId?;
37
- private showAnimTimer?;
38
- private hideAnimTimer?;
39
- private resolvePendingHide?;
37
+ private cancelShowAnimation?;
38
+ private cancelHideAnimation?;
40
39
  private hovering;
41
40
  private focused;
42
41
  private hiding;
42
+ protected willUpdate(changed: PropertyValues): void;
43
43
  protected updated(changed: PropertyValues): void;
44
44
  firstUpdated(): void;
45
45
  disconnectedCallback(): void;
@@ -47,6 +47,15 @@ export declare class LyraToastItem extends LyraElement {
47
47
  private resumeTimer;
48
48
  private pauseTimer;
49
49
  private clearTimer;
50
+ /**
51
+ * Wait for the actual CSS transition/animation on the toast surface. The
52
+ * computed duration is intentionally read at runtime so a consumer's
53
+ * `--lyra-toast-show-duration`/`--lyra-toast-hide-duration` override keeps
54
+ * lifecycle events and removal in sync with the pixels on screen. A small
55
+ * timeout remains as a safety net for zero-duration transitions, disabled
56
+ * animation, and browsers that do not dispatch an end event.
57
+ */
58
+ private waitForVisualCompletion;
50
59
  private onPointerEnter;
51
60
  private onPointerLeave;
52
61
  private onFocusIn;
@@ -10,23 +10,19 @@ import { LyraElement } from '../../internal/lyra-element.js';
10
10
  import { defineElement } from '../../internal/prefix.js';
11
11
  import { closeIcon } from '../../internal/icons.js';
12
12
  import { styles } from './toast-item.styles.js';
13
- // Must stay in sync with --lyra-transition-base's 180ms fallback
14
- // (internal/tokens.styles.ts) — toast-item.styles.ts derives
15
- // --show-duration/--hide-duration from that same token. Kept as a literal
16
- // here rather than read via getComputedStyle() because the token's value is
17
- // a full transition shorthand ("180ms ease-out"), not a bare duration, and
18
- // parsing that back out in JS for a single setTimeout would be more fragile
19
- // than this documented literal.
20
- const ANIM_MS = 180;
21
13
  function prefersReducedMotion() {
22
14
  return typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
23
15
  }
24
- // toast-item.styles.ts already collapses the CSS transition itself to ~0 under
25
- // reduced motion; this keeps the lyra-after-show/lyra-after-hide events (and
26
- // the DOM removal in hide()) from still waiting out the full duration of an
27
- // animation nothing is visibly playing.
28
- function animMs() {
29
- return prefersReducedMotion() ? 0 : ANIM_MS;
16
+ function parseTime(value) {
17
+ const trimmed = value.trim();
18
+ if (trimmed.endsWith('ms'))
19
+ return Number.parseFloat(trimmed);
20
+ if (trimmed.endsWith('s'))
21
+ return Number.parseFloat(trimmed) * 1000;
22
+ return 0;
23
+ }
24
+ function maxCssTime(value) {
25
+ return Math.max(0, ...value.split(',').map(parseTime).filter(Number.isFinite));
30
26
  }
31
27
  /**
32
28
  * `<lyra-toast-item>` — a single toast notification.
@@ -117,14 +113,7 @@ export class LyraToastItem extends LyraElement {
117
113
  };
118
114
  }
119
115
  static { this.styles = [LyraElement.styles, styles]; }
120
- updated(changed) {
121
- if (changed.has('variant')) {
122
- // Assertive for actionable severities, polite otherwise. Re-evaluated
123
- // on every `variant` change (not just the first render) so a toast
124
- // reassigned to `danger`/`warning` after creation is announced as an
125
- // interruption instead of keeping its original, now-stale role.
126
- this.setAttribute('role', this.variant === 'danger' || this.variant === 'warning' ? 'alert' : 'status');
127
- }
116
+ willUpdate(changed) {
128
117
  // `elapsedMs`/`duration` are re-read fresh every time the timer is
129
118
  // (re)scheduled, so a `duration` change while paused (hovering/focused)
130
119
  // or before the timer has ever started needs no action here -- the next
@@ -133,11 +122,28 @@ export class LyraToastItem extends LyraElement {
133
122
  // not only while a timer is already actively counting down -- this also
134
123
  // covers duration flipping from disabled (0/Infinity) back to a positive
135
124
  // value, which previously never had `this.timer !== undefined` to gate on.
125
+ //
126
+ // This runs in willUpdate() (before render), not updated(), because a
127
+ // duration shortened below the already-elapsed time makes resumeTimer()
128
+ // call hide(), which sets the `hiding` state property synchronously --
129
+ // doing that from updated() sets a reactive property after Lit considers
130
+ // the update cycle finished, scheduling a redundant extra render pass.
131
+ // willUpdate() runs before that cycle is considered complete, so the same
132
+ // set just folds into the render already in progress.
136
133
  if (changed.has('duration') && this.timerStarted && !this.hovering && !this.focused) {
137
134
  this.pauseTimer();
138
135
  this.resumeTimer();
139
136
  }
140
137
  }
138
+ updated(changed) {
139
+ if (changed.has('variant')) {
140
+ // Assertive for actionable severities, polite otherwise. Re-evaluated
141
+ // on every `variant` change (not just the first render) so a toast
142
+ // reassigned to `danger`/`warning` after creation is announced as an
143
+ // interruption instead of keeping its original, now-stale role.
144
+ this.setAttribute('role', this.variant === 'danger' || this.variant === 'warning' ? 'alert' : 'status');
145
+ }
146
+ }
141
147
  firstUpdated() {
142
148
  this.showRafId = requestAnimationFrame(() => {
143
149
  this.showRafId = undefined;
@@ -148,10 +154,10 @@ export class LyraToastItem extends LyraElement {
148
154
  return;
149
155
  this.setAttribute('data-visible', '');
150
156
  this.emit('lyra-show');
151
- this.showAnimTimer = window.setTimeout(() => {
152
- this.showAnimTimer = undefined;
153
- this.emit('lyra-after-show');
154
- }, animMs());
157
+ void this.waitForVisualCompletion('show').then(() => {
158
+ if (this.isConnected && !this.hiding)
159
+ this.emit('lyra-after-show');
160
+ });
155
161
  this.startTimer();
156
162
  });
157
163
  }
@@ -161,20 +167,10 @@ export class LyraToastItem extends LyraElement {
161
167
  cancelAnimationFrame(this.showRafId);
162
168
  this.showRafId = undefined;
163
169
  }
164
- if (this.showAnimTimer !== undefined) {
165
- clearTimeout(this.showAnimTimer);
166
- this.showAnimTimer = undefined;
167
- }
168
- if (this.hideAnimTimer !== undefined) {
169
- clearTimeout(this.hideAnimTimer);
170
- this.hideAnimTimer = undefined;
171
- // hide()'s own promise is awaiting this timer firing to resolve() --
172
- // a disconnect (e.g. the toast's ancestor is removed from the DOM,
173
- // bypassing this.remove()) must still let that promise settle instead
174
- // of leaving it pending forever.
175
- this.resolvePendingHide?.();
176
- this.resolvePendingHide = undefined;
177
- }
170
+ this.cancelShowAnimation?.();
171
+ this.cancelShowAnimation = undefined;
172
+ this.cancelHideAnimation?.();
173
+ this.cancelHideAnimation = undefined;
178
174
  this.clearTimer();
179
175
  }
180
176
  startTimer() {
@@ -187,6 +183,53 @@ export class LyraToastItem extends LyraElement {
187
183
  clearTimeout(this.timer);
188
184
  this.timer = undefined;
189
185
  }
186
+ /**
187
+ * Wait for the actual CSS transition/animation on the toast surface. The
188
+ * computed duration is intentionally read at runtime so a consumer's
189
+ * `--lyra-toast-show-duration`/`--lyra-toast-hide-duration` override keeps
190
+ * lifecycle events and removal in sync with the pixels on screen. A small
191
+ * timeout remains as a safety net for zero-duration transitions, disabled
192
+ * animation, and browsers that do not dispatch an end event.
193
+ */
194
+ waitForVisualCompletion(kind) {
195
+ const previous = kind === 'show' ? this.cancelShowAnimation : this.cancelHideAnimation;
196
+ previous?.();
197
+ const surface = this.shadowRoot?.querySelector('[part="toast-item"]');
198
+ if (!surface || prefersReducedMotion())
199
+ return Promise.resolve();
200
+ const computed = getComputedStyle(surface);
201
+ const transitionMs = maxCssTime(computed.transitionDuration) + maxCssTime(computed.transitionDelay);
202
+ const animationMs = maxCssTime(computed.animationDuration) + maxCssTime(computed.animationDelay);
203
+ const fallbackMs = Math.max(transitionMs, animationMs);
204
+ if (fallbackMs <= 0)
205
+ return Promise.resolve();
206
+ return new Promise((resolve) => {
207
+ let settled = false;
208
+ let timeout;
209
+ const cancelKey = kind === 'show' ? 'cancelShowAnimation' : 'cancelHideAnimation';
210
+ const finish = () => {
211
+ if (settled)
212
+ return;
213
+ settled = true;
214
+ if (timeout !== undefined)
215
+ window.clearTimeout(timeout);
216
+ surface.removeEventListener('transitionend', onEnd);
217
+ surface.removeEventListener('animationend', onEnd);
218
+ if (this[cancelKey] === cancel)
219
+ this[cancelKey] = undefined;
220
+ resolve();
221
+ };
222
+ const onEnd = (event) => {
223
+ if (event.target === surface)
224
+ finish();
225
+ };
226
+ const cancel = () => finish();
227
+ surface.addEventListener('transitionend', onEnd);
228
+ surface.addEventListener('animationend', onEnd);
229
+ timeout = window.setTimeout(finish, fallbackMs + 50);
230
+ this[cancelKey] = cancel;
231
+ });
232
+ }
190
233
  // A stack of several simultaneously-open toasts otherwise gives every
191
234
  // close button the same bare "Close" label, so screen-reader/switch-access
192
235
  // users can't tell which toast a given button dismisses without first
@@ -215,22 +258,16 @@ export class LyraToastItem extends LyraElement {
215
258
  if (this.hiding)
216
259
  return;
217
260
  this.hiding = true;
261
+ this.cancelShowAnimation?.();
262
+ this.cancelShowAnimation = undefined;
218
263
  this.clearTimer();
219
264
  this.emit('lyra-hide');
220
265
  this.removeAttribute('data-visible');
221
- await new Promise((resolve) => {
222
- this.resolvePendingHide = resolve;
223
- this.hideAnimTimer = window.setTimeout(() => {
224
- this.hideAnimTimer = undefined;
225
- this.resolvePendingHide = undefined;
226
- resolve();
227
- }, animMs());
228
- });
229
- // An out-of-band disconnect (see disconnectedCallback) resolves this same
230
- // promise so callers awaiting hide() never hang, but it does so *without*
231
- // this having reached its normal completion -- guard against then still
232
- // emitting a "finished hiding" event (and redundantly calling remove())
233
- // for a node that's already been torn down some other way.
266
+ this.setAttribute('data-hiding', '');
267
+ await this.waitForVisualCompletion('hide');
268
+ // A disconnect cancels the visual wait, but it does so without this
269
+ // reaching its normal completion -- guard against emitting a finished
270
+ // event or redundantly removing a node already torn down elsewhere.
234
271
  if (!this.isConnected)
235
272
  return;
236
273
  this.emit('lyra-after-hide');