@typed/ui 1.0.0-beta.4 → 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 +196 -53
  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 +45 -18
  153. package/src/HttpRouter.test.ts +0 -256
  154. package/src/HttpRouter.ts +0 -165
  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,48 @@
1
+ import { html } from "@typed/template";
2
+ import * as Dom from "./Dom.js";
3
+ function internalProps({ property, }) {
4
+ return { role: "heading", "aria-level": property("level", 1) };
5
+ }
6
+ /**
7
+ * Renders a host with `role="heading"` and a reactive `aria-level`.
8
+ * @remarks
9
+ * ## Why
10
+ * The primitive lets a design system preserve document-outline semantics when
11
+ * its visual host cannot be selected statically.
12
+ * ## Ownership and lifetime
13
+ * Running the returned Fx owns its dynamic attributes and content in an Effect
14
+ * Scope. A custom host must preserve `role` and `aria-level`.
15
+ * @example
16
+ * ```ts
17
+ * import { Heading } from "@typed/ui/Heading"
18
+ *
19
+ * const title = Heading({ level: 3, content: "Details" })
20
+ * ```
21
+ * @since 1.0.0
22
+ * @category components
23
+ */
24
+ export function Heading(options, host) {
25
+ return Dom.renderHost()(options, host, internalProps, options.content, (props, content) => html `<div ...${props}>${content}</div>`);
26
+ }
27
+ /**
28
+ * Alias for `Heading` retained for level-oriented imports.
29
+ * @remarks
30
+ * ## Why
31
+ * The alias names the role the component plays in a contextual heading system.
32
+ * ## Ownership and lifetime
33
+ * It has exactly the same Scope and host ownership as `Heading`.
34
+ * @since 1.0.0
35
+ * @category aliases
36
+ */
37
+ export const Level = Heading;
38
+ /**
39
+ * Descriptive alias for `Heading`.
40
+ * @remarks
41
+ * ## Why
42
+ * The name remains available without duplicating an implementation contract.
43
+ * ## Ownership and lifetime
44
+ * It has exactly the same Scope and host ownership as `Heading`.
45
+ * @since 1.0.0
46
+ * @category aliases
47
+ */
48
+ export const HeadingLevel = Heading;
@@ -0,0 +1,297 @@
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 hovercard identity and visibility.
10
+ * @remarks
11
+ * ## Why
12
+ * A stable id links the anchor to focusable native popover content while state
13
+ * remains renderer-independent.
14
+ * ## Ownership and lifetime
15
+ * Plain data retains no resources; RefSubject observation is Scope-owned.
16
+ * @since 1.0.0
17
+ * @category state
18
+ */
19
+ export interface State {
20
+ /** Stable id used by `aria-controls` and content.
21
+ * @remarks
22
+ * ## Why
23
+ * Deterministic identity preserves server/client DOM relationships.
24
+ * ## Ownership and lifetime
25
+ * Plain data acquires no resources.
26
+ * @since 1.0.0
27
+ * @category relationships
28
+ */
29
+ readonly id: string;
30
+ /** Whether the hovercard is open.
31
+ * @remarks
32
+ * ## Why
33
+ * One value coordinates delayed interactions and native popover state.
34
+ * ## Ownership and lifetime
35
+ * Plain data acquires no resources.
36
+ * @since 1.0.0
37
+ * @category state
38
+ */
39
+ readonly open: boolean;
40
+ }
41
+ /** Initial hovercard identity and visibility.
42
+ * @remarks
43
+ * ## Why
44
+ * Requiring an id makes the anchor/content relationship explicit.
45
+ * ## Ownership and lifetime
46
+ * Configuration is inert.
47
+ * @since 1.0.0
48
+ * @category state
49
+ */
50
+ export interface InitialState {
51
+ /** Stable content id.
52
+ * @remarks
53
+ * ## Why
54
+ * Explicit ids prevent hydration relationship drift.
55
+ * ## Ownership and lifetime
56
+ * Plain data retains no resources.
57
+ * @since 1.0.0
58
+ * @category relationships
59
+ */
60
+ readonly id: string;
61
+ /** Initial visibility, defaulting to false.
62
+ * @remarks
63
+ * ## Why
64
+ * Closed is deterministic before focus or hover interaction.
65
+ * ## Ownership and lifetime
66
+ * Plain data retains no resources.
67
+ * @since 1.0.0
68
+ * @category state
69
+ */
70
+ readonly open?: boolean;
71
+ }
72
+ /** Schema for hovercard hydration state.
73
+ * @remarks
74
+ * ## Why
75
+ * Shared identity/open encoding keeps SSR and browser state compatible.
76
+ * ## Ownership and lifetime
77
+ * The immutable schema acquires no resources.
78
+ * @since 1.0.0
79
+ * @category schemas
80
+ */
81
+ export declare const StateSchema: Schema.Struct<{
82
+ readonly id: Schema.String;
83
+ readonly open: Schema.Boolean;
84
+ }>;
85
+ /** Creates hydrated hovercard state.
86
+ * @remarks
87
+ * ## Why
88
+ * Visibility and identity can be tested without mounting UI.
89
+ * ## Ownership and lifetime
90
+ * The calling Effect Scope owns the returned RefSubject.
91
+ * @example
92
+ * ```ts
93
+ * import { Effect } from "effect"
94
+ * import * as Hovercard from "@typed/ui/Hovercard"
95
+ *
96
+ * const program = Effect.gen(function* () {
97
+ * return yield* Hovercard.makeState({ id: "author-card" })
98
+ * })
99
+ * ```
100
+ * @since 1.0.0
101
+ * @category constructors
102
+ */
103
+ export declare function makeState(initial: InitialState): Effect.Effect<RefSubject.HydratedRefSubject<{
104
+ readonly id: string;
105
+ readonly open: boolean;
106
+ }, Schema.SchemaError, never, never>, never, Scope.Scope>;
107
+ /** Sets hovercard visibility.
108
+ * @remarks
109
+ * ## Why
110
+ * Delayed focus/hover flows converge on one atomic state update.
111
+ * ## Ownership and lifetime
112
+ * The Effect uses the existing RefSubject lifetime and acquires no resource.
113
+ * @example
114
+ * ```ts
115
+ * import { Effect } from "effect"
116
+ * import * as Hovercard from "@typed/ui/Hovercard"
117
+ *
118
+ * const program = Effect.gen(function* () {
119
+ * const state = yield* Hovercard.makeState({ id: "author-card" })
120
+ * yield* Hovercard.setOpen(state, true)
121
+ * })
122
+ * ```
123
+ * @since 1.0.0
124
+ * @category state
125
+ */
126
+ export declare function setOpen<E, R>(state: RefSubject.RefSubject<State, E, R>, open: boolean): Effect.Effect<State, E, R>;
127
+ /** Options for a hovercard anchor host.
128
+ * @remarks
129
+ * ## Why
130
+ * Pointer entry works with the default span. Keyboard focus behavior requires
131
+ * the host itself to be focusable, for example `props: { tabindex: 0 }` or a
132
+ * focusable custom host; focus and blur on descendants do not bubble to it.
133
+ * ## Ownership and lifetime
134
+ * Options are inert; rendering owns handlers/delayed effects by Scope.
135
+ * @since 1.0.0
136
+ * @category models
137
+ */
138
+ export interface AnchorOptions extends Dom.HostOptions<HTMLSpanElement> {
139
+ /** Hydrated state shared with hovercard content.
140
+ * @remarks
141
+ * ## Why
142
+ * Its id and open state coordinate the whole compound widget.
143
+ * ## Ownership and lifetime
144
+ * The anchor borrows state; its original Scope owns it.
145
+ * @since 1.0.0
146
+ * @category state
147
+ */
148
+ readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
149
+ /** Anchor content.
150
+ * @remarks
151
+ * ## Why
152
+ * The wrapper retains nested content, but focusable descendants do not
153
+ * activate the default span's non-bubbling focus and blur handlers.
154
+ * ## Ownership and lifetime
155
+ * Dynamic content follows the anchor Scope.
156
+ * @since 1.0.0
157
+ * @category content
158
+ */
159
+ readonly content: Renderable.Any;
160
+ /** Milliseconds before opening.
161
+ * @remarks
162
+ * ## Why
163
+ * Delay filters incidental pointer transit.
164
+ * ## Ownership and lifetime
165
+ * New schedules invalidate older delayed updates.
166
+ * @since 1.0.0
167
+ * @category timing
168
+ */
169
+ readonly showDelay?: number;
170
+ /** Milliseconds before closing.
171
+ * @remarks
172
+ * ## Why
173
+ * Delay gives users time to move focus or pointer toward content.
174
+ * ## Ownership and lifetime
175
+ * New schedules invalidate older delayed updates.
176
+ * @since 1.0.0
177
+ * @category timing
178
+ */
179
+ readonly hideDelay?: number;
180
+ }
181
+ declare function anchorInternalProps<const Options extends AnchorOptions>(options: Options): () => {
182
+ readonly "aria-controls": RefSubject.Computed<string, Schema.SchemaError, never>;
183
+ readonly onfocus: Effect.Effect<void, Schema.SchemaError, never>;
184
+ readonly onblur: EventHandler.EventHandler<FocusEvent, Schema.SchemaError, never>;
185
+ readonly onkeydown: EventHandler.EventHandler<KeyboardEvent, Schema.SchemaError, never>;
186
+ readonly onmouseenter: Effect.Effect<void, Schema.SchemaError, never>;
187
+ readonly onmouseleave: Effect.Effect<void, Schema.SchemaError, never>;
188
+ };
189
+ type AnchorInternalProps<Options extends AnchorOptions> = ReturnType<ReturnType<typeof anchorInternalProps<Options>>>;
190
+ /** Renders an anchor coordinating pointer and optional host-focus behavior.
191
+ * @remarks
192
+ * ## Why
193
+ * The default host is a non-focusable `<span>`, so it provides pointer behavior
194
+ * only. Give the span `tabindex`, or supply a natively focusable custom host,
195
+ * for direct focus, blur, and keydown handling. Unlike a tooltip, hovercard
196
+ * content may receive focus; host blur checks `relatedTarget` and stays open
197
+ * while focus moves inside the card. Nested-anchor focus does not bubble.
198
+ * ## Ownership and lifetime
199
+ * Running the Fx owns real DOM handlers and dynamic relationships in its Scope.
200
+ * Custom hosts must preserve `aria-controls` and all supplied handlers.
201
+ * @example
202
+ * ```ts
203
+ * import { Effect } from "effect"
204
+ * import * as Hovercard from "@typed/ui/Hovercard"
205
+ *
206
+ * const program = Effect.gen(function* () {
207
+ * const state = yield* Hovercard.makeState({ id: "author-card" })
208
+ * return Hovercard.Anchor({
209
+ * state,
210
+ * content: "Ada Lovelace",
211
+ * props: { tabindex: 0 }
212
+ * })
213
+ * })
214
+ * ```
215
+ * @since 1.0.0
216
+ * @category components
217
+ */
218
+ export declare function Anchor<const Options extends AnchorOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, AnchorInternalProps<Options>>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
219
+ interface ContentOptionsBase extends Dom.HostOptions<HTMLDivElement> {
220
+ readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
221
+ readonly content: Renderable.Any;
222
+ }
223
+ type AccessibleName = {
224
+ readonly label: Renderable.Any<string | null | undefined>;
225
+ readonly labelledBy?: never;
226
+ } | {
227
+ readonly label?: never;
228
+ readonly labelledBy: Renderable.Any<string | null | undefined>;
229
+ };
230
+ /** Hovercard content options requiring one accessible naming strategy.
231
+ * @remarks
232
+ * ## Why
233
+ * Focusable dialog-like content needs an explicit name; the type makes label
234
+ * and labelled-by mutually exclusive.
235
+ *
236
+ * ## Options
237
+ *
238
+ * `state` provides the stable content id and open state, while `content` may
239
+ * contain focusable output. `label` names the card directly; `labelledBy`
240
+ * references visible naming content. Supplying both is rejected by the type.
241
+ * ## Ownership and lifetime
242
+ * Options are inert; rendering owns content/listeners/native ref by Scope.
243
+ * @since 1.0.0
244
+ * @category models
245
+ */
246
+ export type ContentOptions = ContentOptionsBase & AccessibleName;
247
+ declare function contentInternalProps<const Options extends ContentOptions>(options: Options): ({ property }: Dom.InternalPropsHelpers<Options>) => {
248
+ readonly id: RefSubject.Computed<string, Schema.SchemaError, never>;
249
+ readonly role: "dialog";
250
+ readonly "aria-label": Exclude<Dom.Property<Options, "label">, Dom.Nullish> | undefined;
251
+ readonly "aria-labelledby": Exclude<Dom.Property<Options, "labelledBy">, Dom.Nullish> | undefined;
252
+ readonly popover: "manual";
253
+ readonly onfocusin: Effect.Effect<void, Schema.SchemaError, never>;
254
+ readonly onfocusout: EventHandler.EventHandler<FocusEvent, Schema.SchemaError, never>;
255
+ readonly onmouseenter: Effect.Effect<void, Schema.SchemaError, never>;
256
+ readonly onmouseleave: EventHandler.EventHandler<MouseEvent, Schema.SchemaError, never>;
257
+ readonly onkeydown: EventHandler.EventHandler<KeyboardEvent, Schema.SchemaError, never>;
258
+ readonly ontoggle: EventHandler.EventHandler<Event, Schema.SchemaError, never>;
259
+ readonly ref: import("./Dom/Refs.js").ComposedRef<RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>, (element: HTMLElement) => Effect.Effect<void, Schema.SchemaError, Scope.Scope>> | undefined;
260
+ };
261
+ type ContentInternalProps<Options extends ContentOptions> = ReturnType<ReturnType<typeof contentInternalProps<Options>>>;
262
+ /** Renders named, focusable hovercard content in the native top layer.
263
+ * @remarks
264
+ * ## Why
265
+ * `role="dialog"` distinguishes interactive content from a tooltip. Native
266
+ * popover placement is retained, and focus/pointer transitions prevent closing
267
+ * while interaction remains inside the card.
268
+ * ## Ownership and lifetime
269
+ * Running the Fx owns handlers and NativePopover observation in its Scope. A
270
+ * custom host must preserve id, role, accessible name, manual popover,
271
+ * lifecycle handlers, and one hydration ref owner.
272
+ * @example
273
+ * ```ts
274
+ * import { Effect } from "effect"
275
+ * import * as Hovercard from "@typed/ui/Hovercard"
276
+ *
277
+ * const program = Effect.gen(function* () {
278
+ * const state = yield* Hovercard.makeState({ id: "author-card" })
279
+ * return Hovercard.Content({ state, label: "Author", content: "Mathematician" })
280
+ * })
281
+ * ```
282
+ * @since 1.0.0
283
+ * @category components
284
+ */
285
+ 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>;
286
+ /** Canonical widget alias for `Content`.
287
+ * @remarks
288
+ * ## Why
289
+ * The alias provides the widget name while `Content` names compound use.
290
+ * ## Ownership and lifetime
291
+ * It has exactly the same Scope and native popover ownership as `Content`.
292
+ * @since 1.0.0
293
+ * @category aliases
294
+ */
295
+ export declare const Hovercard: typeof Content;
296
+ export {};
297
+ //# sourceMappingURL=Hovercard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Hovercard.d.ts","sourceRoot":"","sources":["../src/Hovercard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,188 @@
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
+ import * as NativePopover from "./NativePopover.js";
7
+ /** Schema for hovercard hydration state.
8
+ * @remarks
9
+ * ## Why
10
+ * Shared identity/open encoding keeps SSR and browser state compatible.
11
+ * ## Ownership and lifetime
12
+ * The immutable schema acquires no resources.
13
+ * @since 1.0.0
14
+ * @category schemas
15
+ */
16
+ export const StateSchema = Schema.Struct({ id: Schema.String, open: Schema.Boolean });
17
+ /** Creates hydrated hovercard state.
18
+ * @remarks
19
+ * ## Why
20
+ * Visibility and identity can be tested without mounting UI.
21
+ * ## Ownership and lifetime
22
+ * The calling Effect Scope owns the returned RefSubject.
23
+ * @example
24
+ * ```ts
25
+ * import { Effect } from "effect"
26
+ * import * as Hovercard from "@typed/ui/Hovercard"
27
+ *
28
+ * const program = Effect.gen(function* () {
29
+ * return yield* Hovercard.makeState({ id: "author-card" })
30
+ * })
31
+ * ```
32
+ * @since 1.0.0
33
+ * @category constructors
34
+ */
35
+ export function makeState(initial) {
36
+ return RefSubject.hydrate(StateSchema, { id: initial.id, open: initial.open ?? false });
37
+ }
38
+ /** Sets hovercard visibility.
39
+ * @remarks
40
+ * ## Why
41
+ * Delayed focus/hover flows converge on one atomic state update.
42
+ * ## Ownership and lifetime
43
+ * The Effect uses the existing RefSubject lifetime and acquires no resource.
44
+ * @example
45
+ * ```ts
46
+ * import { Effect } from "effect"
47
+ * import * as Hovercard from "@typed/ui/Hovercard"
48
+ *
49
+ * const program = Effect.gen(function* () {
50
+ * const state = yield* Hovercard.makeState({ id: "author-card" })
51
+ * yield* Hovercard.setOpen(state, true)
52
+ * })
53
+ * ```
54
+ * @since 1.0.0
55
+ * @category state
56
+ */
57
+ export function setOpen(state, open) {
58
+ return RefSubject.update(state, (current) => ({ ...current, open }));
59
+ }
60
+ const scheduleVersions = new WeakMap();
61
+ const scheduleOpen = Effect.fn(function* (state, open, delay) {
62
+ const version = (scheduleVersions.get(state) ?? 0) + 1;
63
+ scheduleVersions.set(state, version);
64
+ if (delay > 0)
65
+ yield* Effect.sleep(delay);
66
+ if (scheduleVersions.get(state) === version)
67
+ yield* setOpen(state, open);
68
+ });
69
+ function anchorInternalProps(options) {
70
+ const id = RefSubject.map(options.state, (state) => state.id);
71
+ return () => ({
72
+ "aria-controls": id,
73
+ onfocus: scheduleOpen(options.state, true, options.showDelay ?? 0),
74
+ onblur: EventHandler.make(Effect.fn(function* (event) {
75
+ const contentId = (yield* options.state).id;
76
+ const content = Dom.currentTarget(event).ownerDocument.getElementById(contentId);
77
+ if (event.relatedTarget instanceof Node && content?.contains(event.relatedTarget))
78
+ return;
79
+ yield* scheduleOpen(options.state, false, options.hideDelay ?? 0);
80
+ })),
81
+ onkeydown: EventHandler.make(Effect.fn(function* (event) {
82
+ if (event.key === "Escape")
83
+ yield* scheduleOpen(options.state, false, 0);
84
+ })),
85
+ onmouseenter: scheduleOpen(options.state, true, options.showDelay ?? 0),
86
+ onmouseleave: scheduleOpen(options.state, false, options.hideDelay ?? 0),
87
+ });
88
+ }
89
+ /** Renders an anchor coordinating pointer and optional host-focus behavior.
90
+ * @remarks
91
+ * ## Why
92
+ * The default host is a non-focusable `<span>`, so it provides pointer behavior
93
+ * only. Give the span `tabindex`, or supply a natively focusable custom host,
94
+ * for direct focus, blur, and keydown handling. Unlike a tooltip, hovercard
95
+ * content may receive focus; host blur checks `relatedTarget` and stays open
96
+ * while focus moves inside the card. Nested-anchor focus does not bubble.
97
+ * ## Ownership and lifetime
98
+ * Running the Fx owns real DOM handlers and dynamic relationships in its Scope.
99
+ * Custom hosts must preserve `aria-controls` and all supplied handlers.
100
+ * @example
101
+ * ```ts
102
+ * import { Effect } from "effect"
103
+ * import * as Hovercard from "@typed/ui/Hovercard"
104
+ *
105
+ * const program = Effect.gen(function* () {
106
+ * const state = yield* Hovercard.makeState({ id: "author-card" })
107
+ * return Hovercard.Anchor({
108
+ * state,
109
+ * content: "Ada Lovelace",
110
+ * props: { tabindex: 0 }
111
+ * })
112
+ * })
113
+ * ```
114
+ * @since 1.0.0
115
+ * @category components
116
+ */
117
+ export function Anchor(options, host) {
118
+ return Dom.renderHost()(options, host, anchorInternalProps(options), options.content, (props, content) => html `<span ...${props}>${content}</span>`);
119
+ }
120
+ function contentInternalProps(options) {
121
+ const id = RefSubject.map(options.state, (state) => state.id);
122
+ return ({ property }) => ({
123
+ id,
124
+ role: "dialog",
125
+ "aria-label": property("label", undefined),
126
+ "aria-labelledby": property("labelledBy", undefined),
127
+ popover: "manual",
128
+ onfocusin: scheduleOpen(options.state, true, 0),
129
+ onfocusout: EventHandler.make(Effect.fn(function* (event) {
130
+ const content = Dom.currentTarget(event);
131
+ if (event.relatedTarget instanceof Node && content.contains(event.relatedTarget))
132
+ return;
133
+ yield* scheduleOpen(options.state, false, 0);
134
+ })),
135
+ onmouseenter: scheduleOpen(options.state, true, 0),
136
+ onmouseleave: EventHandler.make(Effect.fn(function* (event) {
137
+ const contentId = (yield* options.state).id;
138
+ if (event.relatedTarget instanceof Element &&
139
+ event.relatedTarget.getAttribute("aria-controls") === contentId)
140
+ return;
141
+ yield* scheduleOpen(options.state, false, 0);
142
+ })),
143
+ onkeydown: EventHandler.make(Effect.fn(function* (event) {
144
+ if (event.key === "Escape")
145
+ yield* scheduleOpen(options.state, false, 0);
146
+ })),
147
+ ontoggle: EventHandler.make(Effect.fn((event) => setOpen(options.state, Dom.toggleState(event) === "open"))),
148
+ ref: Dom.composeRefs(options.state, NativePopover.ref(options.state)),
149
+ });
150
+ }
151
+ /** Renders named, focusable hovercard content in the native top layer.
152
+ * @remarks
153
+ * ## Why
154
+ * `role="dialog"` distinguishes interactive content from a tooltip. Native
155
+ * popover placement is retained, and focus/pointer transitions prevent closing
156
+ * while interaction remains inside the card.
157
+ * ## Ownership and lifetime
158
+ * Running the Fx owns handlers and NativePopover observation in its Scope. A
159
+ * custom host must preserve id, role, accessible name, manual popover,
160
+ * lifecycle handlers, and one hydration ref owner.
161
+ * @example
162
+ * ```ts
163
+ * import { Effect } from "effect"
164
+ * import * as Hovercard from "@typed/ui/Hovercard"
165
+ *
166
+ * const program = Effect.gen(function* () {
167
+ * const state = yield* Hovercard.makeState({ id: "author-card" })
168
+ * return Hovercard.Content({ state, label: "Author", content: "Mathematician" })
169
+ * })
170
+ * ```
171
+ * @since 1.0.0
172
+ * @category components
173
+ */
174
+ export function Content(options, host) {
175
+ return Dom.renderHost()(options, host, contentInternalProps(options), options.content, (i, content) => {
176
+ return html `<div ...${i}>${content}</div>`;
177
+ });
178
+ }
179
+ /** Canonical widget alias for `Content`.
180
+ * @remarks
181
+ * ## Why
182
+ * The alias provides the widget name while `Content` names compound use.
183
+ * ## Ownership and lifetime
184
+ * It has exactly the same Scope and native popover ownership as `Content`.
185
+ * @since 1.0.0
186
+ * @category aliases
187
+ */
188
+ export const Hovercard = Content;
@@ -1,13 +1,136 @@
1
1
  import * as Effect from "effect/Effect";
2
- import * as Scope from "effect/Scope";
3
- import { type HttpRouter } from "effect/unstable/http/HttpRouter";
2
+ import { type HttpRouter, type Provided as HttpRouterProvided, type Request as HttpRouterRequest } from "effect/unstable/http/HttpRouter";
3
+ import * as HttpServerError from "effect/unstable/http/HttpServerError";
4
4
  import { type Matcher, type Router } from "@typed/router";
5
+ import { type Navigation } from "@typed/navigation";
5
6
  import { type RenderEvent } from "@typed/template";
6
- type ProvidedForSsr = Scope.Scope | Router;
7
+ type ProvidedForSsr = HttpRouterProvided | Navigation | Router;
8
+ type SsrForHttpRequirement<E, R> = Exclude<R, ProvidedForSsr> | HttpRouterRequest.From<"Error", E | HttpServerError.HttpServerError>;
9
+ type SsrForHttpEffect<E, R> = Effect.Effect<void, never, SsrForHttpRequirement<E, R>>;
10
+ /**
11
+ * Registers buffered, GET-only HTML routes for a matcher.
12
+ *
13
+ * Path captures take precedence over same-named query parameters. Rendering
14
+ * completes before the response is created; this adapter does not stream.
15
+ *
16
+ * @remarks
17
+ * ## Why
18
+ *
19
+ * Typed route matchers already describe decoding, guards, parent prefixes, and
20
+ * render output. `ssrForHttp` installs that same model in Effect's HttpRouter
21
+ * and buffers one complete HTML document for runtimes that require a known body.
22
+ *
23
+ * ## Ownership and lifetime
24
+ *
25
+ * Registration adds GET routes to the supplied Effect HttpRouter. Each request
26
+ * creates request-local memory navigation and current-route services; the
27
+ * request Effect owns rendering and all finalizers. Buffered rendering must
28
+ * complete before the response is returned. Rendering HTML does not mount or
29
+ * hydrate a browser client.
30
+ *
31
+ * ## Routing behavior
32
+ *
33
+ * Matcher cases sharing a path are tried in their compiled order. Path params
34
+ * overwrite same-named query params. Decode/guard failures continue candidate
35
+ * selection; a matched renderer failure stays in the typed request error channel.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { ssrForHttp } from "@typed/ui/HttpRouter"
40
+ * import { Effect } from "effect"
41
+ * import * as HttpRouter from "effect/unstable/http/HttpRouter"
42
+ * import { Parse, match } from "@typed/router"
43
+ * import { html } from "@typed/template"
44
+ *
45
+ * const pages = match(Parse("/"), html`<h1>Home</h1>`)
46
+ * const routes = Effect.gen(function* () {
47
+ * const router = yield* HttpRouter.make
48
+ * yield* ssrForHttp(router, pages)
49
+ * return router
50
+ * })
51
+ * ```
52
+ *
53
+ * @since 1.0.0
54
+ * @category http
55
+ */
7
56
  export declare const ssrForHttp: {
8
- <E, R>(input: Matcher<RenderEvent, E, R>): (router: HttpRouter) => Effect.Effect<void, never, Exclude<R, ProvidedForSsr>>;
9
- <E, R>(router: HttpRouter, input: Matcher<RenderEvent, E, R>): Effect.Effect<void, never, Exclude<R, ProvidedForSsr>>;
57
+ <E, R>(input: Matcher<RenderEvent, E, R>): (router: HttpRouter) => SsrForHttpEffect<E, R>;
58
+ <E, R>(router: HttpRouter, input: Matcher<RenderEvent, E, R>): SsrForHttpEffect<E, R>;
10
59
  };
11
- export declare function handleHttpServerError(router: HttpRouter): Effect.Effect<void, never, import("effect/unstable/http/HttpRouter").Request<"GlobalError", unknown>>;
60
+ /**
61
+ * Registers streaming, GET-only HTML routes for a matcher.
62
+ *
63
+ * Path captures take precedence over same-named query parameters. HTML chunks
64
+ * are emitted as they are rendered via `renderToHtml` and `HttpServerResponse.stream`.
65
+ *
66
+ * @remarks
67
+ * ## Why
68
+ *
69
+ * Streaming lets the server send ordered renderer-owned HTML chunks before the
70
+ * whole page completes while preserving the same Typed matcher and Effect HTTP model.
71
+ *
72
+ * ## Ownership and lifetime
73
+ *
74
+ * Registration adds GET routes to the supplied router. Each request owns its
75
+ * navigation/router services and stream Scope. Client cancellation interrupts
76
+ * rendering and runs finalizers. The stream carries typed renderer failures;
77
+ * producing HTML alone does not attach client hydration behavior.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { streamingSsrForHttp } from "@typed/ui/HttpRouter"
82
+ * import { Effect } from "effect"
83
+ * import * as HttpRouter from "effect/unstable/http/HttpRouter"
84
+ * import { Parse, match } from "@typed/router"
85
+ * import { html } from "@typed/template"
86
+ *
87
+ * const pages = match(Parse("/"), html`<h1>Streamed home</h1>`)
88
+ * const routes = Effect.gen(function* () {
89
+ * const router = yield* HttpRouter.make
90
+ * yield* streamingSsrForHttp(router, pages)
91
+ * return router
92
+ * })
93
+ * ```
94
+ *
95
+ * @since 1.0.0
96
+ * @category http
97
+ */
98
+ export declare const streamingSsrForHttp: {
99
+ <E, R>(input: Matcher<RenderEvent, E, R>): (router: HttpRouter) => SsrForHttpEffect<E, R>;
100
+ <E, R>(router: HttpRouter, input: Matcher<RenderEvent, E, R>): SsrForHttpEffect<E, R>;
101
+ };
102
+ /**
103
+ * Converts Effect HTTP server errors into empty 400, 404, or 500 responses.
104
+ * Other failures remain in the global error channel.
105
+ *
106
+ * @remarks
107
+ * ## Why
108
+ *
109
+ * Effect's structured server failures have a safe transport status, whereas
110
+ * application/domain errors must remain visible for deliberate handling.
111
+ *
112
+ * ## Ownership and lifetime
113
+ *
114
+ * The middleware is installed on the supplied router and runs in each request
115
+ * lifetime. It allocates no long-lived resource and does not log, swallow, or
116
+ * reclassify non-HttpServerError failures.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * import { handleHttpServerError } from "@typed/ui/HttpRouter"
121
+ * import { Effect } from "effect"
122
+ * import * as HttpRouter from "effect/unstable/http/HttpRouter"
123
+ *
124
+ * const router = Effect.gen(function* () {
125
+ * const router = yield* HttpRouter.make
126
+ * yield* handleHttpServerError(router)
127
+ * return router
128
+ * })
129
+ * ```
130
+ *
131
+ * @since 1.0.0
132
+ * @category http
133
+ */
134
+ export declare function handleHttpServerError(router: HttpRouter): Effect.Effect<void, never, HttpRouterRequest<"GlobalError", unknown>>;
12
135
  export {};
13
136
  //# sourceMappingURL=HttpRouter.d.ts.map