@typed/ui 1.0.0-beta.4 → 1.0.0-beta.6
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.
- package/README.md +53 -9
- package/dist/Alert.d.ts +78 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Alert.js +37 -0
- package/dist/Button.d.ts +144 -0
- package/dist/Button.d.ts.map +1 -0
- package/dist/Button.js +38 -0
- package/dist/Carousel.d.ts +547 -0
- package/dist/Carousel.d.ts.map +1 -0
- package/dist/Carousel.js +387 -0
- package/dist/Checkbox.d.ts +203 -0
- package/dist/Checkbox.d.ts.map +1 -0
- package/dist/Checkbox.js +140 -0
- package/dist/Collection.d.ts +254 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Collection.js +218 -0
- package/dist/Combobox.d.ts +516 -0
- package/dist/Combobox.d.ts.map +1 -0
- package/dist/Combobox.js +371 -0
- package/dist/Component.d.ts +127 -0
- package/dist/Component.d.ts.map +1 -0
- package/dist/Component.js +60 -0
- package/dist/Composite.d.ts +823 -0
- package/dist/Composite.d.ts.map +1 -0
- package/dist/Composite.js +615 -0
- package/dist/Dialog.d.ts +544 -0
- package/dist/Dialog.d.ts.map +1 -0
- package/dist/Dialog.js +355 -0
- package/dist/Disclosure.d.ts +219 -0
- package/dist/Disclosure.d.ts.map +1 -0
- package/dist/Disclosure.js +128 -0
- package/dist/Dom/Events.d.ts +122 -0
- package/dist/Dom/Events.d.ts.map +1 -0
- package/dist/Dom/Events.js +192 -0
- package/dist/Dom/Props.d.ts +161 -0
- package/dist/Dom/Props.d.ts.map +1 -0
- package/dist/Dom/Props.js +110 -0
- package/dist/Dom/Refs.d.ts +58 -0
- package/dist/Dom/Refs.d.ts.map +1 -0
- package/dist/Dom/Refs.js +61 -0
- package/dist/Dom/Render.d.ts +59 -0
- package/dist/Dom/Render.d.ts.map +1 -0
- package/dist/Dom/Render.js +71 -0
- package/dist/Dom/Types.d.ts +570 -0
- package/dist/Dom/Types.d.ts.map +1 -0
- package/dist/Dom/Types.js +1 -0
- package/dist/Dom/index.d.ts +20 -0
- package/dist/Dom/index.d.ts.map +1 -0
- package/dist/Dom/index.js +8 -0
- package/dist/Dom.d.ts +14 -0
- package/dist/Dom.d.ts.map +1 -0
- package/dist/Dom.js +13 -0
- package/dist/Focusable.d.ts +85 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Focusable.js +35 -0
- package/dist/Form.d.ts +1695 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/Form.js +987 -0
- package/dist/Grid.d.ts +569 -0
- package/dist/Grid.d.ts.map +1 -0
- package/dist/Grid.js +379 -0
- package/dist/Group.d.ts +147 -0
- package/dist/Group.d.ts.map +1 -0
- package/dist/Group.js +63 -0
- package/dist/Heading.d.ts +86 -0
- package/dist/Heading.d.ts.map +1 -0
- package/dist/Heading.js +48 -0
- package/dist/Hovercard.d.ts +297 -0
- package/dist/Hovercard.d.ts.map +1 -0
- package/dist/Hovercard.js +188 -0
- package/dist/HttpRouter.d.ts +129 -6
- package/dist/HttpRouter.d.ts.map +1 -1
- package/dist/HttpRouter.js +196 -53
- package/dist/Link.d.ts +67 -28
- package/dist/Link.d.ts.map +1 -1
- package/dist/Link.js +91 -37
- package/dist/Listbox.d.ts +437 -0
- package/dist/Listbox.d.ts.map +1 -0
- package/dist/Listbox.js +316 -0
- package/dist/Menu.d.ts +972 -0
- package/dist/Menu.d.ts.map +1 -0
- package/dist/Menu.js +731 -0
- package/dist/Menubar.d.ts +367 -0
- package/dist/Menubar.d.ts.map +1 -0
- package/dist/Menubar.js +265 -0
- package/dist/Meter.d.ts +217 -0
- package/dist/Meter.d.ts.map +1 -0
- package/dist/Meter.js +94 -0
- package/dist/NativeDetails.d.ts +41 -0
- package/dist/NativeDetails.d.ts.map +1 -0
- package/dist/NativeDetails.js +40 -0
- package/dist/NativeDialog.d.ts +64 -0
- package/dist/NativeDialog.d.ts.map +1 -0
- package/dist/NativeDialog.js +50 -0
- package/dist/NativePopover.d.ts +41 -0
- package/dist/NativePopover.d.ts.map +1 -0
- package/dist/NativePopover.js +46 -0
- package/dist/Popover.d.ts +241 -0
- package/dist/Popover.d.ts.map +1 -0
- package/dist/Popover.js +141 -0
- package/dist/RadioGroup.d.ts +432 -0
- package/dist/RadioGroup.d.ts.map +1 -0
- package/dist/RadioGroup.js +291 -0
- package/dist/Role.d.ts +64 -0
- package/dist/Role.d.ts.map +1 -0
- package/dist/Role.js +27 -0
- package/dist/Select.d.ts +529 -0
- package/dist/Select.d.ts.map +1 -0
- package/dist/Select.js +407 -0
- package/dist/Separator.d.ts +54 -0
- package/dist/Separator.d.ts.map +1 -0
- package/dist/Separator.js +26 -0
- package/dist/Slider.d.ts +188 -0
- package/dist/Slider.d.ts.map +1 -0
- package/dist/Slider.js +96 -0
- package/dist/SpinButton.d.ts +188 -0
- package/dist/SpinButton.d.ts.map +1 -0
- package/dist/SpinButton.js +96 -0
- package/dist/Storybook.d.ts +76 -0
- package/dist/Storybook.d.ts.map +1 -0
- package/dist/Storybook.js +102 -0
- package/dist/Switch.d.ts +187 -0
- package/dist/Switch.d.ts.map +1 -0
- package/dist/Switch.js +116 -0
- package/dist/Tab.d.ts +26 -0
- package/dist/Tab.d.ts.map +1 -0
- package/dist/Tab.js +25 -0
- package/dist/Tabs.d.ts +591 -0
- package/dist/Tabs.d.ts.map +1 -0
- package/dist/Tabs.js +346 -0
- package/dist/Toolbar.d.ts +366 -0
- package/dist/Toolbar.d.ts.map +1 -0
- package/dist/Toolbar.js +245 -0
- package/dist/Tooltip.d.ts +296 -0
- package/dist/Tooltip.d.ts.map +1 -0
- package/dist/Tooltip.js +172 -0
- package/dist/Tree.d.ts +591 -0
- package/dist/Tree.d.ts.map +1 -0
- package/dist/Tree.js +434 -0
- package/dist/TreeGrid.d.ts +645 -0
- package/dist/TreeGrid.d.ts.map +1 -0
- package/dist/TreeGrid.js +420 -0
- package/dist/VisuallyHidden.d.ts +54 -0
- package/dist/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden.js +27 -0
- package/dist/WindowSplitter.d.ts +376 -0
- package/dist/WindowSplitter.d.ts.map +1 -0
- package/dist/WindowSplitter.js +222 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -0
- package/package.json +45 -18
- package/src/HttpRouter.test.ts +0 -256
- package/src/HttpRouter.ts +0 -165
- package/src/Link.test.ts +0 -84
- package/src/Link.ts +0 -107
- package/src/index.ts +0 -2
package/dist/Dialog.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Scope from "effect/Scope";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import { RefSubject } from "@typed/fx";
|
|
5
|
+
import { EventHandler, html, } from "@typed/template";
|
|
6
|
+
import * as Dom from "./Dom.js";
|
|
7
|
+
import * as NativeDialog from "./NativeDialog.js";
|
|
8
|
+
const dialogs = new WeakMap();
|
|
9
|
+
/** Schema for dialog hydration state.
|
|
10
|
+
* @remarks
|
|
11
|
+
* ## Why
|
|
12
|
+
* Shared encoding keeps server and browser state compatible.
|
|
13
|
+
* ## Ownership and lifetime
|
|
14
|
+
* The immutable schema acquires no resources.
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
* @category schemas
|
|
17
|
+
*/
|
|
18
|
+
export const StateSchema = Schema.Struct({ open: Schema.Boolean });
|
|
19
|
+
/** Creates hydrated dialog state independent of rendering.
|
|
20
|
+
* @remarks
|
|
21
|
+
* ## Why
|
|
22
|
+
* Application transitions can be composed and tested before a native dialog
|
|
23
|
+
* element exists.
|
|
24
|
+
* ## Ownership and lifetime
|
|
25
|
+
* The calling Effect Scope owns the returned RefSubject.
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { Effect } from "effect"
|
|
29
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
30
|
+
*
|
|
31
|
+
* const program = Effect.gen(function* () {
|
|
32
|
+
* return yield* Dialog.makeState()
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
* @category constructors
|
|
37
|
+
*/
|
|
38
|
+
export function makeState(initial = {}) {
|
|
39
|
+
return RefSubject.hydrate(StateSchema, { open: initial.open ?? false });
|
|
40
|
+
}
|
|
41
|
+
/** Sets desired dialog visibility.
|
|
42
|
+
* @remarks
|
|
43
|
+
* ## Why
|
|
44
|
+
* Explicit state transitions stay outside renderers and retain Effect typing.
|
|
45
|
+
* ## Ownership and lifetime
|
|
46
|
+
* The Effect uses the existing RefSubject lifetime and acquires no resource.
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { Effect } from "effect"
|
|
50
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
51
|
+
*
|
|
52
|
+
* const program = Effect.gen(function* () {
|
|
53
|
+
* const state = yield* Dialog.makeState()
|
|
54
|
+
* yield* Dialog.setOpen(state, true)
|
|
55
|
+
* })
|
|
56
|
+
* ```
|
|
57
|
+
* @since 1.0.0
|
|
58
|
+
* @category state
|
|
59
|
+
*/
|
|
60
|
+
export function setOpen(state, open) {
|
|
61
|
+
return RefSubject.update(state, (current) => ({ ...current, open }));
|
|
62
|
+
}
|
|
63
|
+
/** Marks dialog state closed without requesting native cancellation.
|
|
64
|
+
* @remarks
|
|
65
|
+
* ## Why
|
|
66
|
+
* Programmatic close is distinct from `requestClose`, which runs the browser's
|
|
67
|
+
* cancelable close-request lifecycle.
|
|
68
|
+
* ## Ownership and lifetime
|
|
69
|
+
* The Effect updates existing state and acquires no resource.
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* import { Effect } from "effect"
|
|
73
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
74
|
+
*
|
|
75
|
+
* const program = Effect.gen(function* () {
|
|
76
|
+
* const state = yield* Dialog.makeState({ open: true })
|
|
77
|
+
* yield* Dialog.close(state)
|
|
78
|
+
* })
|
|
79
|
+
* ```
|
|
80
|
+
* @since 1.0.0
|
|
81
|
+
* @category state
|
|
82
|
+
*/
|
|
83
|
+
export function close(state) {
|
|
84
|
+
return setOpen(state, false);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Requests the native dialog close lifecycle, including its cancel event.
|
|
88
|
+
* @remarks
|
|
89
|
+
* ## Why
|
|
90
|
+
* The browser's `requestClose()` is cancelable. The fallback dispatches a real
|
|
91
|
+
* cancelable `cancel` event and calls `close()` only when it is accepted, so
|
|
92
|
+
* user handlers retain the same veto point.
|
|
93
|
+
* ## Ownership and lifetime
|
|
94
|
+
* The Effect consults the dialog currently registered for the hydrated state;
|
|
95
|
+
* registration is removed when `Content`'s Scope closes. No dialog is retained
|
|
96
|
+
* after that finalizer.
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { Effect } from "effect"
|
|
100
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
101
|
+
*
|
|
102
|
+
* const program = Effect.gen(function* () {
|
|
103
|
+
* const state = yield* Dialog.makeState({ open: true })
|
|
104
|
+
* yield* Dialog.requestClose(state)
|
|
105
|
+
* })
|
|
106
|
+
* ```
|
|
107
|
+
* @since 1.0.0
|
|
108
|
+
* @category lifecycle
|
|
109
|
+
*/
|
|
110
|
+
export function requestClose(state, returnValue) {
|
|
111
|
+
return Effect.sync(() => {
|
|
112
|
+
const dialog = dialogs.get(state);
|
|
113
|
+
const requestClose = dialog === undefined ? undefined : Reflect.get(dialog, "requestClose");
|
|
114
|
+
if (typeof requestClose === "function") {
|
|
115
|
+
requestClose.call(dialog, returnValue);
|
|
116
|
+
}
|
|
117
|
+
else if (dialog !== undefined) {
|
|
118
|
+
const accepted = dialog.dispatchEvent(new Event("cancel", { cancelable: true }));
|
|
119
|
+
if (accepted)
|
|
120
|
+
dialog.close(returnValue);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function triggerInternalProps(options) {
|
|
125
|
+
const open = RefSubject.map(options.state, (state) => state.open);
|
|
126
|
+
const show = setOpen(options.state, true);
|
|
127
|
+
return ({ property }) => ({
|
|
128
|
+
type: "button",
|
|
129
|
+
"aria-haspopup": "dialog",
|
|
130
|
+
"aria-expanded": open,
|
|
131
|
+
"aria-controls": property("controls", undefined),
|
|
132
|
+
commandfor: property("controls", undefined),
|
|
133
|
+
command: options.controls === undefined ? undefined : "show-modal",
|
|
134
|
+
onclick: options.controls === undefined ? show : undefined,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Renders a native button that opens dialog content.
|
|
138
|
+
* @remarks
|
|
139
|
+
* ## Why
|
|
140
|
+
* It preserves button semantics, real events, and ARIA relationship state.
|
|
141
|
+
* Supplying `controls` selects native `commandfor` / `show-modal` behavior and
|
|
142
|
+
* disables the Fx click fallback, so that form requires browser command support.
|
|
143
|
+
* Omitting `controls` installs the portable state-driven click handler.
|
|
144
|
+
* ## Ownership and lifetime
|
|
145
|
+
* Running the Fx owns reactive props/listeners in its Scope. A custom host must
|
|
146
|
+
* preserve button type, ARIA, command, click props, and content.
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* import { Effect } from "effect"
|
|
150
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
151
|
+
*
|
|
152
|
+
* const program = Effect.gen(function* () {
|
|
153
|
+
* const state = yield* Dialog.makeState()
|
|
154
|
+
* // Omitting controls selects the Fx state fallback.
|
|
155
|
+
* return Dialog.Trigger({ state, content: "Open" })
|
|
156
|
+
* })
|
|
157
|
+
* ```
|
|
158
|
+
* @since 1.0.0
|
|
159
|
+
* @category components
|
|
160
|
+
*/
|
|
161
|
+
export function Trigger(options, host) {
|
|
162
|
+
return Dom.renderHost()(options, host, triggerInternalProps(options), options.content, (props, content) => html `<button ...${props}>${content}</button>`);
|
|
163
|
+
}
|
|
164
|
+
function contentInternalProps(options) {
|
|
165
|
+
const synchronize = NativeDialog.ref(options.state, { modal: options.modal });
|
|
166
|
+
return ({ property }) => ({
|
|
167
|
+
id: property("id", undefined),
|
|
168
|
+
"aria-labelledby": property("labelledBy", undefined),
|
|
169
|
+
"aria-describedby": property("describedBy", undefined),
|
|
170
|
+
"aria-label": property("label", undefined),
|
|
171
|
+
oncancel: close(options.state),
|
|
172
|
+
onclose: close(options.state),
|
|
173
|
+
ontoggle: EventHandler.make(Effect.fn((event) => setOpen(options.state, Dom.currentTarget(event).open))),
|
|
174
|
+
ref: Dom.composeRefs(options.state, Dom.composeRefs(synchronize, dialogRef(options.state))),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function dialogRef(state) {
|
|
178
|
+
return Effect.fn(function* (dialog) {
|
|
179
|
+
dialogs.set(state, dialog);
|
|
180
|
+
const scope = yield* Effect.scope;
|
|
181
|
+
yield* Scope.addFinalizer(scope, Effect.sync(() => {
|
|
182
|
+
if (dialogs.get(state) === dialog)
|
|
183
|
+
dialogs.delete(state);
|
|
184
|
+
}));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Renders native dialog content synchronized with hydrated state.
|
|
189
|
+
* @remarks
|
|
190
|
+
* ## Why
|
|
191
|
+
* `Content` uses a real `<dialog>` and its `show()`, `showModal()`, `close()`,
|
|
192
|
+
* `cancel`, `close`, and `toggle` lifecycle. The browser therefore owns focus,
|
|
193
|
+
* top-layer placement, modal inertness, and default Escape behavior.
|
|
194
|
+
* ## Ownership and lifetime
|
|
195
|
+
* Running the Fx owns native listeners, dialog registration, and the
|
|
196
|
+
* NativeDialog observer in its Effect Scope. Finalization removes registration
|
|
197
|
+
* only when it still points at this element. A custom host must preserve native
|
|
198
|
+
* dialog identity, naming props, lifecycle handlers, and exactly one hydration
|
|
199
|
+
* ref owner.
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* import { Effect } from "effect"
|
|
203
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
204
|
+
*
|
|
205
|
+
* const program = Effect.gen(function* () {
|
|
206
|
+
* const state = yield* Dialog.makeState()
|
|
207
|
+
* return Dialog.Content({
|
|
208
|
+
* state,
|
|
209
|
+
* id: "confirm",
|
|
210
|
+
* label: "Confirm",
|
|
211
|
+
* content: "Continue?"
|
|
212
|
+
* })
|
|
213
|
+
* })
|
|
214
|
+
* ```
|
|
215
|
+
* @since 1.0.0
|
|
216
|
+
* @category components
|
|
217
|
+
*/
|
|
218
|
+
export function Content(options, host) {
|
|
219
|
+
return Dom.renderHost()(options, host, contentInternalProps(options), options.content, (props, content) => {
|
|
220
|
+
return html `<dialog ...${props}>${content}</dialog>`;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/** Canonical widget alias for `Content`.
|
|
224
|
+
* @remarks
|
|
225
|
+
* ## Why
|
|
226
|
+
* The alias provides the widget name while `Content` identifies compound use.
|
|
227
|
+
* ## Ownership and lifetime
|
|
228
|
+
* It has exactly the same native element and Scope ownership as `Content`.
|
|
229
|
+
* @since 1.0.0
|
|
230
|
+
* @category aliases
|
|
231
|
+
*/
|
|
232
|
+
export const Dialog = Content;
|
|
233
|
+
function closeInternalProps(options, requestCloseLifecycle) {
|
|
234
|
+
return ({ property }) => ({
|
|
235
|
+
type: "button",
|
|
236
|
+
commandfor: property("controls", undefined),
|
|
237
|
+
command: options.controls === undefined
|
|
238
|
+
? undefined
|
|
239
|
+
: requestCloseLifecycle
|
|
240
|
+
? "request-close"
|
|
241
|
+
: "close",
|
|
242
|
+
onclick: options.controls !== undefined
|
|
243
|
+
? undefined
|
|
244
|
+
: requestCloseLifecycle
|
|
245
|
+
? requestClose(options.state)
|
|
246
|
+
: close(options.state),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/** Renders a button that closes a dialog without a cancel request.
|
|
250
|
+
* @remarks
|
|
251
|
+
* ## Why
|
|
252
|
+
* Accepted actions need a direct close path distinct from cancelable dismissal.
|
|
253
|
+
* `controls` selects native `command="close"` and disables the Fx click
|
|
254
|
+
* fallback; omit it when native dialog commands are unavailable.
|
|
255
|
+
* ## Ownership and lifetime
|
|
256
|
+
* Running the Fx owns handlers/dynamic content by Scope. Custom hosts must
|
|
257
|
+
* preserve type, command or fallback click behavior, and content.
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* import { Effect } from "effect"
|
|
261
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
262
|
+
*
|
|
263
|
+
* const program = Effect.gen(function* () {
|
|
264
|
+
* const state = yield* Dialog.makeState({ open: true })
|
|
265
|
+
* return Dialog.Close({ state, content: "Done" })
|
|
266
|
+
* })
|
|
267
|
+
* ```
|
|
268
|
+
* @since 1.0.0
|
|
269
|
+
* @category components
|
|
270
|
+
*/
|
|
271
|
+
export function Close(options, host) {
|
|
272
|
+
return Dom.renderHost()(options, host, closeInternalProps(options, false), options.content, (props, content) => html `<button ...${props}>${content}</button>`);
|
|
273
|
+
}
|
|
274
|
+
/** Alias for direct `Close` behavior.
|
|
275
|
+
* @remarks
|
|
276
|
+
* ## Why
|
|
277
|
+
* The alias supports dismissal vocabulary without introducing another lifecycle.
|
|
278
|
+
* ## Ownership and lifetime
|
|
279
|
+
* It has exactly the same Scope and native button ownership as `Close`.
|
|
280
|
+
* @since 1.0.0
|
|
281
|
+
* @category aliases
|
|
282
|
+
*/
|
|
283
|
+
export const Dismiss = Close;
|
|
284
|
+
/** Renders a button that starts the cancelable native close-request lifecycle.
|
|
285
|
+
* @remarks
|
|
286
|
+
* ## Why
|
|
287
|
+
* Consumers can intercept a real `cancel` event before the dialog closes.
|
|
288
|
+
* `controls` selects native `command="request-close"` and disables the Fx click
|
|
289
|
+
* fallback; omit it when native dialog commands are unavailable.
|
|
290
|
+
* ## Ownership and lifetime
|
|
291
|
+
* Running the Fx owns handlers by Scope; dialog registration remains owned by
|
|
292
|
+
* the corresponding `Content` Scope.
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* import { Effect } from "effect"
|
|
296
|
+
* import * as Dialog from "@typed/ui/Dialog"
|
|
297
|
+
*
|
|
298
|
+
* const program = Effect.gen(function* () {
|
|
299
|
+
* const state = yield* Dialog.makeState({ open: true })
|
|
300
|
+
* return Dialog.RequestClose({ state, content: "Cancel" })
|
|
301
|
+
* })
|
|
302
|
+
* ```
|
|
303
|
+
* @since 1.0.0
|
|
304
|
+
* @category components
|
|
305
|
+
*/
|
|
306
|
+
export function RequestClose(options, host) {
|
|
307
|
+
return Dom.renderHost()(options, host, closeInternalProps(options, true), options.content, (props, content) => html `<button ...${props}>${content}</button>`);
|
|
308
|
+
}
|
|
309
|
+
function headingInternalProps() {
|
|
310
|
+
return ({ property }) => ({
|
|
311
|
+
id: property("id", undefined),
|
|
312
|
+
role: "heading",
|
|
313
|
+
"aria-level": property("level", 2),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/** Renders a dialog heading with explicit ARIA level semantics.
|
|
317
|
+
* @remarks
|
|
318
|
+
* ## Why
|
|
319
|
+
* The helper creates a reliable naming target and preserves heading hierarchy.
|
|
320
|
+
* ## Ownership and lifetime
|
|
321
|
+
* Running the Fx owns dynamic content by Scope. A custom host must preserve id,
|
|
322
|
+
* role, and `aria-level`.
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* import { Heading } from "@typed/ui/Dialog"
|
|
326
|
+
*
|
|
327
|
+
* const title = Heading({ id: "confirm-title", content: "Confirm" })
|
|
328
|
+
* ```
|
|
329
|
+
* @since 1.0.0
|
|
330
|
+
* @category components
|
|
331
|
+
*/
|
|
332
|
+
export function Heading(options, host) {
|
|
333
|
+
return Dom.renderHost()(options, host, headingInternalProps(), options.content, (props, content) => html `<h2 ...${props}>${content}</h2>`);
|
|
334
|
+
}
|
|
335
|
+
function descriptionInternalProps() {
|
|
336
|
+
return ({ property }) => ({ id: property("id", undefined) });
|
|
337
|
+
}
|
|
338
|
+
/** Renders descriptive dialog text with an optional relationship id.
|
|
339
|
+
* @remarks
|
|
340
|
+
* ## Why
|
|
341
|
+
* The helper keeps accessible description wiring explicit and platform-native.
|
|
342
|
+
* ## Ownership and lifetime
|
|
343
|
+
* Running the Fx owns dynamic content by Scope. A custom host must preserve id.
|
|
344
|
+
* @example
|
|
345
|
+
* ```ts
|
|
346
|
+
* import { Description } from "@typed/ui/Dialog"
|
|
347
|
+
*
|
|
348
|
+
* const detail = Description({ id: "confirm-detail", content: "This cannot be undone." })
|
|
349
|
+
* ```
|
|
350
|
+
* @since 1.0.0
|
|
351
|
+
* @category components
|
|
352
|
+
*/
|
|
353
|
+
export function Description(options, host) {
|
|
354
|
+
return Dom.renderHost()(options, host, descriptionInternalProps(), options.content, (props, content) => html `<p ...${props}>${content}</p>`);
|
|
355
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type * as Scope from "effect/Scope";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import type { Fx } from "@typed/fx/Fx";
|
|
5
|
+
import { RefSubject } from "@typed/fx";
|
|
6
|
+
import { EventHandler, type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
|
|
7
|
+
import * as Dom from "./Dom.js";
|
|
8
|
+
import type { HostResult } from "./Dom/Types.js";
|
|
9
|
+
/** Current renderer-independent disclosure state.
|
|
10
|
+
* @remarks
|
|
11
|
+
* ## Why
|
|
12
|
+
* Open state remains testable without mounting native details content.
|
|
13
|
+
* ## Ownership and lifetime
|
|
14
|
+
* Plain state retains no resources; RefSubject lifetime is Scope-owned.
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
* @category state
|
|
17
|
+
*/
|
|
18
|
+
export interface State {
|
|
19
|
+
/** Whether the details element is open.
|
|
20
|
+
* @remarks
|
|
21
|
+
* ## Why
|
|
22
|
+
* One field coordinates application state with native element state.
|
|
23
|
+
* ## Ownership and lifetime
|
|
24
|
+
* Plain data acquires no resources.
|
|
25
|
+
* @since 1.0.0
|
|
26
|
+
* @category state
|
|
27
|
+
*/
|
|
28
|
+
readonly open: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Optional initial disclosure state.
|
|
31
|
+
* @remarks
|
|
32
|
+
* ## Why
|
|
33
|
+
* Omission produces a deterministic closed SSR snapshot.
|
|
34
|
+
* ## Ownership and lifetime
|
|
35
|
+
* Configuration is inert.
|
|
36
|
+
* @since 1.0.0
|
|
37
|
+
* @category state
|
|
38
|
+
*/
|
|
39
|
+
export interface InitialState {
|
|
40
|
+
/** Initial open state, defaulting to false.
|
|
41
|
+
* @remarks
|
|
42
|
+
* ## Why
|
|
43
|
+
* The value seeds the hydration contract.
|
|
44
|
+
* ## Ownership and lifetime
|
|
45
|
+
* Plain data retains no resources.
|
|
46
|
+
* @since 1.0.0
|
|
47
|
+
* @category state
|
|
48
|
+
*/
|
|
49
|
+
readonly open?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Schema for disclosure hydration state.
|
|
52
|
+
* @remarks
|
|
53
|
+
* ## Why
|
|
54
|
+
* Shared encoding keeps server and browser state compatible.
|
|
55
|
+
* ## Ownership and lifetime
|
|
56
|
+
* The immutable schema acquires no resources.
|
|
57
|
+
* @since 1.0.0
|
|
58
|
+
* @category schemas
|
|
59
|
+
*/
|
|
60
|
+
export declare const StateSchema: Schema.Struct<{
|
|
61
|
+
readonly open: Schema.Boolean;
|
|
62
|
+
}>;
|
|
63
|
+
/** Creates hydrated disclosure state.
|
|
64
|
+
* @remarks
|
|
65
|
+
* ## Why
|
|
66
|
+
* Applications can own and test open-state transitions independently of UI.
|
|
67
|
+
* ## Ownership and lifetime
|
|
68
|
+
* The calling Effect Scope owns the returned RefSubject.
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { Effect } from "effect"
|
|
72
|
+
* import * as Disclosure from "@typed/ui/Disclosure"
|
|
73
|
+
*
|
|
74
|
+
* const program = Effect.gen(function* () {
|
|
75
|
+
* return yield* Disclosure.makeState({ open: true })
|
|
76
|
+
* })
|
|
77
|
+
* ```
|
|
78
|
+
* @since 1.0.0
|
|
79
|
+
* @category constructors
|
|
80
|
+
*/
|
|
81
|
+
export declare function makeState(initial?: InitialState): Effect.Effect<RefSubject.HydratedRefSubject<{
|
|
82
|
+
readonly open: boolean;
|
|
83
|
+
}, Schema.SchemaError, never, never>, never, Scope.Scope>;
|
|
84
|
+
/** Sets disclosure visibility.
|
|
85
|
+
* @remarks
|
|
86
|
+
* ## Why
|
|
87
|
+
* Explicit state transitions remain composable with Effect and outside renderers.
|
|
88
|
+
* ## Ownership and lifetime
|
|
89
|
+
* The Effect reuses the existing state lifetime and acquires no resource.
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { Effect } from "effect"
|
|
93
|
+
* import * as Disclosure from "@typed/ui/Disclosure"
|
|
94
|
+
*
|
|
95
|
+
* const program = Effect.gen(function* () {
|
|
96
|
+
* const state = yield* Disclosure.makeState()
|
|
97
|
+
* yield* Disclosure.setOpen(state, true)
|
|
98
|
+
* })
|
|
99
|
+
* ```
|
|
100
|
+
* @since 1.0.0
|
|
101
|
+
* @category state
|
|
102
|
+
*/
|
|
103
|
+
export declare function setOpen<E, R>(state: RefSubject.RefSubject<State, E, R>, open: boolean): Effect.Effect<State, E, R>;
|
|
104
|
+
/** Options for the native `<summary>` trigger.
|
|
105
|
+
* @remarks
|
|
106
|
+
* ## Why
|
|
107
|
+
* Summary preserves built-in pointer, keyboard, and disclosure behavior.
|
|
108
|
+
* ## Ownership and lifetime
|
|
109
|
+
* Options are inert; dynamic content follows the rendered Scope.
|
|
110
|
+
* @since 1.0.0
|
|
111
|
+
* @category models
|
|
112
|
+
*/
|
|
113
|
+
export interface ButtonOptions extends Dom.HostOptions<HTMLElement> {
|
|
114
|
+
/** Visible summary content and accessible name.
|
|
115
|
+
* @remarks
|
|
116
|
+
* ## Why
|
|
117
|
+
* Native summary content labels its containing details element.
|
|
118
|
+
* ## Ownership and lifetime
|
|
119
|
+
* Dynamic content follows the trigger Scope.
|
|
120
|
+
* @since 1.0.0
|
|
121
|
+
* @category content
|
|
122
|
+
*/
|
|
123
|
+
readonly content: Renderable.Any;
|
|
124
|
+
}
|
|
125
|
+
declare function buttonInternalProps(): {};
|
|
126
|
+
type ButtonInternalProps = ReturnType<typeof buttonInternalProps>;
|
|
127
|
+
/** Renders a native `<summary>` disclosure trigger.
|
|
128
|
+
* @remarks
|
|
129
|
+
* ## Why
|
|
130
|
+
* The browser owns activation and toggling; Typed does not emulate them with
|
|
131
|
+
* click handlers or synthetic events.
|
|
132
|
+
* ## Ownership and lifetime
|
|
133
|
+
* Running the Fx owns dynamic content in its Scope. A custom host must remain a
|
|
134
|
+
* valid summary participant inside the associated details element.
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { Button } from "@typed/ui/Disclosure"
|
|
138
|
+
*
|
|
139
|
+
* const summary = Button({ content: "Advanced settings" })
|
|
140
|
+
* ```
|
|
141
|
+
* @since 1.0.0
|
|
142
|
+
* @category components
|
|
143
|
+
*/
|
|
144
|
+
export declare function Button<const Options extends ButtonOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, ButtonInternalProps>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
|
|
145
|
+
/** Options for native `<details>` content.
|
|
146
|
+
* @remarks
|
|
147
|
+
* ## Why
|
|
148
|
+
* A shared state lets application code observe and control the browser-owned
|
|
149
|
+
* disclosure lifecycle.
|
|
150
|
+
* ## Ownership and lifetime
|
|
151
|
+
* Options are inert; rendering owns listeners and the NativeDetails observer.
|
|
152
|
+
* @since 1.0.0
|
|
153
|
+
* @category models
|
|
154
|
+
*/
|
|
155
|
+
export interface ContentOptions extends Dom.HostOptions<HTMLDetailsElement> {
|
|
156
|
+
/** Hydrated state synchronized with the details element.
|
|
157
|
+
* @remarks
|
|
158
|
+
* ## Why
|
|
159
|
+
* Native toggle events and application updates converge on one source.
|
|
160
|
+
* ## Ownership and lifetime
|
|
161
|
+
* The content borrows state; its originating Scope owns it.
|
|
162
|
+
* @since 1.0.0
|
|
163
|
+
* @category state
|
|
164
|
+
*/
|
|
165
|
+
readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
|
|
166
|
+
/** Summary and disclosed body content.
|
|
167
|
+
* @remarks
|
|
168
|
+
* ## Why
|
|
169
|
+
* Keeping the subtree renderable preserves Typed error/service composition.
|
|
170
|
+
* ## Ownership and lifetime
|
|
171
|
+
* Dynamic content follows the details Scope.
|
|
172
|
+
* @since 1.0.0
|
|
173
|
+
* @category content
|
|
174
|
+
*/
|
|
175
|
+
readonly content: Renderable.Any;
|
|
176
|
+
}
|
|
177
|
+
declare function contentInternalProps<const Options extends ContentOptions>(options: Options): () => {
|
|
178
|
+
readonly ontoggle: EventHandler.EventHandler<Event, Schema.SchemaError, never>;
|
|
179
|
+
readonly ref: import("./Dom/Refs.js").ComposedRef<RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>, (element: HTMLDetailsElement) => Effect.Effect<void, Schema.SchemaError, Scope.Scope>> | undefined;
|
|
180
|
+
};
|
|
181
|
+
type ContentInternalProps<Options extends ContentOptions> = ReturnType<ReturnType<typeof contentInternalProps<Options>>>;
|
|
182
|
+
/** Renders native details content synchronized with hydrated state.
|
|
183
|
+
* @remarks
|
|
184
|
+
* ## Why
|
|
185
|
+
* Native disclosure behavior, semantics, and `toggle` events stay intact while
|
|
186
|
+
* Effect state can control or observe visibility.
|
|
187
|
+
* ## Ownership and lifetime
|
|
188
|
+
* Running the Fx owns the native listener and scoped observer. A custom host
|
|
189
|
+
* must preserve the toggle handler and exactly one composed hydration ref.
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* import { Effect } from "effect"
|
|
193
|
+
* import * as Disclosure from "@typed/ui/Disclosure"
|
|
194
|
+
*
|
|
195
|
+
* const program = Effect.gen(function* () {
|
|
196
|
+
* const state = yield* Disclosure.makeState()
|
|
197
|
+
* return Disclosure.Content({
|
|
198
|
+
* state,
|
|
199
|
+
* content: Disclosure.Button({ content: "More" })
|
|
200
|
+
* })
|
|
201
|
+
* })
|
|
202
|
+
* ```
|
|
203
|
+
* @since 1.0.0
|
|
204
|
+
* @category components
|
|
205
|
+
*/
|
|
206
|
+
export declare function Content<const Options extends ContentOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, ContentInternalProps<Options>>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
|
|
207
|
+
/** Canonical alias for `Content`.
|
|
208
|
+
* @remarks
|
|
209
|
+
* ## Why
|
|
210
|
+
* The widget name remains convenient while `Content` identifies its role in
|
|
211
|
+
* compound composition.
|
|
212
|
+
* ## Ownership and lifetime
|
|
213
|
+
* It has exactly the same Scope and details-element ownership as `Content`.
|
|
214
|
+
* @since 1.0.0
|
|
215
|
+
* @category aliases
|
|
216
|
+
*/
|
|
217
|
+
export declare const Disclosure: typeof Content;
|
|
218
|
+
export {};
|
|
219
|
+
//# sourceMappingURL=Disclosure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Disclosure.d.ts","sourceRoot":"","sources":["../src/Disclosure.ts"],"names":[],"mappings":"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"}
|