foldkit 0.100.1 → 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 (217) hide show
  1. package/README.md +3 -2
  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 +156 -149
  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 +11 -11
  56. package/dist/runtime/runtime.d.ts.map +1 -1
  57. package/dist/runtime/runtime.js +118 -63
  58. package/dist/runtime/subscription.d.ts +139 -19
  59. package/dist/runtime/subscription.d.ts.map +1 -1
  60. package/dist/runtime/subscription.js +90 -9
  61. package/dist/submodel/public.d.ts +4 -0
  62. package/dist/submodel/public.d.ts.map +1 -0
  63. package/dist/submodel/public.js +1 -0
  64. package/dist/submodel/submodel.d.ts +32 -0
  65. package/dist/submodel/submodel.d.ts.map +1 -0
  66. package/dist/submodel/submodel.js +1 -0
  67. package/dist/subscription/animationFrame.d.ts +23 -26
  68. package/dist/subscription/animationFrame.d.ts.map +1 -1
  69. package/dist/subscription/animationFrame.js +17 -18
  70. package/dist/subscription/public.d.ts +2 -2
  71. package/dist/subscription/public.d.ts.map +1 -1
  72. package/dist/subscription/public.js +1 -1
  73. package/dist/test/apps/disabledButton.d.ts +4 -5
  74. package/dist/test/apps/disabledButton.d.ts.map +1 -1
  75. package/dist/test/apps/disabledButton.js +16 -16
  76. package/dist/test/scene.d.ts +8 -8
  77. package/dist/test/scene.d.ts.map +1 -1
  78. package/dist/test/scene.js +25 -13
  79. package/dist/test/story.d.ts +15 -8
  80. package/dist/test/story.d.ts.map +1 -1
  81. package/dist/test/story.js +21 -9
  82. package/dist/ui/animation/index.d.ts +30 -14
  83. package/dist/ui/animation/index.d.ts.map +1 -1
  84. package/dist/ui/animation/index.js +9 -19
  85. package/dist/ui/animation/public.d.ts +2 -2
  86. package/dist/ui/animation/public.d.ts.map +1 -1
  87. package/dist/ui/animation/public.js +1 -1
  88. package/dist/ui/calendar/index.d.ts +199 -84
  89. package/dist/ui/calendar/index.d.ts.map +1 -1
  90. package/dist/ui/calendar/index.js +129 -140
  91. package/dist/ui/calendar/public.d.ts +2 -2
  92. package/dist/ui/calendar/public.d.ts.map +1 -1
  93. package/dist/ui/calendar/public.js +1 -1
  94. package/dist/ui/checkbox/index.d.ts +93 -21
  95. package/dist/ui/checkbox/index.d.ts.map +1 -1
  96. package/dist/ui/checkbox/index.js +62 -33
  97. package/dist/ui/checkbox/public.d.ts +2 -2
  98. package/dist/ui/checkbox/public.d.ts.map +1 -1
  99. package/dist/ui/checkbox/public.js +1 -1
  100. package/dist/ui/combobox/multi.d.ts +35 -91
  101. package/dist/ui/combobox/multi.d.ts.map +1 -1
  102. package/dist/ui/combobox/multi.js +34 -17
  103. package/dist/ui/combobox/multiPublic.d.ts +2 -2
  104. package/dist/ui/combobox/multiPublic.d.ts.map +1 -1
  105. package/dist/ui/combobox/multiPublic.js +1 -1
  106. package/dist/ui/combobox/public.d.ts +3 -3
  107. package/dist/ui/combobox/public.d.ts.map +1 -1
  108. package/dist/ui/combobox/public.js +2 -2
  109. package/dist/ui/combobox/shared.d.ts +56 -31
  110. package/dist/ui/combobox/shared.d.ts.map +1 -1
  111. package/dist/ui/combobox/shared.js +333 -322
  112. package/dist/ui/combobox/single.d.ts +46 -93
  113. package/dist/ui/combobox/single.d.ts.map +1 -1
  114. package/dist/ui/combobox/single.js +44 -17
  115. package/dist/ui/datePicker/index.d.ts +256 -48
  116. package/dist/ui/datePicker/index.d.ts.map +1 -1
  117. package/dist/ui/datePicker/index.js +149 -104
  118. package/dist/ui/datePicker/public.d.ts +2 -2
  119. package/dist/ui/datePicker/public.d.ts.map +1 -1
  120. package/dist/ui/datePicker/public.js +1 -1
  121. package/dist/ui/dialog/index.d.ts +95 -39
  122. package/dist/ui/dialog/index.d.ts.map +1 -1
  123. package/dist/ui/dialog/index.js +71 -62
  124. package/dist/ui/dialog/public.d.ts +2 -2
  125. package/dist/ui/dialog/public.d.ts.map +1 -1
  126. package/dist/ui/dialog/public.js +1 -1
  127. package/dist/ui/disclosure/index.d.ts +71 -31
  128. package/dist/ui/disclosure/index.d.ts.map +1 -1
  129. package/dist/ui/disclosure/index.js +57 -62
  130. package/dist/ui/disclosure/public.d.ts +2 -2
  131. package/dist/ui/disclosure/public.d.ts.map +1 -1
  132. package/dist/ui/disclosure/public.js +1 -1
  133. package/dist/ui/dragAndDrop/index.d.ts +385 -103
  134. package/dist/ui/dragAndDrop/index.d.ts.map +1 -1
  135. package/dist/ui/dragAndDrop/index.js +26 -31
  136. package/dist/ui/dragAndDrop/public.d.ts +1 -1
  137. package/dist/ui/dragAndDrop/public.d.ts.map +1 -1
  138. package/dist/ui/dragAndDrop/public.js +1 -1
  139. package/dist/ui/fileDrop/index.d.ts +42 -46
  140. package/dist/ui/fileDrop/index.d.ts.map +1 -1
  141. package/dist/ui/fileDrop/index.js +30 -46
  142. package/dist/ui/fileDrop/public.d.ts +2 -2
  143. package/dist/ui/fileDrop/public.d.ts.map +1 -1
  144. package/dist/ui/fileDrop/public.js +1 -1
  145. package/dist/ui/listbox/multi.d.ts +39 -84
  146. package/dist/ui/listbox/multi.d.ts.map +1 -1
  147. package/dist/ui/listbox/multi.js +38 -20
  148. package/dist/ui/listbox/multiPublic.d.ts +2 -2
  149. package/dist/ui/listbox/multiPublic.d.ts.map +1 -1
  150. package/dist/ui/listbox/multiPublic.js +1 -1
  151. package/dist/ui/listbox/public.d.ts +3 -3
  152. package/dist/ui/listbox/public.d.ts.map +1 -1
  153. package/dist/ui/listbox/public.js +2 -2
  154. package/dist/ui/listbox/shared.d.ts +71 -30
  155. package/dist/ui/listbox/shared.d.ts.map +1 -1
  156. package/dist/ui/listbox/shared.js +319 -296
  157. package/dist/ui/listbox/single.d.ts +57 -85
  158. package/dist/ui/listbox/single.d.ts.map +1 -1
  159. package/dist/ui/listbox/single.js +48 -24
  160. package/dist/ui/menu/index.d.ts +80 -36
  161. package/dist/ui/menu/index.d.ts.map +1 -1
  162. package/dist/ui/menu/index.js +117 -86
  163. package/dist/ui/menu/public.d.ts +2 -2
  164. package/dist/ui/menu/public.d.ts.map +1 -1
  165. package/dist/ui/menu/public.js +1 -1
  166. package/dist/ui/popover/index.d.ts +117 -44
  167. package/dist/ui/popover/index.d.ts.map +1 -1
  168. package/dist/ui/popover/index.js +88 -101
  169. package/dist/ui/popover/public.d.ts +2 -2
  170. package/dist/ui/popover/public.d.ts.map +1 -1
  171. package/dist/ui/popover/public.js +1 -1
  172. package/dist/ui/radioGroup/index.d.ts +122 -45
  173. package/dist/ui/radioGroup/index.d.ts.map +1 -1
  174. package/dist/ui/radioGroup/index.js +111 -72
  175. package/dist/ui/radioGroup/public.d.ts +2 -2
  176. package/dist/ui/radioGroup/public.d.ts.map +1 -1
  177. package/dist/ui/radioGroup/public.js +1 -1
  178. package/dist/ui/slider/index.d.ts +247 -103
  179. package/dist/ui/slider/index.d.ts.map +1 -1
  180. package/dist/ui/slider/index.js +52 -68
  181. package/dist/ui/slider/public.d.ts +2 -2
  182. package/dist/ui/slider/public.d.ts.map +1 -1
  183. package/dist/ui/slider/public.js +1 -1
  184. package/dist/ui/switch/index.d.ts +74 -21
  185. package/dist/ui/switch/index.d.ts.map +1 -1
  186. package/dist/ui/switch/index.js +62 -33
  187. package/dist/ui/switch/public.d.ts +2 -2
  188. package/dist/ui/switch/public.d.ts.map +1 -1
  189. package/dist/ui/switch/public.js +1 -1
  190. package/dist/ui/tabs/index.d.ts +107 -45
  191. package/dist/ui/tabs/index.d.ts.map +1 -1
  192. package/dist/ui/tabs/index.js +99 -81
  193. package/dist/ui/tabs/public.d.ts +2 -2
  194. package/dist/ui/tabs/public.d.ts.map +1 -1
  195. package/dist/ui/tabs/public.js +1 -1
  196. package/dist/ui/toast/index.d.ts +93 -109
  197. package/dist/ui/toast/index.d.ts.map +1 -1
  198. package/dist/ui/toast/index.js +16 -29
  199. package/dist/ui/toast/schema.d.ts +15 -4
  200. package/dist/ui/toast/schema.d.ts.map +1 -1
  201. package/dist/ui/toast/schema.js +11 -4
  202. package/dist/ui/toast/update.d.ts +36 -18
  203. package/dist/ui/toast/update.d.ts.map +1 -1
  204. package/dist/ui/toast/update.js +33 -14
  205. package/dist/ui/tooltip/index.d.ts +94 -42
  206. package/dist/ui/tooltip/index.d.ts.map +1 -1
  207. package/dist/ui/tooltip/index.js +64 -73
  208. package/dist/ui/tooltip/public.d.ts +2 -2
  209. package/dist/ui/tooltip/public.d.ts.map +1 -1
  210. package/dist/ui/tooltip/public.js +1 -1
  211. package/dist/ui/virtualList/index.d.ts +63 -80
  212. package/dist/ui/virtualList/index.d.ts.map +1 -1
  213. package/dist/ui/virtualList/index.js +22 -49
  214. package/dist/ui/virtualList/public.d.ts +2 -2
  215. package/dist/ui/virtualList/public.d.ts.map +1 -1
  216. package/dist/ui/virtualList/public.js +1 -1
  217. package/package.json +1 -1
@@ -1,13 +1,13 @@
1
- import { Duration, Effect, Equal, Match as M, Number, Option, Schema as S, } from 'effect';
1
+ import { Duration, Effect, Equal, Function, Match as M, Number, Option, Schema as S, } from 'effect';
2
2
  import * as Command from '../../command/index.js';
3
3
  import { OptionExt } from '../../effectExtensions/index.js';
4
- import { createLazy, html, } from '../../html/index.js';
4
+ import { childAttributes, defineView, html, } from '../../html/index.js';
5
5
  import { m } from '../../message/index.js';
6
6
  import * as Mount from '../../mount/index.js';
7
7
  import { evo } from '../../struct/index.js';
8
8
  import { AnchorConfig, anchorSetup } from '../anchor.js';
9
9
  // MODEL
10
- /** Schema for the tooltip component's state. `isOpen` is visibility; `isHovered` tracks pointer on trigger; `isFocused` tracks tooltip-affirming focus on the trigger (focus arriving without a preceding mouse press keyboard, touch, or pen; mouse-click-induced focus is excluded since it doesn't affirm the user wants the tooltip visible); `isDismissed` suppresses re-opening after the user dismissed the tooltip (via Escape or left-click) until they disengage (leave or blur). `showDelay` is the hover-to-show duration. `maybeLastPointerType` records the most recent pointer type that pressed the trigger, so a mouse-click-induced focus can be distinguished from other focus. */
10
+ /** Schema for the tooltip component's state. `isOpen` is visibility; `isHovered` tracks pointer on trigger; `isFocused` tracks tooltip-affirming focus on the trigger (focus arriving without a preceding mouse press, like keyboard, touch, or pen; mouse-click-induced focus is excluded since it doesn't affirm the user wants the tooltip visible); `isDismissed` suppresses re-opening after the user dismissed the tooltip (via Escape or left-click) until they disengage (leave or blur). `showDelay` is the hover-to-show duration. `maybeLastPointerType` records the most recent pointer type that pressed the trigger, so a mouse-click-induced focus can be distinguished from other focus. */
11
11
  export const Model = S.Struct({
12
12
  id: S.String,
13
13
  isOpen: S.Boolean,
@@ -19,30 +19,26 @@ export const Model = S.Struct({
19
19
  maybeLastPointerType: S.Option(S.String),
20
20
  });
21
21
  // MESSAGE
22
- /** Sent when the pointer enters the tooltip trigger. Starts the show-delay timer. */
22
+ /** Sent when the pointer enters the tooltip trigger. */
23
23
  export const EnteredTrigger = m('EnteredTrigger');
24
- /** Sent when the pointer leaves the tooltip trigger. Cancels any pending show-delay and hides the tooltip unless focus is active. */
24
+ /** Sent when the pointer leaves the tooltip trigger. */
25
25
  export const LeftTrigger = m('LeftTrigger');
26
- /** Sent when focus enters the trigger. Shows the tooltip immediately unless the focus was caused by a mouse press, in which case the hover-delay path handles it instead. */
26
+ /** Sent when focus enters the trigger. */
27
27
  export const FocusedTrigger = m('FocusedTrigger');
28
- /** Sent when focus leaves the trigger. Hides the tooltip unless hover is active. */
28
+ /** Sent when focus leaves the trigger. */
29
29
  export const BlurredTrigger = m('BlurredTrigger');
30
- /** Sent when Escape is pressed while the tooltip is visible. Hides the tooltip and flags `isDismissed` so hover and focus do not re-open it until the user disengages (leaves or blurs the trigger). */
30
+ /** Sent when Escape is pressed while the tooltip is visible. */
31
31
  export const PressedEscape = m('PressedEscape');
32
- /** Sent when a pointer presses the trigger. Records the pointer type so a following focus event from the same mouse click can be suppressed, and a left-click on an open tooltip dismisses it (the user is clicking the button for its action, not to keep the tooltip visible). */
32
+ /** Sent when a pointer presses the trigger. */
33
33
  export const PressedPointerOnTrigger = m('PressedPointerOnTrigger', {
34
34
  pointerType: S.String,
35
35
  button: S.Number,
36
36
  });
37
- /** Sent when the show-delay timer fires. Carries a generation number that is compared against the current pending version to discard stale timers. */
37
+ /** Sent when the show-delay timer fires. */
38
38
  export const ElapsedShowDelay = m('ElapsedShowDelay', {
39
39
  version: S.Number,
40
40
  });
41
- /** Signals that the show-delay has changed (e.g. in response to a user preference, input-method change, or reduced-motion setting). Does not affect the current open/closed state; the new delay applies to the next hover. Typically dispatched via the `setShowDelay` helper. */
42
- export const ChangedShowDelay = m('ChangedShowDelay', {
43
- showDelay: S.DurationFromMillis,
44
- });
45
- /** Sent when the tooltip panel mounts and Floating UI has positioned it. Update no-ops; the side effect is the act of positioning, surfaced for DevTools observability. */
41
+ /** Sent when the tooltip panel mounts and Floating UI has positioned it. */
46
42
  export const CompletedAnchorTooltip = m('CompletedAnchorTooltip');
47
43
  /** Union of all messages the tooltip component can produce. */
48
44
  export const Message = S.Union([
@@ -53,9 +49,17 @@ export const Message = S.Union([
53
49
  PressedEscape,
54
50
  PressedPointerOnTrigger,
55
51
  ElapsedShowDelay,
56
- ChangedShowDelay,
57
52
  CompletedAnchorTooltip,
58
53
  ]);
54
+ // OUT MESSAGE
55
+ /** Emitted once the tooltip transitions to visible (`isOpen` becomes true).
56
+ * Consumers typically use this for analytics, instrumentation, or to
57
+ * coordinate with other transient UI. */
58
+ export const Shown = m('Shown');
59
+ /** Emitted once the tooltip transitions to hidden (`isOpen` becomes false). */
60
+ export const Hidden = m('Hidden');
61
+ /** Union of out-messages the tooltip component can produce. */
62
+ export const OutMessage = S.Union([Shown, Hidden]);
59
63
  // INIT
60
64
  const DEFAULT_SHOW_DELAY = Duration.millis(500);
61
65
  const LEFT_MOUSE_BUTTON = 0;
@@ -73,11 +77,9 @@ export const init = (config) => ({
73
77
  maybeLastPointerType: Option.none(),
74
78
  });
75
79
  const withUpdateReturn = M.withReturnType();
76
- /** Waits for the tooltip's show delay before emitting `ElapsedShowDelay`. The version is echoed back so a stale timer is ignored when the user leaves before the delay fires. */
80
+ /** Waits for the tooltip's show delay before emitting `ElapsedShowDelay`. */
77
81
  export const ShowAfterDelay = Command.define('ShowAfterDelay', { delay: S.DurationFromMillis, version: S.Number }, ElapsedShowDelay)(({ delay, version }) => Effect.sleep(delay).pipe(Effect.as(ElapsedShowDelay({ version }))));
78
- /** The anchor-positioning Mount this Tooltip renders on its panel. Exposed so
79
- * Scene tests can call `Scene.Mount.resolve(AnchorTooltip, CompletedAnchorTooltip())`
80
- * to acknowledge the mount produced by the rendered panel. */
82
+ /** The anchor-positioning Mount this Tooltip renders on its panel. */
81
83
  export const AnchorTooltip = Mount.define('AnchorTooltip', { buttonId: S.String, anchor: AnchorConfig }, CompletedAnchorTooltip)(({ buttonId, anchor }) => element => Effect.gen(function* () {
82
84
  yield* Effect.acquireRelease(Effect.sync(() => anchorSetup({
83
85
  buttonId,
@@ -86,8 +88,7 @@ export const AnchorTooltip = Mount.define('AnchorTooltip', { buttonId: S.String,
86
88
  })(element)), cleanup => Effect.sync(cleanup));
87
89
  return CompletedAnchorTooltip();
88
90
  }));
89
- /** Processes a tooltip message and returns the next model and commands. */
90
- export const update = (model, message) => M.value(message).pipe(withUpdateReturn, M.tagsExhaustive({
91
+ const computeUpdate = (model, message) => M.value(message).pipe(withUpdateReturn, M.tagsExhaustive({
91
92
  EnteredTrigger: () => {
92
93
  if (model.isOpen || model.isDismissed) {
93
94
  return [evo(model, { isHovered: () => true }), []];
@@ -186,21 +187,36 @@ export const update = (model, message) => M.value(message).pipe(withUpdateReturn
186
187
  }
187
188
  return [evo(model, { isOpen: () => true }), []];
188
189
  },
189
- ChangedShowDelay: ({ showDelay }) => [
190
- evo(model, { showDelay: () => showDelay }),
191
- [],
192
- ],
193
190
  CompletedAnchorTooltip: () => [model, []],
194
191
  }));
195
- /** Programmatically updates the tooltip's hover show-delay. Use this in response to user preference changes, input-method switches, or reduced-motion settings. The new delay applies to the next hover; any pending timer is unaffected (its stale version will discard harmlessly when it fires). */
196
- export const setShowDelay = (model, showDelay) => update(model, ChangedShowDelay({ showDelay: Duration.fromInputUnsafe(showDelay) }));
197
- /** Renders a headless tooltip with an anchored non-interactive panel. Shows on hover (after delay) or focus (from keyboard, touch, or pen; mouse-click focus is excluded); hides on leave, blur, Escape, or left-click of the trigger. Uses `role="tooltip"` and links the trigger via `aria-describedby`. */
198
- export const view = (config) => {
192
+ /** Processes a tooltip message and returns the next model, commands, and
193
+ * an optional OutMessage. `Shown`/`Hidden` fire only on `isOpen`
194
+ * transitions, so consumers don't get spurious events for messages that
195
+ * only update hover/focus/delay state without changing visibility. */
196
+ export const update = (model, message) => {
197
+ const [nextModel, commands] = computeUpdate(model, message);
198
+ const maybeOutMessage = !model.isOpen && nextModel.isOpen
199
+ ? Option.some(Shown())
200
+ : model.isOpen && !nextModel.isOpen
201
+ ? Option.some(Hidden())
202
+ : Option.none();
203
+ return [nextModel, commands, maybeOutMessage];
204
+ };
205
+ /** Reflects an externally-sourced hover show-delay onto the model without
206
+ * emitting an OutMessage. Use to mirror an external config value (a user
207
+ * preference, a restored setting) onto the tooltip. */
208
+ export const reflectShowDelay = Function.dual(2, (model, showDelay) => evo(model, { showDelay: () => Duration.fromInputUnsafe(showDelay) }));
209
+ /** Renders a headless tooltip with an anchored non-interactive panel.
210
+ * Shows on hover (after delay) or focus (from keyboard, touch, or pen;
211
+ * mouse-click focus is excluded); hides on leave, blur, Escape, or
212
+ * left-click of the trigger. */
213
+ export const view = defineView((model, viewInputs) => {
199
214
  const h = html();
200
- const { model: { id, isOpen }, toParentMessage, anchor, triggerContent, triggerClassName, triggerAttributes = [], content, panelClassName, panelAttributes = [], isDisabled, className, attributes = [], } = config;
201
- const handleTriggerKeyDown = (key) => M.value(key).pipe(M.when('Escape', () => OptionExt.when(isOpen, toParentMessage(PressedEscape()))), M.orElse(() => Option.none()));
202
- const handleTriggerPointerDown = (pointerType, button) => Option.some(toParentMessage(PressedPointerOnTrigger({ pointerType, button })));
203
- const resolvedTriggerAttributes = [
215
+ const { id, isOpen } = model;
216
+ const { anchor, toView, isDisabled } = viewInputs;
217
+ const handleTriggerKeyDown = (key) => M.value(key).pipe(M.when('Escape', () => OptionExt.when(isOpen, PressedEscape())), M.orElse(() => Option.none()));
218
+ const handleTriggerPointerDown = (pointerType, button) => Option.some(PressedPointerOnTrigger({ pointerType, button }));
219
+ const triggerAttributes = [
204
220
  h.Id(`${id}-trigger`),
205
221
  h.Type('button'),
206
222
  h.AriaDescribedBy(`${id}-panel`),
@@ -208,54 +224,29 @@ export const view = (config) => {
208
224
  ...(isDisabled
209
225
  ? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
210
226
  : [
211
- h.OnMouseEnter(toParentMessage(EnteredTrigger())),
212
- h.OnMouseLeave(toParentMessage(LeftTrigger())),
213
- h.OnFocus(toParentMessage(FocusedTrigger())),
214
- h.OnBlur(toParentMessage(BlurredTrigger())),
227
+ h.OnMouseEnter(EnteredTrigger()),
228
+ h.OnMouseLeave(LeftTrigger()),
229
+ h.OnFocus(FocusedTrigger()),
230
+ h.OnBlur(BlurredTrigger()),
215
231
  h.OnKeyDownPreventDefault(handleTriggerKeyDown),
216
232
  h.OnPointerDown(handleTriggerPointerDown),
217
233
  ]),
218
- ...(triggerClassName ? [h.Class(triggerClassName)] : []),
219
- ...triggerAttributes,
220
234
  ];
221
- const anchorTooltip = Mount.mapMessage(AnchorTooltip({ buttonId: `${id}-trigger`, anchor }), toParentMessage);
222
- const anchorAttributes = [
235
+ const panelAttributes = [
236
+ h.Id(`${id}-panel`),
237
+ h.Role('tooltip'),
223
238
  h.Style({
224
239
  position: 'absolute',
225
240
  margin: '0',
226
241
  visibility: 'hidden',
227
242
  pointerEvents: 'none',
228
243
  }),
229
- h.OnMount(anchorTooltip),
230
- ];
231
- const resolvedPanelAttributes = [
232
- h.Id(`${id}-panel`),
233
- h.Role('tooltip'),
234
- ...anchorAttributes,
244
+ h.OnMount(AnchorTooltip({ buttonId: `${id}-trigger`, anchor })),
235
245
  ...(isOpen ? [h.DataAttribute('open', '')] : []),
236
- ...(panelClassName ? [h.Class(panelClassName)] : []),
237
- ...panelAttributes,
238
- ];
239
- const wrapperAttributes = [
240
- ...(className ? [h.Class(className)] : []),
241
- ...attributes,
242
246
  ];
243
- return h.div(wrapperAttributes, [
244
- h.keyed('button')(`${id}-trigger`, resolvedTriggerAttributes, [
245
- triggerContent,
246
- ]),
247
- ...(isOpen
248
- ? [h.keyed('div')(`${id}-panel`, resolvedPanelAttributes, [content])]
249
- : []),
250
- ]);
251
- };
252
- /** Creates a memoized tooltip view. Static config is captured in a closure;
253
- * only `model` and `toParentMessage` are compared per render via `createLazy`. */
254
- export const lazy = (staticConfig) => {
255
- const lazyView = createLazy();
256
- return (model, toParentMessage) => lazyView((currentModel, currentToParentMessage) => view({
257
- ...staticConfig,
258
- model: currentModel,
259
- toParentMessage: currentToParentMessage,
260
- }), [model, toParentMessage]);
261
- };
247
+ return toView({
248
+ trigger: childAttributes(triggerAttributes),
249
+ panel: childAttributes(panelAttributes),
250
+ isVisible: isOpen,
251
+ });
252
+ });
@@ -1,4 +1,4 @@
1
- export { init, update, view, lazy, setShowDelay, Model, Message, EnteredTrigger, LeftTrigger, FocusedTrigger, BlurredTrigger, PressedEscape, PressedPointerOnTrigger, ElapsedShowDelay, ChangedShowDelay, ShowAfterDelay, CompletedAnchorTooltip, AnchorTooltip, } from './index.js';
2
- export type { InitConfig, ViewConfig } from './index.js';
1
+ export { init, update, view, reflectShowDelay, Model, Message, OutMessage, Shown, Hidden, EnteredTrigger, LeftTrigger, FocusedTrigger, BlurredTrigger, PressedEscape, PressedPointerOnTrigger, ElapsedShowDelay, ShowAfterDelay, CompletedAnchorTooltip, AnchorTooltip, } from './index.js';
2
+ export type { InitConfig, ViewInputs, RenderInfo } from './index.js';
3
3
  export type { AnchorConfig } from '../anchor.js';
4
4
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/tooltip/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,OAAO,EACP,cAAc,EACd,WAAW,EACX,cAAc,EACd,cAAc,EACd,aAAa,EACb,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,sBAAsB,EACtB,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAExD,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/tooltip/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,KAAK,EACL,OAAO,EACP,UAAU,EACV,KAAK,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,cAAc,EACd,cAAc,EACd,aAAa,EACb,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,EACd,sBAAsB,EACtB,aAAa,GACd,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEpE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
@@ -1 +1 @@
1
- export { init, update, view, lazy, setShowDelay, Model, Message, EnteredTrigger, LeftTrigger, FocusedTrigger, BlurredTrigger, PressedEscape, PressedPointerOnTrigger, ElapsedShowDelay, ChangedShowDelay, ShowAfterDelay, CompletedAnchorTooltip, AnchorTooltip, } from './index.js';
1
+ export { init, update, view, reflectShowDelay, Model, Message, OutMessage, Shown, Hidden, EnteredTrigger, LeftTrigger, FocusedTrigger, BlurredTrigger, PressedEscape, PressedPointerOnTrigger, ElapsedShowDelay, ShowAfterDelay, CompletedAnchorTooltip, AnchorTooltip, } from './index.js';
@@ -1,6 +1,6 @@
1
- import { Effect, Option, Schema as S } from 'effect';
1
+ import { Effect, Option, Schema as S, Stream } from 'effect';
2
2
  import * as Command from '../../command/index.js';
3
- import { type Attribute, type Html, type TagName } from '../../html/index.js';
3
+ import { type ChildAttribute, type Html, type SubmodelView, type TagName } from '../../html/index.js';
4
4
  /** Schema for the virtual list's state. Tracks scroll position, container
5
5
  * measurement, and any in-flight programmatic scroll. */
6
6
  export declare const Model: S.Struct<{
@@ -123,13 +123,6 @@ export declare const visibleWindow: (model: Model, itemCount: number, overscan:
123
123
  *
124
124
  * Returns `Option.none()` when the container has not yet been measured. */
125
125
  export declare const visibleWindowVariable: <Item>(model: Model, items: ReadonlyArray<Item>, itemToRowHeightPx: (item: Item, index: number) => number, overscan: number) => Option.Option<VisibleWindow>;
126
- /** Schema describing the subscription dependencies for container scroll and
127
- * resize tracking. */
128
- export declare const SubscriptionDependencies: S.Struct<{
129
- readonly containerEvents: S.Struct<{
130
- readonly id: S.String;
131
- }>;
132
- }>;
133
126
  /** Subscriptions that track the container's scroll position and size.
134
127
  *
135
128
  * - **scroll**: listens for `scroll` events on the container element and
@@ -143,87 +136,77 @@ export declare const SubscriptionDependencies: S.Struct<{
143
136
  * makes the subscription robust across SPA route changes: navigating to a
144
137
  * page that mounts the list, away, and back all reattach correctly without
145
138
  * the consumer having to teach the framework about navigation. */
146
- export declare const subscriptions: import("../../runtime/subscription.js").Subscriptions<{
147
- readonly id: string;
148
- readonly rowHeightPx: number;
149
- readonly scrollTop: number;
150
- readonly measurement: {
151
- readonly _tag: "Unmeasured";
152
- } | {
153
- readonly _tag: "Measured";
154
- readonly containerHeight: number;
155
- };
156
- readonly pendingScroll: {
157
- readonly _tag: "Idle";
158
- } | {
159
- readonly _tag: "ScrollingToIndex";
160
- readonly index: number;
161
- readonly version: number;
139
+ export declare const subscriptions: {
140
+ readonly containerEvents: {
141
+ readonly dependenciesSchema: S.Schema<{
142
+ readonly id: string;
143
+ }> & {
144
+ readonly fields: S.Struct.Fields;
145
+ };
146
+ readonly modelToDependencies: (model: {
147
+ readonly id: string;
148
+ readonly rowHeightPx: number;
149
+ readonly scrollTop: number;
150
+ readonly measurement: {
151
+ readonly _tag: "Unmeasured";
152
+ } | {
153
+ readonly _tag: "Measured";
154
+ readonly containerHeight: number;
155
+ };
156
+ readonly pendingScroll: {
157
+ readonly _tag: "Idle";
158
+ } | {
159
+ readonly _tag: "ScrollingToIndex";
160
+ readonly index: number;
161
+ readonly version: number;
162
+ };
163
+ readonly pendingScrollVersion: number;
164
+ }) => {
165
+ readonly id: string;
166
+ };
167
+ readonly keepAliveEquivalence?: never;
168
+ readonly dependenciesToStream: (dependencies: {
169
+ readonly id: string;
170
+ }) => Stream.Stream<{
171
+ readonly _tag: "ScrolledContainer";
172
+ readonly scrollTop: number;
173
+ } | {
174
+ readonly _tag: "MeasuredContainer";
175
+ readonly containerHeight: number;
176
+ } | {
177
+ readonly _tag: "CompletedApplyScroll";
178
+ readonly version: number;
179
+ }, never, never>;
180
+ } & {
181
+ readonly __subscription: never;
162
182
  };
163
- readonly pendingScrollVersion: number;
164
- }, {
165
- readonly _tag: "ScrolledContainer";
166
- readonly scrollTop: number;
167
- } | {
168
- readonly _tag: "MeasuredContainer";
169
- readonly containerHeight: number;
170
- } | {
171
- readonly _tag: "CompletedApplyScroll";
172
- readonly version: number;
173
- }, S.Struct<{
174
- readonly containerEvents: S.Struct<{
175
- readonly id: S.String;
176
- }>;
177
- }>, never>;
178
- /** Configuration for rendering a virtual list with `view`.
183
+ };
184
+ /** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field.
179
185
  *
180
- * VirtualList does not take a `toParentMessage` callback. All input
186
+ * VirtualList does not surface event handlers in the view. All input
181
187
  * (scroll events and resize observations) flows through the
182
- * `containerEvents` Subscription, not through view-bound handlers.
183
- * Consumers wrap the subscription's stream into their parent Message in
184
- * their own `subscriptions` definition. */
185
- export type ViewConfig<ParentMessage, Item> = Readonly<{
186
- model: Model;
188
+ * `containerEvents` Subscription. The consumer wraps that
189
+ * Subscription's stream into their parent Message in their own
190
+ * `subscriptions` definition. */
191
+ export type ViewInputs<Item> = Readonly<{
187
192
  items: ReadonlyArray<Item>;
188
193
  itemToKey: (item: Item, index: number) => string;
189
194
  itemToView: (item: Item, index: number) => Html;
190
- /** Optional per-item row height in pixels. When provided, the list renders
191
- * with variable-height rows: each row's wrapper takes the height returned
192
- * by this callback, and scroll math walks the items to compute the visible
193
- * slice and spacers. When absent, all rows use `model.rowHeightPx`. Use
194
- * this for tables with wrapping cells, taller detail rows, or any list
195
- * where rows differ. Prefer the uniform `rowHeightPx` path when row
196
- * heights are stable: it avoids the per-render walk over `items`. */
197
195
  itemToRowHeightPx?: (item: Item, index: number) => number;
198
- /** Number of rows rendered above and below the visible viewport. Higher
199
- * values smooth out fast scroll at the cost of mounting more DOM. Default
200
- * is 5; react-window uses 1 and react-virtualized uses 3. Pick a value
201
- * that suits the row's mount cost. */
202
196
  overscan?: number;
203
197
  rowElement?: TagName;
204
- className?: string;
205
- attributes?: ReadonlyArray<Attribute<ParentMessage>>;
198
+ containerClassName?: string;
199
+ containerAttributes?: ReadonlyArray<ChildAttribute>;
206
200
  }>;
207
201
  /** Renders a virtualized list. Only items inside the viewport (plus an
208
- * overscan buffer) are mounted; spacer elements above and below the slice
209
- * keep the scrollbar's apparent total height correct.
210
- *
211
- * Items must be keyed via `itemToKey` so the VDOM matches `row 150` to
212
- * `row 150` after the slice shifts during scroll, rather than matching by
213
- * position and producing stale DOM.
214
- *
215
- * Each row wrapper is rendered with `display: grid` so the consumer's
216
- * `itemToView` content fills the configured `rowHeightPx` and the full row
217
- * width. Use flex/grid with `align-items: center` inside `itemToView` to
218
- * vertically center content within the row.
202
+ * overscan buffer) are mounted; spacer elements above and below the
203
+ * slice keep the scrollbar's apparent total height correct.
219
204
  *
220
- * Each row carries `aria-setsize` (total item count) and `aria-posinset`
221
- * (1-based logical row index) so screen readers announce the full list
222
- * size and each row's position within it, rather than the much smaller
223
- * count of currently mounted rows. */
224
- export declare const view: <ParentMessage, Item>(config: ViewConfig<ParentMessage, Item>) => Html;
225
- /** Creates a memoized virtual list view. Static config is captured in a
226
- * closure; only `model` and `items` are compared per render via
227
- * `createLazy`. */
228
- export declare const lazy: <ParentMessage, Item>(staticConfig: Omit<ViewConfig<ParentMessage, Item>, "model" | "items">) => ((model: Model, items: ReadonlyArray<Item>) => Html);
205
+ * Generic over `Item`: call as `Ui.VirtualList.view<MyItem>()` at the
206
+ * embed site to get a `SubmodelView` typed for your item type. The
207
+ * underlying view implementation is shared; the call only narrows the
208
+ * type. */
209
+ type ViewForItem<Item> = SubmodelView<Model, Message, ViewInputs<Item>>;
210
+ export declare const view: <Item>() => ViewForItem<Item>;
211
+ export {};
229
212
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/index.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { Array, Effect, Match as M, Number, Option, Queue, Schema as S, Stream, pipe, } from 'effect';
2
2
  import * as Command from '../../command/index.js';
3
- import { createLazy, html, } from '../../html/index.js';
3
+ import { childAttributes, defineView, html, } from '../../html/index.js';
4
4
  import { m } from '../../message/index.js';
5
- import { makeSubscriptions } from '../../runtime/subscription.js';
5
+ import * as Subscription from '../../runtime/subscription.js';
6
6
  import { ts } from '../../schema/index.js';
7
7
  import { evo } from '../../struct/index.js';
8
8
  // MODEL
@@ -235,13 +235,6 @@ export const visibleWindowVariable = (model, items, itemToRowHeightPx, overscan)
235
235
  }));
236
236
  // SUBSCRIPTION
237
237
  const containerElement = (id) => Option.fromNullishOr(document.getElementById(id));
238
- /** Schema describing the subscription dependencies for container scroll and
239
- * resize tracking. */
240
- export const SubscriptionDependencies = S.Struct({
241
- containerEvents: S.Struct({
242
- id: S.String,
243
- }),
244
- });
245
238
  /** Subscriptions that track the container's scroll position and size.
246
239
  *
247
240
  * - **scroll**: listens for `scroll` events on the container element and
@@ -255,8 +248,8 @@ export const SubscriptionDependencies = S.Struct({
255
248
  * makes the subscription robust across SPA route changes: navigating to a
256
249
  * page that mounts the list, away, and back all reattach correctly without
257
250
  * the consumer having to teach the framework about navigation. */
258
- export const subscriptions = makeSubscriptions(SubscriptionDependencies)({
259
- containerEvents: {
251
+ export const subscriptions = Subscription.make()(entry => ({
252
+ containerEvents: entry({ id: S.String }, {
260
253
  modelToDependencies: model => ({ id: model.id }),
261
254
  dependenciesToStream: ({ id }) => Stream.callback(queue => Effect.acquireRelease(Effect.sync(() => {
262
255
  const state = {
@@ -335,31 +328,17 @@ export const subscriptions = makeSubscriptions(SubscriptionDependencies)({
335
328
  }
336
329
  detach();
337
330
  })).pipe(Effect.flatMap(() => Effect.never))),
338
- },
339
- });
331
+ }),
332
+ }));
340
333
  // VIEW
341
334
  const DEFAULT_OVERSCAN = 5;
342
- /** Renders a virtualized list. Only items inside the viewport (plus an
343
- * overscan buffer) are mounted; spacer elements above and below the slice
344
- * keep the scrollbar's apparent total height correct.
345
- *
346
- * Items must be keyed via `itemToKey` so the VDOM matches `row 150` to
347
- * `row 150` after the slice shifts during scroll, rather than matching by
348
- * position and producing stale DOM.
349
- *
350
- * Each row wrapper is rendered with `display: grid` so the consumer's
351
- * `itemToView` content fills the configured `rowHeightPx` and the full row
352
- * width. Use flex/grid with `align-items: center` inside `itemToView` to
353
- * vertically center content within the row.
354
- *
355
- * Each row carries `aria-setsize` (total item count) and `aria-posinset`
356
- * (1-based logical row index) so screen readers announce the full list
357
- * size and each row's position within it, rather than the much smaller
358
- * count of currently mounted rows. */
359
- export const view = (config) => {
335
+ export const view = () =>
336
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
337
+ viewImpl;
338
+ const viewImpl = defineView((model, viewInputs) => {
360
339
  const h = html();
361
- const { model, items, itemToKey, itemToView, itemToRowHeightPx, overscan = DEFAULT_OVERSCAN, rowElement = 'li', className, attributes = [], } = config;
362
- const containerAttributes = [
340
+ const { items, itemToKey, itemToView, itemToRowHeightPx, overscan = DEFAULT_OVERSCAN, rowElement = 'li', containerClassName, containerAttributes = [], } = viewInputs;
341
+ const baseContainerAttributes = [
363
342
  h.Id(model.id),
364
343
  h.Role('list'),
365
344
  h.DataAttribute('virtual-list-id', model.id),
@@ -369,10 +348,15 @@ export const view = (config) => {
369
348
  margin: '0',
370
349
  padding: '0',
371
350
  }),
372
- ...(className !== undefined ? [h.Class(className)] : []),
373
- ...attributes,
351
+ ...(containerClassName !== undefined
352
+ ? [h.Class(containerClassName)]
353
+ : []),
354
+ ];
355
+ const allContainerAttributes = [
356
+ ...childAttributes(baseContainerAttributes),
357
+ ...containerAttributes,
374
358
  ];
375
- const renderContainer = (children) => h.keyed('ul')(model.id, containerAttributes, children);
359
+ const renderContainer = (children) => h.keyed('ul')(model.id, allContainerAttributes, children);
376
360
  const maybeWindow = itemToRowHeightPx !== undefined
377
361
  ? visibleWindowVariable(model, items, itemToRowHeightPx, overscan)
378
362
  : visibleWindow(model, items.length, overscan);
@@ -381,7 +365,7 @@ export const view = (config) => {
381
365
  : model.rowHeightPx;
382
366
  return Option.match(maybeWindow, {
383
367
  onNone: () => renderContainer([]),
384
- onSome: ({ startIndex, endIndex, topSpacerHeight, bottomSpacerHeight }) => {
368
+ onSome: ({ startIndex, endIndex, topSpacerHeight, bottomSpacerHeight, }) => {
385
369
  const visibleItems = items.slice(startIndex, endIndex);
386
370
  const topSpacer = h.keyed('li')(`${model.id}-top-spacer`, [h.Role('presentation'), h.Style({ height: `${topSpacerHeight}px` })], []);
387
371
  const bottomSpacer = h.keyed('li')(`${model.id}-bottom-spacer`, [
@@ -404,15 +388,4 @@ export const view = (config) => {
404
388
  return renderContainer([topSpacer, ...renderedRows, bottomSpacer]);
405
389
  },
406
390
  });
407
- };
408
- /** Creates a memoized virtual list view. Static config is captured in a
409
- * closure; only `model` and `items` are compared per render via
410
- * `createLazy`. */
411
- export const lazy = (staticConfig) => {
412
- const lazyView = createLazy();
413
- return (model, items) => lazyView((currentModel, currentItems) => view({
414
- ...staticConfig,
415
- model: currentModel,
416
- items: currentItems,
417
- }), [model, items]);
418
- };
391
+ });
@@ -1,3 +1,3 @@
1
- export { init, update, scrollToIndex, scrollToIndexVariable, view, lazy, subscriptions, visibleWindow, visibleWindowVariable, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, SubscriptionDependencies, } from './index.js';
2
- export type { InitConfig, ViewConfig, VisibleWindow } from './index.js';
1
+ export { init, update, scrollToIndex, scrollToIndexVariable, view, subscriptions, visibleWindow, visibleWindowVariable, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, } from './index.js';
2
+ export type { InitConfig, ViewInputs, VisibleWindow } from './index.js';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,aAAa,EACb,qBAAqB,EACrB,IAAI,EACJ,IAAI,EACJ,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/ui/virtualList/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,aAAa,EACb,qBAAqB,EACrB,IAAI,EACJ,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
@@ -1 +1 @@
1
- export { init, update, scrollToIndex, scrollToIndexVariable, view, lazy, subscriptions, visibleWindow, visibleWindowVariable, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, SubscriptionDependencies, } from './index.js';
1
+ export { init, update, scrollToIndex, scrollToIndexVariable, view, subscriptions, visibleWindow, visibleWindowVariable, Model, Message, ScrolledContainer, MeasuredContainer, CompletedApplyScroll, } from './index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.100.1",
3
+ "version": "0.102.0",
4
4
  "description": "A TypeScript frontend framework, built on Effect and architected like Elm",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",