foldkit 0.101.0 → 0.102.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 (208) hide show
  1. package/README.md +2 -1
  2. package/dist/canvas/view.d.ts +1 -1
  3. package/dist/canvas/view.d.ts.map +1 -1
  4. package/dist/canvas/view.js +5 -5
  5. package/dist/command/index.d.ts +71 -0
  6. package/dist/command/index.d.ts.map +1 -1
  7. package/dist/command/index.js +34 -1
  8. package/dist/command/public.d.ts +1 -1
  9. package/dist/command/public.d.ts.map +1 -1
  10. package/dist/command/public.js +1 -1
  11. package/dist/devTools/overlay.d.ts.map +1 -1
  12. package/dist/devTools/overlay.js +137 -110
  13. package/dist/dom/dom.d.ts +8 -11
  14. package/dist/dom/dom.d.ts.map +1 -1
  15. package/dist/dom/dom.js +8 -11
  16. package/dist/dom/elementMovement.d.ts +1 -3
  17. package/dist/dom/elementMovement.d.ts.map +1 -1
  18. package/dist/dom/elementMovement.js +1 -3
  19. package/dist/dom/inert.d.ts +2 -4
  20. package/dist/dom/inert.d.ts.map +1 -1
  21. package/dist/dom/inert.js +2 -4
  22. package/dist/dom/scrollLock.d.ts +2 -2
  23. package/dist/dom/scrollLock.js +2 -2
  24. package/dist/dom/waitForAnimation.d.ts +1 -1
  25. package/dist/dom/waitForAnimation.js +1 -1
  26. package/dist/html/boundary.d.ts +98 -0
  27. package/dist/html/boundary.d.ts.map +1 -0
  28. package/dist/html/boundary.js +176 -0
  29. package/dist/html/childAttribute.d.ts +44 -0
  30. package/dist/html/childAttribute.d.ts.map +1 -0
  31. package/dist/html/childAttribute.js +34 -0
  32. package/dist/html/index.d.ts +70 -23
  33. package/dist/html/index.d.ts.map +1 -1
  34. package/dist/html/index.js +639 -575
  35. package/dist/html/lazy.d.ts +12 -7
  36. package/dist/html/lazy.d.ts.map +1 -1
  37. package/dist/html/lazy.js +30 -11
  38. package/dist/html/public.d.ts +2 -2
  39. package/dist/html/public.d.ts.map +1 -1
  40. package/dist/html/public.js +1 -1
  41. package/dist/html/runtimeSingleton.d.ts +72 -0
  42. package/dist/html/runtimeSingleton.d.ts.map +1 -0
  43. package/dist/html/runtimeSingleton.js +112 -0
  44. package/dist/html/submodel.d.ts +98 -0
  45. package/dist/html/submodel.d.ts.map +1 -0
  46. package/dist/html/submodel.js +190 -0
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -0
  50. package/dist/render/render.d.ts +1 -1
  51. package/dist/render/render.js +1 -1
  52. package/dist/runtime/messagePriority.d.ts +5 -1
  53. package/dist/runtime/messagePriority.d.ts.map +1 -1
  54. package/dist/runtime/messagePriority.js +25 -4
  55. package/dist/runtime/runtime.d.ts +1 -1
  56. package/dist/runtime/runtime.d.ts.map +1 -1
  57. package/dist/runtime/runtime.js +115 -61
  58. package/dist/submodel/public.d.ts +4 -0
  59. package/dist/submodel/public.d.ts.map +1 -0
  60. package/dist/submodel/public.js +1 -0
  61. package/dist/submodel/submodel.d.ts +32 -0
  62. package/dist/submodel/submodel.d.ts.map +1 -0
  63. package/dist/submodel/submodel.js +1 -0
  64. package/dist/test/apps/disabledButton.d.ts +4 -5
  65. package/dist/test/apps/disabledButton.d.ts.map +1 -1
  66. package/dist/test/apps/disabledButton.js +16 -16
  67. package/dist/test/scene.d.ts +8 -8
  68. package/dist/test/scene.d.ts.map +1 -1
  69. package/dist/test/scene.js +25 -13
  70. package/dist/test/story.d.ts +15 -8
  71. package/dist/test/story.d.ts.map +1 -1
  72. package/dist/test/story.js +21 -9
  73. package/dist/ui/animation/index.d.ts +30 -14
  74. package/dist/ui/animation/index.d.ts.map +1 -1
  75. package/dist/ui/animation/index.js +9 -19
  76. package/dist/ui/animation/public.d.ts +2 -2
  77. package/dist/ui/animation/public.d.ts.map +1 -1
  78. package/dist/ui/animation/public.js +1 -1
  79. package/dist/ui/calendar/index.d.ts +199 -84
  80. package/dist/ui/calendar/index.d.ts.map +1 -1
  81. package/dist/ui/calendar/index.js +129 -140
  82. package/dist/ui/calendar/public.d.ts +2 -2
  83. package/dist/ui/calendar/public.d.ts.map +1 -1
  84. package/dist/ui/calendar/public.js +1 -1
  85. package/dist/ui/checkbox/index.d.ts +93 -21
  86. package/dist/ui/checkbox/index.d.ts.map +1 -1
  87. package/dist/ui/checkbox/index.js +62 -33
  88. package/dist/ui/checkbox/public.d.ts +2 -2
  89. package/dist/ui/checkbox/public.d.ts.map +1 -1
  90. package/dist/ui/checkbox/public.js +1 -1
  91. package/dist/ui/combobox/multi.d.ts +35 -91
  92. package/dist/ui/combobox/multi.d.ts.map +1 -1
  93. package/dist/ui/combobox/multi.js +34 -17
  94. package/dist/ui/combobox/multiPublic.d.ts +2 -2
  95. package/dist/ui/combobox/multiPublic.d.ts.map +1 -1
  96. package/dist/ui/combobox/multiPublic.js +1 -1
  97. package/dist/ui/combobox/public.d.ts +3 -3
  98. package/dist/ui/combobox/public.d.ts.map +1 -1
  99. package/dist/ui/combobox/public.js +2 -2
  100. package/dist/ui/combobox/shared.d.ts +56 -31
  101. package/dist/ui/combobox/shared.d.ts.map +1 -1
  102. package/dist/ui/combobox/shared.js +333 -322
  103. package/dist/ui/combobox/single.d.ts +46 -93
  104. package/dist/ui/combobox/single.d.ts.map +1 -1
  105. package/dist/ui/combobox/single.js +44 -17
  106. package/dist/ui/datePicker/index.d.ts +256 -48
  107. package/dist/ui/datePicker/index.d.ts.map +1 -1
  108. package/dist/ui/datePicker/index.js +149 -104
  109. package/dist/ui/datePicker/public.d.ts +2 -2
  110. package/dist/ui/datePicker/public.d.ts.map +1 -1
  111. package/dist/ui/datePicker/public.js +1 -1
  112. package/dist/ui/dialog/index.d.ts +95 -39
  113. package/dist/ui/dialog/index.d.ts.map +1 -1
  114. package/dist/ui/dialog/index.js +71 -62
  115. package/dist/ui/dialog/public.d.ts +2 -2
  116. package/dist/ui/dialog/public.d.ts.map +1 -1
  117. package/dist/ui/dialog/public.js +1 -1
  118. package/dist/ui/disclosure/index.d.ts +71 -31
  119. package/dist/ui/disclosure/index.d.ts.map +1 -1
  120. package/dist/ui/disclosure/index.js +57 -62
  121. package/dist/ui/disclosure/public.d.ts +2 -2
  122. package/dist/ui/disclosure/public.d.ts.map +1 -1
  123. package/dist/ui/disclosure/public.js +1 -1
  124. package/dist/ui/dragAndDrop/index.d.ts +6 -6
  125. package/dist/ui/dragAndDrop/index.d.ts.map +1 -1
  126. package/dist/ui/dragAndDrop/index.js +7 -7
  127. package/dist/ui/dragAndDrop/public.d.ts +1 -1
  128. package/dist/ui/dragAndDrop/public.d.ts.map +1 -1
  129. package/dist/ui/dragAndDrop/public.js +1 -1
  130. package/dist/ui/fileDrop/index.d.ts +42 -46
  131. package/dist/ui/fileDrop/index.d.ts.map +1 -1
  132. package/dist/ui/fileDrop/index.js +30 -46
  133. package/dist/ui/fileDrop/public.d.ts +2 -2
  134. package/dist/ui/fileDrop/public.d.ts.map +1 -1
  135. package/dist/ui/fileDrop/public.js +1 -1
  136. package/dist/ui/listbox/multi.d.ts +39 -84
  137. package/dist/ui/listbox/multi.d.ts.map +1 -1
  138. package/dist/ui/listbox/multi.js +38 -20
  139. package/dist/ui/listbox/multiPublic.d.ts +2 -2
  140. package/dist/ui/listbox/multiPublic.d.ts.map +1 -1
  141. package/dist/ui/listbox/multiPublic.js +1 -1
  142. package/dist/ui/listbox/public.d.ts +3 -3
  143. package/dist/ui/listbox/public.d.ts.map +1 -1
  144. package/dist/ui/listbox/public.js +2 -2
  145. package/dist/ui/listbox/shared.d.ts +71 -30
  146. package/dist/ui/listbox/shared.d.ts.map +1 -1
  147. package/dist/ui/listbox/shared.js +319 -296
  148. package/dist/ui/listbox/single.d.ts +57 -85
  149. package/dist/ui/listbox/single.d.ts.map +1 -1
  150. package/dist/ui/listbox/single.js +48 -24
  151. package/dist/ui/menu/index.d.ts +80 -36
  152. package/dist/ui/menu/index.d.ts.map +1 -1
  153. package/dist/ui/menu/index.js +117 -86
  154. package/dist/ui/menu/public.d.ts +2 -2
  155. package/dist/ui/menu/public.d.ts.map +1 -1
  156. package/dist/ui/menu/public.js +1 -1
  157. package/dist/ui/popover/index.d.ts +117 -44
  158. package/dist/ui/popover/index.d.ts.map +1 -1
  159. package/dist/ui/popover/index.js +88 -101
  160. package/dist/ui/popover/public.d.ts +2 -2
  161. package/dist/ui/popover/public.d.ts.map +1 -1
  162. package/dist/ui/popover/public.js +1 -1
  163. package/dist/ui/radioGroup/index.d.ts +122 -45
  164. package/dist/ui/radioGroup/index.d.ts.map +1 -1
  165. package/dist/ui/radioGroup/index.js +111 -72
  166. package/dist/ui/radioGroup/public.d.ts +2 -2
  167. package/dist/ui/radioGroup/public.d.ts.map +1 -1
  168. package/dist/ui/radioGroup/public.js +1 -1
  169. package/dist/ui/slider/index.d.ts +72 -34
  170. package/dist/ui/slider/index.d.ts.map +1 -1
  171. package/dist/ui/slider/index.js +40 -49
  172. package/dist/ui/slider/public.d.ts +2 -2
  173. package/dist/ui/slider/public.d.ts.map +1 -1
  174. package/dist/ui/slider/public.js +1 -1
  175. package/dist/ui/switch/index.d.ts +74 -21
  176. package/dist/ui/switch/index.d.ts.map +1 -1
  177. package/dist/ui/switch/index.js +62 -33
  178. package/dist/ui/switch/public.d.ts +2 -2
  179. package/dist/ui/switch/public.d.ts.map +1 -1
  180. package/dist/ui/switch/public.js +1 -1
  181. package/dist/ui/tabs/index.d.ts +107 -45
  182. package/dist/ui/tabs/index.d.ts.map +1 -1
  183. package/dist/ui/tabs/index.js +99 -81
  184. package/dist/ui/tabs/public.d.ts +2 -2
  185. package/dist/ui/tabs/public.d.ts.map +1 -1
  186. package/dist/ui/tabs/public.js +1 -1
  187. package/dist/ui/toast/index.d.ts +93 -109
  188. package/dist/ui/toast/index.d.ts.map +1 -1
  189. package/dist/ui/toast/index.js +16 -29
  190. package/dist/ui/toast/schema.d.ts +15 -4
  191. package/dist/ui/toast/schema.d.ts.map +1 -1
  192. package/dist/ui/toast/schema.js +11 -4
  193. package/dist/ui/toast/update.d.ts +36 -18
  194. package/dist/ui/toast/update.d.ts.map +1 -1
  195. package/dist/ui/toast/update.js +33 -14
  196. package/dist/ui/tooltip/index.d.ts +94 -42
  197. package/dist/ui/tooltip/index.d.ts.map +1 -1
  198. package/dist/ui/tooltip/index.js +64 -73
  199. package/dist/ui/tooltip/public.d.ts +2 -2
  200. package/dist/ui/tooltip/public.d.ts.map +1 -1
  201. package/dist/ui/tooltip/public.js +1 -1
  202. package/dist/ui/virtualList/index.d.ts +18 -41
  203. package/dist/ui/virtualList/index.d.ts.map +1 -1
  204. package/dist/ui/virtualList/index.js +17 -37
  205. package/dist/ui/virtualList/public.d.ts +2 -2
  206. package/dist/ui/virtualList/public.d.ts.map +1 -1
  207. package/dist/ui/virtualList/public.js +1 -1
  208. package/package.json +1 -1
@@ -46,7 +46,7 @@ const handleTouchMove = (event) => {
46
46
  *
47
47
  * @example
48
48
  * ```typescript
49
- * Dom.lockScroll.pipe(Effect.as(CompletedLockScroll()))
49
+ * Dom.lockScroll
50
50
  * ```
51
51
  */
52
52
  export const lockScroll = Effect.sync(() => {
@@ -73,7 +73,7 @@ export const lockScroll = Effect.sync(() => {
73
73
  *
74
74
  * @example
75
75
  * ```typescript
76
- * Dom.unlockScroll.pipe(Effect.as(CompletedUnlockScroll()))
76
+ * Dom.unlockScroll
77
77
  * ```
78
78
  */
79
79
  export const unlockScroll = Effect.sync(() => {
@@ -10,7 +10,7 @@ import { Effect } from 'effect';
10
10
  *
11
11
  * @example
12
12
  * ```typescript
13
- * Dom.waitForAnimationSettled('#menu-items').pipe(Effect.as(EndedAnimation()))
13
+ * Dom.waitForAnimationSettled('#menu-items')
14
14
  * ```
15
15
  */
16
16
  export declare const waitForAnimationSettled: (selector: string) => Effect.Effect<void>;
@@ -10,7 +10,7 @@ import { Effect } from 'effect';
10
10
  *
11
11
  * @example
12
12
  * ```typescript
13
- * Dom.waitForAnimationSettled('#menu-items').pipe(Effect.as(EndedAnimation()))
13
+ * Dom.waitForAnimationSettled('#menu-items')
14
14
  * ```
15
15
  */
16
16
  export const waitForAnimationSettled = (selector) => Effect.callback(resume => {
@@ -0,0 +1,98 @@
1
+ import type { DispatchSync } from './runtimeSingleton.js';
2
+ /** Wrapping descriptor stored per Submodel boundary. */
3
+ export type WrapDescriptor = Readonly<{
4
+ toParentMessage: (message: unknown) => unknown;
5
+ }>;
6
+ /** Boundary id is a `|`-joined chain of Submodel slot ids. Empty
7
+ * string represents the root boundary. Two-level example:
8
+ * `"work-history|entry-abc123"`. User-supplied slot ids must not
9
+ * contain the separator character; {@link composeBoundary} throws when
10
+ * they do. */
11
+ export type BoundaryId = string;
12
+ export declare const ROOT_BOUNDARY: BoundaryId;
13
+ export declare const composeBoundary: (parent: BoundaryId, childId: string) => BoundaryId;
14
+ /** Per-runtime registry of Submodel wrapping descriptors. The runtime
15
+ * creates one of these in `start` and reuses it across renders.
16
+ * `h.submodel` writes into `wraps` each render and attaches a snabbdom
17
+ * `destroy` hook that calls `deregisterBoundaryWrap` when the
18
+ * corresponding vnode is removed from the DOM tree. The dispatch path
19
+ * reads from `wraps` at event-fire time.
20
+ *
21
+ * `boundaryDispatches` caches per-(outerDispatch, boundaryId) dispatcher
22
+ * closures so `requireDispatch` returns a stable reference across
23
+ * repeated calls with the same outerDispatch (necessary for
24
+ * `createLazy`'s dispatch-identity check). Keyed by outerDispatch as a
25
+ * WeakMap so DevTools jump-to renders with a different
26
+ * outerDispatch (typically a noOpDispatch that drops messages) get
27
+ * their own per-boundary cache. Without this two-level keying, a
28
+ * dispatcher created during a live render would still close over the
29
+ * live outerDispatch after a jump-to and silently mutate the live app.
30
+ *
31
+ * `seenThisRender` tracks boundaries marked alive during the current
32
+ * render for duplicate-slotId detection: two `h.submodel` calls
33
+ * inside the same parent boundary must use different `slotId`s.
34
+ * Values are the call site captured at register time, surfaced when a
35
+ * second register collides so both locations land in the throw
36
+ * message. The map is cleared at the start of each render via
37
+ * `beginRender`. Boundaries behind a `createLazy`/`createKeyedLazy`
38
+ * cache hit are replayed into this map via {@link markSeenForLazyHit}
39
+ * so the duplicate-slotId guard catches collisions against memoized
40
+ * siblings, not just against siblings that re-ran this frame. It is
41
+ * NOT used for pruning; pruning is driven by VNode destroy hooks
42
+ * instead.
43
+ *
44
+ * `lazyTrackingStack` is a stack of sets used by `createLazy` and
45
+ * `createKeyedLazy` to capture which boundary ids were marked alive
46
+ * during the wrapped function's first execution. On a later cache
47
+ * hit, the lazy helper replays the captured ids into
48
+ * `seenThisRender` so the duplicate-slotId guard sees them. Each
49
+ * active lazy invocation pushes its own set; `registerBoundaryWrap`
50
+ * and `markSeenForLazyHit` write to every set on the stack so an
51
+ * outer lazy correctly captures ids contributed by inner lazies it
52
+ * wraps. */
53
+ export type BoundaryRegistry = {
54
+ readonly wraps: Map<BoundaryId, WrapDescriptor>;
55
+ readonly boundaryDispatches: WeakMap<DispatchSync, Map<BoundaryId, DispatchSync>>;
56
+ readonly seenThisRender: Map<BoundaryId, string>;
57
+ readonly lazyTrackingStack: Array<Map<BoundaryId, string>>;
58
+ };
59
+ export declare const createBoundaryRegistry: () => BoundaryRegistry;
60
+ export declare const registerBoundaryWrap: (registry: BoundaryRegistry, boundaryId: BoundaryId, descriptor: WrapDescriptor) => void;
61
+ /** Starts capturing boundary registrations on a fresh set pushed onto
62
+ * `lazyTrackingStack`. Used by `createLazy`/`createKeyedLazy` around the
63
+ * wrapped view function. Must be paired with {@link endLazyTracking} on
64
+ * the same call stack so an exception inside the view does not leak the
65
+ * tracking frame to a later render. */
66
+ export declare const beginLazyTracking: (registry: BoundaryRegistry) => Map<BoundaryId, string>;
67
+ /** Pops the most recent tracking set. Throws when called on an empty
68
+ * stack to surface unmatched begin/end pairs immediately rather than
69
+ * silently corrupting later renders. */
70
+ export declare const endLazyTracking: (registry: BoundaryRegistry) => void;
71
+ /** Replays a set of boundary ids captured during a previous lazy run
72
+ * into `seenThisRender` so the duplicate-slotId guard sees them. Also
73
+ * forwards them into any active tracking sets so an outer lazy
74
+ * wrapping this cache hit captures the ids in its own snapshot.
75
+ *
76
+ * Skips ids already present in `seenThisRender` to preserve the
77
+ * original call site of the live entry (the first registration this
78
+ * render still wins the error message). */
79
+ export declare const markSeenForLazyHit: (registry: BoundaryRegistry, trackedIds: ReadonlyMap<BoundaryId, string>) => void;
80
+ /** Removes a boundary's wrap. Called by `h.submodel`'s destroy hook when
81
+ * the corresponding vnode leaves the DOM.
82
+ *
83
+ * Does not touch `boundaryDispatches`: it is a WeakMap keyed by
84
+ * outerDispatch, so per-outerDispatch inner Maps become unreachable and
85
+ * are GC'd when their outerDispatch is. Cached dispatcher closures that
86
+ * outlive a deregister become inert. `dispatchAcrossBoundary` throws
87
+ * when it cannot find an ancestor wrap, which surfaces a clear error
88
+ * rather than letting events from a destroyed boundary silently
89
+ * misroute. */
90
+ export declare const deregisterBoundaryWrap: (registry: BoundaryRegistry, boundaryId: BoundaryId) => void;
91
+ export declare const getOrCreateBoundaryDispatch: (registry: BoundaryRegistry, outerDispatch: DispatchSync, boundaryId: BoundaryId) => DispatchSync;
92
+ /** Called at the start of each top-level render. Clears the
93
+ * per-render duplicate-slotId tracking map so siblings inside the
94
+ * same parent boundary can be re-validated. Does NOT touch `wraps`
95
+ * or `boundaryDispatches`. Those persist across renders and are
96
+ * evicted by vnode destroy hooks instead. */
97
+ export declare const beginRender: (registry: BoundaryRegistry) => void;
98
+ //# sourceMappingURL=boundary.d.ts.map
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAsCa;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;CAC3D,CAAA;AAED,eAAO,MAAM,sBAAsB,QAAO,gBAKxC,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;AA4CD,eAAO,MAAM,2BAA2B,GACtC,UAAU,gBAAgB,EAC1B,eAAe,YAAY,EAC3B,YAAY,UAAU,KACrB,YAkBF,CAAA;AAED;;;;8CAI8C;AAC9C,eAAO,MAAM,WAAW,GAAI,UAAU,gBAAgB,KAAG,IAExD,CAAA"}
@@ -0,0 +1,176 @@
1
+ const BOUNDARY_SEPARATOR = '|';
2
+ export const ROOT_BOUNDARY = '';
3
+ export const composeBoundary = (parent, childId) => {
4
+ if (childId.includes(BOUNDARY_SEPARATOR)) {
5
+ throw new Error(`Foldkit: h.submodel slotId cannot contain the boundary separator ` +
6
+ `"${BOUNDARY_SEPARATOR}". Got ${JSON.stringify(childId)}.`);
7
+ }
8
+ return parent === ROOT_BOUNDARY
9
+ ? childId
10
+ : `${parent}${BOUNDARY_SEPARATOR}${childId}`;
11
+ };
12
+ const splitBoundary = (boundaryId) => boundaryId === ROOT_BOUNDARY ? [] : boundaryId.split(BOUNDARY_SEPARATOR);
13
+ export const createBoundaryRegistry = () => ({
14
+ wraps: new Map(),
15
+ boundaryDispatches: new WeakMap(),
16
+ seenThisRender: new Map(),
17
+ lazyTrackingStack: [],
18
+ });
19
+ const captureCallSite = () => {
20
+ const stack = new Error().stack ?? '';
21
+ const lines = stack.split('\n');
22
+ for (const line of lines) {
23
+ const trimmed = line.trim();
24
+ if (trimmed.length === 0 ||
25
+ trimmed.startsWith('Error') ||
26
+ trimmed.includes('captureCallSite') ||
27
+ trimmed.includes('registerBoundaryWrap') ||
28
+ trimmed.includes('at submodel')) {
29
+ continue;
30
+ }
31
+ return trimmed;
32
+ }
33
+ return '(call site unavailable)';
34
+ };
35
+ export const registerBoundaryWrap = (registry, boundaryId, descriptor) => {
36
+ const existingCallSite = registry.seenThisRender.get(boundaryId);
37
+ if (existingCallSite !== undefined) {
38
+ const ownSlotId = boundaryId.includes(BOUNDARY_SEPARATOR)
39
+ ? boundaryId.slice(boundaryId.lastIndexOf(BOUNDARY_SEPARATOR) + 1)
40
+ : boundaryId;
41
+ const newCallSite = captureCallSite();
42
+ throw new Error(`Foldkit: duplicate h.submodel slotId "${ownSlotId}" at boundary "${boundaryId}".\n` +
43
+ ` First registration: ${existingCallSite}\n` +
44
+ ` Second registration: ${newCallSite}\n` +
45
+ `Each h.submodel call inside the same parent boundary must use a unique \`slotId\`. ` +
46
+ `The slotId is DOM-slot identity, not model identity. If the same model is ` +
47
+ `rendered in two locations (desktop + mobile, master + detail), each slot ` +
48
+ `needs its own id (e.g. "desktop-foo", "mobile-foo"). For lists, use a stable ` +
49
+ `per-item identifier.`);
50
+ }
51
+ // NOTE: compute the call site before writing either map. If
52
+ // captureCallSite throws (e.g. hardened runtime without
53
+ // Error.stack), neither map is mutated, so a later registration
54
+ // with the same slotId throws the duplicate error correctly instead
55
+ // of silently overwriting after a half-finished prior write.
56
+ const callSite = captureCallSite();
57
+ registry.wraps.set(boundaryId, descriptor);
58
+ registry.seenThisRender.set(boundaryId, callSite);
59
+ for (const tracked of registry.lazyTrackingStack) {
60
+ tracked.set(boundaryId, callSite);
61
+ }
62
+ };
63
+ /** Starts capturing boundary registrations on a fresh set pushed onto
64
+ * `lazyTrackingStack`. Used by `createLazy`/`createKeyedLazy` around the
65
+ * wrapped view function. Must be paired with {@link endLazyTracking} on
66
+ * the same call stack so an exception inside the view does not leak the
67
+ * tracking frame to a later render. */
68
+ export const beginLazyTracking = (registry) => {
69
+ const tracked = new Map();
70
+ registry.lazyTrackingStack.push(tracked);
71
+ return tracked;
72
+ };
73
+ /** Pops the most recent tracking set. Throws when called on an empty
74
+ * stack to surface unmatched begin/end pairs immediately rather than
75
+ * silently corrupting later renders. */
76
+ export const endLazyTracking = (registry) => {
77
+ if (registry.lazyTrackingStack.length === 0) {
78
+ throw new Error('Foldkit: endLazyTracking called on an empty stack. This means a ' +
79
+ '`beginLazyTracking` was not paired with `endLazyTracking` upstream.');
80
+ }
81
+ registry.lazyTrackingStack.pop();
82
+ };
83
+ /** Replays a set of boundary ids captured during a previous lazy run
84
+ * into `seenThisRender` so the duplicate-slotId guard sees them. Also
85
+ * forwards them into any active tracking sets so an outer lazy
86
+ * wrapping this cache hit captures the ids in its own snapshot.
87
+ *
88
+ * Skips ids already present in `seenThisRender` to preserve the
89
+ * original call site of the live entry (the first registration this
90
+ * render still wins the error message). */
91
+ export const markSeenForLazyHit = (registry, trackedIds) => {
92
+ for (const [boundaryId, callSite] of trackedIds) {
93
+ if (!registry.seenThisRender.has(boundaryId)) {
94
+ registry.seenThisRender.set(boundaryId, callSite);
95
+ }
96
+ for (const outerTracked of registry.lazyTrackingStack) {
97
+ if (!outerTracked.has(boundaryId)) {
98
+ outerTracked.set(boundaryId, callSite);
99
+ }
100
+ }
101
+ }
102
+ };
103
+ /** Removes a boundary's wrap. Called by `h.submodel`'s destroy hook when
104
+ * the corresponding vnode leaves the DOM.
105
+ *
106
+ * Does not touch `boundaryDispatches`: it is a WeakMap keyed by
107
+ * outerDispatch, so per-outerDispatch inner Maps become unreachable and
108
+ * are GC'd when their outerDispatch is. Cached dispatcher closures that
109
+ * outlive a deregister become inert. `dispatchAcrossBoundary` throws
110
+ * when it cannot find an ancestor wrap, which surfaces a clear error
111
+ * rather than letting events from a destroyed boundary silently
112
+ * misroute. */
113
+ export const deregisterBoundaryWrap = (registry, boundaryId) => {
114
+ registry.wraps.delete(boundaryId);
115
+ };
116
+ /** Applies the wrapping chain for `boundaryId` from innermost to
117
+ * outermost, then dispatches the fully-wrapped message via
118
+ * `outerDispatch`. Called at event-fire time by the dispatcher closure
119
+ * returned from `getOrCreateBoundaryDispatch`.
120
+ *
121
+ * Throws when an ancestor wrap is missing from the registry. DOM events
122
+ * fire synchronously, so a sync handler against a live boundary always
123
+ * finds a complete chain. A missing wrap implies one of: (a) the wrap
124
+ * was deregistered between event scheduling and dispatch (e.g. a slot
125
+ * callback captured at one render is invoked from a deferred context
126
+ * after the Submodel unmounted), or (b) the registry is corrupt.
127
+ * Either way, silently skipping the ancestor and applying only outer
128
+ * wraps would produce a malformed Message that the outermost
129
+ * `Match.tagsExhaustive` would then crash on with no useful trace. */
130
+ const dispatchAcrossBoundary = (registry, outerDispatch, boundaryId, message) => {
131
+ let wrapped = message;
132
+ const parts = splitBoundary(boundaryId);
133
+ for (let depth = parts.length; depth > 0; depth--) {
134
+ const ancestorBoundary = parts.slice(0, depth).join(BOUNDARY_SEPARATOR);
135
+ const descriptor = registry.wraps.get(ancestorBoundary);
136
+ if (descriptor === undefined) {
137
+ throw new Error(`Foldkit: dispatchAcrossBoundary missing wrap for ancestor ` +
138
+ `"${ancestorBoundary}" of boundary "${boundaryId}". This means a ` +
139
+ `Submodel's wrap was deregistered between event scheduling and ` +
140
+ `dispatch. Most likely cause: a slot callback (h.submodel ` +
141
+ `\`viewInputs\` function value) was invoked from a deferred context ` +
142
+ `(setTimeout, Promise.then, stored callback) after the parent ` +
143
+ `Submodel unmounted. Slot callbacks must be invoked synchronously ` +
144
+ `inside the render in which they were created.`);
145
+ }
146
+ wrapped = descriptor.toParentMessage(wrapped);
147
+ }
148
+ outerDispatch(wrapped);
149
+ };
150
+ export const getOrCreateBoundaryDispatch = (registry, outerDispatch, boundaryId) => {
151
+ if (boundaryId === ROOT_BOUNDARY) {
152
+ return outerDispatch;
153
+ }
154
+ let perOuterDispatch = registry.boundaryDispatches.get(outerDispatch);
155
+ if (perOuterDispatch === undefined) {
156
+ perOuterDispatch = new Map();
157
+ registry.boundaryDispatches.set(outerDispatch, perOuterDispatch);
158
+ }
159
+ const existing = perOuterDispatch.get(boundaryId);
160
+ if (existing !== undefined) {
161
+ return existing;
162
+ }
163
+ const dispatch = message => {
164
+ dispatchAcrossBoundary(registry, outerDispatch, boundaryId, message);
165
+ };
166
+ perOuterDispatch.set(boundaryId, dispatch);
167
+ return dispatch;
168
+ };
169
+ /** Called at the start of each top-level render. Clears the
170
+ * per-render duplicate-slotId tracking map so siblings inside the
171
+ * same parent boundary can be re-validated. Does NOT touch `wraps`
172
+ * or `boundaryDispatches`. Those persist across renders and are
173
+ * evicted by vnode destroy hooks instead. */
174
+ export const beginRender = (registry) => {
175
+ registry.seenThisRender.clear();
176
+ };
@@ -0,0 +1,44 @@
1
+ import { type DispatchSync } from './runtimeSingleton.js';
2
+ declare const BRAND = "__childAttribute";
3
+ /** An attribute carrying a handler that dispatches through a Submodel
4
+ * boundary's wrapping chain. Published by Submodels (typically Foldkit's
5
+ * `Ui.*` primitives) for a parent to spread into its own element
6
+ * attribute arrays. The parent does not know or care which child
7
+ * produced these; the runtime routes each handler through the
8
+ * originating Submodel's wrap chain at event-fire time.
9
+ *
10
+ * Created via {@link childAttributes}. Element constructors accept
11
+ * `ChildAttribute` alongside `Attribute<Message>` in their attribute
12
+ * arrays. */
13
+ export type ChildAttribute = Readonly<{
14
+ readonly [BRAND]: true;
15
+ readonly attribute: unknown;
16
+ readonly dispatch: DispatchSync;
17
+ }>;
18
+ export declare const isChildAttribute: (value: unknown) => value is ChildAttribute;
19
+ /** Captures the current boundary's dispatcher and wraps each attribute
20
+ * so handlers inside it route through that boundary's wrapping chain at
21
+ * event-fire time, even when the attribute is later spread into a
22
+ * parent's element in a different boundary.
23
+ *
24
+ * Submodels call this when publishing attribute groups to a consumer's
25
+ * `toView` slot callback:
26
+ *
27
+ * ```ts
28
+ * // Inside a SubmodelView running in the child's boundary:
29
+ * return viewInputs.toView({
30
+ * checkbox: childAttributes([
31
+ * h.OnClick(Toggled()),
32
+ * h.Role('checkbox'),
33
+ * ]),
34
+ * ...
35
+ * })
36
+ * ```
37
+ *
38
+ * Without this binding step the consumer's element constructor would
39
+ * process `h.OnClick(Toggled())` using the parent's dispatcher (because
40
+ * the consumer's `toView` runs in the parent's boundary), bypassing the
41
+ * Submodel's `toParentMessage`. */
42
+ export declare const childAttributes: <Attribute>(attributes: ReadonlyArray<Attribute>) => ReadonlyArray<ChildAttribute>;
43
+ export {};
44
+ //# sourceMappingURL=childAttribute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"childAttribute.d.ts","sourceRoot":"","sources":["../../src/html/childAttribute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,uBAAuB,CAAA;AAE1E,QAAA,MAAM,KAAK,qBAAqB,CAAA;AAEhC;;;;;;;;;cASc;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;CAChC,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,CAO9B,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { requireDispatch } from './runtimeSingleton.js';
2
+ const BRAND = '__childAttribute';
3
+ export const isChildAttribute = (value) => typeof value === 'object' && value !== null && BRAND in value;
4
+ /** Captures the current boundary's dispatcher and wraps each attribute
5
+ * so handlers inside it route through that boundary's wrapping chain at
6
+ * event-fire time, even when the attribute is later spread into a
7
+ * parent's element in a different boundary.
8
+ *
9
+ * Submodels call this when publishing attribute groups to a consumer's
10
+ * `toView` slot callback:
11
+ *
12
+ * ```ts
13
+ * // Inside a SubmodelView running in the child's boundary:
14
+ * return viewInputs.toView({
15
+ * checkbox: childAttributes([
16
+ * h.OnClick(Toggled()),
17
+ * h.Role('checkbox'),
18
+ * ]),
19
+ * ...
20
+ * })
21
+ * ```
22
+ *
23
+ * Without this binding step the consumer's element constructor would
24
+ * process `h.OnClick(Toggled())` using the parent's dispatcher (because
25
+ * the consumer's `toView` runs in the parent's boundary), bypassing the
26
+ * Submodel's `toParentMessage`. */
27
+ export const childAttributes = (attributes) => {
28
+ const dispatch = requireDispatch();
29
+ return attributes.map(attribute => ({
30
+ [BRAND]: true,
31
+ attribute,
32
+ dispatch,
33
+ }));
34
+ };
@@ -1,9 +1,32 @@
1
- import { Data, Effect, Option, Stream } from 'effect';
1
+ import { Context, Data, Option, Stream } from 'effect';
2
2
  import type { File } from '../file/index.js';
3
3
  import type { MountAction } from '../mount/index.js';
4
- import { Dispatch } from '../runtime/index.js';
5
4
  import { VNode } from '../vdom.js';
5
+ import { type ChildAttribute } from './childAttribute.js';
6
+ import { type DispatchSync } from './runtimeSingleton.js';
6
7
  export { createKeyedLazy, createLazy } from './lazy.js';
8
+ export { beginRender as __beginRender, createBoundaryRegistry as __createBoundaryRegistry, } from './boundary.js';
9
+ export type { BoundaryRegistry } from './boundary.js';
10
+ export { childAttributes } from './childAttribute.js';
11
+ export type { ChildAttribute } from './childAttribute.js';
12
+ export { defineView, submodel } from './submodel.js';
13
+ export type { SubmodelConfig, SubmodelView } from './submodel.js';
14
+ /** Pushes a dispatch and runtime context frame for the duration of a render.
15
+ * The runtime calls this immediately before invoking a user `view` and
16
+ * matches it with {@link __clearRuntime} in a `finally` so an exception
17
+ * inside view code does not leak the frame to the next render. Test
18
+ * scaffolding that builds VNodes outside of a real program (Scene, Canvas
19
+ * view-only tests, Mount tests) uses the same pair. Nested frames are
20
+ * supported via an internal stack. */
21
+ export declare const __setRuntime: (dispatch: DispatchSync, runtimeContext: Context.Context<never>, boundaryRegistry?: import("./boundary.js").BoundaryRegistry) => void;
22
+ /** Pops the current dispatch and runtime context frame. Must be paired with a
23
+ * prior {@link __setRuntime} on the same call stack. */
24
+ export declare const __clearRuntime: () => void;
25
+ /** Returns the current dispatch function. Foldkit's `Canvas.view` reads this
26
+ * to build its synchronous pointer handlers, since it builds VNodes directly
27
+ * rather than going through the html factory. Most application code never
28
+ * needs to call this. */
29
+ export declare const __requireDispatch: () => DispatchSync;
7
30
  /**
8
31
  * The `id` of the DOM element that hosts the Foldkit DevTools shadow root.
9
32
  * Lives here (not in `devTools/`) because the `OnBlur` handler below uses it
@@ -16,7 +39,7 @@ export declare const DEVTOOLS_HOST_ID = "foldkit-devtools";
16
39
  * Tag symbol attached to file-aware event handler functions so Scene test
17
40
  * helpers can distinguish `OnFileChange` from `OnChange` (both register on
18
41
  * the DOM `change` event) and `OnDropFiles` from `OnDrop` (both register on
19
- * the DOM `drop` event). Internal implementation detail consumer code
42
+ * the DOM `drop` event). Internal implementation detail. Consumer code
20
43
  * should never need to reference this directly.
21
44
  */
22
45
  export declare const FileHandlerSymbol: unique symbol;
@@ -29,8 +52,10 @@ export type KeyboardModifiers = Readonly<{
29
52
  altKey: boolean;
30
53
  metaKey: boolean;
31
54
  }>;
32
- /** A virtual DOM element represented as an `Effect` that produces a `VNode`. */
33
- export type Html = Effect.Effect<VNode | null, never, Dispatch>;
55
+ /** A virtual DOM element. Constructed synchronously by the element factories
56
+ * returned from {@link html}. The runtime patches a `VNode` (or `null` to
57
+ * render nothing) into the application container. */
58
+ export type Html = VNode | null;
34
59
  export type Child = Html | string;
35
60
  /** A view's complete output for the runtime: title, body, and optional document
36
61
  * metadata. The runtime applies `title` to `document.title`, syncs `canonical`
@@ -60,7 +85,15 @@ export type FoldkitMountMarker = Readonly<{
60
85
  name: string;
61
86
  args?: Record<string, unknown>;
62
87
  }>;
63
- /** Union of all HTML, SVG, and MathML attributes a virtual DOM element can carry. */
88
+ /** Union of all HTML, SVG, and MathML attributes a virtual DOM element can carry.
89
+ *
90
+ * When a Submodel publishes attribute groups to a consumer's `toView`
91
+ * slot, those attributes are wrapped via {@link childAttributes} into
92
+ * {@link ChildAttribute}, a distinct type that carries the Submodel's
93
+ * own dispatcher. Element constructors accept the union
94
+ * `ReadonlyArray<Attribute<Message> | ChildAttribute>`, so consumers
95
+ * can spread published bundles directly into their own attribute
96
+ * arrays. */
64
97
  export type Attribute<Message> = Data.TaggedEnum<{
65
98
  Key: {
66
99
  readonly value: string;
@@ -798,22 +831,27 @@ declare const Prop: <A>(args: {
798
831
  readonly f: (event: CustomEvent<any>) => A;
799
832
  };
800
833
  export { Prop, OnCustomEvent };
801
- export declare const customElement: <Message>() => (tagName: string) => (attributes?: ReadonlyArray<Attribute<Message>>, children?: ReadonlyArray<Child>) => Html;
802
- type ElementFunction<Message> = (attributes: ReadonlyArray<Attribute<Message>>, children: ReadonlyArray<Child>) => Html;
803
- type VoidElementFunction<Message> = (attributes: ReadonlyArray<Attribute<Message>>) => Html;
804
- /**
805
- * Factory that returns all HTML, SVG, and MathML element constructors,
806
- * attribute constructors, a `keyed` helper for keyed elements, and `empty`
807
- * for rendering nothing.
808
- */
809
- export declare const html: <Message = never>() => {
810
- empty: Effect.Effect<null, never, never>;
811
- keyed: (tagName: TagName) => (key: string, attributes?: readonly ({
834
+ export declare const customElement: <Message>() => (tagName: string) => (attributes?: ReadonlyArray<Attribute<Message> | ChildAttribute>, children?: ReadonlyArray<Child>) => Html;
835
+ type ElementFunction<Message> = (attributes: ReadonlyArray<Attribute<Message> | ChildAttribute>, children: ReadonlyArray<Child>) => Html;
836
+ type VoidElementFunction<Message> = (attributes: ReadonlyArray<Attribute<Message> | ChildAttribute>) => Html;
837
+ declare const buildHtmlFactory: <Message>() => {
838
+ empty: null;
839
+ keyed: (tagName: TagName) => (key: string, attributes?: readonly (Readonly<{
840
+ readonly attribute: unknown;
841
+ readonly dispatch: DispatchSync;
842
+ readonly __childAttribute: true;
843
+ }> | {
812
844
  readonly _tag: "Type";
813
845
  readonly value: string;
814
846
  } | {
815
847
  readonly _tag: "Start";
816
848
  readonly value: number;
849
+ } | {
850
+ readonly _tag: "OnFileChange";
851
+ readonly f: (files: ReadonlyArray<File>) => Message;
852
+ } | {
853
+ readonly _tag: "OnDropFiles";
854
+ readonly f: (files: ReadonlyArray<File>) => Message;
817
855
  } | {
818
856
  readonly _tag: "Key";
819
857
  readonly value: string;
@@ -953,9 +991,6 @@ export declare const html: <Message = never>() => {
953
991
  } | {
954
992
  readonly _tag: "OnChange";
955
993
  readonly f: (value: string) => Message;
956
- } | {
957
- readonly _tag: "OnFileChange";
958
- readonly f: (files: ReadonlyArray<File>) => Message;
959
994
  } | {
960
995
  readonly _tag: "OnSubmit";
961
996
  readonly message: Message;
@@ -1009,9 +1044,6 @@ export declare const html: <Message = never>() => {
1009
1044
  } | {
1010
1045
  readonly _tag: "OnDrop";
1011
1046
  readonly message: Message;
1012
- } | {
1013
- readonly _tag: "OnDropFiles";
1014
- readonly f: (files: ReadonlyArray<File>) => Message;
1015
1047
  } | {
1016
1048
  readonly _tag: "OnTouchStart";
1017
1049
  readonly message: Message;
@@ -1558,6 +1590,9 @@ export declare const html: <Message = never>() => {
1558
1590
  f: (element: Element) => Stream.Stream<Message, any, never>;
1559
1591
  }>;
1560
1592
  })[], children?: ReadonlyArray<Child>) => Html;
1593
+ submodel: <View extends ((...args: ReadonlyArray<any>) => VNode | null) & {
1594
+ readonly __submodelMessage: unknown;
1595
+ }>(config: import("./submodel.js").SubmodelConfig<View>) => VNode | null;
1561
1596
  Key: (value: string) => {
1562
1597
  readonly _tag: "Key";
1563
1598
  readonly value: string;
@@ -2779,4 +2814,16 @@ export declare const html: <Message = never>() => {
2779
2814
  munderover: ElementFunction<Message>;
2780
2815
  semantics: ElementFunction<Message>;
2781
2816
  };
2817
+ /**
2818
+ * Returns all HTML, SVG, and MathML element constructors, attribute
2819
+ * constructors, a `keyed` helper for keyed elements, and `empty` for
2820
+ * rendering nothing.
2821
+ *
2822
+ * The returned object is a process-wide singleton. The `Message` type
2823
+ * parameter is erased at runtime, and the element and attribute constructors
2824
+ * carry no per-program state (dispatch is read from the runtime singleton at
2825
+ * call time), so calling `html()` repeatedly from inside view functions does
2826
+ * not allocate a fresh object.
2827
+ */
2828
+ export declare const html: <Message = never>() => ReturnType<typeof buildHtmlFactory<Message>>;
2782
2829
  //# sourceMappingURL=index.d.ts.map