@jxsuite/runtime 3.1.0 → 4.0.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/src/statements.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * (interpreter) and `compileStatements` (JS emitter), shared by the runtime and every compiler
8
8
  * target. Statement kinds reuse web-platform names only: bare expression nodes (§19), the JSON
9
9
  * Schema `if`/`then`/`else` triple, `$switch`/`cases` in statement position, and WHATWG's
10
- * `dispatchEvent` with `CustomEventInit` members.
10
+ * `dispatchEvent` with `CustomEventInit` members, `stopPropagation` and `preventDefault`.
11
11
  */
12
12
 
13
13
  import {
@@ -24,11 +24,23 @@ import type { JxScope } from "./types.ts";
24
24
  interface RunOpts {
25
25
  /** Named-formula-style arguments bound for $args/<name> refs in the body. */
26
26
  args?: Record<string, unknown>;
27
- /** Dispatch target for dispatchEvent statements; defaults to event?.currentTarget. */
28
- target?: EventTarget | null;
27
+ /**
28
+ * Dispatch target for dispatchEvent statements when there is no event to read `currentTarget`
29
+ * from — a parameterised body invoked through `call`, or a lifecycle hook. A thunk is read at
30
+ * dispatch time, because a mount's root does not exist yet when its scope is built.
31
+ */
32
+ target?: EventTarget | null | (() => EventTarget | null);
29
33
  }
30
34
 
31
- function statementKind(statement: JxStatement): "expression" | "if" | "switch" | "dispatch" {
35
+ type StatementKind =
36
+ | "expression"
37
+ | "if"
38
+ | "switch"
39
+ | "dispatch"
40
+ | "stopPropagation"
41
+ | "preventDefault";
42
+
43
+ function statementKind(statement: JxStatement): StatementKind {
32
44
  if ("operator" in statement) {
33
45
  return "expression";
34
46
  }
@@ -38,6 +50,12 @@ function statementKind(statement: JxStatement): "expression" | "if" | "switch" |
38
50
  if ("$switch" in statement) {
39
51
  return "switch";
40
52
  }
53
+ if ("stopPropagation" in statement) {
54
+ return "stopPropagation";
55
+ }
56
+ if ("preventDefault" in statement) {
57
+ return "preventDefault";
58
+ }
41
59
  return "dispatch";
42
60
  }
43
61
 
@@ -91,7 +109,9 @@ export async function runStatements(
91
109
  bubbles?: boolean;
92
110
  composed?: boolean;
93
111
  };
94
- const target = opts.target ?? event?.currentTarget;
112
+ // An event's own target wins; the option is the fallback for bodies run without one.
113
+ const fallback = typeof opts.target === "function" ? opts.target() : opts.target;
114
+ const target = event?.currentTarget ?? fallback;
95
115
  if (!target) {
96
116
  break;
97
117
  }
@@ -108,6 +128,15 @@ export async function runStatements(
108
128
  target.dispatchEvent(new CustomEvent(dispatch.dispatchEvent, init));
109
129
  break;
110
130
  }
131
+ case "stopPropagation": {
132
+ // The event the handler is running for; a body run without one has nothing to stop.
133
+ event?.stopPropagation();
134
+ break;
135
+ }
136
+ case "preventDefault": {
137
+ event?.preventDefault();
138
+ break;
139
+ }
111
140
  default: {
112
141
  break;
113
142
  }
@@ -203,6 +232,14 @@ export function compileStatements(
203
232
  );
204
233
  break;
205
234
  }
235
+ case "stopPropagation": {
236
+ lines.push(`${indent}${opts.eventParam ?? "event"}?.stopPropagation();`);
237
+ break;
238
+ }
239
+ case "preventDefault": {
240
+ lines.push(`${indent}${opts.eventParam ?? "event"}?.preventDefault();`);
241
+ break;
242
+ }
206
243
  default: {
207
244
  break;
208
245
  }
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="dom" />
2
- import type { JxElement, JxPath } from "@jxsuite/schema/types";
2
+ import type { JxDocument, JxElement, JxPath } from "@jxsuite/schema/types";
3
3
 
4
4
  export type { JxPath } from "@jxsuite/schema/types";
5
5
 
@@ -13,8 +13,74 @@ export type JxScope = Record<string, unknown>;
13
13
  /** An event handler stored in the scope: `(scope, event) => void`. */
14
14
  export type JxEventHandler = (scope: JxScope, event: Event) => unknown;
15
15
 
16
+ /**
17
+ * A host-supplied document source consulted before the network. Returning `undefined` means "not
18
+ * mine" and the runtime fetches as usual; a hit is served for THIS mount only and never enters the
19
+ * shared resolve cache.
20
+ */
21
+ export type DocumentResolver = (url: string) => JxDocument | Promise<JxDocument> | undefined;
22
+
23
+ /**
24
+ * Per-mount configuration, threaded through a render instead of read from module globals. A shell
25
+ * that hosts many roots beside a canvas needs each root to answer "which `$media`, which base,
26
+ * which documents" for itself; the module-level setters remain the defaults a context inherits.
27
+ */
28
+ export interface JxContext {
29
+ /** Base URL the document's own references resolve against. */
30
+ base: string;
31
+ /**
32
+ * Root `$media` map for components that declare `@--name` blocks but carry no own `$media`; null
33
+ * inherits the module default.
34
+ */
35
+ media: Record<string, string> | null;
36
+ /** The mount's rendered root, set once render completes — the default `dispatchEvent` target. */
37
+ root: HTMLElement | Text | null;
38
+ resolver?: DocumentResolver;
39
+ /** Per-mount override of `setSkipServerFunctions`; undefined inherits the module default. */
40
+ skipServerFunctions?: boolean;
41
+ /** Per-mount override of `setSkipAutoRequests`; undefined inherits the module default. */
42
+ skipAutoRequests?: boolean;
43
+ }
44
+
45
+ /** Options for {@link mount}: the host's half of the embedding contract (specs/embedding.md). */
46
+ export interface JxMountOptions {
47
+ /**
48
+ * Host members merged into the document's scope before its own `state` is built: reactive
49
+ * records, refs, computeds and plain functions. A document `state` entry of the same name wins.
50
+ * Keys beginning with `$` or `#` are runtime vocabulary and are refused.
51
+ */
52
+ scope?: Record<string, unknown>;
53
+ base?: string;
54
+ media?: Record<string, string>;
55
+ resolver?: DocumentResolver;
56
+ skipServerFunctions?: boolean;
57
+ skipAutoRequests?: boolean;
58
+ /** Aborting disposes the mount; an already-aborted signal mounts nothing. */
59
+ signal?: AbortSignal;
60
+ onNodeCreated?: JxRenderOptions["onNodeCreated"];
61
+ onNodeMoved?: JxRenderOptions["onNodeMoved"];
62
+ }
63
+
64
+ /** A mounted document: its live scope, its root node, and the disposer that tears it down. */
65
+ export interface JxMount {
66
+ /** The reactive scope. Host writes to members it supplied propagate into the render. */
67
+ readonly scope: JxScope;
68
+ readonly root: HTMLElement | Text;
69
+ /**
70
+ * Custom-element tag names this render instantiated, so a host can re-mount what a redefinition
71
+ * touches.
72
+ */
73
+ readonly elements: ReadonlySet<string>;
74
+ /** Stops every effect, calls `onUnmount`, removes the root. Idempotent. */
75
+ readonly dispose: () => void;
76
+ }
77
+
16
78
  export interface JxRenderOptions {
17
79
  _path?: JxPath;
80
+ /** The mount context this render belongs to; absent on the legacy direct-render path. */
81
+ _ctx?: JxContext;
82
+ /** Read by {@link Jx} only: aborting disposes the mount. */
83
+ signal?: AbortSignal;
18
84
  /**
19
85
  * Base URL the document's own references resolve against — `$ref`, `$elements`, `$head` and the
20
86
  * asset paths beneath them.
@@ -38,6 +104,12 @@ export interface JxRenderOptions {
38
104
  def: JxElement | string,
39
105
  state?: JxScope,
40
106
  ) => void;
107
+ /**
108
+ * Called when a keyed mapped-array row is reused at a new index. The node is the same one
109
+ * `onNodeCreated` reported; `path` is its new document path, so a host that stamped the old one
110
+ * can re-stamp it.
111
+ */
112
+ onNodeMoved?: (node: HTMLElement | Text, path: JxPath) => void;
41
113
  }
42
114
 
43
115
  export interface DynamicClass {