foldkit 0.127.0 → 0.128.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.
Files changed (92) hide show
  1. package/README.md +2 -1
  2. package/dist/asyncData/asyncData.d.ts +6 -6
  3. package/dist/asyncData/asyncData.d.ts.map +1 -1
  4. package/dist/asyncData/asyncData.js +7 -7
  5. package/dist/asyncData/public.d.ts +1 -1
  6. package/dist/asyncData/public.d.ts.map +1 -1
  7. package/dist/asyncData/public.js +1 -1
  8. package/dist/command/index.d.ts +23 -5
  9. package/dist/command/index.d.ts.map +1 -1
  10. package/dist/command/index.js +35 -7
  11. package/dist/experimental/index.d.ts +2 -0
  12. package/dist/experimental/index.d.ts.map +1 -0
  13. package/dist/experimental/index.js +1 -0
  14. package/dist/experimental/machine/index.d.ts +2 -0
  15. package/dist/experimental/machine/index.d.ts.map +1 -0
  16. package/dist/experimental/machine/index.js +1 -0
  17. package/dist/experimental/machine/machine.d.ts +194 -0
  18. package/dist/experimental/machine/machine.d.ts.map +1 -0
  19. package/dist/experimental/machine/machine.js +237 -0
  20. package/dist/experimental/machine/public.d.ts +2 -0
  21. package/dist/experimental/machine/public.d.ts.map +1 -0
  22. package/dist/experimental/machine/public.js +1 -0
  23. package/dist/experimental/public.d.ts +2 -0
  24. package/dist/experimental/public.d.ts.map +1 -0
  25. package/dist/experimental/public.js +1 -0
  26. package/dist/html/boundary.d.ts +9 -0
  27. package/dist/html/boundary.d.ts.map +1 -1
  28. package/dist/html/boundary.js +24 -0
  29. package/dist/html/childAttribute.d.ts +4 -1
  30. package/dist/html/childAttribute.d.ts.map +1 -1
  31. package/dist/html/childAttribute.js +3 -1
  32. package/dist/html/index.d.ts +45 -2
  33. package/dist/html/index.d.ts.map +1 -1
  34. package/dist/html/index.js +46 -3
  35. package/dist/html/runtimeSingleton.d.ts +7 -0
  36. package/dist/html/runtimeSingleton.d.ts.map +1 -1
  37. package/dist/html/runtimeSingleton.js +14 -1
  38. package/dist/html/submodel.d.ts +10 -3
  39. package/dist/html/submodel.d.ts.map +1 -1
  40. package/dist/managedResource/index.d.ts +1 -37
  41. package/dist/managedResource/index.d.ts.map +1 -1
  42. package/dist/managedResource/index.js +1 -20
  43. package/dist/{runtime → managedResource}/managedResource.d.ts +36 -2
  44. package/dist/managedResource/managedResource.d.ts.map +1 -0
  45. package/dist/{runtime → managedResource}/managedResource.js +20 -1
  46. package/dist/managedResource/public.d.ts +2 -4
  47. package/dist/managedResource/public.d.ts.map +1 -1
  48. package/dist/managedResource/public.js +1 -2
  49. package/dist/port/port.d.ts +1 -1
  50. package/dist/port/port.d.ts.map +1 -1
  51. package/dist/port/port.js +1 -1
  52. package/dist/route/index.d.ts +1 -1
  53. package/dist/route/index.d.ts.map +1 -1
  54. package/dist/route/index.js +1 -1
  55. package/dist/route/parser.d.ts +31 -0
  56. package/dist/route/parser.d.ts.map +1 -1
  57. package/dist/route/parser.js +64 -0
  58. package/dist/route/public.d.ts +3 -2
  59. package/dist/route/public.d.ts.map +1 -1
  60. package/dist/route/public.js +2 -1
  61. package/dist/route/transition.d.ts +73 -12
  62. package/dist/route/transition.d.ts.map +1 -1
  63. package/dist/route/transition.js +63 -12
  64. package/dist/runtime/runtime.d.ts +2 -4
  65. package/dist/runtime/runtime.d.ts.map +1 -1
  66. package/dist/runtime/runtime.js +5 -17
  67. package/dist/submodel/submodel.d.ts +4 -4
  68. package/dist/submodel/submodel.d.ts.map +1 -1
  69. package/dist/subscription/public.d.ts +2 -2
  70. package/dist/subscription/public.d.ts.map +1 -1
  71. package/dist/subscription/public.js +1 -1
  72. package/dist/subscription/subscription.d.ts.map +1 -0
  73. package/dist/test/apps/formChild.d.ts +1 -1
  74. package/dist/test/apps/formChild.d.ts.map +1 -1
  75. package/dist/test/apps/formChild.js +2 -1
  76. package/dist/test/internal.d.ts +44 -28
  77. package/dist/test/internal.d.ts.map +1 -1
  78. package/dist/test/internal.js +52 -50
  79. package/dist/test/scene.d.ts +1 -6
  80. package/dist/test/scene.d.ts.map +1 -1
  81. package/dist/test/scene.js +13 -24
  82. package/dist/test/story.d.ts +0 -2
  83. package/dist/test/story.d.ts.map +1 -1
  84. package/dist/test/story.js +2 -5
  85. package/dist/vdom.d.ts +2 -0
  86. package/dist/vdom.d.ts.map +1 -1
  87. package/dist/vdom.js +11 -1
  88. package/package.json +9 -1
  89. package/dist/runtime/managedResource.d.ts.map +0 -1
  90. package/dist/runtime/subscription.d.ts.map +0 -1
  91. /package/dist/{runtime → subscription}/subscription.d.ts +0 -0
  92. /package/dist/{runtime → subscription}/subscription.js +0 -0
@@ -0,0 +1,237 @@
1
+ import { Array, Match as M, Option, Predicate, Record, pipe, } from 'effect';
2
+ const makeEdge = (target, build, commands) => {
3
+ const narrowGuardValue = (input) => {
4
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
5
+ return input;
6
+ };
7
+ return {
8
+ _tag: 'Edge',
9
+ target,
10
+ build: input => build(narrowGuardValue(input)),
11
+ maybeCommands: commands === undefined
12
+ ? Option.none()
13
+ : Option.some(input => commands(narrowGuardValue(input))),
14
+ };
15
+ };
16
+ /**
17
+ * Declares a transition Edge to the state variant named by `target`. The
18
+ * `build` callback receives an {@link EdgeInput} whose state and Message are
19
+ * narrowed to the variants the Edge sits under in the transition table, and
20
+ * must return the target variant. Optional `commands` attach transition-time
21
+ * effects.
22
+ *
23
+ * Only meaningful inside a {@link TransitionTable}: the source and trigger
24
+ * types flow in from the table position contextually.
25
+ */
26
+ export const to = (target, build, commands) => makeEdge(target, build, commands);
27
+ /**
28
+ * Guards an Edge. Guard lists run in order, and the first guard that passes
29
+ * fires its Edge. A guard either resolves the state and Message to an
30
+ * `Option` (returning `Option.some` passes, and the wrapped value flows into
31
+ * the Edge's build and commands callbacks as `guardValue`) or returns a
32
+ * plain boolean when there is nothing to extract (returning `true` passes,
33
+ * and `guardValue` is `void`).
34
+ *
35
+ * The state and Message parameters are `NoInfer` so they resolve from the
36
+ * guard list's table position alone.
37
+ */
38
+ export const when = (guard, target, build, commands) => ({
39
+ _tag: 'When',
40
+ guard: (state, message) => {
41
+ const result = guard(state, message);
42
+ if (Predicate.isBoolean(result)) {
43
+ return result ? Option.some(undefined) : Option.none();
44
+ }
45
+ else {
46
+ return result;
47
+ }
48
+ },
49
+ edge: makeEdge(target, build, commands),
50
+ });
51
+ /** The unconditional fallback at the end of a guard list. The parameter is `NoInfer` for the same reason as {@link when}'s. */
52
+ export const otherwise = (edge) => ({
53
+ _tag: 'Otherwise',
54
+ edge,
55
+ });
56
+ const isGuardList = (edgeOrGuardedEdges) => globalThis.Array.isArray(edgeOrGuardedEdges);
57
+ const extractLiteralTag = (tagField) => {
58
+ if (Predicate.hasProperty(tagField, 'literal') &&
59
+ Predicate.isString(tagField.literal)) {
60
+ return Option.some(tagField.literal);
61
+ }
62
+ else if (Predicate.hasProperty(tagField, 'schema')) {
63
+ return extractLiteralTag(tagField.schema);
64
+ }
65
+ else {
66
+ return Option.none();
67
+ }
68
+ };
69
+ const extractMemberTag = (member) => pipe(Option.some(member), Option.filter(Predicate.hasProperty('fields')), Option.map(struct => struct.fields), Option.filter(Predicate.hasProperty('_tag')), Option.flatMap(fields => extractLiteralTag(fields._tag)));
70
+ /**
71
+ * Compiles a declarative transition table into a {@link Machine}.
72
+ *
73
+ * Two stages: the first takes the state and Message union Schemas and fixes
74
+ * the type parameters, the second takes the initial state and the transition
75
+ * table. The split is what lets TypeScript narrow `state` and `message`
76
+ * inside every Edge from its table position: a single-call form checks the
77
+ * table while the type parameters are still being inferred, and the
78
+ * narrowing collapses.
79
+ *
80
+ * The Machine is not a runtime: `transition` has the same shape as a Foldkit
81
+ * `update` branch and returns `[nextState, commands]`, so the machine state
82
+ * lives in the Model and the Foldkit runtime never learns the Machine
83
+ * exists. Messages that match no Edge leave the state unchanged; use `step`
84
+ * when the `Ignored` outcome should be observable.
85
+ *
86
+ * Because every Edge names a literal target tag, the Edge set is plain data:
87
+ * `reachableFrom`, `unreachableStates`, `deadTransitions`, and `toMermaid`
88
+ * all read it directly.
89
+ */
90
+ export const define = (schemas) => (definition) => {
91
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
92
+ const looseStates = definition.states;
93
+ const initialTag = definition.initial._tag;
94
+ const stateTags = pipe(schemas.state.members, Array.map(member => Option.getOrThrowWith(extractMemberTag(member), () => new Error('Machine.define: every member of the state union Schema must be a Struct with a literal _tag field'))),
95
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
96
+ tags => tags);
97
+ const makeEdgeSummary = (from, messageTag, target, guard) => ({ from, messageTag, target, guard });
98
+ const summarizeEntry = (from, messageTag, edgeOrGuardedEdges) => isGuardList(edgeOrGuardedEdges)
99
+ ? Array.map(edgeOrGuardedEdges, (guardedEdge, position) => guardedEdge._tag === 'When'
100
+ ? makeEdgeSummary(from, messageTag, guardedEdge.edge.target, {
101
+ _tag: 'When',
102
+ position,
103
+ })
104
+ : makeEdgeSummary(from, messageTag, guardedEdge.edge.target, {
105
+ _tag: 'Otherwise',
106
+ position,
107
+ }))
108
+ : [
109
+ makeEdgeSummary(from, messageTag, edgeOrGuardedEdges.target, {
110
+ _tag: 'Unguarded',
111
+ }),
112
+ ];
113
+ const edges = pipe(Record.toEntries(looseStates), Array.flatMap(([sourceTag, stateEntry]) => pipe(Record.toEntries(stateEntry.on), Array.flatMap(([messageTag, edgeOrGuardedEdges]) => summarizeEntry(sourceTag, messageTag, edgeOrGuardedEdges)))));
114
+ const selectFromGuardList = (guardedEdges, state, message) => Array.matchLeft(guardedEdges, {
115
+ onEmpty: () => Option.none(),
116
+ onNonEmpty: (guardedEdge, rest) => {
117
+ if (guardedEdge._tag === 'When') {
118
+ const maybeGuardValue = guardedEdge.guard(state, message);
119
+ if (Option.isSome(maybeGuardValue)) {
120
+ return Option.some({
121
+ edge: guardedEdge.edge,
122
+ guardValue: maybeGuardValue.value,
123
+ });
124
+ }
125
+ else {
126
+ return selectFromGuardList(rest, state, message);
127
+ }
128
+ }
129
+ else {
130
+ return Option.some({
131
+ edge: guardedEdge.edge,
132
+ guardValue: undefined,
133
+ });
134
+ }
135
+ },
136
+ });
137
+ const chooseEdge = (edgeOrGuardedEdges, state, message) => isGuardList(edgeOrGuardedEdges)
138
+ ? selectFromGuardList(edgeOrGuardedEdges, state, message)
139
+ : Option.some({ edge: edgeOrGuardedEdges, guardValue: undefined });
140
+ const makeTransitioned = (state, message, selectedEdge) => ({
141
+ _tag: 'Transitioned',
142
+ from: state._tag,
143
+ target: selectedEdge.edge.target,
144
+ messageTag: message._tag,
145
+ state: selectedEdge.edge.build({
146
+ state,
147
+ message,
148
+ guardValue: selectedEdge.guardValue,
149
+ }),
150
+ commands: Option.match(selectedEdge.edge.maybeCommands, {
151
+ onNone: () => [],
152
+ onSome: buildCommands => buildCommands({
153
+ state,
154
+ message,
155
+ guardValue: selectedEdge.guardValue,
156
+ }),
157
+ }),
158
+ });
159
+ const makeIgnored = (state, message) => ({
160
+ _tag: 'Ignored',
161
+ stateTag: state._tag,
162
+ messageTag: message._tag,
163
+ state,
164
+ });
165
+ const step = (state, message) => pipe(Record.get(looseStates, state._tag), Option.flatMap(stateEntry => Record.get(stateEntry.on, message._tag)), Option.flatMap(edgeOrGuardedEdges => chooseEdge(edgeOrGuardedEdges, state, message)), Option.match({
166
+ onNone: () => makeIgnored(state, message),
167
+ onSome: selectedEdge => makeTransitioned(state, message, selectedEdge),
168
+ }));
169
+ const transition = (state, message) => {
170
+ const result = step(state, message);
171
+ if (result._tag === 'Transitioned') {
172
+ return [result.state, result.commands];
173
+ }
174
+ else {
175
+ return [result.state, []];
176
+ }
177
+ };
178
+ const targetsFrom = (tag) => pipe(edges, Array.filter(edgeSummary => edgeSummary.from === tag), Array.map(edgeSummary => edgeSummary.target));
179
+ const reachableFrom = (tag) => {
180
+ const visit = (frontier, visited) => Array.matchLeft(frontier, {
181
+ onEmpty: () => visited,
182
+ onNonEmpty: (head, tail) => visited.has(head)
183
+ ? visit(tail, visited)
184
+ : visit([...tail, ...targetsFrom(head)], new Set([...visited, head])),
185
+ });
186
+ return visit([tag], new Set());
187
+ };
188
+ const unreachableStates = () => {
189
+ const reachable = reachableFrom(initialTag);
190
+ return Array.filter(stateTags, stateTag => !reachable.has(stateTag));
191
+ };
192
+ const makeDeadTransition = (edge, reason) => ({ edge, reason });
193
+ const guardPosition = (edgeSummary) => edgeSummary.guard._tag === 'Unguarded'
194
+ ? Option.none()
195
+ : Option.some(edgeSummary.guard.position);
196
+ const shadowedEdges = () => pipe(edges, Array.groupBy(edgeSummary => `${edgeSummary.from}|${edgeSummary.messageTag}`), Record.values, Array.flatMap(group => {
197
+ const maybeOtherwisePosition = pipe(group, Array.findFirst(edgeSummary => edgeSummary.guard._tag === 'Otherwise'), Option.flatMap(guardPosition));
198
+ return Option.match(maybeOtherwisePosition, {
199
+ onNone: () => [],
200
+ onSome: otherwisePosition => pipe(group, Array.filter(edgeSummary => Option.match(guardPosition(edgeSummary), {
201
+ onNone: () => false,
202
+ onSome: position => position > otherwisePosition,
203
+ })), Array.map(edgeSummary => makeDeadTransition(edgeSummary, 'ShadowedByOtherwise'))),
204
+ });
205
+ }));
206
+ const deadTransitions = () => {
207
+ const reachable = reachableFrom(initialTag);
208
+ const unreachableSourceEdges = pipe(edges, Array.filter(edgeSummary => !reachable.has(edgeSummary.from)), Array.map(edgeSummary => makeDeadTransition(edgeSummary, 'UnreachableSource')));
209
+ return [...unreachableSourceEdges, ...shadowedEdges()];
210
+ };
211
+ const toMermaid = () => {
212
+ const guardLabel = (guard) => M.value(guard).pipe(M.tagsExhaustive({
213
+ Unguarded: () => '',
214
+ When: ({ position }) => ` [when ${position + 1}]`,
215
+ Otherwise: () => ' [otherwise]',
216
+ }));
217
+ const stateLines = Array.map(stateTags, stateTag => ` ${stateTag}`);
218
+ const transitionLines = Array.map(edges, edgeSummary => ` ${edgeSummary.from} --> ${edgeSummary.target}: ${edgeSummary.messageTag}${guardLabel(edgeSummary.guard)}`);
219
+ return Array.join([
220
+ 'stateDiagram-v2',
221
+ ...stateLines,
222
+ ` [*] --> ${initialTag}`,
223
+ ...transitionLines,
224
+ ], '\n');
225
+ };
226
+ return {
227
+ initial: definition.initial,
228
+ stateTags,
229
+ edges,
230
+ transition,
231
+ step,
232
+ reachableFrom,
233
+ unreachableStates,
234
+ deadTransitions,
235
+ toMermaid,
236
+ };
237
+ };
@@ -0,0 +1,2 @@
1
+ export * from './index.js';
2
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/experimental/machine/public.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1,2 @@
1
+ export * from './index.js';
2
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/experimental/public.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -105,6 +105,15 @@ export declare const deregisterBoundaryWrap: (registry: BoundaryRegistry, bounda
105
105
  * avoids that race. Throws here (at resolve time, boundary alive) if a wrap is
106
106
  * somehow already missing, surfacing a real corruption rather than misrouting. */
107
107
  export declare const resolveBoundaryDispatchThunk: (registry: BoundaryRegistry, outerDispatch: DispatchSync, boundaryId: BoundaryId, message: unknown) => (() => void);
108
+ /** Collects the `toParentMessage` wrapping chain for `boundaryId`, innermost
109
+ * ancestor first, against the wraps present right now. Folding the returned
110
+ * functions left-to-right over a child message reproduces exactly what
111
+ * {@link dispatchAcrossBoundary} dispatches, without dispatching. Returns an
112
+ * empty array at the root boundary. Used by `OnMount` to snapshot a
113
+ * Submodel-embedded mount's lift eagerly so the Scene test harness can replay
114
+ * it when the mount is resolved. Throws when an ancestor wrap is missing
115
+ * (boundary alive at call time), matching {@link dispatchAcrossBoundary}. */
116
+ export declare const boundaryMappers: (registry: BoundaryRegistry, boundaryId: BoundaryId) => ReadonlyArray<(message: unknown) => unknown>;
108
117
  export declare const getOrCreateBoundaryDispatch: (registry: BoundaryRegistry, outerDispatch: DispatchSync, boundaryId: BoundaryId) => DispatchSync;
109
118
  /** Called at the start of each top-level render. Clears the
110
119
  * per-render duplicate-slotId tracking map so siblings inside the
@@ -1 +1 @@
1
- {"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../../src/html/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;CAC/C,CAAC,CAAA;AAEF;;;;eAIe;AACf,MAAM,MAAM,UAAU,GAAG,MAAM,CAAA;AAI/B,eAAO,MAAM,aAAa,EAAE,UAAe,CAAA;AAE3C,eAAO,MAAM,eAAe,GAC1B,QAAQ,UAAU,EAClB,SAAS,MAAM,KACd,UAUF,CAAA;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAwCa;AACb,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAClC,YAAY,EACZ,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAC9B,CAAA;IACD,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA;IAI1D,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,sBAAsB,QAAO,gBAMxC,CAAA;AAqBF,eAAO,MAAM,oBAAoB,GAC/B,UAAU,gBAAgB,EAC1B,YAAY,UAAU,EACtB,YAAY,cAAc,KACzB,IA6BF,CAAA;AAED;;;;wCAIwC;AACxC,eAAO,MAAM,iBAAiB,GAC5B,UAAU,gBAAgB,KACzB,GAAG,CAAC,UAAU,EAAE,MAAM,CAIxB,CAAA;AAED;;yCAEyC;AACzC,eAAO,MAAM,eAAe,GAAI,UAAU,gBAAgB,KAAG,IAQ5D,CAAA;AAED;;;;;;;4CAO4C;AAC5C,eAAO,MAAM,kBAAkB,GAC7B,UAAU,gBAAgB,EAC1B,YAAY,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,KAC1C,IAWF,CAAA;AAED;;;;;;;;;gBASgB;AAChB,eAAO,MAAM,sBAAsB,GACjC,UAAU,gBAAgB,EAC1B,YAAY,UAAU,KACrB,IAEF,CAAA;AA8CD;;;;;;;;;;;;mFAYmF;AACnF,eAAO,MAAM,4BAA4B,GACvC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,EACtB,SAAS,OAAO,KACf,CAAC,MAAM,IAAI,CAqBb,CAAA;AAED,eAAO,MAAM,2BAA2B,GACtC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,KACrB,YAkBF,CAAA;AAED;;;;;;;;;;iEAUiE;AACjE,eAAO,MAAM,WAAW,GAAI,UAAU,gBAAgB,KAAG,IAGxD,CAAA"}
1
+ {"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../../src/html/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;CAC/C,CAAC,CAAA;AAEF;;;;eAIe;AACf,MAAM,MAAM,UAAU,GAAG,MAAM,CAAA;AAI/B,eAAO,MAAM,aAAa,EAAE,UAAe,CAAA;AAE3C,eAAO,MAAM,eAAe,GAC1B,QAAQ,UAAU,EAClB,SAAS,MAAM,KACd,UAUF,CAAA;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAwCa;AACb,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAClC,YAAY,EACZ,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAC9B,CAAA;IACD,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA;IAI1D,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,sBAAsB,QAAO,gBAMxC,CAAA;AAqBF,eAAO,MAAM,oBAAoB,GAC/B,UAAU,gBAAgB,EAC1B,YAAY,UAAU,EACtB,YAAY,cAAc,KACzB,IA6BF,CAAA;AAED;;;;wCAIwC;AACxC,eAAO,MAAM,iBAAiB,GAC5B,UAAU,gBAAgB,KACzB,GAAG,CAAC,UAAU,EAAE,MAAM,CAIxB,CAAA;AAED;;yCAEyC;AACzC,eAAO,MAAM,eAAe,GAAI,UAAU,gBAAgB,KAAG,IAQ5D,CAAA;AAED;;;;;;;4CAO4C;AAC5C,eAAO,MAAM,kBAAkB,GAC7B,UAAU,gBAAgB,EAC1B,YAAY,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,KAC1C,IAWF,CAAA;AAED;;;;;;;;;gBASgB;AAChB,eAAO,MAAM,sBAAsB,GACjC,UAAU,gBAAgB,EAC1B,YAAY,UAAU,KACrB,IAEF,CAAA;AA8CD;;;;;;;;;;;;mFAYmF;AACnF,eAAO,MAAM,4BAA4B,GACvC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,EACtB,SAAS,OAAO,KACf,CAAC,MAAM,IAAI,CAqBb,CAAA;AAED;;;;;;;8EAO8E;AAC9E,eAAO,MAAM,eAAe,GAC1B,UAAU,gBAAgB,EAC1B,YAAY,UAAU,KACrB,aAAa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAiB7C,CAAA;AAED,eAAO,MAAM,2BAA2B,GACtC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,KACrB,YAkBF,CAAA;AAED;;;;;;;;;;iEAUiE;AACjE,eAAO,MAAM,WAAW,GAAI,UAAU,gBAAgB,KAAG,IAGxD,CAAA"}
@@ -183,6 +183,30 @@ export const resolveBoundaryDispatchThunk = (registry, outerDispatch, boundaryId
183
183
  const rootMessage = wrapped;
184
184
  return () => outerDispatch(rootMessage);
185
185
  };
186
+ /** Collects the `toParentMessage` wrapping chain for `boundaryId`, innermost
187
+ * ancestor first, against the wraps present right now. Folding the returned
188
+ * functions left-to-right over a child message reproduces exactly what
189
+ * {@link dispatchAcrossBoundary} dispatches, without dispatching. Returns an
190
+ * empty array at the root boundary. Used by `OnMount` to snapshot a
191
+ * Submodel-embedded mount's lift eagerly so the Scene test harness can replay
192
+ * it when the mount is resolved. Throws when an ancestor wrap is missing
193
+ * (boundary alive at call time), matching {@link dispatchAcrossBoundary}. */
194
+ export const boundaryMappers = (registry, boundaryId) => {
195
+ const parts = splitBoundary(boundaryId);
196
+ const mappers = [];
197
+ for (let depth = parts.length; depth > 0; depth--) {
198
+ const ancestorBoundary = parts.slice(0, depth).join(BOUNDARY_SEPARATOR);
199
+ const descriptor = registry.wraps.get(ancestorBoundary);
200
+ if (descriptor === undefined) {
201
+ throw new Error(`Foldkit: boundaryMappers missing wrap for ancestor ` +
202
+ `"${ancestorBoundary}" of boundary "${boundaryId}" while snapshotting ` +
203
+ `an OnMount lift. The Submodel's wrap was absent from the registry ` +
204
+ `during render, which should not happen for a live boundary.`);
205
+ }
206
+ mappers.push(descriptor.toParentMessage);
207
+ }
208
+ return mappers;
209
+ };
186
210
  export const getOrCreateBoundaryDispatch = (registry, outerDispatch, boundaryId) => {
187
211
  if (boundaryId === ROOT_BOUNDARY) {
188
212
  return outerDispatch;
@@ -10,7 +10,9 @@ declare const BRAND = "__childAttribute";
10
10
  * `resolveUnmount` snapshots the boundary's wrapping chain at the time the
11
11
  * group was published (child boundary alive) so `OnUnmount` can dispatch a
12
12
  * root message from a destroy hook that fires after the boundary has been
13
- * torn down.
13
+ * torn down. `boundaryMappers` snapshots the same chain as a pure list of
14
+ * `toParentMessage` lifts (innermost first) so `OnMount` can stamp it on the
15
+ * mount marker; the Scene test harness folds it to replay the lift.
14
16
  *
15
17
  * Created via {@link childAttributes}. Element constructors accept
16
18
  * `ChildAttribute` alongside `Attribute<Message>` in their attribute
@@ -20,6 +22,7 @@ export type ChildAttribute = Readonly<{
20
22
  readonly attribute: unknown;
21
23
  readonly dispatch: DispatchSync;
22
24
  readonly resolveUnmount: (message: unknown) => () => void;
25
+ readonly boundaryMappers: ReadonlyArray<(message: unknown) => unknown>;
23
26
  }>;
24
27
  export declare const isChildAttribute: (value: unknown) => value is ChildAttribute;
25
28
  /** Captures the current boundary's dispatcher and wraps each attribute
@@ -1 +1 @@
1
- {"version":3,"file":"childAttribute.d.ts","sourceRoot":"","sources":["../../src/html/childAttribute.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,uBAAuB,CAAA;AAE9B,QAAA,MAAM,KAAK,qBAAqB,CAAA;AAEhC;;;;;;;;;;;;;;cAcc;AACd,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;CAC1D,CAAC,CAAA;AAEF,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,cACI,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;;oCAsBoC;AACpC,eAAO,MAAM,eAAe,GAAI,SAAS,EACvC,YAAY,aAAa,CAAC,SAAS,CAAC,KACnC,aAAa,CAAC,cAAc,CAS9B,CAAA"}
1
+ {"version":3,"file":"childAttribute.d.ts","sourceRoot":"","sources":["../../src/html/childAttribute.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAIlB,MAAM,uBAAuB,CAAA;AAE9B,QAAA,MAAM,KAAK,qBAAqB,CAAA;AAEhC;;;;;;;;;;;;;;;;cAgBc;AACd,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;IACzD,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,CAAA;CACvE,CAAC,CAAA;AAEF,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,cACI,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;;oCAsBoC;AACpC,eAAO,MAAM,eAAe,GAAI,SAAS,EACvC,YAAY,aAAa,CAAC,SAAS,CAAC,KACnC,aAAa,CAAC,cAAc,CAW9B,CAAA"}
@@ -1,4 +1,4 @@
1
- import { requireDispatch, requireUnmountResolver, } from './runtimeSingleton.js';
1
+ import { requireBoundaryMappers, requireDispatch, requireUnmountResolver, } from './runtimeSingleton.js';
2
2
  const BRAND = '__childAttribute';
3
3
  export const isChildAttribute = (value) => typeof value === 'object' && value !== null && BRAND in value;
4
4
  /** Captures the current boundary's dispatcher and wraps each attribute
@@ -27,10 +27,12 @@ export const isChildAttribute = (value) => typeof value === 'object' && value !=
27
27
  export const childAttributes = (attributes) => {
28
28
  const dispatch = requireDispatch();
29
29
  const resolveUnmount = requireUnmountResolver();
30
+ const boundaryMappers = requireBoundaryMappers();
30
31
  return attributes.map(attribute => ({
31
32
  [BRAND]: true,
32
33
  attribute,
33
34
  dispatch,
34
35
  resolveUnmount,
36
+ boundaryMappers,
35
37
  }));
36
38
  };
@@ -93,11 +93,16 @@ export declare const __endReplayRender: () => void;
93
93
  * observable by VNode walkers (Scene tests). */
94
94
  export declare const FOLDKIT_MOUNT_KEY: "foldkitMount";
95
95
  /** Marker stamped on `VNodeData[FOLDKIT_MOUNT_KEY]` for any element with an
96
- * `OnMount` attribute. Carries the Mount Definition's name so test
97
- * introspection can identify pending mounts. */
96
+ * `OnMount` attribute. Carries the Mount Definition's name (and args) so test
97
+ * introspection can identify pending mounts. When the mount lives inside a
98
+ * Submodel boundary, it also carries that boundary's `toParentMessage` chain
99
+ * (innermost first), snapshotted at render time, so `Scene.Mount.resolve` can
100
+ * replay the lift the result travels through in production. The chain is read
101
+ * only by the Scene harness; production dispatch lifts via `ctx.dispatch`. */
98
102
  export type FoldkitMountMarker = Readonly<{
99
103
  name: string;
100
104
  args?: Record<string, unknown>;
105
+ messageMappers?: ReadonlyArray<(message: unknown) => unknown>;
101
106
  }>;
102
107
  /** Union of all HTML, SVG, and MathML attributes a virtual DOM element can carry.
103
108
  *
@@ -206,6 +211,12 @@ export type Attribute<Message> = Data.TaggedEnum<{
206
211
  OnKeyDownPreventDefault: {
207
212
  readonly f: (key: string, modifiers: KeyboardModifiers) => Option.Option<Message>;
208
213
  };
214
+ OnKeyDownFocus: {
215
+ readonly f: (key: string, modifiers: KeyboardModifiers) => Option.Option<Readonly<{
216
+ focusSelector: string;
217
+ message: Message;
218
+ }>>;
219
+ };
209
220
  OnKeyUp: {
210
221
  readonly f: (key: string, modifiers: KeyboardModifiers) => Message;
211
222
  };
@@ -1063,6 +1074,7 @@ declare const buildHtmlFactory: <Message>() => {
1063
1074
  readonly attribute: unknown;
1064
1075
  readonly dispatch: DispatchSync;
1065
1076
  readonly resolveUnmount: (message: unknown) => () => void;
1077
+ readonly boundaryMappers: ReadonlyArray<(message: unknown) => unknown>;
1066
1078
  readonly __childAttribute: true;
1067
1079
  }> | {
1068
1080
  readonly _tag: "Type";
@@ -1189,6 +1201,17 @@ declare const buildHtmlFactory: <Message>() => {
1189
1201
  altKey: boolean;
1190
1202
  metaKey: boolean;
1191
1203
  }>) => Option.Option<Message>;
1204
+ } | {
1205
+ readonly _tag: "OnKeyDownFocus";
1206
+ readonly f: (key: string, modifiers: Readonly<{
1207
+ shiftKey: boolean;
1208
+ ctrlKey: boolean;
1209
+ altKey: boolean;
1210
+ metaKey: boolean;
1211
+ }>) => Option.Option<Readonly<{
1212
+ focusSelector: string;
1213
+ message: Message;
1214
+ }>>;
1192
1215
  } | {
1193
1216
  readonly _tag: "OnKeyUp";
1194
1217
  readonly f: (key: string, modifiers: Readonly<{
@@ -2161,6 +2184,26 @@ declare const buildHtmlFactory: <Message>() => {
2161
2184
  metaKey: boolean;
2162
2185
  }>) => Message;
2163
2186
  };
2187
+ OnKeyDownFocus: (f: (key: string, modifiers: Readonly<{
2188
+ shiftKey: boolean;
2189
+ ctrlKey: boolean;
2190
+ altKey: boolean;
2191
+ metaKey: boolean;
2192
+ }>) => Option.Option<Readonly<{
2193
+ focusSelector: string;
2194
+ message: Message;
2195
+ }>>) => {
2196
+ readonly _tag: "OnKeyDownFocus";
2197
+ readonly f: (key: string, modifiers: Readonly<{
2198
+ shiftKey: boolean;
2199
+ ctrlKey: boolean;
2200
+ altKey: boolean;
2201
+ metaKey: boolean;
2202
+ }>) => Option.Option<Readonly<{
2203
+ focusSelector: string;
2204
+ message: Message;
2205
+ }>>;
2206
+ };
2164
2207
  OnKeyDownPreventDefault: (f: (key: string, modifiers: Readonly<{
2165
2208
  shiftKey: boolean;
2166
2209
  ctrlKey: boolean;