@fusion.dev/helix 0.58.1 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +60 -196
- package/dist/aura/root/aura-root.d.ts +37 -49
- package/dist/aura/root/aura-root.js +53 -105
- package/dist/orb/root/orb-root.d.ts +10 -12
- package/dist/orb/root/orb-root.js +24 -78
- package/package.json +6 -6
- package/types/jsx.d.ts +64 -64
- package/types/vue.d.ts +16 -16
|
@@ -193,9 +193,7 @@ export declare class HelixAura extends HelixElement {
|
|
|
193
193
|
private release;
|
|
194
194
|
/** What every context is asked for, here rather than at two call sites. */
|
|
195
195
|
private static readonly CONTEXT;
|
|
196
|
-
private frame;
|
|
197
196
|
private elapsed;
|
|
198
|
-
private last;
|
|
199
197
|
private width;
|
|
200
198
|
private height;
|
|
201
199
|
/**
|
|
@@ -233,13 +231,16 @@ export declare class HelixAura extends HelixElement {
|
|
|
233
231
|
*/
|
|
234
232
|
private readonly target;
|
|
235
233
|
private readonly eased;
|
|
236
|
-
private motion;
|
|
237
|
-
private visible;
|
|
238
234
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
235
|
+
* Whether anybody is looking, and the loop that draws for them.
|
|
236
|
+
*
|
|
237
|
+
* **Four reasons to refuse a frame that the gate cannot know**: a context it
|
|
238
|
+
* was never given, one the browser has taken, a drawing buffer no measurement
|
|
239
|
+
* has sized, and an opening frame still owed. The first two draw nothing at
|
|
240
|
+
* all and the last two draw at the wrong scale, which `measure` and `opened`
|
|
241
|
+
* each have their own note about.
|
|
241
242
|
*/
|
|
242
|
-
private
|
|
243
|
+
private readonly gate;
|
|
243
244
|
/**
|
|
244
245
|
* The element's box, cached.
|
|
245
246
|
*
|
|
@@ -251,8 +252,6 @@ export declare class HelixAura extends HelixElement {
|
|
|
251
252
|
* from the handler that reads it.
|
|
252
253
|
*/
|
|
253
254
|
private box;
|
|
254
|
-
/** Watches the element's own size, so no frame has to ask for it. */
|
|
255
|
-
private sized;
|
|
256
255
|
/**
|
|
257
256
|
* Whether the field has drawn its opening frame yet.
|
|
258
257
|
*
|
|
@@ -397,9 +396,9 @@ export declare class HelixAura extends HelixElement {
|
|
|
397
396
|
*
|
|
398
397
|
* **Taken on a GPU reset, and past the cap** of about sixteen, where the
|
|
399
398
|
* browser drops the oldest context for the newest while its aura is still on
|
|
400
|
-
* screen.
|
|
401
|
-
*
|
|
402
|
-
*
|
|
399
|
+
* screen. Unstopped, the loop runs at 30 frames a second against a context
|
|
400
|
+
* that draws nothing, and nothing makes the program again once the context
|
|
401
|
+
* comes back: the aura never recovers.
|
|
403
402
|
*
|
|
404
403
|
* **The canvas is hidden until then, because a lost one is not reliably
|
|
405
404
|
* transparent.** Measured 15 Sep 2026: headless Chromium painted a plain
|
|
@@ -436,15 +435,27 @@ export declare class HelixAura extends HelixElement {
|
|
|
436
435
|
* Re-reads the box and resizes the drawing buffer to match.
|
|
437
436
|
*
|
|
438
437
|
* **Called from the observer, never from the frame.** `clientWidth` is a
|
|
439
|
-
* layout read, and doing it
|
|
438
|
+
* layout read, and doing it per frame meant a forced synchronous layout on
|
|
440
439
|
* every single frame to learn a number that changes when the box changes and
|
|
441
|
-
* at no other time.
|
|
440
|
+
* at no other time. The gate's `ResizeObserver` is told when that is.
|
|
442
441
|
*
|
|
443
442
|
* Returns whether the drawing buffer was resized, which is the same question
|
|
444
443
|
* as whether it was cleared, and so whether the caller owes it a frame.
|
|
445
444
|
*/
|
|
446
445
|
private measure;
|
|
447
446
|
private paint;
|
|
447
|
+
/**
|
|
448
|
+
* The box changed, which is the one moment the buffer is re-measured.
|
|
449
|
+
*
|
|
450
|
+
* **A resize repaints unconditionally rather than asking.** `measure` clears
|
|
451
|
+
* the drawing buffer whenever it resizes it, because assigning `canvas.width`
|
|
452
|
+
* is a reset and not a hint. `paintOnce` declines while the loop holds a
|
|
453
|
+
* frame, which is right for a property change the loop is about to draw
|
|
454
|
+
* anyway and wrong here: the canvas is already transparent by the time it
|
|
455
|
+
* declines, so the fallback gradient underneath showed through until the next
|
|
456
|
+
* frame landed. One frame, on every reflow that changed the box.
|
|
457
|
+
*/
|
|
458
|
+
private resized;
|
|
448
459
|
/**
|
|
449
460
|
* The opening frame, one animation frame after the box last changed.
|
|
450
461
|
*
|
|
@@ -456,12 +467,13 @@ export declare class HelixAura extends HelixElement {
|
|
|
456
467
|
/** One frame, now, for a change that has to show while the loop is stopped. */
|
|
457
468
|
private paintOnce;
|
|
458
469
|
/**
|
|
459
|
-
* The shortest gap between two painted frames, for a field nobody
|
|
470
|
+
* The shortest gap between two painted frames, in seconds, for a field nobody
|
|
471
|
+
* points at.
|
|
460
472
|
*
|
|
461
473
|
* **A drifting field does not need the display's rate and a pointer does.**
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
474
|
+
* The field advances on the gate's own step, so motion is wall-clock: at half
|
|
475
|
+
* the frames it moves the same distance in the same time, sampled more
|
|
476
|
+
* coarsely. That step is clamped at `MAX_FRAME_STEP`, which is a floor of 20
|
|
465
477
|
* frames a second, and 30 sits inside that with room. A cap at 15 would land
|
|
466
478
|
* on the clamp and the field would genuinely drag, which is the failure that
|
|
467
479
|
* would be misdiagnosed as choppiness.
|
|
@@ -480,23 +492,16 @@ export declare class HelixAura extends HelixElement {
|
|
|
480
492
|
* callback. Capping the two that nobody points at is most of that back.
|
|
481
493
|
*/
|
|
482
494
|
private static readonly CAPPED;
|
|
483
|
-
private readonly tick;
|
|
484
495
|
/**
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* **Never while detached.** `tick` books its own next frame, so the only
|
|
488
|
-
* thing that ends the loop is `stop`, and a disconnection is the last time
|
|
489
|
-
* `stop` is called. Everything that plays is canceled or disconnected there,
|
|
490
|
-
* and this holds for whatever callback was already on its way.
|
|
496
|
+
* What the skipped frames were worth, in seconds, spent on the next one drawn.
|
|
491
497
|
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
* again. Nor on a lost context, which draws nothing.
|
|
498
|
+
* Dropping it instead would make the field move at a fraction of wall-clock
|
|
499
|
+
* speed, which is the thing the cap is written not to do: a capped frame is
|
|
500
|
+
* the same motion sampled more coarsely and not less motion. Held rather than
|
|
501
|
+
* measured from a stamp, because the gate owns the clock.
|
|
497
502
|
*/
|
|
498
|
-
private
|
|
499
|
-
private
|
|
503
|
+
private held;
|
|
504
|
+
private advance;
|
|
500
505
|
/**
|
|
501
506
|
* The angle actually drawn, which is `terminus` resolved against the box.
|
|
502
507
|
*
|
|
@@ -535,23 +540,6 @@ export declare class HelixAura extends HelixElement {
|
|
|
535
540
|
*/
|
|
536
541
|
private advanceWake;
|
|
537
542
|
private readonly handleLeave;
|
|
538
|
-
/**
|
|
539
|
-
* Answers the preference changing, rather than only its value at setup.
|
|
540
|
-
*
|
|
541
|
-
* **The query was created and never listened to**, so the preference was read
|
|
542
|
-
* at `play` and at the pointer and nowhere else. `tick` books its own next
|
|
543
|
-
* frame with no check of its own, which means a reader who turned reduce-motion
|
|
544
|
-
* on while the page was open kept a full-page shader running for the rest of
|
|
545
|
-
* it: the one reader the promise at the top of this file is about, and the one
|
|
546
|
-
* it was broken for. The other direction failed the quieter way, a field frozen
|
|
547
|
-
* on its opening frame until a scroll or a tab switch happened to ask again.
|
|
548
|
-
*
|
|
549
|
-
* The still frame is drawn rather than assumed, because the canvas holds
|
|
550
|
-
* whatever was on it when the loop stopped and mid-drift is not the opening
|
|
551
|
-
* shape. `Orb` does the same, on the same two arms.
|
|
552
|
-
*/
|
|
553
|
-
private readonly handleMotion;
|
|
554
|
-
private readonly handleVisibility;
|
|
555
543
|
private readonly handleScroll;
|
|
556
544
|
}
|
|
557
545
|
declare global {
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
var HelixAura_1;
|
|
8
|
-
import { customElement, HelixElement, TokenColorController, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { customElement, HelixElement, MAX_FRAME_STEP, PaintGateController, TokenColorController, } from '@fusion.dev/helix-core';
|
|
9
9
|
import { html } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
11
11
|
import { tiltFor } from '../axis.js';
|
|
@@ -42,9 +42,7 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
42
42
|
this.program = null;
|
|
43
43
|
this.uniforms = {};
|
|
44
44
|
this.release = 0;
|
|
45
|
-
this.frame = 0;
|
|
46
45
|
this.elapsed = 0;
|
|
47
|
-
this.last = 0;
|
|
48
46
|
this.width = 0;
|
|
49
47
|
this.height = 0;
|
|
50
48
|
this.trail = new Float32Array(8 * 3).fill(1);
|
|
@@ -53,7 +51,20 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
53
51
|
this.head = { x: 1, y: 1 };
|
|
54
52
|
this.target = { presence: 0, x: 0, y: 0 };
|
|
55
53
|
this.eased = { presence: 0, x: 0, y: 0 };
|
|
56
|
-
this.
|
|
54
|
+
this.gate = new PaintGateController(this, {
|
|
55
|
+
canRun: () => !!this.gl && !this.gl.isContextLost() && this.width > 0 && this.opened,
|
|
56
|
+
onFrame: (delta) => {
|
|
57
|
+
this.advance(delta);
|
|
58
|
+
},
|
|
59
|
+
onMotion: (reduced) => {
|
|
60
|
+
if (reduced) {
|
|
61
|
+
this.paintOnce();
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
onResize: () => {
|
|
65
|
+
this.resized();
|
|
66
|
+
},
|
|
67
|
+
});
|
|
57
68
|
this.box = { height: 1, left: 0, top: 0, width: 1 };
|
|
58
69
|
this.opened = false;
|
|
59
70
|
this.opening = 0;
|
|
@@ -69,7 +80,7 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
69
80
|
};
|
|
70
81
|
this.handleLost = (event) => {
|
|
71
82
|
event.preventDefault();
|
|
72
|
-
this.stop();
|
|
83
|
+
this.gate.stop();
|
|
73
84
|
if (this.opening) {
|
|
74
85
|
cancelAnimationFrame(this.opening);
|
|
75
86
|
this.opening = 0;
|
|
@@ -86,40 +97,15 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
86
97
|
this.measure();
|
|
87
98
|
if (this.opened) {
|
|
88
99
|
this.paint();
|
|
89
|
-
this.play();
|
|
100
|
+
this.gate.play();
|
|
90
101
|
}
|
|
91
102
|
else if (this.width) {
|
|
92
103
|
this.open();
|
|
93
104
|
}
|
|
94
105
|
};
|
|
95
|
-
this.
|
|
96
|
-
if (!this.last) {
|
|
97
|
-
this.last = now;
|
|
98
|
-
}
|
|
99
|
-
if (!this.interactive && now - this.last < HelixAura_1.CAPPED) {
|
|
100
|
-
this.frame = requestAnimationFrame(this.tick);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const delta = Math.min((now - this.last) / 1000, 0.05);
|
|
104
|
-
this.last = now;
|
|
105
|
-
this.elapsed += delta * Math.max(0, this.drifted(this.speed, 0.4, 0.043));
|
|
106
|
-
const drift = Math.hypot(this.target.x - this.eased.x, this.target.y - this.eased.y);
|
|
107
|
-
const follow = 1 - Math.exp(-delta / (0.028 / (1 + drift * 2.4)));
|
|
108
|
-
if (!this.interactive) {
|
|
109
|
-
this.target.presence = 0;
|
|
110
|
-
}
|
|
111
|
-
this.eased.x += (this.target.x - this.eased.x) * follow;
|
|
112
|
-
this.eased.y += (this.target.y - this.eased.y) * follow;
|
|
113
|
-
const rising = this.target.presence > this.eased.presence;
|
|
114
|
-
const fade = 1 - Math.exp(-delta / (rising ? 0.05 : 0.22));
|
|
115
|
-
this.eased.presence += (this.target.presence - this.eased.presence) * fade;
|
|
116
|
-
this.age(delta);
|
|
117
|
-
this.advanceWake();
|
|
118
|
-
this.paint();
|
|
119
|
-
this.frame = requestAnimationFrame(this.tick);
|
|
120
|
-
};
|
|
106
|
+
this.held = 0;
|
|
121
107
|
this.handlePointer = (event) => {
|
|
122
|
-
if (this.
|
|
108
|
+
if (this.gate.reduced) {
|
|
123
109
|
return;
|
|
124
110
|
}
|
|
125
111
|
const { height, left, top, width } = this.box;
|
|
@@ -131,23 +117,6 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
131
117
|
this.handleLeave = () => {
|
|
132
118
|
this.target.presence = 0;
|
|
133
119
|
};
|
|
134
|
-
this.handleMotion = () => {
|
|
135
|
-
if (this.motion?.matches) {
|
|
136
|
-
this.stop();
|
|
137
|
-
this.paintOnce();
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
this.play();
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
this.handleVisibility = () => {
|
|
144
|
-
if (document.hidden) {
|
|
145
|
-
this.stop();
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
this.play();
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
120
|
this.handleScroll = () => {
|
|
152
121
|
const rect = this.getBoundingClientRect();
|
|
153
122
|
this.box = { ...this.box, left: rect.left, top: rect.top };
|
|
@@ -185,35 +154,10 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
185
154
|
}
|
|
186
155
|
this.canvas?.addEventListener('webglcontextlost', this.handleLost);
|
|
187
156
|
this.canvas?.addEventListener('webglcontextrestored', this.handleRestored);
|
|
188
|
-
this.motion ??= window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
189
|
-
this.motion.addEventListener('change', this.handleMotion);
|
|
190
157
|
this.addEventListener('pointermove', this.handlePointer);
|
|
191
158
|
this.addEventListener('pointerleave', this.handleLeave);
|
|
192
|
-
this.
|
|
193
|
-
this.onScreen = entry?.isIntersecting ?? false;
|
|
194
|
-
if (this.onScreen) {
|
|
195
|
-
this.play();
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
this.stop();
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
this.visible.observe(this);
|
|
202
|
-
document.addEventListener('visibilitychange', this.handleVisibility);
|
|
159
|
+
this.gate.watch();
|
|
203
160
|
window.addEventListener('scroll', this.handleScroll, { capture: true, passive: true });
|
|
204
|
-
this.sized = new ResizeObserver(() => {
|
|
205
|
-
if (!this.measure()) {
|
|
206
|
-
this.paintOnce();
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
if (this.opened) {
|
|
210
|
-
this.paint();
|
|
211
|
-
this.play();
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
this.open();
|
|
215
|
-
});
|
|
216
|
-
this.sized.observe(this);
|
|
217
161
|
this.readPalette();
|
|
218
162
|
if (this.measure() || (this.width && !this.opened)) {
|
|
219
163
|
this.open();
|
|
@@ -307,25 +251,16 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
307
251
|
}
|
|
308
252
|
disconnectedCallback() {
|
|
309
253
|
super.disconnectedCallback();
|
|
310
|
-
this.stop();
|
|
311
254
|
if (this.opening) {
|
|
312
255
|
cancelAnimationFrame(this.opening);
|
|
313
256
|
this.opening = 0;
|
|
314
257
|
this.openBy = 0;
|
|
315
258
|
}
|
|
316
|
-
this.visible?.disconnect();
|
|
317
|
-
this.visible = undefined;
|
|
318
|
-
this.onScreen = false;
|
|
319
259
|
this.canvas?.removeEventListener('webglcontextlost', this.handleLost);
|
|
320
260
|
this.canvas?.removeEventListener('webglcontextrestored', this.handleRestored);
|
|
321
261
|
this.removeEventListener('pointermove', this.handlePointer);
|
|
322
262
|
this.removeEventListener('pointerleave', this.handleLeave);
|
|
323
|
-
this.motion?.removeEventListener('change', this.handleMotion);
|
|
324
|
-
this.motion = undefined;
|
|
325
|
-
document.removeEventListener('visibilitychange', this.handleVisibility);
|
|
326
263
|
window.removeEventListener('scroll', this.handleScroll, { capture: true });
|
|
327
|
-
this.sized?.disconnect();
|
|
328
|
-
this.sized = undefined;
|
|
329
264
|
this.release = window.setTimeout(this.drop, 0);
|
|
330
265
|
}
|
|
331
266
|
readPalette() {
|
|
@@ -443,6 +378,18 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
443
378
|
gl.uniform1i(uniforms.uInteraction ?? null, Math.max(0, INTERACTIONS.indexOf(this.interaction ?? 'native')));
|
|
444
379
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
445
380
|
}
|
|
381
|
+
resized() {
|
|
382
|
+
if (!this.measure()) {
|
|
383
|
+
this.paintOnce();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (this.opened) {
|
|
387
|
+
this.paint();
|
|
388
|
+
this.gate.play();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
this.open();
|
|
392
|
+
}
|
|
446
393
|
open() {
|
|
447
394
|
if (this.opening) {
|
|
448
395
|
cancelAnimationFrame(this.opening);
|
|
@@ -455,7 +402,7 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
455
402
|
this.opening = 0;
|
|
456
403
|
this.opened = true;
|
|
457
404
|
this.paint();
|
|
458
|
-
this.play();
|
|
405
|
+
this.gate.play();
|
|
459
406
|
};
|
|
460
407
|
if (performance.now() >= this.openBy) {
|
|
461
408
|
draw();
|
|
@@ -464,32 +411,33 @@ let HelixAura = class HelixAura extends HelixElement {
|
|
|
464
411
|
this.opening = requestAnimationFrame(draw);
|
|
465
412
|
}
|
|
466
413
|
paintOnce() {
|
|
467
|
-
if (this.
|
|
414
|
+
if (this.gate.running || !this.opened || !this.isConnected) {
|
|
468
415
|
return;
|
|
469
416
|
}
|
|
470
417
|
this.paint();
|
|
471
418
|
}
|
|
472
|
-
static { this.CAPPED =
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
!this.onScreen ||
|
|
477
|
-
this.motion?.matches ||
|
|
478
|
-
document.hidden ||
|
|
479
|
-
!this.gl ||
|
|
480
|
-
this.gl.isContextLost() ||
|
|
481
|
-
!this.width ||
|
|
482
|
-
!this.opened) {
|
|
419
|
+
static { this.CAPPED = 1 / 30; }
|
|
420
|
+
advance(delta) {
|
|
421
|
+
this.held += delta;
|
|
422
|
+
if (!this.interactive && this.held < HelixAura_1.CAPPED) {
|
|
483
423
|
return;
|
|
484
424
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
425
|
+
const step = Math.min(this.held, MAX_FRAME_STEP);
|
|
426
|
+
this.held = 0;
|
|
427
|
+
this.elapsed += step * Math.max(0, this.drifted(this.speed, 0.4, 0.043));
|
|
428
|
+
const drift = Math.hypot(this.target.x - this.eased.x, this.target.y - this.eased.y);
|
|
429
|
+
const follow = 1 - Math.exp(-step / (0.028 / (1 + drift * 2.4)));
|
|
430
|
+
if (!this.interactive) {
|
|
431
|
+
this.target.presence = 0;
|
|
491
432
|
}
|
|
492
|
-
this.
|
|
433
|
+
this.eased.x += (this.target.x - this.eased.x) * follow;
|
|
434
|
+
this.eased.y += (this.target.y - this.eased.y) * follow;
|
|
435
|
+
const rising = this.target.presence > this.eased.presence;
|
|
436
|
+
const fade = 1 - Math.exp(-step / (rising ? 0.05 : 0.22));
|
|
437
|
+
this.eased.presence += (this.target.presence - this.eased.presence) * fade;
|
|
438
|
+
this.age(step);
|
|
439
|
+
this.advanceWake();
|
|
440
|
+
this.paint();
|
|
493
441
|
}
|
|
494
442
|
get bearing() {
|
|
495
443
|
if (this.terminus === undefined) {
|
|
@@ -93,12 +93,16 @@ export declare class HelixOrb extends HelixElement {
|
|
|
93
93
|
state: OrbState;
|
|
94
94
|
private canvas;
|
|
95
95
|
private context;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Whether anybody is looking, and the loop that draws for them.
|
|
98
|
+
*
|
|
99
|
+
* A context it has not been given and one the browser has taken are both
|
|
100
|
+
* reasons to draw nothing that the gate cannot see, so they are said here.
|
|
101
|
+
*
|
|
102
|
+
* Every frame paints: a settled orb has movement of its own in every state,
|
|
103
|
+
* and only the gate's own three answers stop it.
|
|
104
|
+
*/
|
|
105
|
+
private readonly gate;
|
|
102
106
|
/** Whether the browser has taken the canvas, as its own two events say. */
|
|
103
107
|
private lost;
|
|
104
108
|
/** The box the canvas was last measured at, in CSS pixels. */
|
|
@@ -127,7 +131,6 @@ export declare class HelixOrb extends HelixElement {
|
|
|
127
131
|
* rate has no such term.
|
|
128
132
|
*/
|
|
129
133
|
private phase;
|
|
130
|
-
private last;
|
|
131
134
|
protected willUpdate(changed: PropertyValues): void;
|
|
132
135
|
protected render(): TemplateResult;
|
|
133
136
|
protected firstUpdated(changed: PropertyValues): void;
|
|
@@ -155,10 +158,6 @@ export declare class HelixOrb extends HelixElement {
|
|
|
155
158
|
/** How far through a transition, `1` when there is not one. */
|
|
156
159
|
private get progress();
|
|
157
160
|
private paramsNow;
|
|
158
|
-
private play;
|
|
159
|
-
private stop;
|
|
160
|
-
private readonly tick;
|
|
161
|
-
private readonly handleVisibility;
|
|
162
161
|
/**
|
|
163
162
|
* Stops for a canvas the browser has taken, on a GPU reset, until it is
|
|
164
163
|
* given back.
|
|
@@ -180,7 +179,6 @@ export declare class HelixOrb extends HelixElement {
|
|
|
180
179
|
* the top left corner, for as long as the box holds still.
|
|
181
180
|
*/
|
|
182
181
|
private readonly handleRestored;
|
|
183
|
-
private readonly handleMotion;
|
|
184
182
|
/**
|
|
185
183
|
* One frame.
|
|
186
184
|
*
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, HelixElement, hostAria, readColor, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, hostAria, PaintGateController, readColor, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { blendStaged, evenSphere, fibonacci, meshFor, ORB_STATES, orbPoint, paramsFor, project, smoother, stateDistance, TRAIL, } from '../shapes.js';
|
|
@@ -29,37 +29,32 @@ let HelixOrb = class HelixOrb extends HelixElement {
|
|
|
29
29
|
this.label = '';
|
|
30
30
|
this.state = 'thinking';
|
|
31
31
|
this.context = null;
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
this.gate = new PaintGateController(this, {
|
|
33
|
+
canRun: () => !!this.context && !this.lost,
|
|
34
|
+
onFrame: (delta) => {
|
|
35
|
+
this.paint(delta);
|
|
36
|
+
},
|
|
37
|
+
onMotion: (reduced) => {
|
|
38
|
+
if (reduced) {
|
|
39
|
+
this.phase = STILL_PHASE;
|
|
40
|
+
this.morphAt = 0;
|
|
41
|
+
this.paint(0);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
onResize: () => {
|
|
45
|
+
this.measure();
|
|
46
|
+
this.paint(0);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
35
49
|
this.lost = false;
|
|
36
50
|
this.box = 0;
|
|
37
51
|
this.from = paramsFor('thinking');
|
|
38
52
|
this.morphAt = 0;
|
|
39
53
|
this.reach = 0;
|
|
40
54
|
this.phase = STILL_PHASE;
|
|
41
|
-
this.last = 0;
|
|
42
|
-
this.tick = () => {
|
|
43
|
-
if (!this.running) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const now = performance.now();
|
|
47
|
-
const dt = Math.min(0.05, (now - this.last) / 1000 || 0.016);
|
|
48
|
-
this.last = now;
|
|
49
|
-
this.paint(dt);
|
|
50
|
-
this.frame = requestAnimationFrame(this.tick);
|
|
51
|
-
};
|
|
52
|
-
this.handleVisibility = () => {
|
|
53
|
-
if (document.visibilityState === 'hidden') {
|
|
54
|
-
this.stop();
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
this.play();
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
55
|
this.handleLost = () => {
|
|
61
56
|
this.lost = true;
|
|
62
|
-
this.stop();
|
|
57
|
+
this.gate.stop();
|
|
63
58
|
this.canvas?.toggleAttribute('hidden', true);
|
|
64
59
|
};
|
|
65
60
|
this.handleRestored = () => {
|
|
@@ -68,18 +63,7 @@ let HelixOrb = class HelixOrb extends HelixElement {
|
|
|
68
63
|
this.box = 0;
|
|
69
64
|
this.measure();
|
|
70
65
|
this.paint(0);
|
|
71
|
-
this.play();
|
|
72
|
-
};
|
|
73
|
-
this.handleMotion = () => {
|
|
74
|
-
if (this.motion?.matches) {
|
|
75
|
-
this.stop();
|
|
76
|
-
this.phase = STILL_PHASE;
|
|
77
|
-
this.morphAt = 0;
|
|
78
|
-
this.paint(0);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
this.play();
|
|
82
|
-
}
|
|
66
|
+
this.gate.play();
|
|
83
67
|
};
|
|
84
68
|
}
|
|
85
69
|
static { this.styles = styles; }
|
|
@@ -112,18 +96,9 @@ let HelixOrb = class HelixOrb extends HelixElement {
|
|
|
112
96
|
}
|
|
113
97
|
disconnectedCallback() {
|
|
114
98
|
super.disconnectedCallback();
|
|
115
|
-
this.stop();
|
|
116
|
-
this.onScreen = false;
|
|
117
99
|
this.box = 0;
|
|
118
100
|
this.canvas?.removeEventListener('contextlost', this.handleLost);
|
|
119
101
|
this.canvas?.removeEventListener('contextrestored', this.handleRestored);
|
|
120
|
-
this.visible?.disconnect();
|
|
121
|
-
this.visible = undefined;
|
|
122
|
-
this.sized?.disconnect();
|
|
123
|
-
this.sized = undefined;
|
|
124
|
-
this.motion?.removeEventListener('change', this.handleMotion);
|
|
125
|
-
this.motion = undefined;
|
|
126
|
-
document.removeEventListener('visibilitychange', this.handleVisibility);
|
|
127
102
|
}
|
|
128
103
|
start() {
|
|
129
104
|
if (!this.isConnected) {
|
|
@@ -144,24 +119,7 @@ let HelixOrb = class HelixOrb extends HelixElement {
|
|
|
144
119
|
this.lost = false;
|
|
145
120
|
canvas.toggleAttribute('hidden', false);
|
|
146
121
|
}
|
|
147
|
-
this.
|
|
148
|
-
this.motion.addEventListener('change', this.handleMotion);
|
|
149
|
-
this.visible ??= new IntersectionObserver(([entry]) => {
|
|
150
|
-
this.onScreen = entry?.isIntersecting ?? false;
|
|
151
|
-
if (this.onScreen && document.visibilityState !== 'hidden') {
|
|
152
|
-
this.play();
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
this.stop();
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
this.visible.observe(this);
|
|
159
|
-
document.addEventListener('visibilitychange', this.handleVisibility);
|
|
160
|
-
this.sized ??= new ResizeObserver(() => {
|
|
161
|
-
this.measure();
|
|
162
|
-
this.paint(0);
|
|
163
|
-
});
|
|
164
|
-
this.sized.observe(this);
|
|
122
|
+
this.gate.watch();
|
|
165
123
|
this.measure();
|
|
166
124
|
this.paint(0);
|
|
167
125
|
}
|
|
@@ -185,9 +143,9 @@ let HelixOrb = class HelixOrb extends HelixElement {
|
|
|
185
143
|
this.from = this.paramsNow();
|
|
186
144
|
this.reach = stateDistance(this.from, paramsFor(this.state));
|
|
187
145
|
this.morphAt = performance.now();
|
|
188
|
-
if (!this.running) {
|
|
146
|
+
if (!this.gate.running) {
|
|
189
147
|
this.paint(0);
|
|
190
|
-
this.play();
|
|
148
|
+
this.gate.play();
|
|
191
149
|
}
|
|
192
150
|
}
|
|
193
151
|
get span() {
|
|
@@ -204,25 +162,13 @@ let HelixOrb = class HelixOrb extends HelixElement {
|
|
|
204
162
|
}
|
|
205
163
|
return blendStaged(this.from, to, smoother(k), smoother(Math.max(0, k - TRAIL) / (1 - TRAIL)));
|
|
206
164
|
}
|
|
207
|
-
play() {
|
|
208
|
-
if (this.running || this.motion?.matches || !this.onScreen || this.lost) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
this.running = true;
|
|
212
|
-
this.last = performance.now();
|
|
213
|
-
this.frame = requestAnimationFrame(this.tick);
|
|
214
|
-
}
|
|
215
|
-
stop() {
|
|
216
|
-
this.running = false;
|
|
217
|
-
cancelAnimationFrame(this.frame);
|
|
218
|
-
}
|
|
219
165
|
paint(dt) {
|
|
220
166
|
const ctx = this.context;
|
|
221
167
|
const size = this.box;
|
|
222
168
|
if (!ctx || size <= 0) {
|
|
223
169
|
return;
|
|
224
170
|
}
|
|
225
|
-
const still = this.
|
|
171
|
+
const still = this.gate.reduced;
|
|
226
172
|
const params = still ? paramsFor(this.state) : this.paramsNow();
|
|
227
173
|
this.phase += dt * params.rate;
|
|
228
174
|
const painted = getComputedStyle(ctx.canvas);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix",
|
|
3
3
|
"description": "Styled component tier. Extends the headless tier with token-driven presentation.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.59.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
},
|
|
48
48
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@fusion.dev/helix-core": "0.
|
|
51
|
-
"@fusion.dev/helix-headless": "0.
|
|
52
|
-
"@fusion.dev/helix-styles": "0.
|
|
50
|
+
"@fusion.dev/helix-core": "0.59.0",
|
|
51
|
+
"@fusion.dev/helix-headless": "0.59.0",
|
|
52
|
+
"@fusion.dev/helix-styles": "0.59.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
56
|
-
"@fusion.dev/helix-cem": "0.
|
|
57
|
-
"@fusion.dev/helix-testing": "0.
|
|
56
|
+
"@fusion.dev/helix-cem": "0.59.0",
|
|
57
|
+
"@fusion.dev/helix-testing": "0.59.0",
|
|
58
58
|
"@fusion.dev/scripts": "0.0.0-dev",
|
|
59
59
|
"@happy-dom/global-registrator": "^20.0.0",
|
|
60
60
|
"@lit-labs/ssr": "^4.1.0",
|