@lovo/matter 0.4.0 → 0.5.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/CHANGELOG.md +22 -0
- package/README.md +3 -3
- package/dist/index.cjs +156 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -56
- package/dist/index.d.ts +29 -56
- package/dist/index.js +153 -115
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @lovo/matter
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c67eb98: Rename engine exports to spelled-out, domain-accurate names (breaking).
|
|
8
|
+
|
|
9
|
+
- `fbm` → `fractalNoise` (and `FBMOptions` → `FractalNoiseOptions`)
|
|
10
|
+
- `noise` → `simplexNoise`
|
|
11
|
+
- `sdfCircle` → `signedDistanceFieldCircle`
|
|
12
|
+
- `time` → `elapsedTime`
|
|
13
|
+
- `Vec2` → `Vector2`
|
|
14
|
+
|
|
15
|
+
`TSLNode`, `voronoi`, `colorRamp`, `quantize`, `displace`, `cursorRipple`, and `filmGrain` are unchanged.
|
|
16
|
+
|
|
17
|
+
**Migration:** one-pass find-and-replace in your imports and call sites. No behavioral changes.
|
|
18
|
+
|
|
19
|
+
## 0.4.1
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- b4ecdda: Reorganize engine source into kebab-case module folders under `inputs/`, `primitives/`, and `runtime/` (matching `matter-react` and `registry` layout). No public API changes.
|
|
24
|
+
|
|
3
25
|
## 0.4.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -15,18 +15,18 @@ npm install @lovo/matter three
|
|
|
15
15
|
|
|
16
16
|
## What's inside
|
|
17
17
|
|
|
18
|
-
- **TSL primitives**: `
|
|
18
|
+
- **TSL primitives**: `fractalNoise`, `voronoi`, `colorRamp`, `quantize`, and a handful of others — composable shader fragments for procedural visuals.
|
|
19
19
|
- **Renderer**: thin wrapper around `WebGPURenderer` that handles canvas resize, DPR, and `setClearColor`.
|
|
20
20
|
- **Scheduler**: visibility/intersection-aware render loop that pauses when the canvas is off-screen or the tab is hidden.
|
|
21
21
|
|
|
22
22
|
## Minimal usage
|
|
23
23
|
|
|
24
24
|
```typescript
|
|
25
|
-
import {
|
|
25
|
+
import { fractalNoise, colorRamp } from '@lovo/matter'
|
|
26
26
|
import { uv, vec3, time } from 'three/tsl'
|
|
27
27
|
|
|
28
28
|
// Inside your TSL fragment graph:
|
|
29
|
-
const noise =
|
|
29
|
+
const noise = fractalNoise(uv().mul(4).add(time.mul(0.1)))
|
|
30
30
|
const color = colorRamp(noise, [
|
|
31
31
|
{ stop: 0.0, color: vec3(0.05, 0.05, 0.1) },
|
|
32
32
|
{ stop: 1.0, color: vec3(0.3, 0.5, 0.95) },
|
package/dist/index.cjs
CHANGED
|
@@ -29,20 +29,20 @@ __export(index_exports, {
|
|
|
29
29
|
createVisibilityWatcher: () => createVisibilityWatcher,
|
|
30
30
|
cursorRipple: () => cursorRipple,
|
|
31
31
|
displace: () => displace,
|
|
32
|
-
|
|
32
|
+
elapsedTime: () => elapsedTime,
|
|
33
33
|
filmGrain: () => filmGrain,
|
|
34
|
+
fractalNoise: () => fractalNoise,
|
|
34
35
|
getReducedMotionPolicy: () => getReducedMotionPolicy,
|
|
35
36
|
getReducedMotionTimeScale: () => getReducedMotionTimeScale,
|
|
36
|
-
noise: () => noise,
|
|
37
37
|
quantize: () => quantize,
|
|
38
|
-
sdfCircle: () => sdfCircle,
|
|
39
38
|
setReducedMotionPolicy: () => setReducedMotionPolicy,
|
|
40
|
-
|
|
39
|
+
signedDistanceFieldCircle: () => signedDistanceFieldCircle,
|
|
40
|
+
simplexNoise: () => simplexNoise,
|
|
41
41
|
voronoi: () => voronoi
|
|
42
42
|
});
|
|
43
43
|
module.exports = __toCommonJS(index_exports);
|
|
44
44
|
|
|
45
|
-
// src/runtime/
|
|
45
|
+
// src/runtime/create-renderer/create-renderer.ts
|
|
46
46
|
var import_three = require("three");
|
|
47
47
|
var import_webgpu = require("three/webgpu");
|
|
48
48
|
async function createRenderer(canvas, opts = {}) {
|
|
@@ -63,10 +63,10 @@ async function createRenderer(canvas, opts = {}) {
|
|
|
63
63
|
const resolvedClearColor = clearColor instanceof import_three.Color ? clearColor : new import_three.Color(clearColor);
|
|
64
64
|
three.setClearColor(resolvedClearColor, clearAlpha);
|
|
65
65
|
const resize = () => {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
if (canvas.width !==
|
|
69
|
-
three.setSize(
|
|
66
|
+
const canvasWidth = canvas.clientWidth;
|
|
67
|
+
const canvasHeight = canvas.clientHeight;
|
|
68
|
+
if (canvas.width !== canvasWidth * three.getPixelRatio() || canvas.height !== canvasHeight * three.getPixelRatio()) {
|
|
69
|
+
three.setSize(canvasWidth, canvasHeight, false);
|
|
70
70
|
}
|
|
71
71
|
};
|
|
72
72
|
resize();
|
|
@@ -80,7 +80,7 @@ async function createRenderer(canvas, opts = {}) {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// src/inputs/
|
|
83
|
+
// src/inputs/cursor-input/cursor-input.ts
|
|
84
84
|
var CursorInput = class {
|
|
85
85
|
value;
|
|
86
86
|
target;
|
|
@@ -100,16 +100,19 @@ var CursorInput = class {
|
|
|
100
100
|
this.element = element;
|
|
101
101
|
this.handleMouseMove = (e) => {
|
|
102
102
|
if (!(e instanceof MouseEvent)) return;
|
|
103
|
-
const
|
|
103
|
+
const mouseEvent = e;
|
|
104
104
|
if (this.element) {
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
this.target = [
|
|
105
|
+
const elementRect = this.element.getBoundingClientRect();
|
|
106
|
+
const elementWidth = elementRect.width || 1;
|
|
107
|
+
const elementHeight = elementRect.height || 1;
|
|
108
|
+
this.target = [
|
|
109
|
+
(mouseEvent.clientX - elementRect.left) / elementWidth,
|
|
110
|
+
(mouseEvent.clientY - elementRect.top) / elementHeight
|
|
111
|
+
];
|
|
109
112
|
} else {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
this.target = [
|
|
113
|
+
const viewportWidth = typeof window !== "undefined" && window.innerWidth || 1;
|
|
114
|
+
const viewportHeight = typeof window !== "undefined" && window.innerHeight || 1;
|
|
115
|
+
this.target = [mouseEvent.clientX / viewportWidth, mouseEvent.clientY / viewportHeight];
|
|
113
116
|
}
|
|
114
117
|
this.targetDirty = true;
|
|
115
118
|
};
|
|
@@ -120,9 +123,9 @@ var CursorInput = class {
|
|
|
120
123
|
return this.value;
|
|
121
124
|
}
|
|
122
125
|
/** Subscribe to change events. Returns an unsubscribe function. */
|
|
123
|
-
on(
|
|
124
|
-
this.listeners.add(
|
|
125
|
-
return () => this.listeners.delete(
|
|
126
|
+
on(_eventType, changeListener) {
|
|
127
|
+
this.listeners.add(changeListener);
|
|
128
|
+
return () => this.listeners.delete(changeListener);
|
|
126
129
|
}
|
|
127
130
|
/**
|
|
128
131
|
* Advance the smoothing one tick. Called by the host scheduler; not
|
|
@@ -151,10 +154,10 @@ var CursorInput = class {
|
|
|
151
154
|
this.listeners.clear();
|
|
152
155
|
}
|
|
153
156
|
};
|
|
154
|
-
var clamp01 = (
|
|
155
|
-
var lerp = (
|
|
157
|
+
var clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
158
|
+
var lerp = (startValue, endValue, blendFactor) => startValue + (endValue - startValue) * blendFactor;
|
|
156
159
|
|
|
157
|
-
// src/primitives/
|
|
160
|
+
// src/primitives/color-ramp/color-ramp.ts
|
|
158
161
|
var import_tsl = require("three/tsl");
|
|
159
162
|
var import_tsl2 = require("three/tsl");
|
|
160
163
|
function colorRamp(t, stops) {
|
|
@@ -163,78 +166,78 @@ function colorRamp(t, stops) {
|
|
|
163
166
|
if (stops.length === 1) return (0, import_tsl.mix)(first.color, first.color, 0);
|
|
164
167
|
let result = (0, import_tsl.mix)(first.color, first.color, 0);
|
|
165
168
|
for (let i = 1; i < stops.length; i += 1) {
|
|
166
|
-
const
|
|
169
|
+
const previousStop = stops[i - 1];
|
|
167
170
|
const next = stops[i];
|
|
168
|
-
if (
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
const localT = (0, import_tsl2.clamp)((0, import_tsl2.div)((0, import_tsl2.sub)(t,
|
|
171
|
+
if (previousStop === void 0 || next === void 0) continue;
|
|
172
|
+
const positionSpan = next.position - previousStop.position;
|
|
173
|
+
if (positionSpan <= 0) continue;
|
|
174
|
+
const localT = (0, import_tsl2.clamp)((0, import_tsl2.div)((0, import_tsl2.sub)(t, previousStop.position), positionSpan), 0, 1);
|
|
172
175
|
result = (0, import_tsl.mix)(result, next.color, localT);
|
|
173
176
|
}
|
|
174
177
|
return result;
|
|
175
178
|
}
|
|
176
179
|
|
|
177
|
-
// src/primitives/noise.ts
|
|
180
|
+
// src/primitives/noise/noise.ts
|
|
178
181
|
var import_tsl3 = require("three/tsl");
|
|
179
|
-
function
|
|
182
|
+
function simplexNoise(p) {
|
|
180
183
|
return (0, import_tsl3.mx_noise_float)(p);
|
|
181
184
|
}
|
|
182
185
|
|
|
183
|
-
// src/primitives/fbm.ts
|
|
186
|
+
// src/primitives/fbm/fbm.ts
|
|
184
187
|
var import_tsl4 = require("three/tsl");
|
|
185
|
-
function
|
|
188
|
+
function fractalNoise(p, opts = {}) {
|
|
186
189
|
const octaves = opts.octaves ?? 4;
|
|
187
190
|
const lacunarity = opts.lacunarity ?? 2;
|
|
188
191
|
const gain = opts.gain ?? 0.5;
|
|
189
|
-
let sum =
|
|
190
|
-
let
|
|
191
|
-
let
|
|
192
|
-
let total =
|
|
192
|
+
let sum = simplexNoise(p);
|
|
193
|
+
let amplitude = 1;
|
|
194
|
+
let frequency = 1;
|
|
195
|
+
let total = amplitude;
|
|
193
196
|
for (let i = 1; i < octaves; i += 1) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
total +=
|
|
197
|
-
const pAtFreq = (0, import_tsl4.add)((0, import_tsl4.mul)(p,
|
|
198
|
-
const layer =
|
|
197
|
+
frequency *= lacunarity;
|
|
198
|
+
amplitude *= gain;
|
|
199
|
+
total += amplitude;
|
|
200
|
+
const pAtFreq = (0, import_tsl4.add)((0, import_tsl4.mul)(p, frequency), i * 100);
|
|
201
|
+
const layer = simplexNoise(pAtFreq).mul(amplitude);
|
|
199
202
|
sum = sum.add(layer);
|
|
200
203
|
}
|
|
201
204
|
return sum.div(total);
|
|
202
205
|
}
|
|
203
206
|
|
|
204
|
-
// src/primitives/voronoi.ts
|
|
207
|
+
// src/primitives/voronoi/voronoi.ts
|
|
205
208
|
var import_tsl5 = require("three/tsl");
|
|
206
209
|
function voronoi(p) {
|
|
207
210
|
return (0, import_tsl5.mx_worley_noise_float)(p);
|
|
208
211
|
}
|
|
209
212
|
|
|
210
|
-
// src/primitives/quantize.ts
|
|
213
|
+
// src/primitives/quantize/quantize.ts
|
|
211
214
|
function quantize(t, steps) {
|
|
212
215
|
if (steps <= 1) {
|
|
213
216
|
return t.mul(0);
|
|
214
217
|
}
|
|
215
|
-
const
|
|
216
|
-
return t.mul(
|
|
218
|
+
const denominator = steps - 1;
|
|
219
|
+
return t.mul(denominator).add(0.5).floor().div(denominator);
|
|
217
220
|
}
|
|
218
221
|
|
|
219
|
-
// src/primitives/
|
|
222
|
+
// src/primitives/sdf-circle/sdf-circle.ts
|
|
220
223
|
var import_tsl6 = require("three/tsl");
|
|
221
|
-
function
|
|
224
|
+
function signedDistanceFieldCircle(p, radius) {
|
|
222
225
|
return (0, import_tsl6.length)(p).sub(radius);
|
|
223
226
|
}
|
|
224
227
|
|
|
225
|
-
// src/primitives/displace.ts
|
|
228
|
+
// src/primitives/displace/displace.ts
|
|
226
229
|
var import_tsl7 = require("three/tsl");
|
|
227
230
|
function displace(p, by) {
|
|
228
231
|
return (0, import_tsl7.add)(p, by);
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
// src/primitives/
|
|
234
|
+
// src/primitives/cursor-ripple/cursor-ripple.ts
|
|
232
235
|
var import_tsl10 = require("three/tsl");
|
|
233
236
|
|
|
234
|
-
// src/primitives/time.ts
|
|
237
|
+
// src/primitives/time/time.ts
|
|
235
238
|
var import_tsl9 = require("three/tsl");
|
|
236
239
|
|
|
237
|
-
// src/runtime/
|
|
240
|
+
// src/runtime/reduced-motion/reduced-motion.ts
|
|
238
241
|
var import_tsl8 = require("three/tsl");
|
|
239
242
|
var state = {
|
|
240
243
|
policy: "auto",
|
|
@@ -243,7 +246,7 @@ var state = {
|
|
|
243
246
|
function setReducedMotionPolicy(policy) {
|
|
244
247
|
if (state.policy === policy) return;
|
|
245
248
|
state.policy = policy;
|
|
246
|
-
for (const
|
|
249
|
+
for (const watcher of state.watchers) watcher.recompute();
|
|
247
250
|
}
|
|
248
251
|
function getReducedMotionPolicy() {
|
|
249
252
|
return state.policy;
|
|
@@ -264,8 +267,8 @@ function createReducedMotionWatcher() {
|
|
|
264
267
|
if (typeof matchMedia !== "function") {
|
|
265
268
|
return {
|
|
266
269
|
scale: () => computeScale(false),
|
|
267
|
-
subscribe: (
|
|
268
|
-
void
|
|
270
|
+
subscribe: (listener) => {
|
|
271
|
+
void listener;
|
|
269
272
|
return () => {
|
|
270
273
|
};
|
|
271
274
|
},
|
|
@@ -274,33 +277,33 @@ function createReducedMotionWatcher() {
|
|
|
274
277
|
}
|
|
275
278
|
};
|
|
276
279
|
}
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
let
|
|
280
|
+
const mediaQueryList = matchMedia("(prefers-reduced-motion: reduce)");
|
|
281
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
282
|
+
let lastComputedScale = computeScale(mediaQueryList.matches);
|
|
280
283
|
const onChange = () => {
|
|
281
|
-
const next = computeScale(
|
|
282
|
-
if (next !==
|
|
283
|
-
|
|
284
|
-
for (const
|
|
284
|
+
const next = computeScale(mediaQueryList.matches);
|
|
285
|
+
if (next !== lastComputedScale) {
|
|
286
|
+
lastComputedScale = next;
|
|
287
|
+
for (const listener of subscriptions) listener(next);
|
|
285
288
|
}
|
|
286
289
|
};
|
|
287
|
-
|
|
290
|
+
mediaQueryList.addEventListener("change", onChange);
|
|
288
291
|
const watcher = {
|
|
289
|
-
scale: () =>
|
|
290
|
-
subscribe(
|
|
291
|
-
|
|
292
|
-
return () =>
|
|
292
|
+
scale: () => lastComputedScale,
|
|
293
|
+
subscribe(listener) {
|
|
294
|
+
subscriptions.add(listener);
|
|
295
|
+
return () => subscriptions.delete(listener);
|
|
293
296
|
},
|
|
294
297
|
recompute() {
|
|
295
|
-
const next = computeScale(
|
|
296
|
-
if (next !==
|
|
297
|
-
|
|
298
|
-
for (const
|
|
298
|
+
const next = computeScale(mediaQueryList.matches);
|
|
299
|
+
if (next !== lastComputedScale) {
|
|
300
|
+
lastComputedScale = next;
|
|
301
|
+
for (const listener of subscriptions) listener(next);
|
|
299
302
|
}
|
|
300
303
|
},
|
|
301
304
|
dispose() {
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
mediaQueryList.removeEventListener("change", onChange);
|
|
306
|
+
subscriptions.clear();
|
|
304
307
|
state.watchers.delete(watcher);
|
|
305
308
|
}
|
|
306
309
|
};
|
|
@@ -321,32 +324,30 @@ function getReducedMotionTimeScale() {
|
|
|
321
324
|
return globalScaleUniform;
|
|
322
325
|
}
|
|
323
326
|
|
|
324
|
-
// src/primitives/time.ts
|
|
325
|
-
var
|
|
327
|
+
// src/primitives/time/time.ts
|
|
328
|
+
var elapsedTime = import_tsl9.time.mul(getReducedMotionTimeScale());
|
|
326
329
|
|
|
327
|
-
// src/primitives/
|
|
330
|
+
// src/primitives/cursor-ripple/cursor-ripple.ts
|
|
328
331
|
function cursorRipple(p, center, opts = {}) {
|
|
329
332
|
const reach = opts.reach ?? 0.4;
|
|
330
333
|
const frequency = opts.frequency ?? 30;
|
|
331
334
|
const speed = opts.speed ?? 6;
|
|
332
335
|
const amplitude = opts.amplitude ?? 0.5;
|
|
333
336
|
const d = (0, import_tsl10.length)((0, import_tsl10.sub)(p, center));
|
|
334
|
-
const wave = (0, import_tsl10.sin)(d.mul(frequency).sub(
|
|
337
|
+
const wave = (0, import_tsl10.sin)(d.mul(frequency).sub(elapsedTime.mul(speed)));
|
|
335
338
|
const decay = (0, import_tsl10.smoothstep)(reach, 0, d);
|
|
336
339
|
return wave.mul(amplitude).mul(decay);
|
|
337
340
|
}
|
|
338
341
|
|
|
339
|
-
// src/primitives/
|
|
342
|
+
// src/primitives/film-grain/film-grain.ts
|
|
340
343
|
var import_tsl11 = require("three/tsl");
|
|
341
|
-
function filmGrain(
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
const hash = (0, import_tsl11.fract)((0, import_tsl11.sin)(base).mul(43758.5453));
|
|
346
|
-
return (0, import_tsl11.length)(hash).sub(0.765).mul(intensity);
|
|
344
|
+
function filmGrain(intensity, timeOffset = 0) {
|
|
345
|
+
const pixel = import_tsl11.screenCoordinate.xy.floor();
|
|
346
|
+
const seed = pixel.x.toUint().mul(1973).add(pixel.y.toUint().mul(9277)).add((0, import_tsl11.mul)(timeOffset, 26699).toUint());
|
|
347
|
+
return (0, import_tsl11.hash)(seed).sub(0.5).mul(intensity);
|
|
347
348
|
}
|
|
348
349
|
|
|
349
|
-
// src/runtime/visibility.ts
|
|
350
|
+
// src/runtime/visibility/visibility.ts
|
|
350
351
|
function createVisibilityWatcher() {
|
|
351
352
|
if (typeof document === "undefined") {
|
|
352
353
|
return {
|
|
@@ -357,26 +358,26 @@ function createVisibilityWatcher() {
|
|
|
357
358
|
}
|
|
358
359
|
};
|
|
359
360
|
}
|
|
360
|
-
const
|
|
361
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
361
362
|
const onChange = () => {
|
|
362
|
-
const
|
|
363
|
-
for (const
|
|
363
|
+
const isVisible = document.visibilityState === "visible";
|
|
364
|
+
for (const listener of subscriptions) listener(isVisible);
|
|
364
365
|
};
|
|
365
366
|
document.addEventListener("visibilitychange", onChange);
|
|
366
367
|
return {
|
|
367
368
|
isVisible: () => document.visibilityState === "visible",
|
|
368
|
-
subscribe(
|
|
369
|
-
|
|
370
|
-
return () =>
|
|
369
|
+
subscribe(listener) {
|
|
370
|
+
subscriptions.add(listener);
|
|
371
|
+
return () => subscriptions.delete(listener);
|
|
371
372
|
},
|
|
372
373
|
dispose() {
|
|
373
374
|
document.removeEventListener("visibilitychange", onChange);
|
|
374
|
-
|
|
375
|
+
subscriptions.clear();
|
|
375
376
|
}
|
|
376
377
|
};
|
|
377
378
|
}
|
|
378
379
|
|
|
379
|
-
// src/runtime/intersection.ts
|
|
380
|
+
// src/runtime/intersection/intersection.ts
|
|
380
381
|
function createIntersectionWatcher(canvas) {
|
|
381
382
|
if (typeof IntersectionObserver === "undefined") {
|
|
382
383
|
return {
|
|
@@ -387,41 +388,50 @@ function createIntersectionWatcher(canvas) {
|
|
|
387
388
|
}
|
|
388
389
|
};
|
|
389
390
|
}
|
|
390
|
-
const
|
|
391
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
391
392
|
let inView = true;
|
|
392
|
-
const
|
|
393
|
+
const observer = new IntersectionObserver(
|
|
393
394
|
(entries) => {
|
|
394
|
-
const next = entries.some((
|
|
395
|
+
const next = entries.some((entry) => entry.isIntersecting);
|
|
395
396
|
if (next === inView) return;
|
|
396
397
|
inView = next;
|
|
397
|
-
for (const
|
|
398
|
+
for (const listener of subscriptions) listener(inView);
|
|
398
399
|
},
|
|
399
400
|
{ threshold: 0 }
|
|
400
401
|
);
|
|
401
|
-
|
|
402
|
+
observer.observe(canvas);
|
|
402
403
|
return {
|
|
403
404
|
isInView: () => inView,
|
|
404
|
-
subscribe(
|
|
405
|
-
|
|
406
|
-
return () =>
|
|
405
|
+
subscribe(listener) {
|
|
406
|
+
subscriptions.add(listener);
|
|
407
|
+
return () => subscriptions.delete(listener);
|
|
407
408
|
},
|
|
408
409
|
dispose() {
|
|
409
|
-
|
|
410
|
-
|
|
410
|
+
observer.disconnect();
|
|
411
|
+
subscriptions.clear();
|
|
411
412
|
}
|
|
412
413
|
};
|
|
413
414
|
}
|
|
414
415
|
|
|
415
|
-
// src/runtime/frame-scheduler.ts
|
|
416
|
+
// src/runtime/frame-scheduler/frame-scheduler.ts
|
|
416
417
|
var FrameScheduler = class {
|
|
417
418
|
clients = /* @__PURE__ */ new Set();
|
|
418
419
|
rafId = null;
|
|
419
420
|
running = false;
|
|
420
421
|
paused = false;
|
|
421
|
-
idle = false;
|
|
422
422
|
flushPending = false;
|
|
423
423
|
startedAt = 0;
|
|
424
424
|
lastTickAt = 0;
|
|
425
|
+
// Reference-counted idle voting. The scheduler is idle only when at least
|
|
426
|
+
// one component has voted idle AND no component has voted animated. This
|
|
427
|
+
// prevents a static component (e.g. LinearGradient speed=0) from halting
|
|
428
|
+
// the loop while an animated overlay (e.g. FilmGrain) is still running.
|
|
429
|
+
idleVotes = 0;
|
|
430
|
+
animatedVotes = 0;
|
|
431
|
+
/** True when all participating components prefer idle and none need animation. */
|
|
432
|
+
get idle() {
|
|
433
|
+
return this.idleVotes > 0 && this.animatedVotes === 0;
|
|
434
|
+
}
|
|
425
435
|
/** Activate the scheduler. The rAF loop starts on the first client added. */
|
|
426
436
|
start() {
|
|
427
437
|
this.running = true;
|
|
@@ -457,19 +467,39 @@ var FrameScheduler = class {
|
|
|
457
467
|
this.clients.clear();
|
|
458
468
|
}
|
|
459
469
|
/**
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
470
|
+
* Cast a vote on whether the scheduler should be idle.
|
|
471
|
+
*
|
|
472
|
+
* `setIdle(true)` increments the idle-vote count; the returned cleanup
|
|
473
|
+
* decrements it. `setIdle(false)` increments the animated-vote count;
|
|
474
|
+
* its cleanup decrements that. The scheduler halts (after one flush tick)
|
|
475
|
+
* only when `idleVotes > 0 && animatedVotes === 0`.
|
|
476
|
+
*
|
|
477
|
+
* Callers are responsible for calling the returned cleanup on unmount.
|
|
478
|
+
* Use `requestRender()` or cast a `setIdle(false)` vote to wake the loop
|
|
479
|
+
* without permanently registering an animated preference.
|
|
463
480
|
*/
|
|
464
481
|
setIdle(idle) {
|
|
465
|
-
if (this.idle === idle) return;
|
|
466
|
-
this.idle = idle;
|
|
467
482
|
if (idle) {
|
|
468
|
-
|
|
469
|
-
this.
|
|
483
|
+
const wasIdle = this.idle;
|
|
484
|
+
this.idleVotes += 1;
|
|
485
|
+
const nowIdle = this.idle;
|
|
486
|
+
if (!wasIdle && nowIdle) this.onBecameIdle();
|
|
487
|
+
return () => {
|
|
488
|
+
const prevIdle = this.idle;
|
|
489
|
+
this.idleVotes = Math.max(0, this.idleVotes - 1);
|
|
490
|
+
const afterIdle = this.idle;
|
|
491
|
+
if (prevIdle && !afterIdle) this.onBecameAnimated();
|
|
492
|
+
};
|
|
470
493
|
} else {
|
|
471
|
-
|
|
472
|
-
this.
|
|
494
|
+
const wasIdle = this.idle;
|
|
495
|
+
this.animatedVotes += 1;
|
|
496
|
+
if (wasIdle) this.onBecameAnimated();
|
|
497
|
+
return () => {
|
|
498
|
+
const prevIdle = this.idle;
|
|
499
|
+
this.animatedVotes = Math.max(0, this.animatedVotes - 1);
|
|
500
|
+
const nowIdle = this.idle;
|
|
501
|
+
if (!prevIdle && nowIdle) this.onBecameIdle();
|
|
502
|
+
};
|
|
473
503
|
}
|
|
474
504
|
}
|
|
475
505
|
/** Force a single tick while idle. Useful for prop-change invalidation. */
|
|
@@ -478,6 +508,14 @@ var FrameScheduler = class {
|
|
|
478
508
|
this.flushPending = true;
|
|
479
509
|
this.maybeQueue();
|
|
480
510
|
}
|
|
511
|
+
onBecameIdle() {
|
|
512
|
+
this.flushPending = true;
|
|
513
|
+
this.maybeQueue();
|
|
514
|
+
}
|
|
515
|
+
onBecameAnimated() {
|
|
516
|
+
this.flushPending = false;
|
|
517
|
+
this.maybeQueue();
|
|
518
|
+
}
|
|
481
519
|
maybeQueue() {
|
|
482
520
|
if (this.rafId !== null) return;
|
|
483
521
|
if (!this.running) return;
|
|
@@ -520,15 +558,15 @@ var FrameScheduler = class {
|
|
|
520
558
|
createVisibilityWatcher,
|
|
521
559
|
cursorRipple,
|
|
522
560
|
displace,
|
|
523
|
-
|
|
561
|
+
elapsedTime,
|
|
524
562
|
filmGrain,
|
|
563
|
+
fractalNoise,
|
|
525
564
|
getReducedMotionPolicy,
|
|
526
565
|
getReducedMotionTimeScale,
|
|
527
|
-
noise,
|
|
528
566
|
quantize,
|
|
529
|
-
sdfCircle,
|
|
530
567
|
setReducedMotionPolicy,
|
|
531
|
-
|
|
568
|
+
signedDistanceFieldCircle,
|
|
569
|
+
simplexNoise,
|
|
532
570
|
voronoi
|
|
533
571
|
});
|
|
534
572
|
//# sourceMappingURL=index.cjs.map
|