@vectojs/core 1.35.3 → 1.37.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/dist/index.mjs CHANGED
@@ -11,14 +11,14 @@ import {
11
11
  parseColorToRGBA,
12
12
  sanitizeUrl,
13
13
  setRendererDevMode
14
- } from "./chunk-25PS5K7R.mjs";
14
+ } from "./chunk-LWEMD34D.mjs";
15
15
  import {
16
16
  Entity,
17
17
  MSDFTextEntity,
18
18
  SVGEntity,
19
19
  VectoJSEvent,
20
20
  contentLineInHint
21
- } from "./chunk-CHDJEHML.mjs";
21
+ } from "./chunk-3MTVDXZQ.mjs";
22
22
 
23
23
  // src/tree/ComputeParticleEntity.ts
24
24
  var PARTICLE_STRIDE_FLOATS = 8;
@@ -178,8 +178,8 @@ var ComputeParticleEntity = class extends Entity {
178
178
  * render-tree walk, but this entity is still a normal tree member that
179
179
  * walk visits — without this override, the base `Entity.hasPendingAnimations()`
180
180
  * (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
181
- * auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
182
- * the tree is animating, even while thousands of particles are visibly
181
+ * auto-throttle drops the WHOLE scene to the idle FPS floor the instant
182
+ * nothing else in the tree is animating, even while thousands of particles are visibly
183
183
  * drifting or spring-settling. A spring+damping system asymptotically
184
184
  * approaches zero velocity but never reaches it exactly, so this checks
185
185
  * against a small epsilon rather than `!== 0` — otherwise this would
@@ -3435,6 +3435,7 @@ var SCENE_OPTION_KEYS = [
3435
3435
  "contentSemanticMargin",
3436
3436
  "debugA11y",
3437
3437
  "disableWindowResize",
3438
+ "idleFPS",
3438
3439
  "maxDPR",
3439
3440
  "maxFPS",
3440
3441
  "particleBackend",
@@ -3863,8 +3864,17 @@ var Scene = class _Scene {
3863
3864
  if (value) this._dirty.mark(void 0, this.currentFrame);
3864
3865
  else this._dirty.clear();
3865
3866
  }
3866
- /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
3867
+ /**
3868
+ * Whether to throttle the `'always'` loop when the scene is static to save
3869
+ * power. The idle floor is {@link Scene.idleFPS} (default 60).
3870
+ */
3867
3871
  autoThrottle = true;
3872
+ /**
3873
+ * Frame-rate floor for an idle `'always'` scene when {@link Scene.autoThrottle}
3874
+ * is on. Default `60`. Set `2` for the legacy aggressive idle sleep, `0` to
3875
+ * keep the scene's `maxFPS` cadence while idle.
3876
+ */
3877
+ idleFPS = 60;
3868
3878
  // --- Frame telemetry (read via `frameStats`) ---------------------------
3869
3879
  /** Wall-clock ms spent inside the last `render()` call. */
3870
3880
  _lastFrameMs = 0;
@@ -4794,6 +4804,7 @@ var Scene = class _Scene {
4794
4804
  this.maxFPS = options.maxFPS ?? (isTest ? 0 : 60);
4795
4805
  this.respectReducedMotion = options.respectReducedMotion ?? true;
4796
4806
  this.autoThrottle = options.autoThrottle ?? true;
4807
+ this.idleFPS = options.idleFPS ?? 60;
4797
4808
  this.phases.userTiming = options.userTiming ?? false;
4798
4809
  this.particleBackend = options.particleBackend ?? "auto";
4799
4810
  this.a11ySyncInterval = options.a11ySyncInterval ?? 0;
@@ -6739,7 +6750,7 @@ var Scene = class _Scene {
6739
6750
  let cap = this.effectiveMaxFPS();
6740
6751
  const isIdle = !this.dirty && !this.frameHadAnimation && !this.contentSemanticDeferred;
6741
6752
  if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
6742
- cap = Math.min(cap, 2);
6753
+ cap = Math.min(cap, this.idleFPS);
6743
6754
  }
6744
6755
  if (cap > 0 && time - this.lastTime < 1e3 / cap - 1) {
6745
6756
  this._skippedFrames++;
@@ -6990,7 +7001,7 @@ var Scene = class _Scene {
6990
7001
  if (this._devActive && this._devFrameCount % 120 === 0) {
6991
7002
  if (overridesUpdate && node.hasPendingAnimations === Entity.prototype.hasPendingAnimations) {
6992
7003
  this._devWarn(
6993
- `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
7004
+ `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to slow the animation. Override hasPendingAnimations() to return true while motion is in flight.`
6994
7005
  );
6995
7006
  }
6996
7007
  }
@@ -133,7 +133,7 @@ export declare class CanvasRenderer implements IRenderer {
133
133
  /** @inheritdoc */
134
134
  setGlobalAlpha(alpha: number): void;
135
135
  /** @inheritdoc */
136
- clip(x: number, y: number, width: number, height: number): void;
136
+ clip(x: number, y: number, width: number, height: number, radii?: number | number[]): void;
137
137
  /** @inheritdoc */
138
138
  beginPath(): void;
139
139
  /** @inheritdoc */
@@ -129,12 +129,19 @@ export interface IRenderer {
129
129
  * Intersect the current clip region with a rectangle. Affects all subsequent
130
130
  * draws until the next {@link restore}; wrap in {@link save}/{@link restore}.
131
131
  *
132
+ * When `radii` is provided the clip is a rounded rectangle instead of a sharp
133
+ * one. Backends that can only express a rectangular clip (e.g. a scissor-test
134
+ * GPU path) may ignore `radii`, so it must be treated as a progressive
135
+ * enhancement rather than a guarantee.
136
+ *
132
137
  * @param x - Left edge.
133
138
  * @param y - Top edge.
134
139
  * @param width - Rectangle width.
135
140
  * @param height - Rectangle height.
141
+ * @param radii - Optional corner radius (uniform) or per-corner array, matching
142
+ * {@link roundRect}. Omit for a sharp rectangle.
136
143
  */
137
- clip(x: number, y: number, width: number, height: number): void;
144
+ clip(x: number, y: number, width: number, height: number, radii?: number | number[]): void;
138
145
  /** Begin a new sub-path, discarding the current path. */
139
146
  beginPath(): void;
140
147
  /**
@@ -72,7 +72,7 @@ export declare class SVGRenderer implements IRenderer {
72
72
  stop: number;
73
73
  color: string;
74
74
  }[]): SVGLinearGradient;
75
- clip(x: number, y: number, width: number, height: number): void;
75
+ clip(x: number, y: number, width: number, height: number, radii?: number | number[]): void;
76
76
  toXMLString(): string;
77
77
  private resolveGradient;
78
78
  private escapeXML;
package/dist/renderer.js CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
 
12
12
 
13
- var _chunkMO24PVGTjs = require('./chunk-MO24PVGT.js');
13
+ var _chunkZYYP2M5Djs = require('./chunk-ZYYP2M5D.js');
14
14
 
15
15
 
16
16
 
@@ -22,4 +22,4 @@ var _chunkMO24PVGTjs = require('./chunk-MO24PVGT.js');
22
22
 
23
23
 
24
24
 
25
- exports.CanvasRenderer = _chunkMO24PVGTjs.CanvasRenderer; exports.GlyphRasterAtlas = _chunkMO24PVGTjs.GlyphRasterAtlas; exports.SVGRenderer = _chunkMO24PVGTjs.SVGRenderer; exports.TextRasterCache = _chunkMO24PVGTjs.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkMO24PVGTjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkMO24PVGTjs.createWebGLPointRenderer; exports.installRendererDevTraps = _chunkMO24PVGTjs.installRendererDevTraps; exports.isRendererDevMode = _chunkMO24PVGTjs.isRendererDevMode; exports.parseColorToRGBA = _chunkMO24PVGTjs.parseColorToRGBA; exports.setRendererDevMode = _chunkMO24PVGTjs.setRendererDevMode;
25
+ exports.CanvasRenderer = _chunkZYYP2M5Djs.CanvasRenderer; exports.GlyphRasterAtlas = _chunkZYYP2M5Djs.GlyphRasterAtlas; exports.SVGRenderer = _chunkZYYP2M5Djs.SVGRenderer; exports.TextRasterCache = _chunkZYYP2M5Djs.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkZYYP2M5Djs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkZYYP2M5Djs.createWebGLPointRenderer; exports.installRendererDevTraps = _chunkZYYP2M5Djs.installRendererDevTraps; exports.isRendererDevMode = _chunkZYYP2M5Djs.isRendererDevMode; exports.parseColorToRGBA = _chunkZYYP2M5Djs.parseColorToRGBA; exports.setRendererDevMode = _chunkZYYP2M5Djs.setRendererDevMode;
package/dist/renderer.mjs CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  isRendererDevMode,
10
10
  parseColorToRGBA,
11
11
  setRendererDevMode
12
- } from "./chunk-25PS5K7R.mjs";
12
+ } from "./chunk-LWEMD34D.mjs";
13
13
  export {
14
14
  CanvasRenderer,
15
15
  GlyphRasterAtlas,
@@ -59,8 +59,8 @@ export declare class MSDFTextEntity extends Entity {
59
59
  *
60
60
  * Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
61
61
  * scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
62
- * `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
63
- * idle, so whether it recovers is down to whether a throttled tick happens to
62
+ * `onDemand` skips idle frames outright; `always` throttles to the idle FPS
63
+ * floor when idle, so whether it recovers is down to whether a throttled tick happens to
64
64
  * land after the decode — Chromium got one, Firefox did not. Neither is a
65
65
  * mechanism, which is why this listener exists rather than relying on the
66
66
  * frame loop to come back around.
package/dist/text.js CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkXHPUCU5Pjs = require('./chunk-XHPUCU5P.js');
5
+ var _chunkDSYYQ6BQjs = require('./chunk-DSYYQ6BQ.js');
6
6
 
7
7
  // src/text/index.ts
8
8
  var _text = require('@vectojs/text'); _createStarExport(_text);
9
9
 
10
10
 
11
11
 
12
- exports.MSDFTextEntity = _chunkXHPUCU5Pjs.MSDFTextEntity; exports.SVGEntity = _chunkXHPUCU5Pjs.SVGEntity;
12
+ exports.MSDFTextEntity = _chunkDSYYQ6BQjs.MSDFTextEntity; exports.SVGEntity = _chunkDSYYQ6BQjs.SVGEntity;
package/dist/text.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MSDFTextEntity,
3
3
  SVGEntity
4
- } from "./chunk-CHDJEHML.mjs";
4
+ } from "./chunk-3MTVDXZQ.mjs";
5
5
 
6
6
  // src/text/index.ts
7
7
  export * from "@vectojs/text";
@@ -113,8 +113,8 @@ export declare class ComputeParticleEntity extends Entity {
113
113
  * render-tree walk, but this entity is still a normal tree member that
114
114
  * walk visits — without this override, the base `Entity.hasPendingAnimations()`
115
115
  * (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
116
- * auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
117
- * the tree is animating, even while thousands of particles are visibly
116
+ * auto-throttle drops the WHOLE scene to the idle FPS floor the instant
117
+ * nothing else in the tree is animating, even while thousands of particles are visibly
118
118
  * drifting or spring-settling. A spring+damping system asymptotically
119
119
  * approaches zero velocity but never reaches it exactly, so this checks
120
120
  * against a small epsilon rather than `!== 0` — otherwise this would
@@ -658,9 +658,20 @@ export declare abstract class Entity {
658
658
  * and promotes it.
659
659
  *
660
660
  * `'never'` suppresses the node entirely. Prefer `interactive = false` unless
661
- * the entity genuinely needs pointer events without any semantic presence;
662
- * this exists so a purely decorative interactive surface can opt out without
663
- * losing canvas hit-testing.
661
+ * the entity genuinely needs to stay hit-testable without any semantic
662
+ * presence — this exists so a purely decorative interactive surface can opt
663
+ * out of the a11y tree.
664
+ *
665
+ * **Pointer input is routed through the projected mirror.** The engine binds
666
+ * `pointerdown`/`pointermove`/`pointerup`/`click`/`dblclick` to each shadow
667
+ * element (the canvas itself only tracks `mouseX`/`mouseY`), so an entity
668
+ * with no materialized node receives **no pointer events at all** — neither
669
+ * in `'never'` mode nor in `'onDemand'` before it is engaged (focused,
670
+ * pointer target, or `requestA11yProjection`). Canvas hit-testing exists
671
+ * (`Scene.findEntityAt`) but is a query API, not a dispatch path. For a
672
+ * pointer-reactive region with no role (e.g. a desktop click-catcher), use
673
+ * `'eager'` + `a11yFullViewport` + `tabIndex: -1` with a role-less
674
+ * `getA11yAttributes()`: the mirror is AT-invisible but pointer-visible.
664
675
  *
665
676
  * **This does not replace an aggregate description.** A thousand `'onDemand'`
666
677
  * danmaku are individually reachable but say nothing collectively. The proven
@@ -98,10 +98,22 @@ export interface SceneOptions {
98
98
  */
99
99
  maxDPR?: number;
100
100
  /**
101
- * Enable automatic throttling to 2 FPS when the scene is static (no active transitions
102
- * and not marked dirty) to save power/CPU. Default is `true`.
101
+ * Enable automatic throttling of the `'always'` loop when the scene is
102
+ * static (no active transitions and not marked dirty) to save power/CPU.
103
+ * The idle floor is 60 FPS by default; raise or lower it with
104
+ * {@link SceneOptions.idleFPS}. Default is `true`.
103
105
  */
104
106
  autoThrottle?: boolean;
107
+ /**
108
+ * Frame-rate floor for an idle `'always'` scene when
109
+ * {@link SceneOptions.autoThrottle} is on. Default `60` — an idle scene
110
+ * keeps animating smoothly instead of stuttering at the old 2 FPS floor.
111
+ * Set e.g. `2` to restore the aggressive deep sleep for battery-critical
112
+ * scenes (the developer's explicit choice); `0` means "keep the scene's
113
+ * `maxFPS` cadence while idle". Ignored when `autoThrottle` is `false` or
114
+ * `renderMode` is `'onDemand'` (onDemand already renders zero idle frames).
115
+ */
116
+ idleFPS?: number;
105
117
  /**
106
118
  * Emit User Timing marks and measures for render phases. Default `false`.
107
119
  * Intended for short profiler captures; enable only while collecting one.
@@ -196,8 +208,9 @@ export interface SceneOptions {
196
208
  readingDirection?: 'ltr' | 'rtl';
197
209
  /**
198
210
  * When to repaint:
199
- * - `'always'` (default): drive a continuous rAF loop, throttling to 2 FPS
200
- * while the scene is idle if {@link SceneOptions.autoThrottle} is on.
211
+ * - `'always'` (default): drive a continuous rAF loop, throttling to the
212
+ * {@link SceneOptions.idleFPS} floor (default 60) while the scene is idle
213
+ * if {@link SceneOptions.autoThrottle} is on.
201
214
  * - `'onDemand'`: paint only after {@link Scene.markDirty} (or an active
202
215
  * transition), so a genuinely static scene costs zero frames.
203
216
  *
@@ -217,14 +230,14 @@ export interface SceneOptions {
217
230
  * plain untranspiled JS, gets no diagnostic at all. `renderMode` was a public
218
231
  * field with no matching option for several releases, and four `@vectojs` demos
219
232
  * shipped `new Scene(canvas, { renderMode: 'onDemand' })` — reading correctly,
220
- * doing nothing, and sitting on the 2 FPS idle floor.
233
+ * doing nothing, and sitting on the idle FPS floor.
221
234
  *
222
235
  * Kept as a literal rather than derived from a type: `keyof SceneOptions` does
223
236
  * not survive to runtime, so this list is the only form a constructor can check
224
237
  * against. A new option must be added here too — the test suite asserts the two
225
238
  * stay in sync.
226
239
  */
227
- export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'contentSemanticBudget', 'contentSemanticMargin', 'debugA11y', 'disableWindowResize', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
240
+ export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'contentSemanticBudget', 'contentSemanticMargin', 'debugA11y', 'disableWindowResize', 'idleFPS', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
228
241
  /** Frame-rate the loop is capped to when the OS requests reduced motion. */
229
242
  export declare const REDUCED_MOTION_FPS = 30;
230
243
  export type { AcceleratorReason, AcceleratorStatus, AcceleratorReport };
@@ -406,8 +419,17 @@ export declare class Scene {
406
419
  */
407
420
  private get dirty();
408
421
  private set dirty(value);
409
- /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
422
+ /**
423
+ * Whether to throttle the `'always'` loop when the scene is static to save
424
+ * power. The idle floor is {@link Scene.idleFPS} (default 60).
425
+ */
410
426
  autoThrottle: boolean;
427
+ /**
428
+ * Frame-rate floor for an idle `'always'` scene when {@link Scene.autoThrottle}
429
+ * is on. Default `60`. Set `2` for the legacy aggressive idle sleep, `0` to
430
+ * keep the scene's `maxFPS` cadence while idle.
431
+ */
432
+ idleFPS: number;
411
433
  /** Wall-clock ms spent inside the last `render()` call. */
412
434
  private _lastFrameMs;
413
435
  /** Rolling exponential average of rendered-frame intervals, in ms. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.35.3",
3
+ "version": "1.37.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },