stage-js 1.0.1 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stage-js",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "2D HTML5 Rendering and Layout",
5
5
  "homepage": "http://piqnt.com/stage.js/",
6
6
  "author": "Ali Shakiba",
@@ -22,7 +22,7 @@
22
22
  "mobile"
23
23
  ],
24
24
  "engines": {
25
- "node": ">=18.0"
25
+ "node": ">=24.0"
26
26
  },
27
27
  "type": "module",
28
28
  "module": "./dist/stage.js",
@@ -55,7 +55,10 @@
55
55
  "change": "changeset",
56
56
  "version": "changeset version",
57
57
  "publish": "changeset publish",
58
- "typedoc": "typedoc --options typedoc.json && mv ./docs/pages/api/README.md ./docs/pages/api/index.md && ./node_modules/.bin/replace-in-files ./docs/pages/api/* --string '.md' --replacement ''"
58
+ "typedoc": "typedoc --options typedoc.json && mv ./docs/pages/api/README.md ./docs/pages/api/index.md",
59
+ "docs:dev": "vitepress dev docs",
60
+ "docs:build": "vitepress build docs",
61
+ "docs:preview": "vitepress preview docs"
59
62
  },
60
63
  "devDependencies": {
61
64
  "@changesets/cli": "^2.27.11",
@@ -68,16 +71,16 @@
68
71
  "expect.js": "^0.3.1",
69
72
  "mocha": "^6.2.3",
70
73
  "prettier": "^3.2.5",
71
- "replace-in-files-cli": "^3.0.0",
72
74
  "rollup-plugin-license": "^3.3.1",
73
75
  "sandboxed-module": "^2.0.0",
74
76
  "sinon": "^9.0.2",
75
- "typedoc": "^0.26.11",
76
- "typedoc-plugin-markdown": "^4.2.10",
77
+ "typedoc": "^0.28.18",
78
+ "typedoc-plugin-markdown": "^4.11.0",
77
79
  "typescript": "^5.6.3",
78
- "vite": "^5.4.11",
79
- "vite-plugin-dts-bundle-generator": "^2.0.5",
80
- "vite-plugin-pages": "^0.32.3",
81
- "vite-plugin-typescript": "^1.0.4"
80
+ "unplugin-dts-bundle-generator": "^3.4.1",
81
+ "vite": "^6.4.1",
82
+ "vite-plugin-pages": "^0.33.3",
83
+ "vite-plugin-typescript": "^1.0.4",
84
+ "vitepress": "^2.0.0-alpha.16"
82
85
  }
83
86
  }
@@ -1,10 +1,11 @@
1
1
  import stats from "../common/stats";
2
- import { Vec2Value } from "../common/matrix";
2
+ import { Matrix, Vec2Value } from "../common/matrix";
3
3
  import { uid } from "../common/uid";
4
4
  import { getDevicePixelRatio } from "../common/browser";
5
5
 
6
6
  import { Pin, FitMode, SetPinType, SetPinKeys, GetPinKeys } from "./pin";
7
7
  import { Transition, TransitionOptions } from "./transition";
8
+ import { renderAxis, renderPoint } from "./debug";
8
9
 
9
10
  // todo: why there are two iids (other in pin)?
10
11
  /** @internal */
@@ -130,6 +131,12 @@ export class Component {
130
131
  /** @internal */ _mo_seqAlign: number;
131
132
  /** @internal */ _mo_box: number;
132
133
 
134
+ /** @hidden Set to true to enable debug rendering */
135
+ _debug: boolean;
136
+
137
+ /** @internal */
138
+ _xf: Matrix;
139
+
133
140
  constructor() {
134
141
  stats.create++;
135
142
  if (this instanceof Component) {
@@ -144,25 +151,30 @@ export class Component {
144
151
  return this._pin.absoluteMatrix();
145
152
  }
146
153
 
147
- /** @hidden @deprecated */
154
+ /** @hidden @deprecated Use getLogicalPixelRatio */
148
155
  getPixelRatio() {
149
156
  // todo: remove this function
150
- const m = this._parent?.matrix();
151
- const pixelRatio = !m ? 1 : Math.max(Math.abs(m.a), Math.abs(m.b)) / getDevicePixelRatio();
152
- return pixelRatio;
157
+ return this.getLogicalPixelRatio();
153
158
  }
154
159
 
155
- /** @hidden This is not accurate before first tick */
160
+ /**
161
+ * @hidden
162
+ * Physical-pixel per unit of parent component.
163
+ * This is not accurate before first tick.
164
+ */
156
165
  getDevicePixelRatio() {
157
166
  // todo: parent matrix is not available in the first call
158
167
  const parentMatrix = this._parent?.matrix();
159
- const pixelRatio = !parentMatrix
160
- ? 1
161
- : Math.max(Math.abs(parentMatrix.a), Math.abs(parentMatrix.b));
168
+ if (!parentMatrix) return 1;
169
+ const pixelRatio = Math.max(Math.abs(parentMatrix.a), Math.abs(parentMatrix.b));
162
170
  return pixelRatio;
163
171
  }
164
172
 
165
- /** @hidden This is not accurate before first tick */
173
+ /**
174
+ * @hidden
175
+ * Logical-pixel per unit of parent component.
176
+ * This is not accurate before first tick.
177
+ */
166
178
  getLogicalPixelRatio() {
167
179
  return this.getDevicePixelRatio() / getDevicePixelRatio();
168
180
  }
@@ -656,6 +668,9 @@ export class Component {
656
668
  stats.component++;
657
669
 
658
670
  const m = this.matrix();
671
+
672
+ this.renderDebug(context, m);
673
+
659
674
  context.setTransform(m.a, m.b, m.c, m.d, m.e, m.f);
660
675
 
661
676
  // move this elsewhere!
@@ -1199,6 +1214,33 @@ export class Component {
1199
1214
  this._spacing = space;
1200
1215
  return this;
1201
1216
  }
1217
+
1218
+ renderDebug(ctx: CanvasRenderingContext2D, xf: Matrix) {
1219
+ if (!this._debug) return;
1220
+ const ppu = this.getLogicalPixelRatio();
1221
+
1222
+ ctx.lineWidth = 1 / ppu;
1223
+ ctx.lineCap = "round";
1224
+ ctx.lineJoin = "round";
1225
+
1226
+ renderAxis(ctx, 1);
1227
+
1228
+ const pin = this._pin;
1229
+ if (pin._pivoted) {
1230
+ ctx.strokeStyle = "orange";
1231
+ renderPoint(ctx, pin._pivotX * pin._width, pin._pivotY * pin._height);
1232
+ }
1233
+
1234
+ if (pin._aligned) {
1235
+ ctx.strokeStyle = "green";
1236
+ renderPoint(ctx, pin._alignX * pin._width, pin._alignY * pin._height);
1237
+ }
1238
+
1239
+ if (pin._handled) {
1240
+ ctx.strokeStyle = "yellow";
1241
+ renderPoint(ctx, pin._handleX * pin._width, pin._handleY * pin._height);
1242
+ }
1243
+ }
1202
1244
  }
1203
1245
 
1204
1246
  /** @hidden @deprecated Node is renamed to Component */
@@ -0,0 +1,33 @@
1
+ export function renderAxis(this: unknown, ctx: CanvasRenderingContext2D, size: number) {
2
+ ctx.strokeStyle = "rgba(93, 173, 226)";
3
+ ctx.beginPath();
4
+ ctx.moveTo(0, 0);
5
+ ctx.lineTo(0, 0.8 * size);
6
+ ctx.lineTo(-0.2 * size, 0.8 * size);
7
+ ctx.lineTo(0, size);
8
+ ctx.lineTo(+0.2 * size, 0.8 * size);
9
+ ctx.lineTo(0, 0.8 * size);
10
+ ctx.stroke();
11
+
12
+ ctx.strokeStyle = "rgba(236, 112, 99)";
13
+ ctx.beginPath();
14
+ ctx.moveTo(0, 0);
15
+ ctx.lineTo(0.8 * size, 0);
16
+ ctx.lineTo(0.8 * size, -0.2 * size);
17
+ ctx.lineTo(size, 0);
18
+ ctx.lineTo(0.8 * size, +0.2 * size);
19
+ ctx.lineTo(0.8 * size, 0);
20
+ ctx.stroke();
21
+ }
22
+
23
+ export function renderPoint(this: unknown, ctx: CanvasRenderingContext2D, px: number, py: number) {
24
+ ctx.beginPath();
25
+ ctx.beginPath();
26
+ ctx.moveTo(px - 0.2, py - 0.2);
27
+ ctx.lineTo(px + 0.2, py + 0.2);
28
+ ctx.stroke();
29
+ ctx.beginPath();
30
+ ctx.moveTo(px - 0.2, py + 0.2);
31
+ ctx.lineTo(px + 0.2, py - 0.2);
32
+ ctx.stroke();
33
+ }
@@ -78,7 +78,7 @@ const initEasing = (query: string, params?: number[]): EasingFunction => {
78
78
  : 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
79
79
 
80
80
  /** @internal */ const poly =
81
- (e: number): EasingFunction =>
81
+ (e: number = 3): EasingFunction =>
82
82
  (t: number) =>
83
83
  Math.pow(t, e);
84
84
 
package/src/core/pin.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Matrix, Vec2Value } from "../common/matrix";
1
+ import { Matrix } from "../common/matrix";
2
2
  import { uid } from "../common/uid";
3
3
 
4
4
  import type { Component } from "./component";
@@ -35,6 +35,10 @@ export function isValidFitMode(value: string) {
35
35
 
36
36
  /** @internal */ let iid = 0;
37
37
 
38
+ /** @internal */ export function getIID() {
39
+ return iid++;
40
+ }
41
+
38
42
  export class Pin {
39
43
  /** @internal */ uid = "pin:" + uid();
40
44
 
@@ -194,6 +198,7 @@ export class Pin {
194
198
  abs.reset(this.relativeMatrix());
195
199
 
196
200
  this._parent && abs.concat(this._parent._absoluteMatrix);
201
+ this._owner._xf && abs.concat(this._owner._xf);
197
202
 
198
203
  this._ts_matrix = ++iid;
199
204
 
@@ -344,6 +349,51 @@ export class Pin {
344
349
  }
345
350
  }
346
351
 
352
+ /** @internal */
353
+ const fitted = {};
354
+
355
+ /** @internal */
356
+ export function fit(
357
+ this: unknown,
358
+ inWidth: number,
359
+ inHeight: number,
360
+ outWidth: number | null,
361
+ outHeight: number | null,
362
+ mode?: FitMode,
363
+ ) {
364
+ if (mode === "contain") mode = "in-pad";
365
+ if (mode === "cover") mode = "out-crop";
366
+
367
+ let scaleX: number;
368
+ let scaleY: number;
369
+
370
+ let width: number;
371
+ let height: number;
372
+
373
+ if (typeof outWidth === "number") {
374
+ scaleX = outWidth / inWidth;
375
+ width = inWidth;
376
+ }
377
+ if (typeof outHeight === "number") {
378
+ scaleY = outHeight / inHeight;
379
+ height = inHeight;
380
+ }
381
+ if (typeof outWidth === "number" && typeof outHeight === "number" && typeof mode === "string") {
382
+ if (mode === "fill") {
383
+ } else if (mode === "out" || mode === "out-crop") {
384
+ scaleX = scaleY = Math.max(scaleX, scaleY);
385
+ } else if (mode === "in" || mode === "in-pad") {
386
+ scaleX = scaleY = Math.min(scaleX, scaleY);
387
+ }
388
+ if (mode === "out-crop" || mode === "in-pad") {
389
+ width = outWidth / scaleX;
390
+ height = outHeight / scaleY;
391
+ }
392
+ }
393
+
394
+ return { scaleX, scaleY, width, height };
395
+ }
396
+
347
397
  /** @internal */ const getters = {
348
398
  alpha: function (pin: Pin) {
349
399
  return pin._alpha;
@@ -652,11 +702,11 @@ export interface SetPinType {
652
702
 
653
703
  rotation?: number;
654
704
 
655
- /** Center of scale/skew/rotate */
705
+ /** Center of scale/skew/rotate, 0 is start, 1 is end */
656
706
  pivot?: number;
657
- /** Center of scale/skew/rotate */
707
+ /** Center of scale/skew/rotate, 0 is start, 1 is end */
658
708
  pivotX?: number;
659
- /** Center of scale/skew/rotate */
709
+ /** Center of scale/skew/rotate, 0 is start, 1 is end */
660
710
  pivotY?: number;
661
711
 
662
712
  /** Offset in parent coordination */
@@ -666,18 +716,18 @@ export interface SetPinType {
666
716
  /** Offset in parent coordination */
667
717
  offsetY?: number;
668
718
 
669
- /** A point on parent where this component is offset from, 0 is top/left, 1 is bottom/right */
719
+ /** A point on parent where this component is offset from, 0 is start, 1 is end */
670
720
  align?: number;
671
- /** A point on parent where this component is offset from, 0 is top/left, 1 is bottom/right */
721
+ /** A point on parent where this component is offset from, 0 is start, 1 is end */
672
722
  alignX?: number;
673
- /** A point on parent where this component is offset from, 0 is top/left, 1 is bottom/right */
723
+ /** A point on parent where this component is offset from, 0 is start, 1 is end */
674
724
  alignY?: number;
675
725
 
676
- /** A point on this component which is offset from parent, 0 is top/left, 1 is bottom/right */
726
+ /** A point on this component which is offset from parent, 0 is start, 1 is end */
677
727
  handle?: number;
678
- /** A point on this component which is offset from parent, 0 is top/left, 1 is bottom/right */
728
+ /** A point on this component which is offset from parent, 0 is start, 1 is end */
679
729
  handleX?: number;
680
- /** A point on this component which is offset from parent, 0 is top/left, 1 is bottom/right */
730
+ /** A point on this component which is offset from parent, 0 is start, 1 is end */
681
731
  handleY?: number;
682
732
 
683
733
  /** @hidden @deprecated Use component.fit() */
@@ -18,6 +18,12 @@ export const POINTER_START = "touchstart mousedown";
18
18
  /** @hidden @deprecated */
19
19
  export const POINTER_END = "touchend mouseup";
20
20
 
21
+ export interface PointerEvent {
22
+ x: number;
23
+ y: number;
24
+ raw: UIEvent;
25
+ }
26
+
21
27
  class EventPoint {
22
28
  x: number;
23
29
  y: number;
package/src/core/root.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import stats from "../common/stats";
2
2
  import { Matrix } from "../common/matrix";
3
3
 
4
+ import { renderAxis } from "./debug";
4
5
  import { Component } from "./component";
5
6
  import { Pointer } from "./pointer";
6
- import { FitMode, isValidFitMode } from "./pin";
7
+ import { fit, FitMode, getIID, isValidFitMode } from "./pin";
7
8
 
8
9
  /** @internal */ const ROOTS: Root[] = [];
9
10
 
@@ -76,7 +77,6 @@ export class Root extends Component {
76
77
 
77
78
  /** @internal */ _viewport: Viewport;
78
79
  /** @internal */ _viewbox: Viewbox;
79
- /** @internal */ _camera: Matrix;
80
80
 
81
81
  constructor() {
82
82
  super();
@@ -255,9 +255,6 @@ export class Root extends Component {
255
255
  if (this.canvasWidth > 0 && this.canvasHeight > 0) {
256
256
  this.context.setTransform(1, 0, 0, 1, 0, 0);
257
257
  this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
258
- if (this.debugDrawAxis > 0) {
259
- this.renderDebug(this.context);
260
- }
261
258
  this.render(this.context);
262
259
  }
263
260
  } else if (tickRequest) {
@@ -271,40 +268,11 @@ export class Root extends Component {
271
268
  stats.fps = elapsed ? 1000 / elapsed : 0;
272
269
  };
273
270
 
274
- /** @hidden */
275
- debugDrawAxis = 0;
276
-
277
- private renderDebug(context: CanvasRenderingContext2D): void {
278
- const size = typeof this.debugDrawAxis === "number" ? this.debugDrawAxis : 10;
279
- const m = this.matrix();
280
- context.setTransform(m.a, m.b, m.c, m.d, m.e, m.f);
281
- const lineWidth = 3 / m.a;
282
-
283
- context.beginPath();
284
- context.moveTo(0, 0);
285
- context.lineTo(0, 0.8 * size);
286
- context.lineTo(-0.2 * size, 0.8 * size);
287
- context.lineTo(0, size);
288
- context.lineTo(+0.2 * size, 0.8 * size);
289
- context.lineTo(0, 0.8 * size);
290
- context.strokeStyle = "rgba(93, 173, 226)";
291
- context.lineJoin = "round";
292
- context.lineCap = "round";
293
- context.lineWidth = lineWidth;
294
- context.stroke();
295
-
296
- context.beginPath();
297
- context.moveTo(0, 0);
298
- context.lineTo(0.8 * size, 0);
299
- context.lineTo(0.8 * size, -0.2 * size);
300
- context.lineTo(size, 0);
301
- context.lineTo(0.8 * size, +0.2 * size);
302
- context.lineTo(0.8 * size, 0);
303
- context.strokeStyle = "rgba(236, 112, 99)";
304
- context.lineJoin = "round";
305
- context.lineCap = "round";
306
- context.lineWidth = lineWidth;
307
- context.stroke();
271
+ renderDebug(ctx: CanvasRenderingContext2D, m: Matrix) {
272
+ if (!this._debug) return;
273
+ ctx.setTransform(m.a, m.b, m.c, m.d, m.e, m.f);
274
+ ctx.lineWidth = 3 / m.a;
275
+ renderAxis(ctx, 10);
308
276
  }
309
277
 
310
278
  resume() {
@@ -380,7 +348,7 @@ export class Root extends Component {
380
348
  height: height,
381
349
  ratio: typeof ratio === "number" ? ratio : 1,
382
350
  };
383
- this.viewbox();
351
+ this.rescale();
384
352
  const data = Object.assign({}, this._viewport);
385
353
  this.visit({
386
354
  start: function (component) {
@@ -402,7 +370,6 @@ export class Root extends Component {
402
370
  viewbox(width?: number, height?: number, mode?: FitMode): this;
403
371
  viewbox(width?: number | Viewbox, height?: number, mode?: FitMode): this {
404
372
  // TODO: static/fixed viewbox
405
- // TODO: use css object-fit values
406
373
  if (typeof width === "number" && typeof height === "number") {
407
374
  this._viewbox = {
408
375
  width,
@@ -420,9 +387,11 @@ export class Root extends Component {
420
387
  return this;
421
388
  }
422
389
 
390
+ /** @hidden */
423
391
  camera(matrix: Matrix) {
424
- this._camera = matrix;
425
- this.rescale();
392
+ this._xf = matrix.clone();
393
+ this._pin._ts_transform = getIID();
394
+ this.touch();
426
395
  return this;
427
396
  }
428
397
 
@@ -430,39 +399,23 @@ export class Root extends Component {
430
399
  rescale() {
431
400
  const viewbox = this._viewbox;
432
401
  const viewport = this._viewport;
433
- const camera = this._camera;
434
402
  if (viewport && viewbox) {
435
- const viewportWidth = viewport.width;
436
- const viewportHeight = viewport.height;
437
403
  const viewboxMode = isValidFitMode(viewbox.mode) ? viewbox.mode : "in-pad";
438
- const viewboxWidth = viewbox.width;
439
- const viewboxHeight = viewbox.height;
440
-
404
+ const fitted = fit(
405
+ viewbox.width,
406
+ viewbox.height,
407
+ viewport.width,
408
+ viewport.height,
409
+ viewboxMode,
410
+ );
441
411
  this.pin({
442
- width: viewboxWidth,
443
- height: viewboxHeight,
412
+ width: fitted.width,
413
+ height: fitted.height,
414
+ scaleX: fitted.scaleX,
415
+ scaleY: fitted.scaleY,
416
+ offsetX: -(viewbox.x || 0) * fitted.scaleX,
417
+ offsetY: -(viewbox.y || 0) * fitted.scaleY,
444
418
  });
445
- this.fit(viewportWidth, viewportHeight, viewboxMode);
446
-
447
- const viewboxX = viewbox.x || 0;
448
- const viewboxY = viewbox.y || 0;
449
-
450
- const cameraZoomX = camera?.a || 1;
451
- const cameraZoomY = camera?.d || 1;
452
- const cameraX = camera?.e || 0;
453
- const cameraY = camera?.f || 0;
454
-
455
- const pinScaleX = this.pin("scaleX");
456
- const pinScaleY = this.pin("scaleY");
457
-
458
- const scaleX = pinScaleX * cameraZoomX;
459
- const scaleY = pinScaleY * cameraZoomY;
460
-
461
- this.pin("scaleX", scaleX);
462
- this.pin("scaleY", scaleY);
463
-
464
- this.pin("offsetX", cameraX - viewboxX * scaleX);
465
- this.pin("offsetY", cameraY - viewboxY * scaleY);
466
419
  } else if (viewport) {
467
420
  this.pin({
468
421
  width: viewport.width,