@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.
- 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/Heading.js
ADDED
|
@@ -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":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EACL,YAAY,EAEZ,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW;;;EAA6D,CAAC;AAEtF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY;;;0DAE9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAC1B,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,EACzC,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAE5B;AAeD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAc,SAAQ,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC;IACrE;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACzE;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,iBAAS,mBAAmB,CAAC,KAAK,CAAC,OAAO,SAAS,aAAa,EAAE,OAAO,EAAE,OAAO;aAK5E,eAAe;aACf,OAAO;aACP,MAAM;aAQN,SAAS;aAKT,YAAY;aACZ,YAAY;EAEjB;AAED,KAAK,mBAAmB,CAAC,OAAO,SAAS,aAAa,IAAI,UAAU,CAClE,UAAU,CAAC,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAChD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,MAAM,CAAC,KAAK,CAAC,OAAO,SAAS,aAAa,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAC/F,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,EAC1D,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,GACA,EAAE,CACH,WAAW,EACX,UAAU,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,EAChC,UAAU,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,cAAc,CACnE,CAcA;AAED,UAAU,kBAAmB,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IAClE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACzE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;CAClC;AAED,KAAK,cAAc,GACf;IACE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAC1D,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC;CAC7B,GACD;IACE,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAChE,CAAC;AAEN;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG,cAAc,CAAC;AAEjE,iBAAS,oBAAoB,CAAC,KAAK,CAAC,OAAO,SAAS,cAAc,EAAE,OAAO,EAAE,OAAO,kBAE5D,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;;mBAG7C,QAAQ;;;sBAGL,QAAQ;;;;;;;;EA+BtB;AAED,KAAK,oBAAoB,CAAC,OAAO,SAAS,cAAc,IAAI,UAAU,CACpE,UAAU,CAAC,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC,CACjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CACrB,KAAK,CAAC,OAAO,SAAS,cAAc,EACpC,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAErC,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC,EAC3D,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,GACA,EAAE,CACH,WAAW,EACX,UAAU,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,EAChC,UAAU,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,cAAc,CACnE,CAUA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,gBAAU,CAAC"}
|
|
@@ -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;
|
package/dist/HttpRouter.d.ts
CHANGED
|
@@ -1,13 +1,136 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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 =
|
|
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) =>
|
|
9
|
-
<E, R>(router: HttpRouter, input: Matcher<RenderEvent, E, R>):
|
|
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
|
-
|
|
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
|