@fusion.dev/helix 0.32.0 → 0.33.1

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 (35) hide show
  1. package/custom-elements.json +1408 -173
  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 +19 -15
  8. package/dist/code-block/root/code-block-root.css.js +33 -6
  9. package/dist/code-block/root/code-block-root.d.ts +6 -3
  10. package/dist/color-picker/root/color-picker-root.css.js +13 -3
  11. package/dist/date-picker/root/date-picker-root.css.js +0 -2
  12. package/dist/donut-chart/root/donut-chart-root.css.js +8 -0
  13. package/dist/drilldown/item/drilldown-item.css.js +0 -4
  14. package/dist/drilldown/root/drilldown-root.css.js +0 -4
  15. package/dist/heatmap/root/heatmap-root.d.ts +8 -0
  16. package/dist/menu/item/menu-item.css.js +0 -4
  17. package/dist/menu/root/menu-root.css.js +0 -4
  18. package/dist/object-inspector/root/object-inspector-root.css.js +0 -4
  19. package/dist/orb/root/orb-root.css.js +4 -0
  20. package/dist/orb/root/orb-root.d.ts +23 -0
  21. package/dist/orb/root/orb-root.js +28 -1
  22. package/dist/pagination/root/pagination-root.css.js +0 -4
  23. package/dist/pin-input/root/pin-input-root.css.js +1 -0
  24. package/dist/scatter-chart/root/scatter-chart-root.css.js +7 -2
  25. package/dist/scroll-area/root/scroll-area-root.css.js +9 -2
  26. package/dist/shared/chart/terminus.css.js +8 -0
  27. package/dist/slider/root/slider-root.css.js +43 -7
  28. package/dist/table/cell/table-cell.css.js +6 -1
  29. package/dist/tooltip/root/tooltip-root.css.js +16 -32
  30. package/dist/tour/root/tour-root.css.js +3 -2
  31. package/dist/tour/stop/tour-stop.css.js +13 -17
  32. package/dist/tree/item/tree-item.css.js +4 -0
  33. package/package.json +6 -6
  34. package/types/jsx.d.ts +1542 -648
  35. package/types/vue.d.ts +53 -21
@@ -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);
@@ -2,7 +2,13 @@ import { createSheet } from '@fusion.dev/helix-core';
2
2
  export const styles = createSheet(`:host {
3
3
  --_carousel-bleed: var(--carousel-bleed, var(--helix-space-md));
4
4
  --_carousel-gap: var(--carousel-gap, var(--helix-space-md));
5
+
6
+ --_carousel-pane: calc(round(var(--_carousel-share) - 0.015625px, 0.03125px) + 0.015625px);
5
7
  --_carousel-per-view: 1;
8
+ --_carousel-share: calc(
9
+ (100% - (var(--_carousel-per-view) - 1) * var(--_carousel-gap)) /
10
+ var(--_carousel-per-view)
11
+ );
6
12
  }
7
13
 
8
14
  :host {
@@ -198,14 +204,21 @@ scroll-snap-type: y mandatory;
198
204
 
199
205
  [part="slide"] {
200
206
  display: flex;
201
- flex: 0 0
202
- calc(
203
- (100% - (var(--_carousel-per-view) - 1) * var(--_carousel-gap)) /
204
- var(--_carousel-per-view)
205
- );
207
+ flex: 0 0 var(--_carousel-pane);
206
208
  scroll-snap-align: start;
207
209
  }
208
210
 
211
+ :host(:not([orientation="vertical"])) [part="slide"]:last-child {
212
+ --_carousel-overrun: calc(
213
+ sibling-count() *
214
+ (var(--_carousel-pane) + var(--_carousel-gap)) -
215
+ var(--_carousel-gap) -
216
+ 100%
217
+ );
218
+ --_carousel-run-end: calc(round(var(--_carousel-overrun), 1px) - var(--_carousel-overrun));
219
+ margin-inline-end: var(--_carousel-run-end);
220
+ }
221
+
209
222
  [part="slide"] ::slotted(*) {
210
223
  flex: 1 1 0;
211
224
  min-block-size: 0;
@@ -214,11 +227,7 @@ min-inline-size: 0;
214
227
 
215
228
  :host([orientation="vertical"]) [part="slide"] {
216
229
  flex-direction: column;
217
- flex: 0 0
218
- calc(
219
- (100% - (var(--_carousel-per-view) - 1) * var(--_carousel-gap)) /
220
- var(--_carousel-per-view)
221
- );
230
+ flex: 0 0 var(--_carousel-share);
222
231
  }
223
232
 
224
233
  :host([per-view="2"]) {
@@ -312,11 +321,6 @@ margin-inline-start: auto;
312
321
  order: 1;
313
322
  }
314
323
 
315
- :host(:dir(rtl)) [part="previous"] svg,
316
- :host(:dir(rtl)) [part="next"] svg {
317
- scale: -1 1;
318
- }
319
-
320
324
  :host([orientation="vertical"]:not([indicator-placement])) [part="indicators"] {
321
325
  align-self: var(--_carousel-indicators-along);
322
326
  flex-direction: column;
@@ -28,16 +28,24 @@ border-end-start-radius: var(--helix-radii-none);
28
28
  border-start-end-radius: var(--code-block-radius, var(--helix-radii-lg));
29
29
  border-start-start-radius: var(--code-block-radius, var(--helix-radii-lg));
30
30
  display: flex;
31
+ gap: var(--helix-space-sm);
31
32
 
32
33
  padding-block: var(--helix-space-xs);
33
34
  padding-inline: var(--helix-space-md);
34
35
  }
35
36
 
37
+ [part="header"]:has(> [part="copy"]) {
38
+ padding-block: var(--helix-space-2xs);
39
+ padding-inline-end: var(--helix-space-2xs);
40
+ }
41
+
36
42
  [part="label"] {
37
43
  color: var(--helix-color-foreground-muted);
38
44
  font-family: var(--helix-font-sans);
39
45
  font-size: var(--helix-text-xs);
40
46
  font-weight: var(--helix-weight-medium);
47
+ min-inline-size: 0;
48
+ overflow-wrap: anywhere;
41
49
  }
42
50
 
43
51
  [part="frame"] {
@@ -50,6 +58,13 @@ block-size: 100%;
50
58
 
51
59
  [part="copy"] {
52
60
  --icon-button-size: var(--helix-size-control-sm);
61
+ }
62
+
63
+ [part="header"] > [part="copy"] {
64
+ margin-inline-start: auto;
65
+ }
66
+
67
+ [part="frame"] > [part="copy"] {
53
68
  inset-block-start: var(--_code-block-copy-inset);
54
69
  inset-inline-end: var(--_code-block-copy-inset);
55
70
  position: absolute;
@@ -67,7 +82,7 @@ padding-block: var(--helix-space-sm);
67
82
  padding-inline: var(--helix-space-md);
68
83
  }
69
84
 
70
- :host(:not([no-copy])) [part="block"] {
85
+ :host(:not([diff], [no-copy])) [part="frame"]:not([part="header"] + *) > [part="block"] {
71
86
  padding-inline-end: var(--_code-block-gutter);
72
87
  }
73
88
 
@@ -114,7 +129,13 @@ padding-block: var(--helix-space-none) var(--helix-space-sm);
114
129
  padding-inline: var(--helix-space-none);
115
130
  }
116
131
 
117
- [part="header"] + [part="frame"] [part="split"] > * {
132
+ :host([diff]:not([no-copy]))
133
+ [part="frame"]:not([part="header"] + *)
134
+ :is([part="block"], [part="old"], [part="new"]) {
135
+ padding-block-start: var(--_code-block-gutter);
136
+ }
137
+
138
+ [part="header"] + [part="frame"] :is([part="block"], [part="split"] > *) {
118
139
  border-block-start-width: var(--helix-space-none);
119
140
  border-start-end-radius: var(--helix-radii-none);
120
141
  border-start-start-radius: var(--helix-radii-none);
@@ -130,6 +151,16 @@ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
130
151
  [part="split"] {
131
152
  grid-template-columns: minmax(0, 1fr);
132
153
  }
154
+
155
+ [part="header"] + [part="frame"] [part="new"] {
156
+ border-block-start-width: var(--helix-border-thin);
157
+ border-start-end-radius: var(--code-block-radius, var(--helix-radii-lg));
158
+ border-start-start-radius: var(--code-block-radius, var(--helix-radii-lg));
159
+ }
160
+
161
+ :host([diff]:not([no-copy])) [part="frame"]:not([part="header"] + *) [part="new"] {
162
+ padding-block-start: var(--helix-space-none);
163
+ }
133
164
  }
134
165
 
135
166
  :host([diff]) [part="code"] {
@@ -147,10 +178,6 @@ padding-inline: var(--helix-space-md);
147
178
  text-decoration: none;
148
179
  }
149
180
 
150
- :host(:not([no-copy])) :is([part="hunk"], [part="context"], [part="added"], [part="removed"]) {
151
- padding-inline-end: var(--_code-block-gutter);
152
- }
153
-
154
181
  [part="hunk"] {
155
182
  background-color: var(--helix-color-surface-sunken);
156
183
  color: var(--helix-color-foreground-muted);
@@ -5,9 +5,12 @@ import '../../copy-button/index.js';
5
5
  * @summary A block of code, with somewhere to put its name and a way to take it.
6
6
  *
7
7
  * @description
8
- * The header sits above the code rather than floating on it. Overlaid, which is
9
- * the usual build, the copy button covers the first line, which is the one line
10
- * of a snippet a reader is most likely to be reading.
8
+ * The header sits above the code rather than floating on it, and the copy
9
+ * button stands at its end. Overlaid, which is the usual build, the button
10
+ * covers the first line, which is the one line of a snippet a reader is most
11
+ * likely to be reading. A block with no name has no header, and the button
12
+ * stands beside the code in room the block keeps clear: an end gutter for code,
13
+ * a band above the first hunk for a diff.
11
14
  *
12
15
  * A diff is drawn a block per file in the same frame, each changed line washed
13
16
  * in its tone at Tag's resting strength with its `+` or `-` in the tone's ink,
@@ -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.