@typed/ui 1.0.0-beta.0 → 1.0.0-beta.10

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 (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -26
  3. package/dist/Alert.d.ts +72 -0
  4. package/dist/Alert.d.ts.map +1 -0
  5. package/dist/Alert.js +40 -0
  6. package/dist/Button.d.ts +100 -0
  7. package/dist/Button.d.ts.map +1 -0
  8. package/dist/Button.js +42 -0
  9. package/dist/Carousel.d.ts +346 -0
  10. package/dist/Carousel.d.ts.map +1 -0
  11. package/dist/Carousel.js +264 -0
  12. package/dist/Checkbox.d.ts +168 -0
  13. package/dist/Checkbox.d.ts.map +1 -0
  14. package/dist/Checkbox.js +146 -0
  15. package/dist/Collection.d.ts +254 -0
  16. package/dist/Collection.d.ts.map +1 -0
  17. package/dist/Collection.js +218 -0
  18. package/dist/Combobox.d.ts +367 -0
  19. package/dist/Combobox.d.ts.map +1 -0
  20. package/dist/Combobox.js +300 -0
  21. package/dist/Composite.d.ts +823 -0
  22. package/dist/Composite.d.ts.map +1 -0
  23. package/dist/Composite.js +615 -0
  24. package/dist/Dialog.d.ts +544 -0
  25. package/dist/Dialog.d.ts.map +1 -0
  26. package/dist/Dialog.js +357 -0
  27. package/dist/Disclosure.d.ts +219 -0
  28. package/dist/Disclosure.d.ts.map +1 -0
  29. package/dist/Disclosure.js +128 -0
  30. package/dist/Dom/Events.d.ts +122 -0
  31. package/dist/Dom/Events.d.ts.map +1 -0
  32. package/dist/Dom/Events.js +192 -0
  33. package/dist/Dom/Props.d.ts +161 -0
  34. package/dist/Dom/Props.d.ts.map +1 -0
  35. package/dist/Dom/Props.js +110 -0
  36. package/dist/Dom/Refs.d.ts +58 -0
  37. package/dist/Dom/Refs.d.ts.map +1 -0
  38. package/dist/Dom/Refs.js +61 -0
  39. package/dist/Dom/Render.d.ts +59 -0
  40. package/dist/Dom/Render.d.ts.map +1 -0
  41. package/dist/Dom/Render.js +71 -0
  42. package/dist/Dom/Types.d.ts +570 -0
  43. package/dist/Dom/Types.d.ts.map +1 -0
  44. package/dist/Dom/Types.js +1 -0
  45. package/dist/Dom/index.d.ts +20 -0
  46. package/dist/Dom/index.d.ts.map +1 -0
  47. package/dist/Dom/index.js +8 -0
  48. package/dist/Dom.d.ts +14 -0
  49. package/dist/Dom.d.ts.map +1 -0
  50. package/dist/Dom.js +13 -0
  51. package/dist/Focusable.d.ts +85 -0
  52. package/dist/Focusable.d.ts.map +1 -0
  53. package/dist/Focusable.js +35 -0
  54. package/dist/Form.d.ts +1727 -0
  55. package/dist/Form.d.ts.map +1 -0
  56. package/dist/Form.js +1142 -0
  57. package/dist/Grid.d.ts +388 -0
  58. package/dist/Grid.d.ts.map +1 -0
  59. package/dist/Grid.js +284 -0
  60. package/dist/Group.d.ts +128 -0
  61. package/dist/Group.d.ts.map +1 -0
  62. package/dist/Group.js +71 -0
  63. package/dist/Heading.d.ts +87 -0
  64. package/dist/Heading.d.ts.map +1 -0
  65. package/dist/Heading.js +58 -0
  66. package/dist/Hovercard.d.ts +297 -0
  67. package/dist/Hovercard.d.ts.map +1 -0
  68. package/dist/Hovercard.js +188 -0
  69. package/dist/HttpRouter.d.ts +129 -6
  70. package/dist/HttpRouter.d.ts.map +1 -1
  71. package/dist/HttpRouter.js +199 -56
  72. package/dist/Link.d.ts +63 -28
  73. package/dist/Link.d.ts.map +1 -1
  74. package/dist/Link.js +84 -37
  75. package/dist/Listbox.d.ts +305 -0
  76. package/dist/Listbox.d.ts.map +1 -0
  77. package/dist/Listbox.js +245 -0
  78. package/dist/Menu.d.ts +663 -0
  79. package/dist/Menu.d.ts.map +1 -0
  80. package/dist/Menu.js +569 -0
  81. package/dist/Menubar.d.ts +249 -0
  82. package/dist/Menubar.d.ts.map +1 -0
  83. package/dist/Menubar.js +207 -0
  84. package/dist/Meter.d.ts +157 -0
  85. package/dist/Meter.d.ts.map +1 -0
  86. package/dist/Meter.js +87 -0
  87. package/dist/NativeDetails.d.ts +41 -0
  88. package/dist/NativeDetails.d.ts.map +1 -0
  89. package/dist/NativeDetails.js +40 -0
  90. package/dist/NativeDialog.d.ts +66 -0
  91. package/dist/NativeDialog.d.ts.map +1 -0
  92. package/dist/NativeDialog.js +88 -0
  93. package/dist/NativePopover.d.ts +43 -0
  94. package/dist/NativePopover.d.ts.map +1 -0
  95. package/dist/NativePopover.js +84 -0
  96. package/dist/Popover.d.ts +240 -0
  97. package/dist/Popover.d.ts.map +1 -0
  98. package/dist/Popover.js +140 -0
  99. package/dist/RadioGroup.d.ts +330 -0
  100. package/dist/RadioGroup.d.ts.map +1 -0
  101. package/dist/RadioGroup.js +241 -0
  102. package/dist/Role.d.ts +64 -0
  103. package/dist/Role.d.ts.map +1 -0
  104. package/dist/Role.js +27 -0
  105. package/dist/Select.d.ts +418 -0
  106. package/dist/Select.d.ts.map +1 -0
  107. package/dist/Select.js +357 -0
  108. package/dist/Separator.d.ts +58 -0
  109. package/dist/Separator.d.ts.map +1 -0
  110. package/dist/Separator.js +32 -0
  111. package/dist/Slider.d.ts +141 -0
  112. package/dist/Slider.d.ts.map +1 -0
  113. package/dist/Slider.js +101 -0
  114. package/dist/SpinButton.d.ts +141 -0
  115. package/dist/SpinButton.d.ts.map +1 -0
  116. package/dist/SpinButton.js +101 -0
  117. package/dist/Storybook.d.ts +76 -0
  118. package/dist/Storybook.d.ts.map +1 -0
  119. package/dist/Storybook.js +102 -0
  120. package/dist/Switch.d.ts +148 -0
  121. package/dist/Switch.d.ts.map +1 -0
  122. package/dist/Switch.js +110 -0
  123. package/dist/Tab.d.ts +26 -0
  124. package/dist/Tab.d.ts.map +1 -0
  125. package/dist/Tab.js +25 -0
  126. package/dist/Tabs.d.ts +411 -0
  127. package/dist/Tabs.d.ts.map +1 -0
  128. package/dist/Tabs.js +262 -0
  129. package/dist/Toolbar.d.ts +248 -0
  130. package/dist/Toolbar.d.ts.map +1 -0
  131. package/dist/Toolbar.js +187 -0
  132. package/dist/Tooltip.d.ts +296 -0
  133. package/dist/Tooltip.d.ts.map +1 -0
  134. package/dist/Tooltip.js +172 -0
  135. package/dist/Tree.d.ts +405 -0
  136. package/dist/Tree.d.ts.map +1 -0
  137. package/dist/Tree.js +333 -0
  138. package/dist/TreeGrid.d.ts +426 -0
  139. package/dist/TreeGrid.d.ts.map +1 -0
  140. package/dist/TreeGrid.js +308 -0
  141. package/dist/VisuallyHidden.d.ts +68 -0
  142. package/dist/VisuallyHidden.d.ts.map +1 -0
  143. package/dist/VisuallyHidden.js +44 -0
  144. package/dist/WindowSplitter.d.ts +336 -0
  145. package/dist/WindowSplitter.d.ts.map +1 -0
  146. package/dist/WindowSplitter.js +305 -0
  147. package/dist/index.d.ts +48 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +48 -0
  150. package/package.json +47 -14
  151. package/src/HttpRouter.test.ts +0 -294
  152. package/src/HttpRouter.ts +0 -168
  153. package/src/Link.test.ts +0 -85
  154. package/src/Link.ts +0 -107
  155. package/src/index.ts +0 -2
  156. package/tsconfig.json +0 -6
@@ -0,0 +1,128 @@
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 NativeDetails from "./NativeDetails.js";
7
+ /** Schema for disclosure hydration state.
8
+ * @remarks
9
+ * ## Why
10
+ * Shared encoding keeps server and browser state compatible.
11
+ * ## Ownership and lifetime
12
+ * The immutable schema acquires no resources.
13
+ * @since 1.0.0
14
+ * @category Open state
15
+ */
16
+ export const StateSchema = Schema.Struct({ open: Schema.Boolean });
17
+ /** Creates hydrated disclosure state.
18
+ * @remarks
19
+ * ## Why
20
+ * Applications can own and test open-state transitions independently of 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 Disclosure from "@typed/ui/Disclosure"
27
+ *
28
+ * const program = Effect.gen(function* () {
29
+ * return yield* Disclosure.makeState({ open: true })
30
+ * })
31
+ * ```
32
+ * @since 1.0.0
33
+ * @category Open state
34
+ */
35
+ export function makeState(initial = {}) {
36
+ return RefSubject.hydrate(StateSchema, { open: initial.open ?? false });
37
+ }
38
+ /** Sets disclosure visibility.
39
+ * @remarks
40
+ * ## Why
41
+ * Explicit state transitions remain composable with Effect and outside renderers.
42
+ * ## Ownership and lifetime
43
+ * The Effect reuses the existing state lifetime and acquires no resource.
44
+ * @example
45
+ * ```ts
46
+ * import { Effect } from "effect"
47
+ * import * as Disclosure from "@typed/ui/Disclosure"
48
+ *
49
+ * const program = Effect.gen(function* () {
50
+ * const state = yield* Disclosure.makeState()
51
+ * yield* Disclosure.setOpen(state, true)
52
+ * })
53
+ * ```
54
+ * @since 1.0.0
55
+ * @category Open state
56
+ */
57
+ export function setOpen(state, open) {
58
+ return RefSubject.update(state, (current) => ({ ...current, open }));
59
+ }
60
+ function buttonInternalProps() {
61
+ return {};
62
+ }
63
+ /** Renders a native `<summary>` disclosure trigger.
64
+ * @remarks
65
+ * ## Why
66
+ * The browser owns activation and toggling; Typed does not emulate them with
67
+ * click handlers or synthetic events.
68
+ * ## Ownership and lifetime
69
+ * Running the Fx owns dynamic content in its Scope. A custom host must remain a
70
+ * valid summary participant inside the associated details element.
71
+ * @example
72
+ * ```ts
73
+ * import { Button } from "@typed/ui/Disclosure"
74
+ *
75
+ * const summary = Button({ content: "Advanced settings" })
76
+ * ```
77
+ * @since 1.0.0
78
+ * @category Summary activation
79
+ */
80
+ export function Button(options, host) {
81
+ return Dom.renderHost()(options, host, buttonInternalProps, options.content, (props, content) => html `<summary ...${props}>${content}</summary>`);
82
+ }
83
+ function contentInternalProps(options) {
84
+ return () => ({
85
+ ontoggle: EventHandler.make(Effect.fn((event) => setOpen(options.state, Dom.currentTarget(event).open))),
86
+ ref: Dom.composeRefs(options.state, NativeDetails.ref(options.state)),
87
+ });
88
+ }
89
+ /** Renders native details content synchronized with hydrated state.
90
+ * @remarks
91
+ * ## Why
92
+ * Native disclosure behavior, semantics, and `toggle` events stay intact while
93
+ * Effect state can control or observe visibility.
94
+ * ## Ownership and lifetime
95
+ * Running the Fx owns the native listener and scoped observer. A custom host
96
+ * must preserve the toggle handler and exactly one composed hydration ref.
97
+ * @example
98
+ * ```ts
99
+ * import { Effect } from "effect"
100
+ * import * as Disclosure from "@typed/ui/Disclosure"
101
+ *
102
+ * const program = Effect.gen(function* () {
103
+ * const state = yield* Disclosure.makeState()
104
+ * return Disclosure.Content({
105
+ * state,
106
+ * content: Disclosure.Button({ content: "More" })
107
+ * })
108
+ * })
109
+ * ```
110
+ * @since 1.0.0
111
+ * @category Native content host
112
+ */
113
+ export function Content(options, host) {
114
+ return Dom.renderHost()(options, host, contentInternalProps(options), options.content, (props, content) => {
115
+ return html `<details ...${props}>${content}</details>`;
116
+ });
117
+ }
118
+ /** Canonical alias for `Content`.
119
+ * @remarks
120
+ * ## Why
121
+ * The widget name remains convenient while `Content` identifies its role in
122
+ * compound composition.
123
+ * ## Ownership and lifetime
124
+ * It has exactly the same Scope and details-element ownership as `Content`.
125
+ * @since 1.0.0
126
+ * @category Native content host
127
+ */
128
+ export const Disclosure = Content;
@@ -0,0 +1,122 @@
1
+ import { EventHandler } from "@typed/template";
2
+ import type { EventHandlerInput, EventHandlerProperty } from "./Types.js";
3
+ /**
4
+ * Combines a user handler with component-required behavior for one native event.
5
+ *
6
+ * @remarks
7
+ * ## Why
8
+ * Component hosts must combine user and required behavior without hiding the
9
+ * precise Effect boundary. User callback invocation constructs the user Effect
10
+ * first. Internal callback invocation may then construct an internal Effect;
11
+ * only execution of that returned Effect is sequenced after the user Effect.
12
+ *
13
+ * ## Ownership and lifetime
14
+ * The returned `EventHandler` owns no listener by itself. The rendering Scope
15
+ * installs it and removes it; `once` and AbortSignal state are honored for each
16
+ * input independently. Capture and passive options are conservatively merged.
17
+ *
18
+ * ## DOM behavior
19
+ * The user handler receives a retained `Proxy` around the browser event so a
20
+ * `preventDefault()` call made while its Effect runs can still cancel the
21
+ * internal step. The proxy is not identity-equal to the native event. Its
22
+ * properties—including `currentTarget`—are read from that native event, so
23
+ * `currentTarget` becomes null after browser dispatch exactly as it normally
24
+ * does. The internal handler receives the original native event, never the
25
+ * proxy.
26
+ *
27
+ * ## Invocation and Effect sequencing
28
+ * `userHandler.handler(proxy)` is called first to construct `userEffect`. If
29
+ * that invocation has already called `preventDefault()`—for example through an
30
+ * EventHandler option—the internal handler is not invoked. Otherwise,
31
+ * `internalHandler.handler(nativeEvent)` is called immediately to construct
32
+ * `internalEffect`, before `userEffect` runs. A void-returning internal callback
33
+ * therefore performs its callback body eagerly and cannot be undone by a later
34
+ * `preventDefault()` inside `userEffect`. The composed Effect runs `userEffect`,
35
+ * checks tracked default prevention again, and only then runs a returned
36
+ * `internalEffect` when it is still allowed.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { chainEvent } from "@typed/ui/Dom/Events"
41
+ * import { EventHandler } from "@typed/template"
42
+ * import { Effect } from "effect"
43
+ *
44
+ * const calls: Array<string> = []
45
+ * const handler = chainEvent(
46
+ * EventHandler.make((event: MouseEvent) =>
47
+ * Effect.sync(() => {
48
+ * calls.push("user effect")
49
+ * event.preventDefault()
50
+ * })),
51
+ * EventHandler.make(() => {
52
+ * calls.push("internal callback") // runs while constructing the Effect
53
+ * return Effect.sync(() => calls.push("internal effect"))
54
+ * })
55
+ * )!
56
+ *
57
+ * const program = handler.handler(new MouseEvent("click", { cancelable: true }))
58
+ * // calls is now ["internal callback"]
59
+ * await Effect.runPromise(program)
60
+ * // calls is ["internal callback", "user effect"];
61
+ * // the returned internal Effect was gated by preventDefault.
62
+ * ```
63
+ *
64
+ * @since 1.0.0
65
+ * @category Event composition
66
+ */
67
+ export declare function chainEvent<Ev extends Event, E1 = never, R1 = never, E2 = never, R2 = never>(user: EventHandlerInput<Ev, E1, R1>, internal: EventHandlerInput<Ev, E2, R2>): EventHandler.EventHandler<Ev, E1 | E2, R1 | R2> | undefined;
68
+ /**
69
+ * Tests whether a prop name uses Typed's native event conventions.
70
+ *
71
+ * @remarks
72
+ * ## Why
73
+ * Host forwarding and merging treat `on*` and `@*` keys as handlers rather
74
+ * than ordinary attributes.
75
+ *
76
+ * ## Ownership and lifetime
77
+ * Pure predicate; it installs no listener and retains no key.
78
+ *
79
+ * @since 1.0.0
80
+ * @category Event inspection
81
+ */
82
+ export declare function isEventKey(key: string): key is EventHandlerProperty;
83
+ /**
84
+ * Reads a native event's non-null current target during handler execution.
85
+ *
86
+ * @remarks
87
+ * ## Why
88
+ * The DOM types `currentTarget` as nullable because the browser clears it after
89
+ * dispatch. This helper makes the temporal constraint explicit and preserves a
90
+ * caller-selected target type.
91
+ *
92
+ * ## Ownership and lifetime
93
+ * The returned target is borrowed from the live dispatch. Do not treat it as a
94
+ * component-owned node; the helper throws after the handler window closes.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * import { currentTarget } from "@typed/ui/Dom/Events"
99
+ *
100
+ * const read = (event: Event) => currentTarget<HTMLInputElement>(event).value
101
+ * ```
102
+ *
103
+ * @since 1.0.0
104
+ * @category Event inspection
105
+ */
106
+ export declare function currentTarget<Target extends EventTarget>(event: Event): Target;
107
+ /**
108
+ * Reads the standards-based `ToggleEvent.newState` value when present.
109
+ *
110
+ * @remarks
111
+ * ## Why
112
+ * Popover and details components can follow the browser's real toggle event
113
+ * while remaining usable with DOM libraries whose Event types lag the platform.
114
+ *
115
+ * ## Ownership and lifetime
116
+ * Pure read of the current event; no state or listener is retained.
117
+ *
118
+ * @since 1.0.0
119
+ * @category Event inspection
120
+ */
121
+ export declare function toggleState(event: Event): "open" | "closed" | undefined;
122
+ //# sourceMappingURL=Events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Events.d.ts","sourceRoot":"","sources":["../../src/Dom/Events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,wBAAgB,UAAU,CAAC,EAAE,SAAS,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK,EACzF,IAAI,EAAE,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EACnC,QAAQ,EAAE,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GACtC,YAAY,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,CA0C7D;AA2BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,oBAAoB,CAEnE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAAC,MAAM,SAAS,WAAW,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAO9E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAGvE"}
@@ -0,0 +1,192 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { EventHandler } from "@typed/template";
3
+ /**
4
+ * Combines a user handler with component-required behavior for one native event.
5
+ *
6
+ * @remarks
7
+ * ## Why
8
+ * Component hosts must combine user and required behavior without hiding the
9
+ * precise Effect boundary. User callback invocation constructs the user Effect
10
+ * first. Internal callback invocation may then construct an internal Effect;
11
+ * only execution of that returned Effect is sequenced after the user Effect.
12
+ *
13
+ * ## Ownership and lifetime
14
+ * The returned `EventHandler` owns no listener by itself. The rendering Scope
15
+ * installs it and removes it; `once` and AbortSignal state are honored for each
16
+ * input independently. Capture and passive options are conservatively merged.
17
+ *
18
+ * ## DOM behavior
19
+ * The user handler receives a retained `Proxy` around the browser event so a
20
+ * `preventDefault()` call made while its Effect runs can still cancel the
21
+ * internal step. The proxy is not identity-equal to the native event. Its
22
+ * properties—including `currentTarget`—are read from that native event, so
23
+ * `currentTarget` becomes null after browser dispatch exactly as it normally
24
+ * does. The internal handler receives the original native event, never the
25
+ * proxy.
26
+ *
27
+ * ## Invocation and Effect sequencing
28
+ * `userHandler.handler(proxy)` is called first to construct `userEffect`. If
29
+ * that invocation has already called `preventDefault()`—for example through an
30
+ * EventHandler option—the internal handler is not invoked. Otherwise,
31
+ * `internalHandler.handler(nativeEvent)` is called immediately to construct
32
+ * `internalEffect`, before `userEffect` runs. A void-returning internal callback
33
+ * therefore performs its callback body eagerly and cannot be undone by a later
34
+ * `preventDefault()` inside `userEffect`. The composed Effect runs `userEffect`,
35
+ * checks tracked default prevention again, and only then runs a returned
36
+ * `internalEffect` when it is still allowed.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { chainEvent } from "@typed/ui/Dom/Events"
41
+ * import { EventHandler } from "@typed/template"
42
+ * import { Effect } from "effect"
43
+ *
44
+ * const calls: Array<string> = []
45
+ * const handler = chainEvent(
46
+ * EventHandler.make((event: MouseEvent) =>
47
+ * Effect.sync(() => {
48
+ * calls.push("user effect")
49
+ * event.preventDefault()
50
+ * })),
51
+ * EventHandler.make(() => {
52
+ * calls.push("internal callback") // runs while constructing the Effect
53
+ * return Effect.sync(() => calls.push("internal effect"))
54
+ * })
55
+ * )!
56
+ *
57
+ * const program = handler.handler(new MouseEvent("click", { cancelable: true }))
58
+ * // calls is now ["internal callback"]
59
+ * await Effect.runPromise(program)
60
+ * // calls is ["internal callback", "user effect"];
61
+ * // the returned internal Effect was gated by preventDefault.
62
+ * ```
63
+ *
64
+ * @since 1.0.0
65
+ * @category Event composition
66
+ */
67
+ export function chainEvent(user, internal) {
68
+ const userHandler = toEventHandler(user);
69
+ const internalHandler = toEventHandler(internal);
70
+ if (!userHandler)
71
+ return internalHandler;
72
+ if (!internalHandler)
73
+ return userHandler;
74
+ let userActive = true;
75
+ let internalActive = true;
76
+ const passive = userHandler.options?.preventDefault === true ||
77
+ internalHandler.options?.preventDefault === true ||
78
+ userHandler.options?.passive === false ||
79
+ internalHandler.options?.passive === false
80
+ ? false
81
+ : (userHandler.options?.passive ?? internalHandler.options?.passive);
82
+ const options = {
83
+ capture: userHandler.options?.capture ?? internalHandler.options?.capture,
84
+ passive,
85
+ };
86
+ return EventHandler.make((event) => {
87
+ const tracked = trackPreventDefault(event);
88
+ const userEffect = userActive && userHandler.options?.signal?.aborted !== true
89
+ ? userHandler.handler(tracked.event)
90
+ : Effect.void;
91
+ if (userActive && userHandler.options?.signal?.aborted !== true) {
92
+ if (userHandler.options?.once === true)
93
+ userActive = false;
94
+ }
95
+ if (tracked.defaultPrevented() ||
96
+ !internalActive ||
97
+ internalHandler.options?.signal?.aborted === true) {
98
+ return userEffect;
99
+ }
100
+ if (internalHandler.options?.once === true)
101
+ internalActive = false;
102
+ const internalEffect = internalHandler.handler(event);
103
+ return Effect.andThen(userEffect, () => tracked.defaultPrevented() ? Effect.void : internalEffect);
104
+ }, options);
105
+ }
106
+ function trackPreventDefault(event) {
107
+ let defaultPrevented = event.defaultPrevented;
108
+ return {
109
+ event: new Proxy(event, {
110
+ get(target, property) {
111
+ if (property === "defaultPrevented")
112
+ return defaultPrevented;
113
+ if (property === "preventDefault") {
114
+ return () => {
115
+ defaultPrevented = true;
116
+ event.preventDefault();
117
+ };
118
+ }
119
+ const value = Reflect.get(target, property);
120
+ return typeof value === "function" ? value.bind(target) : value;
121
+ },
122
+ }),
123
+ defaultPrevented: () => defaultPrevented,
124
+ };
125
+ }
126
+ /**
127
+ * Tests whether a prop name uses Typed's native event conventions.
128
+ *
129
+ * @remarks
130
+ * ## Why
131
+ * Host forwarding and merging treat `on*` and `@*` keys as handlers rather
132
+ * than ordinary attributes.
133
+ *
134
+ * ## Ownership and lifetime
135
+ * Pure predicate; it installs no listener and retains no key.
136
+ *
137
+ * @since 1.0.0
138
+ * @category Event inspection
139
+ */
140
+ export function isEventKey(key) {
141
+ return key[0] === "@" || (key[0] === "o" && key[1] === "n");
142
+ }
143
+ /**
144
+ * Reads a native event's non-null current target during handler execution.
145
+ *
146
+ * @remarks
147
+ * ## Why
148
+ * The DOM types `currentTarget` as nullable because the browser clears it after
149
+ * dispatch. This helper makes the temporal constraint explicit and preserves a
150
+ * caller-selected target type.
151
+ *
152
+ * ## Ownership and lifetime
153
+ * The returned target is borrowed from the live dispatch. Do not treat it as a
154
+ * component-owned node; the helper throws after the handler window closes.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * import { currentTarget } from "@typed/ui/Dom/Events"
159
+ *
160
+ * const read = (event: Event) => currentTarget<HTMLInputElement>(event).value
161
+ * ```
162
+ *
163
+ * @since 1.0.0
164
+ * @category Event inspection
165
+ */
166
+ export function currentTarget(event) {
167
+ if (event.currentTarget === null) {
168
+ throw new TypeError("An event handler can only read its current target while handling an event");
169
+ }
170
+ return event.currentTarget;
171
+ }
172
+ /**
173
+ * Reads the standards-based `ToggleEvent.newState` value when present.
174
+ *
175
+ * @remarks
176
+ * ## Why
177
+ * Popover and details components can follow the browser's real toggle event
178
+ * while remaining usable with DOM libraries whose Event types lag the platform.
179
+ *
180
+ * ## Ownership and lifetime
181
+ * Pure read of the current event; no state or listener is retained.
182
+ *
183
+ * @since 1.0.0
184
+ * @category Event inspection
185
+ */
186
+ export function toggleState(event) {
187
+ const value = Reflect.get(event, "newState");
188
+ return value === "open" || value === "closed" ? value : undefined;
189
+ }
190
+ function toEventHandler(handler) {
191
+ return handler == null ? undefined : EventHandler.fromEffectOrEventHandler(handler);
192
+ }
@@ -0,0 +1,161 @@
1
+ import type { EventHandler, Renderable } from "@typed/template";
2
+ import { type ComposedRef } from "./Refs.js";
3
+ import type { EventHandlerProperty, EventOf, InternalPropsHelpers, Property } from "./Types.js";
4
+ type ObjectValue<Value> = Value extends object ? Value : Record<never, never>;
5
+ type PropsRef<Props> = Property<Props, "ref">;
6
+ type MergedEventKeys<User, Internal> = Extract<keyof ObjectValue<User> | keyof ObjectValue<Internal>, EventHandlerProperty>;
7
+ type MergedEvents<User, Internal> = {
8
+ readonly [Key in MergedEventKeys<User, Internal>]?: EventHandler.EventHandler<EventOf<Property<User, Key> | Property<Internal, Key>>, Renderable.Error<Property<User, Key> | Property<Internal, Key>>, Renderable.Services<Property<User, Key> | Property<Internal, Key>>>;
9
+ };
10
+ /**
11
+ * Type-level result of merging user and component-owned host props.
12
+ *
13
+ * @remarks
14
+ * ## Why
15
+ * Required internal values win, events compose, and refs compose while
16
+ * unrelated user props pass through unchanged.
17
+ *
18
+ * ## Ownership and lifetime
19
+ * The resulting event/ref work is owned by the host's rendering Scope.
20
+ *
21
+ * @since 1.0.0
22
+ * @category Prop composition
23
+ */
24
+ export type MergedHostProps<User, Internal> = Omit<ObjectValue<User>, keyof ObjectValue<Internal> | EventHandlerProperty | "ref"> & Omit<ObjectValue<Internal>, EventHandlerProperty | "ref"> & MergedEvents<User, Internal> & {
25
+ readonly ref?: ComposedRef<PropsRef<Internal>, PropsRef<User>>;
26
+ };
27
+ /**
28
+ * Option keys forwarded directly to a component host.
29
+ *
30
+ * @remarks
31
+ * ## Why
32
+ * Top-level refs and events remain ergonomic while other DOM props stay under `props`.
33
+ *
34
+ * ## Ownership and lifetime
35
+ * Type-only; the rendered host owns listener/ref lifetime.
36
+ *
37
+ * @since 1.0.0
38
+ * @category Prop forwarding
39
+ */
40
+ export type ForwardedHostKeys<Options> = Extract<keyof ObjectValue<Options>, EventHandlerProperty | "ref">;
41
+ /**
42
+ * Top-level event and ref props selected from component options.
43
+ *
44
+ * @remarks
45
+ * ## Why
46
+ * Provides the exact forwarded subset used by `renderHost`.
47
+ *
48
+ * ## Ownership and lifetime
49
+ * Type-only; the rendered host owns listener/ref lifetime.
50
+ *
51
+ * @since 1.0.0
52
+ * @category Prop forwarding
53
+ */
54
+ export type ForwardedHostProps<Options> = Pick<ObjectValue<Options>, ForwardedHostKeys<Options>>;
55
+ /**
56
+ * User host props after combining nested `props` with top-level refs/events.
57
+ *
58
+ * @remarks
59
+ * ## Why
60
+ * Gives both supported authoring forms one deterministic merge model.
61
+ *
62
+ * ## Ownership and lifetime
63
+ * Type-only; runtime ownership begins when the host renders.
64
+ *
65
+ * @since 1.0.0
66
+ * @category Prop composition
67
+ */
68
+ export type HostOptionProps<Options> = MergedHostProps<Property<Options, "props">, ForwardedHostProps<Options>>;
69
+ /**
70
+ * Final props delivered to a default or custom host renderer.
71
+ *
72
+ * @remarks
73
+ * ## Why
74
+ * Custom hosts receive the same accessibility, event, disabled, and ref
75
+ * contract as Typed's default element.
76
+ *
77
+ * ## Ownership and lifetime
78
+ * The rendering Scope owns composed listeners and refs.
79
+ *
80
+ * @since 1.0.0
81
+ * @category Prop composition
82
+ */
83
+ export type RenderHostProps<Options, Internal> = MergedHostProps<HostOptionProps<Options>, Internal>;
84
+ /**
85
+ * Merges user host props with component-required internal props.
86
+ *
87
+ * @remarks
88
+ * ## Why
89
+ * Internal accessibility/state props must remain authoritative, but user
90
+ * events still run first and may cancel component behavior. Disabled hosts
91
+ * suppress user activation handlers. Refs compose instead of overwriting one
92
+ * another.
93
+ *
94
+ * ## Ownership and lifetime
95
+ * The function allocates only a merged object. Listener and ref effects are
96
+ * run by the eventual template Scope. `composeRefs` rejects two hydration
97
+ * owners because one element can restore only one hydration protocol.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * import { mergeProps } from "@typed/ui/Dom/Props"
102
+ * import { EventHandler } from "@typed/template"
103
+ * import { Effect } from "effect"
104
+ *
105
+ * const props = mergeProps(
106
+ * {
107
+ * class: "external",
108
+ * onclick: EventHandler.make(() => Effect.log("user activation"))
109
+ * },
110
+ * { role: "button", "aria-disabled": false }
111
+ * )
112
+ * ```
113
+ *
114
+ * @since 1.0.0
115
+ * @category Prop composition
116
+ */
117
+ export declare function mergeProps<const User extends object | undefined, const Internal extends object>(user: User, internal: Internal): MergedHostProps<User, Internal>;
118
+ /**
119
+ * Reads a property from an unknown generic value without throwing on nullish input.
120
+ *
121
+ * @remarks
122
+ * ## Why
123
+ * Generic option construction needs runtime behavior matching the public `Property` type.
124
+ *
125
+ * ## Ownership and lifetime
126
+ * Pure read; no value is retained.
127
+ *
128
+ * @since 1.0.0
129
+ * @category Property inspection
130
+ */
131
+ export declare function getProperty<const Value, const Key extends PropertyKey>(value: Value, key: Key): Property<Value, Key>;
132
+ /**
133
+ * Builds typed fallback helpers for component-owned host props.
134
+ *
135
+ * @remarks
136
+ * ## Why
137
+ * Components can distinguish an absent option from valid falsy values.
138
+ *
139
+ * ## Ownership and lifetime
140
+ * The helper closes over `options` only during synchronous prop construction.
141
+ *
142
+ * @since 1.0.0
143
+ * @category Internal prop defaults
144
+ */
145
+ export declare function makeInternalPropsHelpers<const Options>(options: Options): InternalPropsHelpers<Options>;
146
+ /**
147
+ * Extracts top-level refs and event handlers from component options.
148
+ *
149
+ * @remarks
150
+ * ## Why
151
+ * Convenience event/ref options can enter the same deterministic host merge as `props`.
152
+ *
153
+ * ## Ownership and lifetime
154
+ * Returns a shallow object. The rendering Scope owns any contained listener/ref work.
155
+ *
156
+ * @since 1.0.0
157
+ * @category Prop forwarding
158
+ */
159
+ export declare function forwardHostProps<const Options extends object>(options: Options): ForwardedHostProps<Options>;
160
+ export {};
161
+ //# sourceMappingURL=Props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Props.d.ts","sourceRoot":"","sources":["../../src/Dom/Props.ts"],"names":[],"mappings":"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"}