@foldkit/ui 0.112.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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/anchor.d.ts +38 -0
  4. package/dist/anchor.d.ts.map +1 -0
  5. package/dist/anchor.js +142 -0
  6. package/dist/animation/index.d.ts +49 -0
  7. package/dist/animation/index.d.ts.map +1 -0
  8. package/dist/animation/index.js +75 -0
  9. package/dist/animation/public.d.ts +3 -0
  10. package/dist/animation/public.d.ts.map +1 -0
  11. package/dist/animation/public.js +1 -0
  12. package/dist/animation/schema.d.ts +43 -0
  13. package/dist/animation/schema.d.ts.map +1 -0
  14. package/dist/animation/schema.js +41 -0
  15. package/dist/animation/update.d.ts +24 -0
  16. package/dist/animation/update.d.ts.map +1 -0
  17. package/dist/animation/update.js +67 -0
  18. package/dist/button/index.d.ts +17 -0
  19. package/dist/button/index.d.ts.map +1 -0
  20. package/dist/button/index.js +22 -0
  21. package/dist/button/public.d.ts +3 -0
  22. package/dist/button/public.d.ts.map +1 -0
  23. package/dist/button/public.js +1 -0
  24. package/dist/calendar/index.d.ts +462 -0
  25. package/dist/calendar/index.d.ts.map +1 -0
  26. package/dist/calendar/index.js +825 -0
  27. package/dist/calendar/public.d.ts +3 -0
  28. package/dist/calendar/public.d.ts.map +1 -0
  29. package/dist/calendar/public.js +1 -0
  30. package/dist/checkbox/index.d.ts +119 -0
  31. package/dist/checkbox/index.d.ts.map +1 -0
  32. package/dist/checkbox/index.js +111 -0
  33. package/dist/checkbox/public.d.ts +3 -0
  34. package/dist/checkbox/public.d.ts.map +1 -0
  35. package/dist/checkbox/public.js +1 -0
  36. package/dist/combobox/multi.d.ts +183 -0
  37. package/dist/combobox/multi.d.ts.map +1 -0
  38. package/dist/combobox/multi.js +81 -0
  39. package/dist/combobox/multiPublic.d.ts +3 -0
  40. package/dist/combobox/multiPublic.d.ts.map +1 -0
  41. package/dist/combobox/multiPublic.js +1 -0
  42. package/dist/combobox/public.d.ts +7 -0
  43. package/dist/combobox/public.d.ts.map +1 -0
  44. package/dist/combobox/public.js +3 -0
  45. package/dist/combobox/shared.d.ts +423 -0
  46. package/dist/combobox/shared.d.ts.map +1 -0
  47. package/dist/combobox/shared.js +708 -0
  48. package/dist/combobox/single.d.ts +198 -0
  49. package/dist/combobox/single.d.ts.map +1 -0
  50. package/dist/combobox/single.js +106 -0
  51. package/dist/datePicker/index.d.ts +457 -0
  52. package/dist/datePicker/index.d.ts.map +1 -0
  53. package/dist/datePicker/index.js +318 -0
  54. package/dist/datePicker/public.d.ts +3 -0
  55. package/dist/datePicker/public.d.ts.map +1 -0
  56. package/dist/datePicker/public.js +1 -0
  57. package/dist/dialog/index.d.ts +160 -0
  58. package/dist/dialog/index.d.ts.map +1 -0
  59. package/dist/dialog/index.js +211 -0
  60. package/dist/dialog/public.d.ts +3 -0
  61. package/dist/dialog/public.d.ts.map +1 -0
  62. package/dist/dialog/public.js +1 -0
  63. package/dist/disclosure/index.d.ts +110 -0
  64. package/dist/disclosure/index.d.ts.map +1 -0
  65. package/dist/disclosure/index.js +111 -0
  66. package/dist/disclosure/public.d.ts +3 -0
  67. package/dist/disclosure/public.d.ts.map +1 -0
  68. package/dist/disclosure/public.js +1 -0
  69. package/dist/dragAndDrop/index.d.ts +540 -0
  70. package/dist/dragAndDrop/index.d.ts.map +1 -0
  71. package/dist/dragAndDrop/index.js +535 -0
  72. package/dist/dragAndDrop/public.d.ts +3 -0
  73. package/dist/dragAndDrop/public.d.ts.map +1 -0
  74. package/dist/dragAndDrop/public.js +1 -0
  75. package/dist/fieldset/index.d.ts +21 -0
  76. package/dist/fieldset/index.d.ts.map +1 -0
  77. package/dist/fieldset/index.js +25 -0
  78. package/dist/fieldset/public.d.ts +3 -0
  79. package/dist/fieldset/public.d.ts.map +1 -0
  80. package/dist/fieldset/public.js +1 -0
  81. package/dist/fileDrop/index.d.ts +109 -0
  82. package/dist/fileDrop/index.d.ts.map +1 -0
  83. package/dist/fileDrop/index.js +127 -0
  84. package/dist/fileDrop/public.d.ts +3 -0
  85. package/dist/fileDrop/public.d.ts.map +1 -0
  86. package/dist/fileDrop/public.js +1 -0
  87. package/dist/group.d.ts +8 -0
  88. package/dist/group.d.ts.map +1 -0
  89. package/dist/group.js +13 -0
  90. package/dist/index.d.ts +25 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +24 -0
  93. package/dist/input/index.d.ts +26 -0
  94. package/dist/input/index.d.ts.map +1 -0
  95. package/dist/input/index.js +43 -0
  96. package/dist/input/public.d.ts +3 -0
  97. package/dist/input/public.d.ts.map +1 -0
  98. package/dist/input/public.js +1 -0
  99. package/dist/internal/optionExtensions.d.ts +6 -0
  100. package/dist/internal/optionExtensions.d.ts.map +1 -0
  101. package/dist/internal/optionExtensions.js +2 -0
  102. package/dist/keyboard.d.ts +6 -0
  103. package/dist/keyboard.d.ts.map +1 -0
  104. package/dist/keyboard.js +9 -0
  105. package/dist/listbox/multi.d.ts +189 -0
  106. package/dist/listbox/multi.d.ts.map +1 -0
  107. package/dist/listbox/multi.js +65 -0
  108. package/dist/listbox/multiPublic.d.ts +3 -0
  109. package/dist/listbox/multiPublic.d.ts.map +1 -0
  110. package/dist/listbox/multiPublic.js +1 -0
  111. package/dist/listbox/public.d.ts +7 -0
  112. package/dist/listbox/public.d.ts.map +1 -0
  113. package/dist/listbox/public.js +3 -0
  114. package/dist/listbox/shared.d.ts +432 -0
  115. package/dist/listbox/shared.d.ts.map +1 -0
  116. package/dist/listbox/shared.js +670 -0
  117. package/dist/listbox/single.d.ts +207 -0
  118. package/dist/listbox/single.d.ts.map +1 -0
  119. package/dist/listbox/single.js +73 -0
  120. package/dist/menu/index.d.ts +368 -0
  121. package/dist/menu/index.d.ts.map +1 -0
  122. package/dist/menu/index.js +682 -0
  123. package/dist/menu/public.d.ts +4 -0
  124. package/dist/menu/public.d.ts.map +1 -0
  125. package/dist/menu/public.js +1 -0
  126. package/dist/popover/index.d.ts +267 -0
  127. package/dist/popover/index.d.ts.map +1 -0
  128. package/dist/popover/index.js +346 -0
  129. package/dist/popover/public.d.ts +4 -0
  130. package/dist/popover/public.d.ts.map +1 -0
  131. package/dist/popover/public.js +1 -0
  132. package/dist/radioGroup/index.d.ts +169 -0
  133. package/dist/radioGroup/index.d.ts.map +1 -0
  134. package/dist/radioGroup/index.js +197 -0
  135. package/dist/radioGroup/public.d.ts +3 -0
  136. package/dist/radioGroup/public.d.ts.map +1 -0
  137. package/dist/radioGroup/public.js +1 -0
  138. package/dist/select/index.d.ts +24 -0
  139. package/dist/select/index.d.ts.map +1 -0
  140. package/dist/select/index.js +40 -0
  141. package/dist/select/public.d.ts +3 -0
  142. package/dist/select/public.d.ts.map +1 -0
  143. package/dist/select/public.js +1 -0
  144. package/dist/slider/index.d.ts +318 -0
  145. package/dist/slider/index.d.ts.map +1 -0
  146. package/dist/slider/index.js +337 -0
  147. package/dist/slider/public.d.ts +3 -0
  148. package/dist/slider/public.d.ts.map +1 -0
  149. package/dist/slider/public.js +1 -0
  150. package/dist/switch/index.d.ts +99 -0
  151. package/dist/switch/index.d.ts.map +1 -0
  152. package/dist/switch/index.js +107 -0
  153. package/dist/switch/public.d.ts +3 -0
  154. package/dist/switch/public.d.ts.map +1 -0
  155. package/dist/switch/public.js +1 -0
  156. package/dist/tabs/index.d.ts +155 -0
  157. package/dist/tabs/index.d.ts.map +1 -0
  158. package/dist/tabs/index.js +185 -0
  159. package/dist/tabs/public.d.ts +3 -0
  160. package/dist/tabs/public.d.ts.map +1 -0
  161. package/dist/tabs/public.js +1 -0
  162. package/dist/test/apps/disabledButton.d.ts +38 -0
  163. package/dist/test/apps/disabledButton.d.ts.map +1 -0
  164. package/dist/test/apps/disabledButton.js +71 -0
  165. package/dist/textarea/index.d.ts +26 -0
  166. package/dist/textarea/index.d.ts.map +1 -0
  167. package/dist/textarea/index.js +44 -0
  168. package/dist/textarea/public.d.ts +3 -0
  169. package/dist/textarea/public.d.ts.map +1 -0
  170. package/dist/textarea/public.js +1 -0
  171. package/dist/toast/index.d.ts +608 -0
  172. package/dist/toast/index.d.ts.map +1 -0
  173. package/dist/toast/index.js +146 -0
  174. package/dist/toast/public.d.ts +4 -0
  175. package/dist/toast/public.d.ts.map +1 -0
  176. package/dist/toast/public.js +1 -0
  177. package/dist/toast/schema.d.ts +154 -0
  178. package/dist/toast/schema.d.ts.map +1 -0
  179. package/dist/toast/schema.js +93 -0
  180. package/dist/toast/update.d.ts +510 -0
  181. package/dist/toast/update.d.ts.map +1 -0
  182. package/dist/toast/update.js +225 -0
  183. package/dist/tooltip/index.d.ts +170 -0
  184. package/dist/tooltip/index.d.ts.map +1 -0
  185. package/dist/tooltip/index.js +253 -0
  186. package/dist/tooltip/public.d.ts +4 -0
  187. package/dist/tooltip/public.d.ts.map +1 -0
  188. package/dist/tooltip/public.js +1 -0
  189. package/dist/typeahead.d.ts +4 -0
  190. package/dist/typeahead.d.ts.map +1 -0
  191. package/dist/typeahead.js +14 -0
  192. package/dist/virtualList/index.d.ts +203 -0
  193. package/dist/virtualList/index.d.ts.map +1 -0
  194. package/dist/virtualList/index.js +392 -0
  195. package/dist/virtualList/public.d.ts +3 -0
  196. package/dist/virtualList/public.d.ts.map +1 -0
  197. package/dist/virtualList/public.js +1 -0
  198. package/dist/vitest-setup.d.ts +2 -0
  199. package/dist/vitest-setup.d.ts.map +1 -0
  200. package/dist/vitest-setup.js +2 -0
  201. package/package.json +161 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Devin Jameson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @foldkit/ui
2
+
3
+ Headless, accessible UI components for [Foldkit](https://foldkit.dev).
4
+
5
+ Each component ships behavior, not markup: the ARIA attributes, keyboard navigation, focus management, and state machine. You provide the elements and the styling. Components follow The Elm Architecture, so they compose into a Foldkit app the same way the rest of your code does.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @foldkit/ui
11
+ # or
12
+ npm install @foldkit/ui
13
+ # or
14
+ yarn add @foldkit/ui
15
+ ```
16
+
17
+ `@foldkit/ui` lists `foldkit` and `effect` as peer dependencies, so install those alongside it.
18
+
19
+ ## Usage
20
+
21
+ Import a component by name. Each import is a namespace that groups the component's `view` and, for stateful components, its `Model`, `Message`, `init`, and `update`.
22
+
23
+ ```typescript
24
+ import { html } from 'foldkit/html'
25
+
26
+ import { Button } from '@foldkit/ui'
27
+
28
+ const view = () => {
29
+ const h = html<Message>()
30
+
31
+ return Button.view({
32
+ onClick: ClickedSave(), // your Message
33
+ toView: attributes =>
34
+ h.button(
35
+ [...attributes.button, h.Class('px-4 py-2 rounded-lg')],
36
+ ['Save'],
37
+ ),
38
+ })
39
+ }
40
+ ```
41
+
42
+ Components come in two shapes:
43
+
44
+ - **Render helpers** (Button, Input, Textarea, Select, Fieldset) are stateless. Call their `view` directly with a typed config.
45
+ - **Submodels** (Checkbox, Combobox, Dialog, Listbox, Menu, Popover, RadioGroup, and the like) own a Model. Embed them with `h.submodel`, drive them through their `update`, and consume their `OutMessage` in the parent.
46
+
47
+ Every component is also available as a subpath import:
48
+
49
+ ```typescript
50
+ import { Button } from '@foldkit/ui/button'
51
+ ```
52
+
53
+ When a component name collides with another import (for example core's `Calendar`), alias it:
54
+
55
+ ```typescript
56
+ import { Calendar as UiCalendar } from '@foldkit/ui'
57
+ ```
58
+
59
+ ## Components
60
+
61
+ Animation, Button, Calendar, Checkbox, Combobox, DatePicker, Dialog, Disclosure, DragAndDrop, Fieldset, FileDrop, Input, Listbox, Menu, Popover, RadioGroup, Select, Slider, Switch, Tabs, Textarea, Toast, Tooltip, and VirtualList.
62
+
63
+ See the [component documentation](https://foldkit.dev/ui/overview) for the full API and a live example of each.
64
+
65
+ ## License
66
+
67
+ MIT
@@ -0,0 +1,38 @@
1
+ import { Schema as S } from 'effect';
2
+ /** Schema mirroring `@floating-ui/dom`'s `Placement` literal union: a side
3
+ * (`top`/`right`/`bottom`/`left`) optionally suffixed with `-start` or `-end`. */
4
+ export declare const Placement: S.Literals<readonly ["top", "right", "bottom", "left", "top-start", "top-end", "right-start", "right-end", "bottom-start", "bottom-end", "left-start", "left-end"]>;
5
+ /** Static configuration for anchor-based positioning of a floating element relative to a button. */
6
+ export declare const AnchorConfig: S.Struct<{
7
+ readonly placement: S.optional<S.Literals<readonly ["top", "right", "bottom", "left", "top-start", "top-end", "right-start", "right-end", "bottom-start", "bottom-end", "left-start", "left-end"]>>;
8
+ readonly gap: S.optional<S.Number>;
9
+ readonly offset: S.optional<S.Number>;
10
+ readonly padding: S.optional<S.Number>;
11
+ readonly portal: S.optional<S.Boolean>;
12
+ }>;
13
+ export type AnchorConfig = typeof AnchorConfig.Type;
14
+ /** Relocates an element into the shared `foldkit-portal-root` div appended to
15
+ * `document.body`, escaping any ancestor stacking context. Returns a cleanup
16
+ * function that removes the element from the portal root. Designed to be
17
+ * called from inside an `OnMount` action: the consumer wraps the call in
18
+ * `Effect.sync` and stashes the returned cleanup in the `Mount` result. */
19
+ export declare const portalToBody: (element: Element) => (() => void);
20
+ /** Positions a floating element relative to its button using Floating UI, then
21
+ * returns a cleanup function. Designed to be called inside an `OnMount`
22
+ * action: the consumer wraps the call in `Effect.sync` and stashes the
23
+ * returned cleanup in the `Mount` result. When `interceptTab` is true
24
+ * (default), Tab key in portal mode refocuses the button — set to false for
25
+ * components like Popover where Tab should navigate naturally within the
26
+ * panel. When `focusAfterPosition` is true, the element is focused after the
27
+ * first position computation clears visibility — deferred via
28
+ * requestAnimationFrame so the element is painted before focus fires.
29
+ * `focusSelector` optionally targets a descendant (e.g. a calendar grid
30
+ * inside a popover panel) instead of the panel itself. */
31
+ export declare const anchorSetup: (config: {
32
+ buttonId: string;
33
+ anchor: AnchorConfig;
34
+ interceptTab?: boolean;
35
+ focusAfterPosition?: boolean;
36
+ focusSelector?: string;
37
+ }) => (element: Element) => (() => void);
38
+ //# sourceMappingURL=anchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../src/anchor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAW9C;mFACmF;AACnF,eAAO,MAAM,SAAS,qKAapB,CAAA;AAEF,oGAAoG;AACpG,eAAO,MAAM,YAAY;;;;;;EAMvB,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAwBnD;;;;4EAI4E;AAC5E,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,CAAC,MAAM,IAAI,CAW1D,CAAA;AAED;;;;;;;;;;2DAU2D;AAC3D,eAAO,MAAM,WAAW,GACrB,QAAQ;IACP,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,YAAY,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,MACA,SAAS,OAAO,KAAG,CAAC,MAAM,IAAI,CAiF9B,CAAA"}
package/dist/anchor.js ADDED
@@ -0,0 +1,142 @@
1
+ import { Function, Schema as S } from 'effect';
2
+ import { autoUpdate, computePosition, flip, offset as floatingOffset, shift, size, } from '@floating-ui/dom';
3
+ /** Schema mirroring `@floating-ui/dom`'s `Placement` literal union: a side
4
+ * (`top`/`right`/`bottom`/`left`) optionally suffixed with `-start` or `-end`. */
5
+ export const Placement = S.Literals([
6
+ 'top',
7
+ 'right',
8
+ 'bottom',
9
+ 'left',
10
+ 'top-start',
11
+ 'top-end',
12
+ 'right-start',
13
+ 'right-end',
14
+ 'bottom-start',
15
+ 'bottom-end',
16
+ 'left-start',
17
+ 'left-end',
18
+ ]);
19
+ /** Static configuration for anchor-based positioning of a floating element relative to a button. */
20
+ export const AnchorConfig = S.Struct({
21
+ placement: S.optional(Placement),
22
+ gap: S.optional(S.Number),
23
+ offset: S.optional(S.Number),
24
+ padding: S.optional(S.Number),
25
+ portal: S.optional(S.Boolean),
26
+ });
27
+ const PORTAL_ROOT_ID = 'foldkit-portal-root';
28
+ const getOrCreatePortalRoot = () => {
29
+ const existing = document.getElementById(PORTAL_ROOT_ID);
30
+ if (existing) {
31
+ return existing;
32
+ }
33
+ const root = document.createElement('div');
34
+ root.id = PORTAL_ROOT_ID;
35
+ // NOTE: prepended (not appended) so portaled overlays sit BEFORE the page's
36
+ // listbox/popover/menu wrappers in tree order. Those wrappers are
37
+ // `position: relative; z-index: auto` and paint at CSS step 8 in tree order;
38
+ // a backdrop appended after them would paint on top of every button on the
39
+ // page, breaking click-outside detection. Prepending makes wrappers paint
40
+ // above the backdrop, while panels (z-10) still win via step 9.
41
+ document.body.insertBefore(root, document.body.firstChild);
42
+ return root;
43
+ };
44
+ /** Relocates an element into the shared `foldkit-portal-root` div appended to
45
+ * `document.body`, escaping any ancestor stacking context. Returns a cleanup
46
+ * function that removes the element from the portal root. Designed to be
47
+ * called from inside an `OnMount` action: the consumer wraps the call in
48
+ * `Effect.sync` and stashes the returned cleanup in the `Mount` result. */
49
+ export const portalToBody = (element) => {
50
+ getOrCreatePortalRoot().appendChild(element);
51
+ return () => {
52
+ try {
53
+ element.remove();
54
+ }
55
+ catch {
56
+ // NOTE: a re-render may unmount the element before this cleanup fires,
57
+ // so the remove() call can throw on a node that's already been removed.
58
+ // Swallow the error.
59
+ }
60
+ };
61
+ };
62
+ /** Positions a floating element relative to its button using Floating UI, then
63
+ * returns a cleanup function. Designed to be called inside an `OnMount`
64
+ * action: the consumer wraps the call in `Effect.sync` and stashes the
65
+ * returned cleanup in the `Mount` result. When `interceptTab` is true
66
+ * (default), Tab key in portal mode refocuses the button — set to false for
67
+ * components like Popover where Tab should navigate naturally within the
68
+ * panel. When `focusAfterPosition` is true, the element is focused after the
69
+ * first position computation clears visibility — deferred via
70
+ * requestAnimationFrame so the element is painted before focus fires.
71
+ * `focusSelector` optionally targets a descendant (e.g. a calendar grid
72
+ * inside a popover panel) instead of the panel itself. */
73
+ export const anchorSetup = (config) => (element) => {
74
+ const button = document.getElementById(config.buttonId);
75
+ if (!(button instanceof HTMLElement) || !(element instanceof HTMLElement)) {
76
+ return Function.constVoid;
77
+ }
78
+ const isPortal = config.anchor.portal ?? true;
79
+ const portalCleanup = isPortal ? portalToBody(element) : undefined;
80
+ const { placement, gap, offset: crossAxis, padding } = config.anchor;
81
+ const shouldInterceptTab = config.interceptTab ?? true;
82
+ let isFirstUpdate = true;
83
+ const floatingCleanup = autoUpdate(button, element, () => {
84
+ computePosition(button, element, {
85
+ placement: placement ?? 'bottom-start',
86
+ strategy: 'absolute',
87
+ middleware: [
88
+ floatingOffset({
89
+ mainAxis: gap ?? 0,
90
+ crossAxis: crossAxis ?? 0,
91
+ }),
92
+ flip({ padding: padding ?? 0 }),
93
+ shift({ padding: padding ?? 0 }),
94
+ size({
95
+ padding: padding ?? 0,
96
+ apply({ rects, availableHeight }) {
97
+ element.style.setProperty('--button-width', `${rects.reference.width}px`);
98
+ element.style.maxHeight = `${availableHeight}px`;
99
+ element.style.overflowY = 'auto';
100
+ element.style.overscrollBehavior = 'none';
101
+ },
102
+ }),
103
+ ],
104
+ }).then(({ x, y }) => {
105
+ element.style.left = `${x}px`;
106
+ element.style.top = `${y}px`;
107
+ if (isFirstUpdate) {
108
+ isFirstUpdate = false;
109
+ element.style.visibility = '';
110
+ if (config.focusAfterPosition ?? false) {
111
+ requestAnimationFrame(() => {
112
+ const target = config.focusSelector
113
+ ? document.querySelector(config.focusSelector)
114
+ : element;
115
+ if (target instanceof HTMLElement) {
116
+ target.focus();
117
+ }
118
+ });
119
+ }
120
+ }
121
+ });
122
+ });
123
+ if (isPortal && shouldInterceptTab) {
124
+ const handleTabKey = (event) => {
125
+ if (event instanceof KeyboardEvent && event.key === 'Tab') {
126
+ button.focus();
127
+ }
128
+ };
129
+ element.addEventListener('keydown', handleTabKey);
130
+ return () => {
131
+ floatingCleanup();
132
+ element.removeEventListener('keydown', handleTabKey);
133
+ portalCleanup?.();
134
+ };
135
+ }
136
+ else {
137
+ return () => {
138
+ floatingCleanup();
139
+ portalCleanup?.();
140
+ };
141
+ }
142
+ };
@@ -0,0 +1,49 @@
1
+ import { type ChildAttribute, type Html, type TagName } from 'foldkit/html';
2
+ import { AdvancedAnimationFrame, EndedAnimation, Hid, Message, Model, OutMessage, Showed, StartedLeaveAnimating, TransitionState, TransitionedOut, init } from './schema.js';
3
+ import { RequestFrame, WaitForAnimationSettled, defaultLeaveCommand, update } from './update.js';
4
+ export type { InitConfig } from './schema.js';
5
+ export { AdvancedAnimationFrame, EndedAnimation, Hid, init, Message, Model, OutMessage, Showed, StartedLeaveAnimating, TransitionState, TransitionedOut, };
6
+ export { RequestFrame, WaitForAnimationSettled, defaultLeaveCommand, update };
7
+ /** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field. */
8
+ export type ViewInputs = Readonly<{
9
+ content: Html;
10
+ className?: string;
11
+ attributes?: ReadonlyArray<ChildAttribute>;
12
+ element?: TagName;
13
+ /** When true, wraps content in a CSS grid container that smoothly animates
14
+ * height via `grid-template-rows: 0fr → 1fr`. The element stays in the DOM
15
+ * when hidden (collapsed to zero height) instead of being removed. */
16
+ animateSize?: boolean;
17
+ }>;
18
+ /** Renders a headless animation wrapper that coordinates CSS transitions and
19
+ * CSS keyframe animations via data attributes.
20
+ *
21
+ * Data attributes reflect the current lifecycle phase:
22
+ * - `data-closed`: element is in its hidden/initial state
23
+ * - `data-enter`: enter animation is active
24
+ * - `data-leave`: leave animation is active
25
+ * - `data-transition`: any animation is active
26
+ */
27
+ export declare const view: import("foldkit/submodel").View<{
28
+ readonly id: string;
29
+ readonly isShowing: boolean;
30
+ readonly transitionState: "Idle" | "EnterStart" | "EnterAnimating" | "LeaveStart" | "LeaveAnimating";
31
+ }, {
32
+ readonly _tag: "Showed";
33
+ } | {
34
+ readonly _tag: "Hid";
35
+ } | {
36
+ readonly _tag: "AdvancedAnimationFrame";
37
+ } | {
38
+ readonly _tag: "EndedAnimation";
39
+ }, Readonly<{
40
+ content: Html;
41
+ className?: string;
42
+ attributes?: ReadonlyArray<ChildAttribute>;
43
+ element?: TagName;
44
+ /** When true, wraps content in a CSS grid container that smoothly animates
45
+ * height via `grid-template-rows: 0fr → 1fr`. The element stays in the DOM
46
+ * when hidden (collapsed to zero height) instead of being removed. */
47
+ animateSize?: boolean;
48
+ }>>;
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/animation/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,OAAO,EAEb,MAAM,cAAc,CAAA;AAGrB,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,GAAG,EACH,OAAO,EACP,KAAK,EACL,UAAU,EACV,MAAM,EACN,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,IAAI,EACL,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,mBAAmB,EACnB,MAAM,EACP,MAAM,aAAa,CAAA;AAEpB,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,GAAG,EACH,IAAI,EACJ,OAAO,EACP,KAAK,EACL,UAAU,EACV,MAAM,EACN,qBAAqB,EACrB,eAAe,EACf,eAAe,GAChB,CAAA;AAED,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,EAAE,CAAA;AAI7E,qFAAqF;AACrF,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,OAAO,EAAE,IAAI,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;2EAEuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI;;;;;;;;;;;;;aAnBN,IAAI;gBACD,MAAM;iBACL,aAAa,CAAC,cAAc,CAAC;cAChC,OAAO;IACjB;;2EAEuE;kBACzD,OAAO;GA6GtB,CAAA"}
@@ -0,0 +1,75 @@
1
+ import { Match as M } from 'effect';
2
+ import { html, } from 'foldkit/html';
3
+ import { defineView } from 'foldkit/submodel';
4
+ import { AdvancedAnimationFrame, EndedAnimation, Hid, Message, Model, OutMessage, Showed, StartedLeaveAnimating, TransitionState, TransitionedOut, init, } from './schema.js';
5
+ import { RequestFrame, WaitForAnimationSettled, defaultLeaveCommand, update, } from './update.js';
6
+ export { AdvancedAnimationFrame, EndedAnimation, Hid, init, Message, Model, OutMessage, Showed, StartedLeaveAnimating, TransitionState, TransitionedOut, };
7
+ export { RequestFrame, WaitForAnimationSettled, defaultLeaveCommand, update };
8
+ /** Renders a headless animation wrapper that coordinates CSS transitions and
9
+ * CSS keyframe animations via data attributes.
10
+ *
11
+ * Data attributes reflect the current lifecycle phase:
12
+ * - `data-closed`: element is in its hidden/initial state
13
+ * - `data-enter`: enter animation is active
14
+ * - `data-leave`: leave animation is active
15
+ * - `data-transition`: any animation is active
16
+ */
17
+ export const view = defineView((model, viewInputs) => {
18
+ const h = html();
19
+ const { id, isShowing, transitionState } = model;
20
+ const { content, className, attributes = [], element = 'div', animateSize = false, } = viewInputs;
21
+ const isLeaving = transitionState === 'LeaveStart' || transitionState === 'LeaveAnimating';
22
+ const isVisible = isShowing || isLeaving;
23
+ const transitionAttributes = M.value(transitionState).pipe(M.when('EnterStart', () => [
24
+ h.DataAttribute('closed', ''),
25
+ h.DataAttribute('enter', ''),
26
+ h.DataAttribute('transition', ''),
27
+ ]), M.when('EnterAnimating', () => [
28
+ h.DataAttribute('enter', ''),
29
+ h.DataAttribute('transition', ''),
30
+ ]), M.when('LeaveStart', () => [
31
+ h.DataAttribute('leave', ''),
32
+ h.DataAttribute('transition', ''),
33
+ ]), M.when('LeaveAnimating', () => [
34
+ h.DataAttribute('closed', ''),
35
+ h.DataAttribute('leave', ''),
36
+ h.DataAttribute('transition', ''),
37
+ ]), M.orElse(() => []));
38
+ if (animateSize) {
39
+ const isClosed = transitionState === 'EnterStart' ||
40
+ transitionState === 'LeaveAnimating' ||
41
+ !isVisible;
42
+ return h.div([
43
+ h.Style({
44
+ display: 'grid',
45
+ gridTemplateRows: isClosed ? '0fr' : '1fr',
46
+ transition: 'grid-template-rows 200ms ease-out',
47
+ overflow: 'hidden',
48
+ }),
49
+ ], [
50
+ h.div([
51
+ h.Style({ minHeight: '0px', overflow: 'hidden' }),
52
+ ...(!isVisible ? [h.AriaHidden(true)] : []),
53
+ ], [
54
+ h.keyed(element)(id, [
55
+ h.Id(id),
56
+ ...(isClosed && transitionState === 'Idle'
57
+ ? [h.DataAttribute('closed', '')]
58
+ : []),
59
+ ...transitionAttributes,
60
+ ...(className ? [h.Class(className)] : []),
61
+ ...attributes,
62
+ ], [content]),
63
+ ]),
64
+ ]);
65
+ }
66
+ if (!isVisible) {
67
+ return h.empty;
68
+ }
69
+ return h.keyed(element)(id, [
70
+ h.Id(id),
71
+ ...transitionAttributes,
72
+ ...(className ? [h.Class(className)] : []),
73
+ ...attributes,
74
+ ], [content]);
75
+ });
@@ -0,0 +1,3 @@
1
+ export { init, update, view, defaultLeaveCommand, Model, Message, OutMessage, Showed, Hid, AdvancedAnimationFrame, EndedAnimation, StartedLeaveAnimating, TransitionedOut, TransitionState, RequestFrame, WaitForAnimationSettled, } from './index.js';
2
+ export type { InitConfig, ViewInputs } from './index.js';
3
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/animation/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,IAAI,EACJ,mBAAmB,EACnB,KAAK,EACL,OAAO,EACP,UAAU,EACV,MAAM,EACN,GAAG,EACH,sBAAsB,EACtB,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,uBAAuB,GACxB,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ export { init, update, view, defaultLeaveCommand, Model, Message, OutMessage, Showed, Hid, AdvancedAnimationFrame, EndedAnimation, StartedLeaveAnimating, TransitionedOut, TransitionState, RequestFrame, WaitForAnimationSettled, } from './index.js';
@@ -0,0 +1,43 @@
1
+ import { Schema as S } from 'effect';
2
+ /** Schema for the animation lifecycle state, tracking enter/leave phases. */
3
+ export declare const TransitionState: S.Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>;
4
+ export type TransitionState = typeof TransitionState.Type;
5
+ /** Schema for the animation component's state, tracking its unique ID, visibility intent, and lifecycle phase. */
6
+ export declare const Model: S.Struct<{
7
+ readonly id: S.String;
8
+ readonly isShowing: S.Boolean;
9
+ readonly transitionState: S.Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>;
10
+ }>;
11
+ export type Model = typeof Model.Type;
12
+ /** Sent when the animation should enter (become visible). Starts the enter sequence. */
13
+ export declare const Showed: import("foldkit/schema").CallableTaggedStruct<"Showed", {}>;
14
+ /** Sent when the animation should leave (become hidden). Starts the leave sequence. */
15
+ export declare const Hid: import("foldkit/schema").CallableTaggedStruct<"Hid", {}>;
16
+ /** Sent internally when a double-rAF completes, advancing the lifecycle to its animating phase. */
17
+ export declare const AdvancedAnimationFrame: import("foldkit/schema").CallableTaggedStruct<"AdvancedAnimationFrame", {}>;
18
+ /** Sent internally when all CSS animations on the element have settled. Covers both CSS transitions and CSS keyframe animations. */
19
+ export declare const EndedAnimation: import("foldkit/schema").CallableTaggedStruct<"EndedAnimation", {}>;
20
+ /** Union of all messages the animation component can produce. */
21
+ export declare const Message: S.Union<[
22
+ typeof Showed,
23
+ typeof Hid,
24
+ typeof AdvancedAnimationFrame,
25
+ typeof EndedAnimation
26
+ ]>;
27
+ export type Message = typeof Message.Type;
28
+ export type Showed = typeof Showed.Type;
29
+ export type Hid = typeof Hid.Type;
30
+ /** Sent to the parent when the leave sequence advances to LeaveAnimating. The parent is responsible for providing the command that detects when the leave animation completes (e.g. WaitForAnimationSettled or a racing command). Use `defaultLeaveCommand` for the standard behavior. */
31
+ export declare const StartedLeaveAnimating: import("foldkit/schema").CallableTaggedStruct<"StartedLeaveAnimating", {}>;
32
+ /** Sent to the parent when the leave animation completes. The parent can use this to unmount content or update its own state. */
33
+ export declare const TransitionedOut: import("foldkit/schema").CallableTaggedStruct<"TransitionedOut", {}>;
34
+ export declare const OutMessage: S.Union<readonly [import("foldkit/schema").CallableTaggedStruct<"StartedLeaveAnimating", {}>, import("foldkit/schema").CallableTaggedStruct<"TransitionedOut", {}>]>;
35
+ export type OutMessage = typeof OutMessage.Type;
36
+ /** Configuration for creating an animation model with `init`. */
37
+ export type InitConfig = Readonly<{
38
+ id: string;
39
+ isShowing?: boolean;
40
+ }>;
41
+ /** Creates an initial animation model from a config. Defaults to hidden. */
42
+ export declare const init: (config: InitConfig) => Model;
43
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/animation/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAKpC,6EAA6E;AAC7E,eAAO,MAAM,eAAe,+FAM1B,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAA;AAIzD,kHAAkH;AAClH,eAAO,MAAM,KAAK;;;;EAIhB,CAAA;AAEF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,wFAAwF;AACxF,eAAO,MAAM,MAAM,6DAAc,CAAA;AACjC,uFAAuF;AACvF,eAAO,MAAM,GAAG,0DAAW,CAAA;AAC3B,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,6EAA8B,CAAA;AACjE,oIAAoI;AACpI,eAAO,MAAM,cAAc,qEAAsB,CAAA;AAEjD,iEAAiE;AACjE,eAAO,MAAM,OAAO,EAAE,CAAC,CAAC,KAAK,CAC3B;IACE,OAAO,MAAM;IACb,OAAO,GAAG;IACV,OAAO,sBAAsB;IAC7B,OAAO,cAAc;CACtB,CAC+D,CAAA;AAClE,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AACvC,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,IAAI,CAAA;AAIjC,0RAA0R;AAC1R,eAAO,MAAM,qBAAqB,4EAA6B,CAAA;AAC/D,iIAAiI;AACjI,eAAO,MAAM,eAAe,sEAAuB,CAAA;AAEnD,eAAO,MAAM,UAAU,sKAAoD,CAAA;AAC3E,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AAI/C,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAC,CAAA;AAEF,4EAA4E;AAC5E,eAAO,MAAM,IAAI,GAAI,QAAQ,UAAU,KAAG,KAIxC,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { Schema as S } from 'effect';
2
+ import { m } from 'foldkit/message';
3
+ // TRANSITION STATE
4
+ /** Schema for the animation lifecycle state, tracking enter/leave phases. */
5
+ export const TransitionState = S.Literals([
6
+ 'Idle',
7
+ 'EnterStart',
8
+ 'EnterAnimating',
9
+ 'LeaveStart',
10
+ 'LeaveAnimating',
11
+ ]);
12
+ // MODEL
13
+ /** Schema for the animation component's state, tracking its unique ID, visibility intent, and lifecycle phase. */
14
+ export const Model = S.Struct({
15
+ id: S.String,
16
+ isShowing: S.Boolean,
17
+ transitionState: TransitionState,
18
+ });
19
+ // MESSAGE
20
+ /** Sent when the animation should enter (become visible). Starts the enter sequence. */
21
+ export const Showed = m('Showed');
22
+ /** Sent when the animation should leave (become hidden). Starts the leave sequence. */
23
+ export const Hid = m('Hid');
24
+ /** Sent internally when a double-rAF completes, advancing the lifecycle to its animating phase. */
25
+ export const AdvancedAnimationFrame = m('AdvancedAnimationFrame');
26
+ /** Sent internally when all CSS animations on the element have settled. Covers both CSS transitions and CSS keyframe animations. */
27
+ export const EndedAnimation = m('EndedAnimation');
28
+ /** Union of all messages the animation component can produce. */
29
+ export const Message = S.Union([Showed, Hid, AdvancedAnimationFrame, EndedAnimation]);
30
+ // OUT MESSAGE
31
+ /** Sent to the parent when the leave sequence advances to LeaveAnimating. The parent is responsible for providing the command that detects when the leave animation completes (e.g. WaitForAnimationSettled or a racing command). Use `defaultLeaveCommand` for the standard behavior. */
32
+ export const StartedLeaveAnimating = m('StartedLeaveAnimating');
33
+ /** Sent to the parent when the leave animation completes. The parent can use this to unmount content or update its own state. */
34
+ export const TransitionedOut = m('TransitionedOut');
35
+ export const OutMessage = S.Union([StartedLeaveAnimating, TransitionedOut]);
36
+ /** Creates an initial animation model from a config. Defaults to hidden. */
37
+ export const init = (config) => ({
38
+ id: config.id,
39
+ isShowing: config.isShowing ?? false,
40
+ transitionState: 'Idle',
41
+ });
@@ -0,0 +1,24 @@
1
+ import { Effect, Option, Schema as S } from 'effect';
2
+ import * as Command from 'foldkit/command';
3
+ import { type Message, type Model, type OutMessage } from './schema.js';
4
+ type UpdateReturn = readonly [
5
+ Model,
6
+ ReadonlyArray<Command.Command<Message>>,
7
+ Option.Option<OutMessage>
8
+ ];
9
+ /** Advances the enter/leave lifecycle by waiting a double-rAF. */
10
+ export declare const RequestFrame: Command.CommandDefinitionNoArgs<"RequestFrame", Effect.Effect<{
11
+ readonly _tag: "AdvancedAnimationFrame";
12
+ }, never, never>>;
13
+ /** Waits for all CSS animations on the element to settle. Covers both CSS transitions and CSS keyframe animations. */
14
+ export declare const WaitForAnimationSettled: Command.CommandDefinitionWithArgs<"WaitForAnimationSettled", {
15
+ id: S.String;
16
+ }, Effect.Effect<{
17
+ readonly _tag: "EndedAnimation";
18
+ }, never, never>>;
19
+ /** Processes an animation message and returns the next model, commands, and optional OutMessage. */
20
+ export declare const update: (model: Model, message: Message) => UpdateReturn;
21
+ /** Creates the standard leave-phase command that waits for CSS animations on the element to settle. Use this when handling the `StartedLeaveAnimating` OutMessage for components that don't need custom leave behavior. */
22
+ export declare const defaultLeaveCommand: (model: Model) => Command.Command<Message>;
23
+ export {};
24
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/animation/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAc,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAChE,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAK1C,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,KAAK,EACV,KAAK,UAAU,EAGhB,MAAM,aAAa,CAAA;AAMpB,KAAK,YAAY,GAAG,SAAS;IAC3B,KAAK;IACL,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;CAC1B,CAAA;AAGD,kEAAkE;AAClE,eAAO,MAAM,YAAY;;iBAGqC,CAAA;AAC9D,sHAAsH;AACtH,eAAO,MAAM,uBAAuB;;;;iBAQnC,CAAA;AAED,oGAAoG;AACpG,eAAO,MAAM,MAAM,GAAI,OAAO,KAAK,EAAE,SAAS,OAAO,KAAG,YAyEvD,CAAA;AAED,2NAA2N;AAC3N,eAAO,MAAM,mBAAmB,GAAI,OAAO,KAAK,KAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAC/B,CAAA"}
@@ -0,0 +1,67 @@
1
+ import { Effect, Match as M, Option, Schema as S } from 'effect';
2
+ import * as Command from 'foldkit/command';
3
+ import * as Dom from 'foldkit/dom';
4
+ import * as Render from 'foldkit/render';
5
+ import { evo } from 'foldkit/struct';
6
+ import { AdvancedAnimationFrame, EndedAnimation, StartedLeaveAnimating, TransitionedOut, } from './schema.js';
7
+ // UPDATE
8
+ const elementSelector = (id) => `#${id}`;
9
+ const withUpdateReturn = M.withReturnType();
10
+ /** Advances the enter/leave lifecycle by waiting a double-rAF. */
11
+ export const RequestFrame = Command.define('RequestFrame', AdvancedAnimationFrame)(Render.afterPaint.pipe(Effect.as(AdvancedAnimationFrame())));
12
+ /** Waits for all CSS animations on the element to settle. Covers both CSS transitions and CSS keyframe animations. */
13
+ export const WaitForAnimationSettled = Command.define('WaitForAnimationSettled', { id: S.String }, EndedAnimation)(({ id }) => Dom.waitForAnimationSettled(elementSelector(id)).pipe(Effect.as(EndedAnimation())));
14
+ /** Processes an animation message and returns the next model, commands, and optional OutMessage. */
15
+ export const update = (model, message) => {
16
+ const maybeNextFrame = RequestFrame();
17
+ return M.value(message).pipe(withUpdateReturn, M.tagsExhaustive({
18
+ Showed: () => {
19
+ if (model.isShowing) {
20
+ return [model, [], Option.none()];
21
+ }
22
+ return [
23
+ evo(model, {
24
+ isShowing: () => true,
25
+ transitionState: () => 'EnterStart',
26
+ }),
27
+ [maybeNextFrame],
28
+ Option.none(),
29
+ ];
30
+ },
31
+ Hid: () => {
32
+ const isLeaving = model.transitionState === 'LeaveStart' ||
33
+ model.transitionState === 'LeaveAnimating';
34
+ if (isLeaving || !model.isShowing) {
35
+ return [model, [], Option.none()];
36
+ }
37
+ return [
38
+ evo(model, {
39
+ isShowing: () => false,
40
+ transitionState: () => 'LeaveStart',
41
+ }),
42
+ [maybeNextFrame],
43
+ Option.none(),
44
+ ];
45
+ },
46
+ AdvancedAnimationFrame: () => M.value(model.transitionState).pipe(withUpdateReturn, M.when('EnterStart', () => [
47
+ evo(model, { transitionState: () => 'EnterAnimating' }),
48
+ [WaitForAnimationSettled({ id: model.id })],
49
+ Option.none(),
50
+ ]), M.when('LeaveStart', () => [
51
+ evo(model, { transitionState: () => 'LeaveAnimating' }),
52
+ [],
53
+ Option.some(StartedLeaveAnimating()),
54
+ ]), M.orElse(() => [model, [], Option.none()])),
55
+ EndedAnimation: () => M.value(model.transitionState).pipe(withUpdateReturn, M.when('EnterAnimating', () => [
56
+ evo(model, { transitionState: () => 'Idle' }),
57
+ [],
58
+ Option.none(),
59
+ ]), M.when('LeaveAnimating', () => [
60
+ evo(model, { transitionState: () => 'Idle' }),
61
+ [],
62
+ Option.some(TransitionedOut()),
63
+ ]), M.orElse(() => [model, [], Option.none()])),
64
+ }));
65
+ };
66
+ /** Creates the standard leave-phase command that waits for CSS animations on the element to settle. Use this when handling the `StartedLeaveAnimating` OutMessage for components that don't need custom leave behavior. */
67
+ export const defaultLeaveCommand = (model) => WaitForAnimationSettled({ id: model.id });
@@ -0,0 +1,17 @@
1
+ import type { Attribute } from 'foldkit/html';
2
+ import type { Html } from 'foldkit/html';
3
+ /** Attribute groups the button component provides to the consumer's `toView` callback. */
4
+ export type ButtonAttributes<ParentMessage> = Readonly<{
5
+ button: ReadonlyArray<Attribute<ParentMessage>>;
6
+ }>;
7
+ /** Configuration for rendering a button with `view`. */
8
+ export type ViewConfig<ParentMessage> = Readonly<{
9
+ toView: (attributes: ButtonAttributes<ParentMessage>) => Html;
10
+ onClick?: ParentMessage;
11
+ isDisabled?: boolean;
12
+ type?: 'button' | 'submit' | 'reset';
13
+ isAutofocus?: boolean;
14
+ }>;
15
+ /** Renders an accessible button by building attribute groups and delegating layout to the consumer's `toView` callback. */
16
+ export declare const view: <ParentMessage>(config: ViewConfig<ParentMessage>) => Html;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/button/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAIxC,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,CAAC,aAAa,IAAI,QAAQ,CAAC;IACrD,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAA;CAChD,CAAC,CAAA;AAEF,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,aAAa,IAAI,QAAQ,CAAC;IAC/C,MAAM,EAAE,CAAC,UAAU,EAAE,gBAAgB,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IAC7D,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;IACpC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAC,CAAA;AAEF,2HAA2H;AAC3H,eAAO,MAAM,IAAI,GAAI,aAAa,EAChC,QAAQ,UAAU,CAAC,aAAa,CAAC,KAChC,IA+BF,CAAA"}