@simpludify/core 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # @simpludify/core
2
2
 
3
- Experimental v0.1.2 of Simpludify, a code-first TypeScript 2D engine. APIs may change between 0.x releases. This package contains host-independent Scene/Actor/Component, collision, geometry, tilemaps, tween/animation, input and Canvas UI models. It has no browser or Node runtime dependencies.
3
+ Experimental v0.1.3 of Simpludify, a code-first TypeScript 2D engine. APIs may change between 0.x releases. This package contains host-independent Scene/Actor/Component, collision, geometry, tilemaps, tween/animation, input and Canvas UI models. It has no browser or Node runtime dependencies.
4
4
 
5
- Registry installation (check availability with `npm view @simpludify/core@0.1.2 version`):
5
+ Registry installation (check availability with `npm view @simpludify/core@0.1.3 version`):
6
6
 
7
7
  ```sh
8
- npm install @simpludify/core@0.1.2
8
+ npm install @simpludify/core@0.1.3
9
9
  ```
10
10
 
11
11
  ```js
@@ -1,2 +1,2 @@
1
1
  /** Source workspace version; checked against package.json by the build validator. */
2
- export declare const ENGINE_VERSION = "0.1.2";
2
+ export declare const ENGINE_VERSION = "0.1.3";
package/dist/index.js CHANGED
@@ -1583,7 +1583,7 @@ var RandomManager = class {
1583
1583
  };
1584
1584
  // packages/core/src/foundation/version.ts
1585
1585
  /** Source workspace version; checked against package.json by the build validator. */
1586
- var ENGINE_VERSION = "0.1.2";
1586
+ var ENGINE_VERSION = "0.1.3";
1587
1587
  // packages/core/src/scene/actor.ts
1588
1588
  var inputPhase$1 = ComponentPhase.Input;
1589
1589
  var updatePhase$1 = ComponentPhase.Update;
@@ -6269,7 +6269,7 @@ var ReplayPlayer = class {
6269
6269
  this.engine = engine;
6270
6270
  this.data = parseReplayData(data);
6271
6271
  this.bound = bindings(engine, options.controllers);
6272
- if (this.data.engineVersion !== "0.1.2" || engine.random.seed !== this.data.random.seed) throw new EngineError("REPLAY_INCOMPATIBLE", "Engine version or initial seed does not match Replay");
6272
+ if (this.data.engineVersion !== "0.1.3" || engine.random.seed !== this.data.random.seed) throw new EngineError("REPLAY_INCOMPATIBLE", "Engine version or initial seed does not match Replay");
6273
6273
  if (!compatible(this.bound.map(([id, c]) => ({
6274
6274
  id,
6275
6275
  actions: c.schema()
@@ -6639,11 +6639,22 @@ var CanvasNode = class {
6639
6639
  }
6640
6640
  click;
6641
6641
  hold;
6642
+ /** @internal Text semantics supplied by CanvasLabel. */
6643
+ labelSemantics;
6642
6644
  /** @internal Prefer CanvasPanel/Label/Button/HoldButton. */
6643
6645
  constructor(kind, options = {}) {
6644
6646
  this.kind = kind;
6645
6647
  this.captionValue = options.text ?? "";
6646
6648
  if (typeof this.captionValue !== "string") throw new EngineError("INVALID_UI", "UI text must be a string");
6649
+ if (options.label) {
6650
+ const { role, level } = options.label;
6651
+ if (kind !== "label" || role !== void 0 && ![
6652
+ "heading",
6653
+ "status",
6654
+ "alert"
6655
+ ].includes(role) || (role === "heading" ? typeof level !== "number" || !Number.isInteger(level) || level < 1 || level > 6 : level !== void 0)) throw new EngineError("INVALID_UI", "Invalid label semantics");
6656
+ this.labelSemantics = Object.freeze({ ...options.label });
6657
+ }
6647
6658
  this.config = checkedStyle(options.style ?? {});
6648
6659
  this.click = options.click;
6649
6660
  this.hold = options.hold;
@@ -6889,9 +6900,17 @@ function CanvasColumn(children, style = {}) {
6889
6900
  direction: "column"
6890
6901
  });
6891
6902
  }
6892
- function CanvasLabel(text, style = {}) {
6903
+ function CanvasLabel(content, style = {}) {
6904
+ if (typeof content === "string") return new CanvasNode("label", {
6905
+ text: content,
6906
+ style
6907
+ });
6908
+ if (!content || typeof content !== "object") throw new EngineError("INVALID_UI", "UI label must be text or options");
6909
+ const { text, ...label } = content;
6910
+ if (typeof text !== "string") throw new EngineError("INVALID_UI", "UI text must be a string");
6893
6911
  return new CanvasNode("label", {
6894
6912
  text,
6913
+ label,
6895
6914
  style
6896
6915
  });
6897
6916
  }
@@ -7361,6 +7380,9 @@ var CanvasTextEditorNode = class extends CanvasInputNode {
7361
7380
  this.buffer = snapshot(initialText);
7362
7381
  this.onDispose(() => this.session?.cancel());
7363
7382
  }
7383
+ get inputMode() {
7384
+ return this.kind === "number-input" ? "decimal" : "text";
7385
+ }
7364
7386
  get limit() {}
7365
7387
  get draft() {
7366
7388
  return this.buffer;
@@ -7386,7 +7408,7 @@ var CanvasTextEditorNode = class extends CanvasInputNode {
7386
7408
  text: this.editing ? this.buffer.text : this.format(this.value),
7387
7409
  placeholder: this.hint,
7388
7410
  maxLength: this.limit,
7389
- inputMode: this.kind === "number-input" ? "decimal" : "text",
7411
+ inputMode: this.inputMode,
7390
7412
  readOnly: this.readOnly
7391
7413
  });
7392
7414
  }
@@ -7669,7 +7691,16 @@ function validText(value, limit) {
7669
7691
  var CanvasTextInputNode = class extends CanvasTextEditorNode {
7670
7692
  constraints;
7671
7693
  constructor(options = {}, style = {}) {
7672
- const constraints = { maxLength: lengthLimit(options.maxLength) }, initial = validText(options.value ?? "", constraints.maxLength);
7694
+ const inputMode = options.inputMode ?? "text";
7695
+ if (![
7696
+ "text",
7697
+ "numeric",
7698
+ "decimal"
7699
+ ].includes(inputMode)) throw new EngineError("INVALID_UI", "Invalid text keyboard hint");
7700
+ const constraints = {
7701
+ maxLength: lengthLimit(options.maxLength),
7702
+ inputMode
7703
+ }, initial = validText(options.value ?? "", constraints.maxLength);
7673
7704
  super("text-input", options.label ?? "Text", initial, (value) => validText(value, constraints.maxLength), options.readOnly ?? false, options.placeholder ?? "", initial, style);
7674
7705
  this.constraints = constraints;
7675
7706
  }
@@ -7680,6 +7711,9 @@ var CanvasTextInputNode = class extends CanvasTextEditorNode {
7680
7711
  if (this.maxLength !== void 0 && text.length > this.maxLength) return;
7681
7712
  return { value: text };
7682
7713
  }
7714
+ get inputMode() {
7715
+ return this.constraints.inputMode;
7716
+ }
7683
7717
  get limit() {
7684
7718
  return this.maxLength;
7685
7719
  }
@@ -7759,6 +7793,7 @@ var CanvasUI = class {
7759
7793
  focusNode;
7760
7794
  factor;
7761
7795
  dirty = true;
7796
+ measure;
7762
7797
  projectionDirty = true;
7763
7798
  baseBounds = /* @__PURE__ */ new WeakMap();
7764
7799
  moving = /* @__PURE__ */ new Set();
@@ -7841,8 +7876,12 @@ var CanvasUI = class {
7841
7876
  /** Logical focus; Hosts mirror it to native controls where available. */
7842
7877
  focus(node) {
7843
7878
  if (this.disposed) throw new EngineError("OWNER_DISPOSED", "UI has been disposed");
7879
+ this.syncFocusLayout();
7844
7880
  if (node && !this.canFocus(node)) throw new EngineError("UI_OWNERSHIP", "Focus requires a reachable interactive node in this UI");
7845
- if (this.focusNode === node) return;
7881
+ if (this.focusNode === node) {
7882
+ if (node) this.reveal(node);
7883
+ return;
7884
+ }
7846
7885
  const previous = this.focusNode;
7847
7886
  this.focusNode = node;
7848
7887
  if (previous) {
@@ -7858,6 +7897,7 @@ var CanvasUI = class {
7858
7897
  }
7859
7898
  /** Ordered traversal, including offscreen controls. Undefined leaves focus at the boundary. */
7860
7899
  focusNext(reverse = false) {
7900
+ this.syncFocusLayout();
7861
7901
  const order = [];
7862
7902
  this.walk((node) => {
7863
7903
  if (this.canFocus(node)) order.push(node);
@@ -7867,6 +7907,10 @@ var CanvasUI = class {
7867
7907
  if (node) this.focus(node);
7868
7908
  return node;
7869
7909
  }
7910
+ /** Reuse the Host's last metrics after a dynamic form/list mutation. */
7911
+ syncFocusLayout() {
7912
+ if (this.measure && (this.dirty || this.projectionDirty)) this.layout(this.width, this.height, this.measure);
7913
+ }
7870
7914
  /** Project scroll reveal before testing clips; static clipped controls must not trap Tab. */
7871
7915
  canFocus(node) {
7872
7916
  if (node.ui !== this || !node.available || !node.interactive || node.style.pointerEvents !== "auto") return false;
@@ -7948,6 +7992,7 @@ var CanvasUI = class {
7948
7992
  }
7949
7993
  }
7950
7994
  /** @internal */ invalidate(layout = false) {
7995
+ if (this.disposed) return;
7951
7996
  if (this.focusNode && (this.focusNode.ui !== this || !this.focusNode.available)) this.focus();
7952
7997
  for (const node of this.moving) if (node.ui !== this || !node.available || !node.style.inertia) {
7953
7998
  this.moving.delete(node);
@@ -7959,6 +8004,7 @@ var CanvasUI = class {
7959
8004
  /** @internal Rendering supplies exact text metrics; unchanged frames do no layout. */
7960
8005
  layout(width, height, measure) {
7961
8006
  if (this.disposed) return false;
8007
+ this.measure = measure;
7962
8008
  const geometry = this.dirty || this.width !== width || this.height !== height;
7963
8009
  if (!geometry && !this.projectionDirty) return false;
7964
8010
  if (geometry) {
@@ -8112,6 +8158,7 @@ var CanvasUI = class {
8112
8158
  this.removeScene = void 0;
8113
8159
  this.nodes = 0;
8114
8160
  this.errors = () => {};
8161
+ this.measure = void 0;
8115
8162
  }
8116
8163
  ], "Canvas UI cleanup failed");
8117
8164
  }