@typed/ui 1.0.0-beta.3 → 1.0.0-beta.5

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 (157) hide show
  1. package/README.md +53 -9
  2. package/dist/Alert.d.ts +78 -0
  3. package/dist/Alert.d.ts.map +1 -0
  4. package/dist/Alert.js +37 -0
  5. package/dist/Button.d.ts +144 -0
  6. package/dist/Button.d.ts.map +1 -0
  7. package/dist/Button.js +38 -0
  8. package/dist/Carousel.d.ts +547 -0
  9. package/dist/Carousel.d.ts.map +1 -0
  10. package/dist/Carousel.js +387 -0
  11. package/dist/Checkbox.d.ts +203 -0
  12. package/dist/Checkbox.d.ts.map +1 -0
  13. package/dist/Checkbox.js +140 -0
  14. package/dist/Collection.d.ts +254 -0
  15. package/dist/Collection.d.ts.map +1 -0
  16. package/dist/Collection.js +218 -0
  17. package/dist/Combobox.d.ts +516 -0
  18. package/dist/Combobox.d.ts.map +1 -0
  19. package/dist/Combobox.js +371 -0
  20. package/dist/Component.d.ts +127 -0
  21. package/dist/Component.d.ts.map +1 -0
  22. package/dist/Component.js +60 -0
  23. package/dist/Composite.d.ts +823 -0
  24. package/dist/Composite.d.ts.map +1 -0
  25. package/dist/Composite.js +615 -0
  26. package/dist/Dialog.d.ts +544 -0
  27. package/dist/Dialog.d.ts.map +1 -0
  28. package/dist/Dialog.js +355 -0
  29. package/dist/Disclosure.d.ts +219 -0
  30. package/dist/Disclosure.d.ts.map +1 -0
  31. package/dist/Disclosure.js +128 -0
  32. package/dist/Dom/Events.d.ts +122 -0
  33. package/dist/Dom/Events.d.ts.map +1 -0
  34. package/dist/Dom/Events.js +192 -0
  35. package/dist/Dom/Props.d.ts +161 -0
  36. package/dist/Dom/Props.d.ts.map +1 -0
  37. package/dist/Dom/Props.js +110 -0
  38. package/dist/Dom/Refs.d.ts +58 -0
  39. package/dist/Dom/Refs.d.ts.map +1 -0
  40. package/dist/Dom/Refs.js +61 -0
  41. package/dist/Dom/Render.d.ts +59 -0
  42. package/dist/Dom/Render.d.ts.map +1 -0
  43. package/dist/Dom/Render.js +71 -0
  44. package/dist/Dom/Types.d.ts +570 -0
  45. package/dist/Dom/Types.d.ts.map +1 -0
  46. package/dist/Dom/Types.js +1 -0
  47. package/dist/Dom/index.d.ts +20 -0
  48. package/dist/Dom/index.d.ts.map +1 -0
  49. package/dist/Dom/index.js +8 -0
  50. package/dist/Dom.d.ts +14 -0
  51. package/dist/Dom.d.ts.map +1 -0
  52. package/dist/Dom.js +13 -0
  53. package/dist/Focusable.d.ts +85 -0
  54. package/dist/Focusable.d.ts.map +1 -0
  55. package/dist/Focusable.js +35 -0
  56. package/dist/Form.d.ts +1695 -0
  57. package/dist/Form.d.ts.map +1 -0
  58. package/dist/Form.js +987 -0
  59. package/dist/Grid.d.ts +569 -0
  60. package/dist/Grid.d.ts.map +1 -0
  61. package/dist/Grid.js +379 -0
  62. package/dist/Group.d.ts +147 -0
  63. package/dist/Group.d.ts.map +1 -0
  64. package/dist/Group.js +63 -0
  65. package/dist/Heading.d.ts +86 -0
  66. package/dist/Heading.d.ts.map +1 -0
  67. package/dist/Heading.js +48 -0
  68. package/dist/Hovercard.d.ts +297 -0
  69. package/dist/Hovercard.d.ts.map +1 -0
  70. package/dist/Hovercard.js +188 -0
  71. package/dist/HttpRouter.d.ts +129 -6
  72. package/dist/HttpRouter.d.ts.map +1 -1
  73. package/dist/HttpRouter.js +197 -54
  74. package/dist/Link.d.ts +67 -28
  75. package/dist/Link.d.ts.map +1 -1
  76. package/dist/Link.js +91 -37
  77. package/dist/Listbox.d.ts +437 -0
  78. package/dist/Listbox.d.ts.map +1 -0
  79. package/dist/Listbox.js +316 -0
  80. package/dist/Menu.d.ts +972 -0
  81. package/dist/Menu.d.ts.map +1 -0
  82. package/dist/Menu.js +731 -0
  83. package/dist/Menubar.d.ts +367 -0
  84. package/dist/Menubar.d.ts.map +1 -0
  85. package/dist/Menubar.js +265 -0
  86. package/dist/Meter.d.ts +217 -0
  87. package/dist/Meter.d.ts.map +1 -0
  88. package/dist/Meter.js +94 -0
  89. package/dist/NativeDetails.d.ts +41 -0
  90. package/dist/NativeDetails.d.ts.map +1 -0
  91. package/dist/NativeDetails.js +40 -0
  92. package/dist/NativeDialog.d.ts +64 -0
  93. package/dist/NativeDialog.d.ts.map +1 -0
  94. package/dist/NativeDialog.js +50 -0
  95. package/dist/NativePopover.d.ts +41 -0
  96. package/dist/NativePopover.d.ts.map +1 -0
  97. package/dist/NativePopover.js +46 -0
  98. package/dist/Popover.d.ts +241 -0
  99. package/dist/Popover.d.ts.map +1 -0
  100. package/dist/Popover.js +141 -0
  101. package/dist/RadioGroup.d.ts +432 -0
  102. package/dist/RadioGroup.d.ts.map +1 -0
  103. package/dist/RadioGroup.js +291 -0
  104. package/dist/Role.d.ts +64 -0
  105. package/dist/Role.d.ts.map +1 -0
  106. package/dist/Role.js +27 -0
  107. package/dist/Select.d.ts +529 -0
  108. package/dist/Select.d.ts.map +1 -0
  109. package/dist/Select.js +407 -0
  110. package/dist/Separator.d.ts +54 -0
  111. package/dist/Separator.d.ts.map +1 -0
  112. package/dist/Separator.js +26 -0
  113. package/dist/Slider.d.ts +188 -0
  114. package/dist/Slider.d.ts.map +1 -0
  115. package/dist/Slider.js +96 -0
  116. package/dist/SpinButton.d.ts +188 -0
  117. package/dist/SpinButton.d.ts.map +1 -0
  118. package/dist/SpinButton.js +96 -0
  119. package/dist/Storybook.d.ts +76 -0
  120. package/dist/Storybook.d.ts.map +1 -0
  121. package/dist/Storybook.js +102 -0
  122. package/dist/Switch.d.ts +187 -0
  123. package/dist/Switch.d.ts.map +1 -0
  124. package/dist/Switch.js +116 -0
  125. package/dist/Tab.d.ts +26 -0
  126. package/dist/Tab.d.ts.map +1 -0
  127. package/dist/Tab.js +25 -0
  128. package/dist/Tabs.d.ts +591 -0
  129. package/dist/Tabs.d.ts.map +1 -0
  130. package/dist/Tabs.js +346 -0
  131. package/dist/Toolbar.d.ts +366 -0
  132. package/dist/Toolbar.d.ts.map +1 -0
  133. package/dist/Toolbar.js +245 -0
  134. package/dist/Tooltip.d.ts +296 -0
  135. package/dist/Tooltip.d.ts.map +1 -0
  136. package/dist/Tooltip.js +172 -0
  137. package/dist/Tree.d.ts +591 -0
  138. package/dist/Tree.d.ts.map +1 -0
  139. package/dist/Tree.js +434 -0
  140. package/dist/TreeGrid.d.ts +645 -0
  141. package/dist/TreeGrid.d.ts.map +1 -0
  142. package/dist/TreeGrid.js +420 -0
  143. package/dist/VisuallyHidden.d.ts +54 -0
  144. package/dist/VisuallyHidden.d.ts.map +1 -0
  145. package/dist/VisuallyHidden.js +27 -0
  146. package/dist/WindowSplitter.d.ts +376 -0
  147. package/dist/WindowSplitter.d.ts.map +1 -0
  148. package/dist/WindowSplitter.js +222 -0
  149. package/dist/index.d.ts +49 -0
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +49 -0
  152. package/package.json +42 -15
  153. package/src/HttpRouter.test.ts +0 -294
  154. package/src/HttpRouter.ts +0 -168
  155. package/src/Link.test.ts +0 -84
  156. package/src/Link.ts +0 -107
  157. package/src/index.ts +0 -2
@@ -0,0 +1,140 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import { RefSubject } from "@typed/fx";
4
+ import { EventHandler, html } from "@typed/template";
5
+ import * as Dom from "./Dom.js";
6
+ /** Schema used to encode and hydrate checkbox state.
7
+ * @remarks
8
+ * ## Why
9
+ * A shared schema keeps server and client state shape identical.
10
+ * ## Ownership and lifetime
11
+ * The schema is immutable and acquires no resources.
12
+ * @since 1.0.0
13
+ * @category schemas
14
+ */
15
+ export const StateSchema = Schema.Struct({
16
+ checked: Schema.Literals([true, false, "mixed"]),
17
+ });
18
+ /** Creates hydrated, renderer-independent checkbox state.
19
+ * @remarks
20
+ * ## Why
21
+ * State transitions remain testable without rendering and can be consumed by
22
+ * any UI producer.
23
+ * ## Ownership and lifetime
24
+ * The caller's Effect Scope owns the hydrated RefSubject and its subscriptions.
25
+ * @example
26
+ * ```ts
27
+ * import { Effect } from "effect"
28
+ * import * as Checkbox from "@typed/ui/Checkbox"
29
+ *
30
+ * const program = Effect.gen(function* () {
31
+ * return yield* Checkbox.makeState({ checked: "mixed" })
32
+ * })
33
+ * ```
34
+ * @since 1.0.0
35
+ * @category constructors
36
+ */
37
+ export function makeState(initial = {}) {
38
+ return RefSubject.hydrate(StateSchema, { checked: initial.checked ?? false });
39
+ }
40
+ /** Sets the checkbox state to a boolean or mixed value.
41
+ * @remarks
42
+ * ## Why
43
+ * A single atomic RefSubject update keeps DOM properties and ARIA state aligned.
44
+ * ## Ownership and lifetime
45
+ * The Effect uses the existing subject lifetime and acquires no new resource.
46
+ * @example
47
+ * ```ts
48
+ * import { Effect } from "effect"
49
+ * import * as Checkbox from "@typed/ui/Checkbox"
50
+ *
51
+ * const program = Effect.gen(function* () {
52
+ * const state = yield* Checkbox.makeState()
53
+ * yield* Checkbox.setChecked(state, true)
54
+ * })
55
+ * ```
56
+ * @since 1.0.0
57
+ * @category state
58
+ */
59
+ export function setChecked(state, checked) {
60
+ return RefSubject.update(state, (current) => ({ ...current, checked }));
61
+ }
62
+ /** Toggles checked to false and false or mixed to true.
63
+ * @remarks
64
+ * ## Why
65
+ * User activation of an indeterminate checkbox resolves to a concrete checked
66
+ * state, matching native input behavior.
67
+ * ## Ownership and lifetime
68
+ * The Effect updates the existing RefSubject and acquires no resource.
69
+ * @example
70
+ * ```ts
71
+ * import { Effect } from "effect"
72
+ * import * as Checkbox from "@typed/ui/Checkbox"
73
+ *
74
+ * const program = Effect.gen(function* () {
75
+ * const state = yield* Checkbox.makeState({ checked: "mixed" })
76
+ * yield* Checkbox.toggle(state)
77
+ * })
78
+ * ```
79
+ * @since 1.0.0
80
+ * @category state
81
+ */
82
+ export function toggle(state) {
83
+ return RefSubject.update(state, (current) => ({
84
+ ...current,
85
+ checked: current.checked === true ? false : true,
86
+ }));
87
+ }
88
+ function internalProps(options) {
89
+ const checked = RefSubject.map(options.state, (state) => state.checked === true);
90
+ const indeterminate = RefSubject.map(options.state, (state) => state.checked === "mixed");
91
+ const onChange = EventHandler.make(Effect.fn((event) => setChecked(options.state, Dom.currentTarget(event).checked)));
92
+ return ({ property }) => ({
93
+ type: "checkbox",
94
+ "aria-checked": RefSubject.map(options.state, (state) => state.checked),
95
+ "?checked": checked,
96
+ ".checked": checked,
97
+ "?disabled": property("disabled", false),
98
+ "?required": property("required", false),
99
+ ".indeterminate": indeterminate,
100
+ onchange: onChange,
101
+ ref: options.state,
102
+ });
103
+ }
104
+ /** Renders a native checkbox input synchronized with hydrated state.
105
+ * @remarks
106
+ * ## Why
107
+ * Native keyboard, form, disabled, required, and accessibility behavior remain
108
+ * available while state is exposed through RefSubject.
109
+ * ## Ownership and lifetime
110
+ * Running the Fx installs native listeners and state subscriptions in its
111
+ * Scope. A custom host must apply `type`, checked, indeterminate, ARIA, and ref
112
+ * props; only one hydration owner may be composed for the element.
113
+ * @example
114
+ * ```ts
115
+ * import { Effect } from "effect"
116
+ * import * as Checkbox from "@typed/ui/Checkbox"
117
+ *
118
+ * const program = Effect.gen(function* () {
119
+ * const state = yield* Checkbox.makeState()
120
+ * return Checkbox.Input({ state })
121
+ * })
122
+ * ```
123
+ * @since 1.0.0
124
+ * @category components
125
+ */
126
+ export function Input(options, host) {
127
+ return Dom.renderHost()(options, host, internalProps(options), "", (i) => {
128
+ return html `<input ...${i} />`;
129
+ });
130
+ }
131
+ /** Canonical component alias for `Input`.
132
+ * @remarks
133
+ * ## Why
134
+ * The alias provides the widget name while retaining the explicit input API.
135
+ * ## Ownership and lifetime
136
+ * It has exactly the same Scope and native-element ownership as `Input`.
137
+ * @since 1.0.0
138
+ * @category aliases
139
+ */
140
+ export const Checkbox = Input;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Collection is the scoped registry shared by composite widgets. Registration order is not
3
+ * navigation order: byDomOrder reads the mounted nodes with compareDocumentPosition, and each
4
+ * register Effect removes its own item when that Effect's Scope closes.
5
+ *
6
+ * @remarks
7
+ * The module keeps policy, state transitions, and DOM rendering separable so applications can use
8
+ * the state and pure operations without mounting UI, or supply custom hosts without replacing native
9
+ * events and browser-owned focus.
10
+ *
11
+ * @since 1.0.0
12
+ * @category modules
13
+ * @packageDocumentation
14
+ */
15
+ import * as Effect from "effect/Effect";
16
+ import * as Scope from "effect/Scope";
17
+ import { RefSubject } from "@typed/fx";
18
+ /**
19
+ * Public Collection.Item behavioral model.
20
+ *
21
+ * @remarks
22
+ * ## Why
23
+ *
24
+ * The public model lets custom composites reuse Collection's deterministic policy without copying
25
+ * an internal shape.
26
+ *
27
+ * ## Ownership and lifetime
28
+ *
29
+ * This declaration is data or schema metadata and acquires no resources.
30
+ *
31
+ * ## Example
32
+ *
33
+ * Import with `import type { Item } from "@typed/ui/Collection";` Extend the [Collection.makeState
34
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). A registered value can retain
35
+ * its real DOM identity:
36
+ * `const item: Item<string> = { id: "save", value: "save", element: document.createElement("button") }`.
37
+ * @since 1.0.0
38
+ * @category models
39
+ */
40
+ export interface Item<Value = unknown, Element extends object = globalThis.Element> {
41
+ /**
42
+ * Stable id used for collection identity and ARIA relationships.
43
+ * @since 1.0.0
44
+ * @category models
45
+ */
46
+ readonly id: string;
47
+ /**
48
+ * Mounted element handle used for DOM ordering, focus, and scrolling.
49
+ * @since 1.0.0
50
+ * @category models
51
+ */
52
+ readonly element?: Element;
53
+ /**
54
+ * Flag used by collection movement and widget handlers to skip activation by default.
55
+ * @since 1.0.0
56
+ * @category models
57
+ */
58
+ readonly disabled?: boolean;
59
+ /**
60
+ * Whether the item participates as a submenu entry.
61
+ * @since 1.0.0
62
+ * @category models
63
+ */
64
+ readonly submenu?: boolean;
65
+ /**
66
+ * Search text used by typeahead independently of rendered markup.
67
+ * @since 1.0.0
68
+ * @category models
69
+ */
70
+ readonly textValue?: string;
71
+ /**
72
+ * Current semantic value selected or edited by the widget.
73
+ * @since 1.0.0
74
+ * @category models
75
+ */
76
+ readonly value?: Value;
77
+ }
78
+ /**
79
+ * Complete renderer-independent state for Collection.
80
+ *
81
+ * @remarks
82
+ * ## Why
83
+ *
84
+ * Applications can inspect, update, and test Collection behavior without mounting or coupling the
85
+ * state to a renderer.
86
+ *
87
+ * ## Ownership and lifetime
88
+ *
89
+ * This declaration is data or schema metadata and acquires no resources.
90
+ *
91
+ * ## Example
92
+ *
93
+ * Import with `import type { State } from "@typed/ui/Collection";` Extend the [Collection.makeState
94
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). Collection state is an
95
+ * immutable sequence: `const snapshot: State<string> = [{ id: "save", value: "save" }]`.
96
+ * @since 1.0.0
97
+ * @category models
98
+ */
99
+ export type State<Value = unknown, Element extends object = globalThis.Element> = readonly Item<Value, Element>[];
100
+ /**
101
+ * Creates hydrated Collection state. The collection starts with the supplied immutable item array,
102
+ * or empty.
103
+ *
104
+ * @remarks
105
+ * ## Why
106
+ *
107
+ * State and collection ownership can be composed and tested independently from any renderer.
108
+ *
109
+ * ## Ownership and lifetime
110
+ *
111
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
112
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
113
+ * creation.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * import * as Effect from "effect/Effect";
118
+ * import * as Collection from "@typed/ui/Collection";
119
+ *
120
+ * const program = Effect.scoped(
121
+ * Effect.gen(function* () {
122
+ * const collection = yield* Collection.makeState<string>();
123
+ * return yield* collection;
124
+ * }),
125
+ * );
126
+ * ```
127
+ * @since 1.0.0
128
+ * @category constructors
129
+ */
130
+ export declare function makeState<Value = unknown, Element extends object = globalThis.Element>(initial?: State<Value, Element>): Effect.Effect<RefSubject.RefSubject<State<Value, Element>>, never, Scope.Scope>;
131
+ /**
132
+ * Upserts an item immediately and installs a finalizer in the Scope that runs this Effect; the
133
+ * finalizer removes only that exact registration.
134
+ *
135
+ * @remarks
136
+ * ## Why
137
+ *
138
+ * The operation exposes Collection's transition directly so callers can compose it in Effect
139
+ * programs and native event handlers.
140
+ *
141
+ * ## Ownership and lifetime
142
+ *
143
+ * Registration is owned by the Scope in which the registration Effect runs. Closing that Scope
144
+ * removes the exact registered item; creating the collection or state does not own this cleanup.
145
+ *
146
+ * ## Example
147
+ *
148
+ * Import with `import { register } from "@typed/ui/Collection";` Extend the [Collection.makeState
149
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). Inside the linked Effect
150
+ * program invoke `yield* register(collection, { id: "save", value: "save" })`, then read the
151
+ * collection to observe the inserted item.
152
+ * @since 1.0.0
153
+ * @category combinators
154
+ */
155
+ export declare function register<Value, Element extends object, E, R>(collection: RefSubject.RefSubject<State<Value, Element>, E, R>, item: Item<Value, Element>): Effect.Effect<void, E, R | Scope.Scope>;
156
+ /**
157
+ * Registers a mounted element and removes it automatically with the Scope in which the returned
158
+ * Effect is run.
159
+ *
160
+ * @remarks
161
+ * ## Why
162
+ *
163
+ * The operation exposes Collection's transition directly so callers can compose it in Effect
164
+ * programs and native event handlers.
165
+ *
166
+ * ## Ownership and lifetime
167
+ *
168
+ * Registration is owned by the Scope in which the registration Effect runs. Closing that Scope
169
+ * removes the exact registered item; creating the collection or state does not own this cleanup.
170
+ *
171
+ * ## Example
172
+ *
173
+ * Import with `import { ref } from "@typed/ui/Collection";` Extend the [Collection.makeState
174
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). Inside the linked Effect
175
+ * program create `const attach = ref(collection, { id: "save", value: "save" })`, then run `yield*
176
+ * attach(document.createElement("button"))` and read the collection before Scope close.
177
+ * @since 1.0.0
178
+ * @category combinators
179
+ */
180
+ export declare function ref<Value, Element extends object, E, R>(collection: RefSubject.RefSubject<State<Value, Element>, E, R>, item: Omit<Item<Value, Element>, "element">): (element: Element) => Effect.Effect<void, E, R | Scope.Scope>;
181
+ /**
182
+ * Removes every registered item with the supplied id and retains the remaining order.
183
+ *
184
+ * @remarks
185
+ * ## Why
186
+ *
187
+ * The operation exposes Collection's transition directly so callers can compose it in Effect
188
+ * programs and native event handlers.
189
+ *
190
+ * ## Ownership and lifetime
191
+ *
192
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
193
+ * error and service channels, and retains no resources after completion.
194
+ *
195
+ * ## Example
196
+ *
197
+ * Import with `import { unregister } from "@typed/ui/Collection";` Extend the [Collection.makeState
198
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). Inside the linked Effect
199
+ * program first register `"save"`, run `yield* unregister(collection, "save")`, then read the
200
+ * collection to observe that only the matching id was removed.
201
+ * @since 1.0.0
202
+ * @category combinators
203
+ */
204
+ export declare function unregister<Value, Element extends object, E, R>(collection: RefSubject.RefSubject<State<Value, Element>, E, R>, id: string): Effect.Effect<void, E, R>;
205
+ /**
206
+ * Returns a new array excluding only items whose disabled flag is exactly true.
207
+ *
208
+ * @remarks
209
+ * ## Why
210
+ *
211
+ * Separating this deterministic policy from event wiring lets applications test it directly and
212
+ * reuse it in custom composites.
213
+ *
214
+ * ## Ownership and lifetime
215
+ *
216
+ * This is a synchronous calculation. It acquires no resources and does not mutate the input array,
217
+ * state, event, or DOM.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * import * as Collection from "@typed/ui/Collection";
222
+ *
223
+ * const enabled = Collection.enabledItems([{ id: "a" }, { id: "b", disabled: true }]);
224
+ * ```
225
+ * @since 1.0.0
226
+ * @category combinators
227
+ */
228
+ export declare function enabledItems<ItemType extends Item<unknown, object>>(items: readonly ItemType[]): readonly ItemType[];
229
+ /**
230
+ * Returns a sorted copy using compareDocumentPosition; items without comparable mounted elements
231
+ * retain an equivalent ordering position.
232
+ *
233
+ * @remarks
234
+ * ## Why
235
+ *
236
+ * Separating this deterministic policy from event wiring lets applications test it directly and
237
+ * reuse it in custom composites.
238
+ *
239
+ * ## Ownership and lifetime
240
+ *
241
+ * This is a synchronous calculation. It acquires no resources and does not mutate the input array,
242
+ * state, event, or DOM.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * import * as Collection from "@typed/ui/Collection";
247
+ *
248
+ * const ordered = Collection.byDomOrder([{ id: "body", element: document.body }]);
249
+ * ```
250
+ * @since 1.0.0
251
+ * @category combinators
252
+ */
253
+ export declare function byDomOrder<ItemType extends Item<unknown, object>>(items: readonly ItemType[]): readonly ItemType[];
254
+ //# sourceMappingURL=Collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Collection is the scoped registry shared by composite widgets. Registration order is not
3
+ * navigation order: byDomOrder reads the mounted nodes with compareDocumentPosition, and each
4
+ * register Effect removes its own item when that Effect's Scope closes.
5
+ *
6
+ * @remarks
7
+ * The module keeps policy, state transitions, and DOM rendering separable so applications can use
8
+ * the state and pure operations without mounting UI, or supply custom hosts without replacing native
9
+ * events and browser-owned focus.
10
+ *
11
+ * @since 1.0.0
12
+ * @category modules
13
+ * @packageDocumentation
14
+ */
15
+ import * as Effect from "effect/Effect";
16
+ import * as Equal from "effect/Equal";
17
+ import * as Scope from "effect/Scope";
18
+ import { RefSubject } from "@typed/fx";
19
+ import * as Equivalence from "effect/Equivalence";
20
+ /**
21
+ * Creates hydrated Collection state. The collection starts with the supplied immutable item array,
22
+ * or empty.
23
+ *
24
+ * @remarks
25
+ * ## Why
26
+ *
27
+ * State and collection ownership can be composed and tested independently from any renderer.
28
+ *
29
+ * ## Ownership and lifetime
30
+ *
31
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
32
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
33
+ * creation.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import * as Effect from "effect/Effect";
38
+ * import * as Collection from "@typed/ui/Collection";
39
+ *
40
+ * const program = Effect.scoped(
41
+ * Effect.gen(function* () {
42
+ * const collection = yield* Collection.makeState<string>();
43
+ * return yield* collection;
44
+ * }),
45
+ * );
46
+ * ```
47
+ * @since 1.0.0
48
+ * @category constructors
49
+ */
50
+ export function makeState(initial = []) {
51
+ // DOM elements are runtime handles; structural equality would traverse browser internals.
52
+ return RefSubject.make(initial, { eq: Equivalence.Array(itemEquivalence()) });
53
+ }
54
+ /**
55
+ * Upserts an item immediately and installs a finalizer in the Scope that runs this Effect; the
56
+ * finalizer removes only that exact registration.
57
+ *
58
+ * @remarks
59
+ * ## Why
60
+ *
61
+ * The operation exposes Collection's transition directly so callers can compose it in Effect
62
+ * programs and native event handlers.
63
+ *
64
+ * ## Ownership and lifetime
65
+ *
66
+ * Registration is owned by the Scope in which the registration Effect runs. Closing that Scope
67
+ * removes the exact registered item; creating the collection or state does not own this cleanup.
68
+ *
69
+ * ## Example
70
+ *
71
+ * Import with `import { register } from "@typed/ui/Collection";` Extend the [Collection.makeState
72
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). Inside the linked Effect
73
+ * program invoke `yield* register(collection, { id: "save", value: "save" })`, then read the
74
+ * collection to observe the inserted item.
75
+ * @since 1.0.0
76
+ * @category combinators
77
+ */
78
+ export function register(collection, item) {
79
+ return Effect.gen(function* () {
80
+ yield* RefSubject.update(collection, (items) => upsert(items, item)).pipe(Effect.asVoid);
81
+ const services = yield* Effect.context();
82
+ const scope = yield* Effect.scope;
83
+ yield* Scope.addFinalizer(scope, unregisterRegistered(collection, item).pipe(Effect.provide(services), Effect.ignore({ log: true })));
84
+ });
85
+ }
86
+ /**
87
+ * Registers a mounted element and removes it automatically with the Scope in which the returned
88
+ * Effect is run.
89
+ *
90
+ * @remarks
91
+ * ## Why
92
+ *
93
+ * The operation exposes Collection's transition directly so callers can compose it in Effect
94
+ * programs and native event handlers.
95
+ *
96
+ * ## Ownership and lifetime
97
+ *
98
+ * Registration is owned by the Scope in which the registration Effect runs. Closing that Scope
99
+ * removes the exact registered item; creating the collection or state does not own this cleanup.
100
+ *
101
+ * ## Example
102
+ *
103
+ * Import with `import { ref } from "@typed/ui/Collection";` Extend the [Collection.makeState
104
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). Inside the linked Effect
105
+ * program create `const attach = ref(collection, { id: "save", value: "save" })`, then run `yield*
106
+ * attach(document.createElement("button"))` and read the collection before Scope close.
107
+ * @since 1.0.0
108
+ * @category combinators
109
+ */
110
+ export function ref(collection, item) {
111
+ return Effect.fn((element) => register(collection, { ...item, element }));
112
+ }
113
+ /**
114
+ * Removes every registered item with the supplied id and retains the remaining order.
115
+ *
116
+ * @remarks
117
+ * ## Why
118
+ *
119
+ * The operation exposes Collection's transition directly so callers can compose it in Effect
120
+ * programs and native event handlers.
121
+ *
122
+ * ## Ownership and lifetime
123
+ *
124
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
125
+ * error and service channels, and retains no resources after completion.
126
+ *
127
+ * ## Example
128
+ *
129
+ * Import with `import { unregister } from "@typed/ui/Collection";` Extend the [Collection.makeState
130
+ * runnable setup](/reference/%40typed%2Fui%2FCollection%23makeState). Inside the linked Effect
131
+ * program first register `"save"`, run `yield* unregister(collection, "save")`, then read the
132
+ * collection to observe that only the matching id was removed.
133
+ * @since 1.0.0
134
+ * @category combinators
135
+ */
136
+ export function unregister(collection, id) {
137
+ return RefSubject.update(collection, (items) => items.filter((item) => item.id !== id)).pipe(Effect.asVoid);
138
+ }
139
+ /**
140
+ * Returns a new array excluding only items whose disabled flag is exactly true.
141
+ *
142
+ * @remarks
143
+ * ## Why
144
+ *
145
+ * Separating this deterministic policy from event wiring lets applications test it directly and
146
+ * reuse it in custom composites.
147
+ *
148
+ * ## Ownership and lifetime
149
+ *
150
+ * This is a synchronous calculation. It acquires no resources and does not mutate the input array,
151
+ * state, event, or DOM.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * import * as Collection from "@typed/ui/Collection";
156
+ *
157
+ * const enabled = Collection.enabledItems([{ id: "a" }, { id: "b", disabled: true }]);
158
+ * ```
159
+ * @since 1.0.0
160
+ * @category combinators
161
+ */
162
+ export function enabledItems(items) {
163
+ return items.filter((item) => item.disabled !== true);
164
+ }
165
+ /**
166
+ * Returns a sorted copy using compareDocumentPosition; items without comparable mounted elements
167
+ * retain an equivalent ordering position.
168
+ *
169
+ * @remarks
170
+ * ## Why
171
+ *
172
+ * Separating this deterministic policy from event wiring lets applications test it directly and
173
+ * reuse it in custom composites.
174
+ *
175
+ * ## Ownership and lifetime
176
+ *
177
+ * This is a synchronous calculation. It acquires no resources and does not mutate the input array,
178
+ * state, event, or DOM.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * import * as Collection from "@typed/ui/Collection";
183
+ *
184
+ * const ordered = Collection.byDomOrder([{ id: "body", element: document.body }]);
185
+ * ```
186
+ * @since 1.0.0
187
+ * @category combinators
188
+ */
189
+ export function byDomOrder(items) {
190
+ return items.toSorted((left, right) => {
191
+ if (left.element === undefined || right.element === undefined)
192
+ return 0;
193
+ const compareDocumentPosition = Reflect.get(left.element, "compareDocumentPosition");
194
+ if (typeof compareDocumentPosition !== "function")
195
+ return 0;
196
+ const position = compareDocumentPosition.call(left.element, right.element);
197
+ if (position & 2)
198
+ return 1;
199
+ if (position & 4)
200
+ return -1;
201
+ return 0;
202
+ });
203
+ }
204
+ function unregisterRegistered(collection, item) {
205
+ return RefSubject.update(collection, (items) => items.filter((current) => current.id !== item.id || current !== item)).pipe(Effect.asVoid);
206
+ }
207
+ function upsert(items, item) {
208
+ const index = items.findIndex((current) => current.id === item.id);
209
+ return index === -1 ? [...items, item] : items.toSpliced(index, 1, item);
210
+ }
211
+ function itemEquivalence() {
212
+ return Equivalence.make((left, right) => left.id === right.id &&
213
+ left.element === right.element &&
214
+ left.disabled === right.disabled &&
215
+ left.submenu === right.submenu &&
216
+ left.textValue === right.textValue &&
217
+ Equal.equals(left.value, right.value));
218
+ }