@fusion.dev/helix 0.32.0 → 0.33.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 (33) hide show
  1. package/custom-elements.json +1351 -169
  2. package/dist/aura/root/aura-root.css.js +4 -0
  3. package/dist/aura/root/aura-root.d.ts +64 -1
  4. package/dist/aura/root/aura-root.js +59 -4
  5. package/dist/bar-chart/root/bar-chart-root.css.js +4 -0
  6. package/dist/breadcrumbs/breadcrumb/breadcrumb.css.js +0 -4
  7. package/dist/carousel/root/carousel-root.css.js +4 -7
  8. package/dist/color-picker/root/color-picker-root.css.js +13 -3
  9. package/dist/date-picker/root/date-picker-root.css.js +0 -2
  10. package/dist/donut-chart/root/donut-chart-root.css.js +8 -0
  11. package/dist/drilldown/item/drilldown-item.css.js +0 -4
  12. package/dist/drilldown/root/drilldown-root.css.js +0 -4
  13. package/dist/heatmap/root/heatmap-root.d.ts +8 -0
  14. package/dist/menu/item/menu-item.css.js +0 -4
  15. package/dist/menu/root/menu-root.css.js +0 -4
  16. package/dist/object-inspector/root/object-inspector-root.css.js +0 -4
  17. package/dist/orb/root/orb-root.css.js +4 -0
  18. package/dist/orb/root/orb-root.d.ts +23 -0
  19. package/dist/orb/root/orb-root.js +28 -1
  20. package/dist/pagination/root/pagination-root.css.js +0 -4
  21. package/dist/pin-input/root/pin-input-root.css.js +1 -0
  22. package/dist/scatter-chart/root/scatter-chart-root.css.js +7 -2
  23. package/dist/scroll-area/root/scroll-area-root.css.js +9 -2
  24. package/dist/shared/chart/terminus.css.js +8 -0
  25. package/dist/slider/root/slider-root.css.js +43 -7
  26. package/dist/table/cell/table-cell.css.js +6 -1
  27. package/dist/tooltip/root/tooltip-root.css.js +16 -32
  28. package/dist/tour/root/tour-root.css.js +3 -2
  29. package/dist/tour/stop/tour-stop.css.js +13 -17
  30. package/dist/tree/item/tree-item.css.js +4 -0
  31. package/package.json +6 -6
  32. package/types/jsx.d.ts +1524 -636
  33. package/types/vue.d.ts +52 -20
@@ -27,6 +27,10 @@ inline-size: 100%;
27
27
  block-size: 100%;
28
28
  }
29
29
 
30
+ [part="canvas"][hidden] {
31
+ display: none;
32
+ }
33
+
30
34
  @media print {
31
35
  :host {
32
36
  display: none;
@@ -20,7 +20,9 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
20
20
  * from `gl_VertexID`, so there is no attribute, no buffer and no upload.
21
21
  *
22
22
  * **It stops when nobody is looking**, off screen and in a background tab, and
23
- * `prefers-reduced-motion` gets one still frame rather than an empty box.
23
+ * `prefers-reduced-motion` gets one still frame rather than an empty box. A
24
+ * context the browser takes back, on a GPU reset or past its cap of about
25
+ * sixteen, stops it too, and the gradient shows until the context returns.
24
26
  *
25
27
  * Styled only, and with behavior, which the tier split does not otherwise
26
28
  * allow: the behavior is drawing to a canvas, so there is no structure a
@@ -218,6 +220,11 @@ export declare class HelixAura extends HelixElement {
218
220
  private readonly eased;
219
221
  private motion;
220
222
  private visible;
223
+ /**
224
+ * What the intersection observer last said, kept because it says so only on
225
+ * a change: every other path to `play` has to ask it.
226
+ */
227
+ private onScreen;
221
228
  /**
222
229
  * The element's box, cached.
223
230
  *
@@ -286,8 +293,21 @@ export declare class HelixAura extends HelixElement {
286
293
  * event default-prevented and the extension held from before the loss, and it
287
294
  * restores asynchronously, so `start` would have to become a state machine to
288
295
  * use it. A fresh canvas is synchronous and cannot half-succeed.
296
+ *
297
+ * That is a context this element released. One the browser takes is not
298
+ * spent: `handleLost` asks for it back, and `start` waits for it rather than
299
+ * coming here.
289
300
  */
290
301
  private build;
302
+ /**
303
+ * The program and where its uniforms live, on the context already held.
304
+ *
305
+ * Apart from `build` because a restored context comes here alone. It is the
306
+ * same object the browser took, with every program and location it had made
307
+ * gone, and `build` would replace a canvas that had been lost again before
308
+ * this ran rather than wait for it a second time.
309
+ */
310
+ private prepare;
291
311
  connectedCallback(): void;
292
312
  protected updated(): void;
293
313
  disconnectedCallback(): void;
@@ -308,6 +328,35 @@ export declare class HelixAura extends HelixElement {
308
328
  * fresh one.
309
329
  */
310
330
  private readonly drop;
331
+ /**
332
+ * Stops for a context the browser has taken, and asks for it back.
333
+ *
334
+ * **Taken on a GPU reset, and past the cap** of about sixteen, where the
335
+ * browser drops the oldest context for the newest while its aura is still on
336
+ * screen. `tick` books its own next frame, so the loop went on at 30 frames a
337
+ * second against a context that draws nothing, and nothing made the program
338
+ * again once the context came back: the aura never recovered.
339
+ *
340
+ * **The canvas is hidden until then, because a lost one is not reliably
341
+ * transparent.** Measured 15 Sep 2026: headless Chromium painted a plain
342
+ * canvas white with a broken-image glyph in its corner, force-lost and on a
343
+ * crashed GPU process alike, while this one came out clear there and on
344
+ * Metal, and the landing page once held a spent one's last frame (`build`
345
+ * has it). Hidden, it shows what a machine with no WebGL is shown, the host's
346
+ * gradient, whatever the engine does with a lost canvas.
347
+ *
348
+ * `preventDefault` is the asking: a browser restores only a context whose
349
+ * loss was canceled.
350
+ */
351
+ private readonly handleLost;
352
+ /**
353
+ * Draws again on the context the browser gave back.
354
+ *
355
+ * Everything made on it went with the loss, so the program is linked again
356
+ * and the buffer measured as though it were new, which is what gives the new
357
+ * program its resolution.
358
+ */
359
+ private readonly handleRestored;
311
360
  /**
312
361
  * Resolves the ramp. Called when the palette moves, not when a frame draws.
313
362
  *
@@ -368,6 +417,20 @@ export declare class HelixAura extends HelixElement {
368
417
  */
369
418
  private static readonly CAPPED;
370
419
  private readonly tick;
420
+ /**
421
+ * Starts the loop, where it is wanted and can be ended.
422
+ *
423
+ * **Never while detached.** `tick` books its own next frame, so the only
424
+ * thing that ends the loop is `stop`, and a disconnection is the last time
425
+ * `stop` is called. Everything that plays is canceled or disconnected there,
426
+ * and this holds for whatever callback was already on its way.
427
+ *
428
+ * **Nor off screen, whoever asks.** The observer is the one caller that
429
+ * knows, and a tab coming back, a resize and a restored context all play
430
+ * too. An aura 2600 pixels below the fold booked no frame at rest and one on
431
+ * every frame after its tab came back, until it had been scrolled in and out
432
+ * again. Nor on a lost context, which draws nothing.
433
+ */
371
434
  private play;
372
435
  private stop;
373
436
  /**
@@ -52,6 +52,7 @@ let HelixAura = class HelixAura extends HelixElement {
52
52
  this.head = { x: 1, y: 1 };
53
53
  this.target = { presence: 0, x: 0, y: 0 };
54
54
  this.eased = { presence: 0, x: 0, y: 0 };
55
+ this.onScreen = false;
55
56
  this.box = { height: 1, left: 0, top: 0, width: 1 };
56
57
  this.opened = false;
57
58
  this.opening = 0;
@@ -65,6 +66,31 @@ let HelixAura = class HelixAura extends HelixElement {
65
66
  this.program = null;
66
67
  this.gl = null;
67
68
  };
69
+ this.handleLost = (event) => {
70
+ event.preventDefault();
71
+ this.stop();
72
+ if (this.opening) {
73
+ cancelAnimationFrame(this.opening);
74
+ this.opening = 0;
75
+ }
76
+ this.program = null;
77
+ this.canvas?.toggleAttribute('hidden', true);
78
+ };
79
+ this.handleRestored = () => {
80
+ if (!this.prepare()) {
81
+ return;
82
+ }
83
+ this.width = 0;
84
+ this.height = 0;
85
+ this.measure();
86
+ if (this.opened) {
87
+ this.paint();
88
+ this.play();
89
+ }
90
+ else if (this.width) {
91
+ this.open();
92
+ }
93
+ };
68
94
  this.tick = (now) => {
69
95
  if (!this.last) {
70
96
  this.last = now;
@@ -134,17 +160,27 @@ let HelixAura = class HelixAura extends HelixElement {
134
160
  this.start();
135
161
  }
136
162
  start() {
163
+ if (!this.isConnected) {
164
+ return;
165
+ }
137
166
  if (!this.renderRoot.querySelector('canvas')) {
138
167
  return;
139
168
  }
140
- if (!this.program && !this.build()) {
169
+ if (this.program && this.gl?.isContextLost()) {
170
+ this.program = null;
171
+ this.gl = null;
172
+ }
173
+ if (!this.program && !this.gl?.isContextLost() && !this.build()) {
141
174
  return;
142
175
  }
176
+ this.canvas?.addEventListener('webglcontextlost', this.handleLost);
177
+ this.canvas?.addEventListener('webglcontextrestored', this.handleRestored);
143
178
  this.motion = window.matchMedia('(prefers-reduced-motion: reduce)');
144
179
  this.addEventListener('pointermove', this.handlePointer);
145
180
  this.addEventListener('pointerleave', this.handleLeave);
146
181
  this.visible = new IntersectionObserver(([entry]) => {
147
- if (entry?.isIntersecting) {
182
+ this.onScreen = entry?.isIntersecting ?? false;
183
+ if (this.onScreen) {
148
184
  this.play();
149
185
  }
150
186
  else {
@@ -168,7 +204,7 @@ let HelixAura = class HelixAura extends HelixElement {
168
204
  });
169
205
  this.sized.observe(this);
170
206
  this.readPalette();
171
- if (this.measure()) {
207
+ if (this.measure() || (this.width && !this.opened)) {
172
208
  this.open();
173
209
  }
174
210
  }
@@ -193,10 +229,18 @@ let HelixAura = class HelixAura extends HelixElement {
193
229
  if (!gl || gl.isContextLost()) {
194
230
  return false;
195
231
  }
232
+ return this.prepare();
233
+ }
234
+ prepare() {
235
+ const gl = this.gl;
236
+ if (!gl) {
237
+ return false;
238
+ }
196
239
  this.program = this.link(VERTEX, FRAGMENT);
197
240
  if (!this.program) {
198
241
  return false;
199
242
  }
243
+ this.canvas?.toggleAttribute('hidden', false);
200
244
  gl.useProgram(this.program);
201
245
  for (const name of [
202
246
  'uBloom',
@@ -237,8 +281,16 @@ let HelixAura = class HelixAura extends HelixElement {
237
281
  disconnectedCallback() {
238
282
  super.disconnectedCallback();
239
283
  this.stop();
284
+ if (this.opening) {
285
+ cancelAnimationFrame(this.opening);
286
+ this.opening = 0;
287
+ this.openBy = 0;
288
+ }
240
289
  this.visible?.disconnect();
241
290
  this.visible = undefined;
291
+ this.onScreen = false;
292
+ this.canvas?.removeEventListener('webglcontextlost', this.handleLost);
293
+ this.canvas?.removeEventListener('webglcontextrestored', this.handleRestored);
242
294
  this.removeEventListener('pointermove', this.handlePointer);
243
295
  this.removeEventListener('pointerleave', this.handleLeave);
244
296
  document.removeEventListener('visibilitychange', this.handleVisibility);
@@ -383,7 +435,7 @@ let HelixAura = class HelixAura extends HelixElement {
383
435
  this.opening = requestAnimationFrame(draw);
384
436
  }
385
437
  paintOnce() {
386
- if (this.frame) {
438
+ if (this.frame || !this.opened || !this.isConnected) {
387
439
  return;
388
440
  }
389
441
  this.paint();
@@ -391,9 +443,12 @@ let HelixAura = class HelixAura extends HelixElement {
391
443
  static { this.CAPPED = 1000 / 30; }
392
444
  play() {
393
445
  if (this.frame ||
446
+ !this.isConnected ||
447
+ !this.onScreen ||
394
448
  this.motion?.matches ||
395
449
  document.hidden ||
396
450
  !this.gl ||
451
+ this.gl.isContextLost() ||
397
452
  !this.width ||
398
453
  !this.opened) {
399
454
  return;
@@ -220,6 +220,10 @@ translate: 0 -50%;
220
220
  translate: -50% 0;
221
221
  }
222
222
 
223
+ :host([direction="horizontal"]:dir(rtl)) [part="tick"] {
224
+ translate: 50% 0;
225
+ }
226
+
223
227
  :host(:not([direction="horizontal"])) [part="axis"][data-axis="secondary"] [part="tick"] {
224
228
  inset-inline-end: auto;
225
229
  inset-inline-start: 0;
@@ -68,10 +68,6 @@ font-weight: var(--helix-weight-medium);
68
68
  color: var(--helix-color-foreground);
69
69
  }
70
70
 
71
- :host(:dir(rtl)) [part="separator"] svg {
72
- scale: -1 1;
73
- }
74
-
75
71
  [part="link"] {
76
72
  align-items: center;
77
73
  border-radius: var(--helix-radii-sm);
@@ -199,9 +199,11 @@ scroll-snap-type: y mandatory;
199
199
  [part="slide"] {
200
200
  display: flex;
201
201
  flex: 0 0
202
- calc(
202
+ round(
203
+ up,
203
204
  (100% - (var(--_carousel-per-view) - 1) * var(--_carousel-gap)) /
204
- var(--_carousel-per-view)
205
+ var(--_carousel-per-view),
206
+ 1px
205
207
  );
206
208
  scroll-snap-align: start;
207
209
  }
@@ -312,11 +314,6 @@ margin-inline-start: auto;
312
314
  order: 1;
313
315
  }
314
316
 
315
- :host(:dir(rtl)) [part="previous"] svg,
316
- :host(:dir(rtl)) [part="next"] svg {
317
- scale: -1 1;
318
- }
319
-
320
317
  :host([orientation="vertical"]:not([indicator-placement])) [part="indicators"] {
321
318
  align-self: var(--_carousel-indicators-along);
322
319
  flex-direction: column;
@@ -1,9 +1,15 @@
1
1
  import { createSheet } from '@fusion.dev/helix-core';
2
2
  export const styles = createSheet(`:host {
3
+
4
+ --_color-picker-along: to right;
3
5
  display: block;
4
6
  padding-block-end: var(--color-picker-padding, var(--helix-space-none));
5
7
  }
6
8
 
9
+ :host(:dir(rtl)) {
10
+ --_color-picker-along: to left;
11
+ }
12
+
7
13
  [part="sample"],
8
14
  [part="swatch"] {
9
15
  background-image:
@@ -29,7 +35,7 @@ box-sizing: border-box;
29
35
  aspect-ratio: var(--color-picker-grid-ratio, 1 / 0.7);
30
36
  background-image:
31
37
  linear-gradient(to top, hsl(0 0% 0%), transparent),
32
- linear-gradient(to right, hsl(0 0% 100%), transparent),
38
+ linear-gradient(var(--_color-picker-along), hsl(0 0% 100%), transparent),
33
39
  linear-gradient(hsl(var(--color-picker-hue) 100% 50%), hsl(var(--color-picker-hue) 100% 50%));
34
40
  border-radius: var(--color-picker-grid-radius, var(--helix-radii-sm));
35
41
  cursor: crosshair;
@@ -50,6 +56,10 @@ position: absolute;
50
56
  translate: -50% -50%;
51
57
  }
52
58
 
59
+ :host(:dir(rtl)) [part="grid-handle"] {
60
+ translate: 50% -50%;
61
+ }
62
+
53
63
  [part="grid-saturation"],
54
64
  [part="grid-brightness"] {
55
65
  appearance: none;
@@ -135,7 +145,7 @@ white-space: nowrap;
135
145
 
136
146
  [part="channel"][data-channel="hue"] {
137
147
  --slider-track-image: linear-gradient(
138
- to right,
148
+ var(--_color-picker-along),
139
149
  hsl(0 100% 50%),
140
150
  hsl(60 100% 50%),
141
151
  hsl(120 100% 50%),
@@ -148,7 +158,7 @@ hsl(360 100% 50%)
148
158
 
149
159
  [part="channel"][data-channel="alpha"] {
150
160
  --slider-track-image:
151
- linear-gradient(to right, transparent, hsl(var(--color-picker-hue) 100% 50%)),
161
+ linear-gradient(var(--_color-picker-along), transparent, hsl(var(--color-picker-hue) 100% 50%)),
152
162
  conic-gradient(
153
163
  var(--helix-color-surface-sunken) 0 25%,
154
164
  var(--helix-color-surface) 0 50%,
@@ -177,8 +177,6 @@ outline: var(--helix-size-focus-ring) solid var(--helix-color-focus-ring);
177
177
  outline-offset: var(--helix-size-focus-offset);
178
178
  }
179
179
 
180
- :host(:dir(rtl)) [part="previous"] svg,
181
- :host(:dir(rtl)) [part="next"] svg,
182
180
  :host(:dir(rtl)) ::slotted([slot="previous-icon"]),
183
181
  :host(:dir(rtl)) ::slotted([slot="next-icon"]) {
184
182
  scale: -1 1;
@@ -128,6 +128,14 @@ block-size: calc(
128
128
  font-size: 6.5cqi;
129
129
  }
130
130
 
131
+ :host(:dir(rtl)) [part="center"] {
132
+ translate: 50% -50%;
133
+ }
134
+
135
+ :host([sweep="half"]:dir(rtl)) [part="center"] {
136
+ translate: 50% 0;
137
+ }
138
+
131
139
  @media print {
132
140
  :host {
133
141
  print-color-adjust: exact;
@@ -34,10 +34,6 @@ flex: none;
34
34
  inline-size: var(--helix-size-icon-tight);
35
35
  }
36
36
 
37
- :host(:dir(rtl)) [part="mark"] {
38
- scale: -1 1;
39
- }
40
-
41
37
  :host(:not([disabled])) [part="item"]:hover {
42
38
  background-color: var(
43
39
  --drilldown-item-hover-color,
@@ -98,10 +98,6 @@ animation: none;
98
98
  }
99
99
  }
100
100
 
101
- :host(:dir(rtl)) [part="back"] svg {
102
- scale: -1 1;
103
- }
104
-
105
101
  :host([loading]) [part="page"] {
106
102
  opacity: 0.5;
107
103
  }
@@ -34,9 +34,17 @@ import '../../tooltip/index.js';
34
34
  * @csspart tooltip - The bubble, forwarded from `Tooltip`.
35
35
  * @csspart tooltip-anchor - The `Tooltip` element itself.
36
36
  * @csspart tooltip-arrow - The bubble's point, forwarded from `Tooltip`.
37
+ * @csspart tooltip-arrow-bottom - The point while the bubble is below the reading, so on its top edge, beside `tooltip-arrow`.
38
+ * @csspart tooltip-arrow-left - The point while the bubble is left of the reading, so on its right edge, beside `tooltip-arrow`.
39
+ * @csspart tooltip-arrow-right - The point while the bubble is right of the reading, so on its left edge, beside `tooltip-arrow`.
40
+ * @csspart tooltip-arrow-top - The point while the bubble is above the reading, so on its bottom edge, beside `tooltip-arrow`.
41
+ * @csspart tooltip-bottom - The bubble while it is below the reading, beside `tooltip`.
37
42
  * @csspart tooltip-cell - One column's name and value inside the tooltip.
38
43
  * @csspart tooltip-label - The reading's own name inside the tooltip.
44
+ * @csspart tooltip-left - The bubble while it is left of the reading, beside `tooltip`.
39
45
  * @csspart tooltip-name - A column's name inside the tooltip.
46
+ * @csspart tooltip-right - The bubble while it is right of the reading, beside `tooltip`.
47
+ * @csspart tooltip-top - The bubble while it is above the reading, beside `tooltip`.
40
48
  * @csspart tooltip-value - A column's value inside the tooltip.
41
49
  *
42
50
  * @cssproperty --heatmap-cell-height - How tall a cell is. A row of a tall thin cell is what a status page draws.
@@ -100,8 +100,4 @@ display: contents;
100
100
 
101
101
  slot[name="submenu"]::slotted(helix-divider) {
102
102
  margin-block: var(--helix-space-2xs);
103
- }
104
-
105
- :host(:dir(rtl)) [part="mark"][data-submenu] {
106
- scale: -1 1;
107
103
  }`);
@@ -100,10 +100,6 @@ margin-block: var(--helix-space-2xs);
100
100
  display: none;
101
101
  }
102
102
 
103
- :host(:dir(rtl)) [part="back"] svg {
104
- scale: -1 1;
105
- }
106
-
107
103
  :host([loading]) [part="surface"] {
108
104
  opacity: 0.5;
109
105
  }
@@ -98,10 +98,6 @@ margin-inline-end: var(--helix-space-2xs);
98
98
  vertical-align: -0.125em;
99
99
  }
100
100
 
101
- :host(:dir(rtl)) [part~="row"][aria-expanded="false"] > [part="toggle"] {
102
- scale: -1 1;
103
- }
104
-
105
101
  [part="key"] {
106
102
  color: var(--helix-color-syntax-name);
107
103
  }
@@ -31,6 +31,10 @@ inline-size: 100%;
31
31
  text-decoration-color: var(--_orb-ground);
32
32
  }
33
33
 
34
+ [part="canvas"][hidden] {
35
+ display: none;
36
+ }
37
+
34
38
  @media print {
35
39
  :host {
36
40
  display: none;
@@ -92,6 +92,8 @@ export declare class HelixOrb extends HelixElement {
92
92
  private frame;
93
93
  private running;
94
94
  private onScreen;
95
+ /** Whether the browser has taken the canvas, as its own two events say. */
96
+ private lost;
95
97
  /** The box the canvas was last measured at, in CSS pixels. */
96
98
  private box;
97
99
  /**
@@ -150,6 +152,27 @@ export declare class HelixOrb extends HelixElement {
150
152
  private stop;
151
153
  private readonly tick;
152
154
  private readonly handleVisibility;
155
+ /**
156
+ * Stops for a canvas the browser has taken, on a GPU reset, until it is
157
+ * given back.
158
+ *
159
+ * **Not canceled, which is the aura's contract turned over.** A 2D context
160
+ * comes back on its own unless its loss is canceled, where a WebGL one comes
161
+ * back only if it is. Hidden meanwhile, because a lost canvas is not reliably
162
+ * transparent (the aura's `handleLost` has what was measured), and hidden it
163
+ * leaves the empty box a machine with no 2D context is shown.
164
+ */
165
+ private readonly handleLost;
166
+ /**
167
+ * Draws again on the canvas the browser gave back.
168
+ *
169
+ * **Sized again, because it comes back in its default state.** Found 15 Sep
170
+ * 2026 by crashing the GPU process under headless Chromium: the transform
171
+ * `measure` sets came back the identity, and `measure` sets it only when the
172
+ * box changes. At twice the density that is an orb drawn at half its size in
173
+ * the top left corner, for as long as the box holds still.
174
+ */
175
+ private readonly handleRestored;
153
176
  private readonly handleMotion;
154
177
  /**
155
178
  * One frame.
@@ -32,6 +32,7 @@ let HelixOrb = class HelixOrb extends HelixElement {
32
32
  this.frame = 0;
33
33
  this.running = false;
34
34
  this.onScreen = false;
35
+ this.lost = false;
35
36
  this.box = 0;
36
37
  this.from = paramsFor('thinking');
37
38
  this.morphAt = 0;
@@ -56,6 +57,19 @@ let HelixOrb = class HelixOrb extends HelixElement {
56
57
  this.play();
57
58
  }
58
59
  };
60
+ this.handleLost = () => {
61
+ this.lost = true;
62
+ this.stop();
63
+ this.canvas?.toggleAttribute('hidden', true);
64
+ };
65
+ this.handleRestored = () => {
66
+ this.lost = false;
67
+ this.canvas?.toggleAttribute('hidden', false);
68
+ this.box = 0;
69
+ this.measure();
70
+ this.paint(0);
71
+ this.play();
72
+ };
59
73
  this.handleMotion = () => {
60
74
  if (this.motion?.matches) {
61
75
  this.stop();
@@ -99,6 +113,10 @@ let HelixOrb = class HelixOrb extends HelixElement {
99
113
  disconnectedCallback() {
100
114
  super.disconnectedCallback();
101
115
  this.stop();
116
+ this.onScreen = false;
117
+ this.box = 0;
118
+ this.canvas?.removeEventListener('contextlost', this.handleLost);
119
+ this.canvas?.removeEventListener('contextrestored', this.handleRestored);
102
120
  this.visible?.disconnect();
103
121
  this.visible = undefined;
104
122
  this.sized?.disconnect();
@@ -108,6 +126,9 @@ let HelixOrb = class HelixOrb extends HelixElement {
108
126
  document.removeEventListener('visibilitychange', this.handleVisibility);
109
127
  }
110
128
  start() {
129
+ if (!this.isConnected) {
130
+ return;
131
+ }
111
132
  const canvas = this.renderRoot.querySelector('canvas');
112
133
  if (!canvas) {
113
134
  return;
@@ -117,6 +138,12 @@ let HelixOrb = class HelixOrb extends HelixElement {
117
138
  if (!this.context) {
118
139
  return;
119
140
  }
141
+ canvas.addEventListener('contextlost', this.handleLost);
142
+ canvas.addEventListener('contextrestored', this.handleRestored);
143
+ if (this.lost && !this.context.isContextLost()) {
144
+ this.lost = false;
145
+ canvas.toggleAttribute('hidden', false);
146
+ }
120
147
  this.motion ??= window.matchMedia('(prefers-reduced-motion: reduce)');
121
148
  this.motion.addEventListener('change', this.handleMotion);
122
149
  this.visible ??= new IntersectionObserver(([entry]) => {
@@ -178,7 +205,7 @@ let HelixOrb = class HelixOrb extends HelixElement {
178
205
  return blendStaged(this.from, to, smoother(k), smoother(Math.max(0, k - TRAIL) / (1 - TRAIL)));
179
206
  }
180
207
  play() {
181
- if (this.running || this.motion?.matches || !this.onScreen) {
208
+ if (this.running || this.motion?.matches || !this.onScreen || this.lost) {
182
209
  return;
183
210
  }
184
211
  this.running = true;
@@ -103,10 +103,6 @@ block-size: var(--helix-size-icon-tight);
103
103
  inline-size: var(--helix-size-icon-tight);
104
104
  }
105
105
 
106
- :host(:dir(rtl)) [part="first"] svg,
107
- :host(:dir(rtl)) [part="last"] svg,
108
- :host(:dir(rtl)) [part="previous"] svg,
109
- :host(:dir(rtl)) [part="next"] svg,
110
106
  :host(:dir(rtl)) ::slotted([slot="first-icon"]),
111
107
  :host(:dir(rtl)) ::slotted([slot="last-icon"]),
112
108
  :host(:dir(rtl)) ::slotted([slot="previous-icon"]),
@@ -6,6 +6,7 @@ gap: var(--helix-space-2xs);
6
6
  }
7
7
 
8
8
  [part="field"] {
9
+ align-self: start;
9
10
  display: flex;
10
11
  gap: var(--pin-input-gap, var(--helix-space-xs));
11
12
  }
@@ -1,7 +1,12 @@
1
1
  import { createSheet } from '@fusion.dev/helix-core';
2
2
  export const styles = createSheet(`:host {
3
- column-gap: var(--helix-space-xs);
4
3
  container-type: inline-size;
4
+ display: block;
5
+ position: relative;
6
+ }
7
+
8
+ [part="plane"] {
9
+ column-gap: var(--helix-space-xs);
5
10
  display: grid;
6
11
  grid-template-areas:
7
12
  "y-name y-name"
@@ -9,7 +14,6 @@ grid-template-areas:
9
14
  ". x-axis"
10
15
  ". x-name";
11
16
  grid-template-columns: auto minmax(0, 1fr);
12
- position: relative;
13
17
  row-gap: var(--scatter-chart-gap, var(--helix-space-xs));
14
18
  }
15
19
 
@@ -113,6 +117,7 @@ justify-self: end;
113
117
 
114
118
  [part="y-name"] {
115
119
  grid-area: y-name;
120
+ justify-self: start;
116
121
  }
117
122
 
118
123
  [part="summary"] {
@@ -59,11 +59,18 @@ opacity: 1;
59
59
  }
60
60
 
61
61
  [part="edges"] {
62
+ --_scroll-area-from-start: to right;
63
+ --_scroll-area-from-end: to left;
62
64
  inset: 0;
63
65
  pointer-events: none;
64
66
  position: absolute;
65
67
  }
66
68
 
69
+ :host(:dir(rtl)) [part="edges"] {
70
+ --_scroll-area-from-start: to left;
71
+ --_scroll-area-from-end: to right;
72
+ }
73
+
67
74
  [part="edges"]::before,
68
75
  [part="edges"]::after {
69
76
  content: "";
@@ -76,7 +83,7 @@ z-index: var(--helix-layer-raised);
76
83
 
77
84
  [part="edges"]::before {
78
85
  background: linear-gradient(
79
- to right,
86
+ var(--_scroll-area-from-start),
80
87
  var(--scroll-area-fade-color, var(--helix-color-background)),
81
88
  transparent
82
89
  );
@@ -86,7 +93,7 @@ inset-inline-start: 0;
86
93
 
87
94
  [part="edges"]::after {
88
95
  background: linear-gradient(
89
- to left,
96
+ var(--_scroll-area-from-end),
90
97
  var(--scroll-area-fade-color, var(--helix-color-background)),
91
98
  transparent
92
99
  );
@@ -10,6 +10,10 @@ position: absolute;
10
10
  translate: -50% -50%;
11
11
  }
12
12
 
13
+ :host(:dir(rtl)) [part="terminus"] {
14
+ translate: 50% -50%;
15
+ }
16
+
13
17
  [part="terminus"] {
14
18
  border-radius: var(--helix-radii-full);
15
19
  }
@@ -37,4 +41,8 @@ inline-size: calc(var(--chart-terminus-size, 5px) * 1.6);
37
41
  background-color: currentColor;
38
42
  border-radius: var(--helix-radii-none);
39
43
  clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
44
+ }
45
+
46
+ :host([terminus="triangle"]:dir(rtl)) [part="terminus"] {
47
+ clip-path: polygon(100% 0%, 0% 50%, 100% 100%);
40
48
  }`);