@uniaria/react 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +50 -2
- package/dist/index.d.ts +51 -0
- package/dist/index.js +444 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrey Yamanov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @uniaria/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The React adapter for UniAria: one generic hook, `useComponent`, binds any
|
|
4
|
+
component definition from [`@uniaria/core`](https://www.npmjs.com/package/@uniaria/core)
|
|
5
|
+
to your elements. React 18 or 19.
|
|
6
|
+
|
|
7
|
+
> **Pre-release.** The API can change in any 0.x version.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @uniaria/core @uniaria/react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Use
|
|
16
|
+
|
|
17
|
+
Pass a definition and its props to `useComponent`, then spread each part's
|
|
18
|
+
props onto your element:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { toggleSwitch } from '@uniaria/core/switch';
|
|
22
|
+
import { useComponent } from '@uniaria/react';
|
|
23
|
+
|
|
24
|
+
export function WifiSwitch() {
|
|
25
|
+
const wifi = useComponent(toggleSwitch, {
|
|
26
|
+
defaultSelected: true,
|
|
27
|
+
labelElementType: 'span',
|
|
28
|
+
});
|
|
29
|
+
return (
|
|
30
|
+
<label {...wifi.parts.root}>
|
|
31
|
+
<input {...wifi.parts.input} name="wifi" />
|
|
32
|
+
<span {...wifi.parts.indicator} />
|
|
33
|
+
<span {...wifi.parts.label}>Wi-Fi</span>
|
|
34
|
+
</label>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Hovering, pressing or toggling doesn't render your component again: UniAria
|
|
40
|
+
writes the new state straight to the elements. When your render needs a
|
|
41
|
+
value, select it with `useComponentState`.
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
[Getting started](https://github.com/tenphi/uniaria/blob/main/docs/guide/getting-started.mdx)
|
|
46
|
+
and every component's page are in the repository's
|
|
47
|
+
[`docs`](https://github.com/tenphi/uniaria/tree/main/docs) folder.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
[MIT](https://github.com/tenphi/uniaria/blob/main/LICENSE)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { ActionsOf, AnyDefinition, Instance, ItemPartOf, PartOf, PropsOf, StateOf } from "@uniaria/core";
|
|
3
|
+
//#region src/owner.d.ts
|
|
4
|
+
interface Scope {
|
|
5
|
+
readonly instance: Instance;
|
|
6
|
+
readonly parent: Scope | null;
|
|
7
|
+
}
|
|
8
|
+
interface OwnerProviderProps<D extends AnyDefinition = AnyDefinition> {
|
|
9
|
+
/** The owner: an instance, or the handle `useComponent` returned. */
|
|
10
|
+
readonly owner: Instance<D> | {
|
|
11
|
+
readonly instance: Instance<D>;
|
|
12
|
+
};
|
|
13
|
+
readonly children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export declare function OwnerProvider<D extends AnyDefinition>({ owner, children }: OwnerProviderProps<D>): import("react").FunctionComponentElement<import("react").ProviderProps<Scope | null>>;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/use-component.d.ts
|
|
18
|
+
/** Props to spread onto a part element: attributes, stable handlers, a ref. */
|
|
19
|
+
type ReactPartProps = Readonly<Record<string, unknown>> & {
|
|
20
|
+
readonly ref: (element: Element | null) => void;
|
|
21
|
+
};
|
|
22
|
+
interface ComponentHandle<D extends AnyDefinition> {
|
|
23
|
+
/** The stable instance: pass it to `useComponentState` or as an `owner`. */
|
|
24
|
+
readonly instance: Instance<D>;
|
|
25
|
+
/** The component's public actions, such as a popover's `open()` and `close()`. */
|
|
26
|
+
readonly actions: ActionsOf<D>;
|
|
27
|
+
/** Props for each part, from this render. */
|
|
28
|
+
readonly parts: Readonly<Record<PartOf<D>, ReactPartProps>>;
|
|
29
|
+
/**
|
|
30
|
+
* Props for one item part of one item (Listbox spec, Item parts), from
|
|
31
|
+
* this render. Later changes reach the element through the binder, so the
|
|
32
|
+
* component rendering the rows doesn't render again for them. Item parts
|
|
33
|
+
* take no live DOM properties as React defaults.
|
|
34
|
+
*/
|
|
35
|
+
getItemProps(part: ItemPartOf<D>, key: string): ReactPartProps;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates an instance of `definition` for this component and returns props
|
|
39
|
+
* for its parts. Props are stored silently during render and synced after
|
|
40
|
+
* commit; internal state changes never re-render this component.
|
|
41
|
+
*/
|
|
42
|
+
export declare function useComponent<D extends AnyDefinition>(definition: D, props: NoInfer<PropsOf<D>>): ComponentHandle<D>;
|
|
43
|
+
/**
|
|
44
|
+
* Reads one value of an instance's public state and re-renders only when it
|
|
45
|
+
* changes (`Object.is`, or `isEqual`). Takes the instance or the handle
|
|
46
|
+
* `useComponent` returned.
|
|
47
|
+
*/
|
|
48
|
+
export declare function useComponentState<D extends AnyDefinition, T>(source: Instance<D> | ComponentHandle<D>, selector: (state: StateOf<D>) => T, isEqual?: (a: T, b: T) => boolean): T;
|
|
49
|
+
//#endregion
|
|
50
|
+
export type { ComponentHandle, OwnerProviderProps, ReactPartProps };
|
|
51
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import { createInstance, isOwnerDefinition } from "@uniaria/core";
|
|
4
|
+
import { createBinder, isDevelopment, provideOwner, requestOwner, warn } from "@uniaria/core/dom";
|
|
5
|
+
//#region src/owner.ts
|
|
6
|
+
const OwnerContext = createContext(null);
|
|
7
|
+
function OwnerProvider({ owner, children }) {
|
|
8
|
+
const parent = useContext(OwnerContext);
|
|
9
|
+
const instance = "instance" in owner ? owner.instance : owner;
|
|
10
|
+
const scope = useMemo(() => ({
|
|
11
|
+
instance,
|
|
12
|
+
parent
|
|
13
|
+
}), [instance, parent]);
|
|
14
|
+
return createElement(OwnerContext.Provider, { value: scope }, children);
|
|
15
|
+
}
|
|
16
|
+
/** The nearest owner of `definition` in the context, if any. */
|
|
17
|
+
function findOwner(scope, definition) {
|
|
18
|
+
for (let current = scope; current; current = current.parent) if (current.instance.definition === definition) return current.instance;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/props.ts
|
|
22
|
+
/**
|
|
23
|
+
* Neutral part props → React props (design decision 4): DOM attribute names
|
|
24
|
+
* become React prop names, empty boolean attributes become `true`, and DOM
|
|
25
|
+
* event names become React handler props.
|
|
26
|
+
*/
|
|
27
|
+
/** DOM attribute → React prop, where they differ. */
|
|
28
|
+
const ATTRIBUTES = {
|
|
29
|
+
class: "className",
|
|
30
|
+
for: "htmlFor",
|
|
31
|
+
tabindex: "tabIndex",
|
|
32
|
+
readonly: "readOnly",
|
|
33
|
+
autofocus: "autoFocus",
|
|
34
|
+
autocomplete: "autoComplete",
|
|
35
|
+
autocorrect: "autoCorrect",
|
|
36
|
+
contenteditable: "contentEditable",
|
|
37
|
+
enterkeyhint: "enterKeyHint",
|
|
38
|
+
inputmode: "inputMode",
|
|
39
|
+
maxlength: "maxLength",
|
|
40
|
+
minlength: "minLength",
|
|
41
|
+
spellcheck: "spellCheck",
|
|
42
|
+
colspan: "colSpan",
|
|
43
|
+
rowspan: "rowSpan",
|
|
44
|
+
formaction: "formAction",
|
|
45
|
+
formenctype: "formEncType",
|
|
46
|
+
formmethod: "formMethod",
|
|
47
|
+
formnovalidate: "formNoValidate",
|
|
48
|
+
formtarget: "formTarget",
|
|
49
|
+
novalidate: "noValidate"
|
|
50
|
+
};
|
|
51
|
+
/** React props that take a boolean, so `''` must become `true`. */
|
|
52
|
+
const BOOLEANS = /* @__PURE__ */ new Set([
|
|
53
|
+
"disabled",
|
|
54
|
+
"hidden",
|
|
55
|
+
"checked",
|
|
56
|
+
"selected",
|
|
57
|
+
"required",
|
|
58
|
+
"readOnly",
|
|
59
|
+
"multiple",
|
|
60
|
+
"open",
|
|
61
|
+
"inert",
|
|
62
|
+
"autoFocus",
|
|
63
|
+
"noValidate",
|
|
64
|
+
"formNoValidate"
|
|
65
|
+
]);
|
|
66
|
+
/** React's DOM event props; the map key is the DOM event name. */
|
|
67
|
+
const EVENTS = new Map([
|
|
68
|
+
"onAnimationEnd",
|
|
69
|
+
"onAnimationIteration",
|
|
70
|
+
"onAnimationStart",
|
|
71
|
+
"onAuxClick",
|
|
72
|
+
"onBeforeInput",
|
|
73
|
+
"onBeforeToggle",
|
|
74
|
+
"onChange",
|
|
75
|
+
"onClick",
|
|
76
|
+
"onCompositionEnd",
|
|
77
|
+
"onCompositionStart",
|
|
78
|
+
"onCompositionUpdate",
|
|
79
|
+
"onContextMenu",
|
|
80
|
+
"onCopy",
|
|
81
|
+
"onCut",
|
|
82
|
+
"onDrag",
|
|
83
|
+
"onDragEnd",
|
|
84
|
+
"onDragEnter",
|
|
85
|
+
"onDragExit",
|
|
86
|
+
"onDragLeave",
|
|
87
|
+
"onDragOver",
|
|
88
|
+
"onDragStart",
|
|
89
|
+
"onDrop",
|
|
90
|
+
"onError",
|
|
91
|
+
"onGotPointerCapture",
|
|
92
|
+
"onInvalid",
|
|
93
|
+
"onKeyDown",
|
|
94
|
+
"onKeyUp",
|
|
95
|
+
"onLoad",
|
|
96
|
+
"onLostPointerCapture",
|
|
97
|
+
"onMouseDown",
|
|
98
|
+
"onMouseEnter",
|
|
99
|
+
"onMouseLeave",
|
|
100
|
+
"onMouseMove",
|
|
101
|
+
"onMouseOut",
|
|
102
|
+
"onMouseOver",
|
|
103
|
+
"onMouseUp",
|
|
104
|
+
"onPaste",
|
|
105
|
+
"onPointerCancel",
|
|
106
|
+
"onPointerDown",
|
|
107
|
+
"onPointerEnter",
|
|
108
|
+
"onPointerLeave",
|
|
109
|
+
"onPointerMove",
|
|
110
|
+
"onPointerOut",
|
|
111
|
+
"onPointerOver",
|
|
112
|
+
"onPointerUp",
|
|
113
|
+
"onReset",
|
|
114
|
+
"onScroll",
|
|
115
|
+
"onScrollEnd",
|
|
116
|
+
"onSelect",
|
|
117
|
+
"onSubmit",
|
|
118
|
+
"onToggle",
|
|
119
|
+
"onTouchCancel",
|
|
120
|
+
"onTouchEnd",
|
|
121
|
+
"onTouchMove",
|
|
122
|
+
"onTouchStart",
|
|
123
|
+
"onTransitionEnd",
|
|
124
|
+
"onWheel"
|
|
125
|
+
].map((name) => [name.slice(2).toLowerCase(), name]));
|
|
126
|
+
EVENTS.set("focusin", "onFocus");
|
|
127
|
+
EVENTS.set("focusout", "onBlur");
|
|
128
|
+
EVENTS.set("input", "onInput");
|
|
129
|
+
EVENTS.set("dblclick", "onDoubleClick");
|
|
130
|
+
/** The React prop for a DOM attribute. */
|
|
131
|
+
function reactPropName(attribute) {
|
|
132
|
+
return ATTRIBUTES[attribute] ?? attribute;
|
|
133
|
+
}
|
|
134
|
+
/** The React value for an attribute value: `''` on a boolean prop is `true`. */
|
|
135
|
+
function reactPropValue(name, value) {
|
|
136
|
+
return value === "" && BOOLEANS.has(name) ? true : value;
|
|
137
|
+
}
|
|
138
|
+
/** The React handler prop for a DOM event name. */
|
|
139
|
+
function reactEventName(type) {
|
|
140
|
+
return EVENTS.get(type) ?? `on${type.charAt(0).toUpperCase()}${type.slice(1)}`;
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/use-component.ts
|
|
144
|
+
/**
|
|
145
|
+
* Live DOM properties React renders once as defaults, so it never controls
|
|
146
|
+
* the element; the binder sets them after that.
|
|
147
|
+
*/
|
|
148
|
+
const DEFAULT_PROPS = {
|
|
149
|
+
value: "defaultValue",
|
|
150
|
+
checked: "defaultChecked"
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Instances whose new props `useComponent` is syncing after a commit. React's
|
|
154
|
+
* `useSyncExternalStore` records the snapshot it rendered only in a passive
|
|
155
|
+
* effect, so a notification from this layout-effect sync is compared with the
|
|
156
|
+
* previous snapshot, and a component whose render already showed the new
|
|
157
|
+
* props would render again with the same value.
|
|
158
|
+
*/
|
|
159
|
+
const syncing = /* @__PURE__ */ new WeakSet();
|
|
160
|
+
const useIsomorphicLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
|
|
161
|
+
/**
|
|
162
|
+
* Warns when the element's attributes differ from the part props React just
|
|
163
|
+
* rendered: the consumer wrote an attribute the binder owns, which the binder
|
|
164
|
+
* would silently overwrite.
|
|
165
|
+
*/
|
|
166
|
+
function checkOwnership(internal, part, element, rendered = (internal.rendered?.connected)?.[part]) {
|
|
167
|
+
const isItem = internal.instance.definition.itemParts?.includes(part) ?? false;
|
|
168
|
+
for (const [name, value] of Object.entries(rendered?.attrs ?? {})) {
|
|
169
|
+
if (name === "id" && !isItem) continue;
|
|
170
|
+
const actual = element.getAttribute(name);
|
|
171
|
+
if (actual === (value ?? null)) continue;
|
|
172
|
+
warn(`${internal.instance.definition.name}: "${name}" on the ${part} part is written by UniAria, but the element has ${actual === null ? "none" : `"${actual}"`}. Pass it as a prop, or drop it from the JSX.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* A style property as React names it: custom properties as they are,
|
|
177
|
+
* others in camelCase (`inset-block-start` → `insetBlockStart`).
|
|
178
|
+
*/
|
|
179
|
+
const reactStyleName = (name) => name.startsWith("--") ? name : name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
180
|
+
/** Attributes and style as React props. */
|
|
181
|
+
function toReactAttrs(props, result) {
|
|
182
|
+
for (const [name, value] of Object.entries(props.attrs)) {
|
|
183
|
+
if (value === void 0) continue;
|
|
184
|
+
const prop = reactPropName(name);
|
|
185
|
+
result[prop] = reactPropValue(prop, value);
|
|
186
|
+
}
|
|
187
|
+
if (props.style) {
|
|
188
|
+
const style = {};
|
|
189
|
+
for (const [name, value] of Object.entries(props.style)) if (value !== void 0) style[reactStyleName(name)] = value;
|
|
190
|
+
result.style = style;
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Hands the binder every item element attached or detached since the last
|
|
196
|
+
* flush: in a microtask, or before the owner's layout effects when the
|
|
197
|
+
* owner rendered in the same commit, so its instance starts and syncs with
|
|
198
|
+
* its items attached.
|
|
199
|
+
*/
|
|
200
|
+
function flushAttaching(internal) {
|
|
201
|
+
if (internal.attaching.size === 0) return;
|
|
202
|
+
const list = Array.from(internal.attaching.values());
|
|
203
|
+
internal.attaching.clear();
|
|
204
|
+
internal.instance.batch(() => {
|
|
205
|
+
for (const { part, key, element } of list) internal.binder.attachItem(part, key, element);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* `getItemProps` for one instance: React props from the cached
|
|
210
|
+
* `connectItem()`, reused while it returns the same object, with handlers
|
|
211
|
+
* stable per part and event type and a ref stable per part and key.
|
|
212
|
+
*/
|
|
213
|
+
function createGetItemProps(internal) {
|
|
214
|
+
return (part, key) => {
|
|
215
|
+
const { instance, binder, itemHandlers, items } = internal();
|
|
216
|
+
const props = instance.connectItem(part, key);
|
|
217
|
+
const id = `${part}\n${key}`;
|
|
218
|
+
let item = items.get(id);
|
|
219
|
+
if (!item) {
|
|
220
|
+
const ref = (element) => {
|
|
221
|
+
const current = internal();
|
|
222
|
+
const entry = current.items.get(id);
|
|
223
|
+
if (element && isDevelopment() && !current.checked.has(element)) {
|
|
224
|
+
current.checked.add(element);
|
|
225
|
+
checkOwnership(current, part, element, entry?.rendered?.props);
|
|
226
|
+
}
|
|
227
|
+
const pending = current.attaching.get(id);
|
|
228
|
+
if (pending) pending.element = element;
|
|
229
|
+
else {
|
|
230
|
+
if (current.attaching.size === 0) queueMicrotask(() => flushAttaching(current));
|
|
231
|
+
current.attaching.set(id, {
|
|
232
|
+
part,
|
|
233
|
+
key,
|
|
234
|
+
element
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (!element && entry?.ref === ref) current.items.delete(id);
|
|
238
|
+
};
|
|
239
|
+
item = { ref };
|
|
240
|
+
items.set(id, item);
|
|
241
|
+
}
|
|
242
|
+
if (item.rendered?.props === props) return item.rendered.react;
|
|
243
|
+
const result = toReactAttrs(props, {});
|
|
244
|
+
for (const type of Object.keys(props.on ?? {})) {
|
|
245
|
+
const handlerKey = `${part}:${type}`;
|
|
246
|
+
let handler = itemHandlers.get(handlerKey);
|
|
247
|
+
if (!handler) {
|
|
248
|
+
handler = (event) => {
|
|
249
|
+
const target = binder.itemOf(event.currentTarget);
|
|
250
|
+
if (!target) return;
|
|
251
|
+
instance.connectItem(target.part, target.key).on?.[type]?.(event.nativeEvent);
|
|
252
|
+
};
|
|
253
|
+
itemHandlers.set(handlerKey, handler);
|
|
254
|
+
}
|
|
255
|
+
result[reactEventName(type)] = handler;
|
|
256
|
+
}
|
|
257
|
+
const react = {
|
|
258
|
+
...result,
|
|
259
|
+
ref: item.ref
|
|
260
|
+
};
|
|
261
|
+
item.rendered = {
|
|
262
|
+
props,
|
|
263
|
+
react
|
|
264
|
+
};
|
|
265
|
+
return react;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function toReactParts(internal, connected) {
|
|
269
|
+
const { instance, binder, handlers, refs, defaults } = internal;
|
|
270
|
+
const parts = {};
|
|
271
|
+
for (const [part, props] of Object.entries(connected)) {
|
|
272
|
+
const result = {};
|
|
273
|
+
let initial = defaults.get(part);
|
|
274
|
+
if (!initial) {
|
|
275
|
+
const first = {};
|
|
276
|
+
for (const [name, value] of Object.entries(props.properties ?? {})) {
|
|
277
|
+
const prop = DEFAULT_PROPS[name];
|
|
278
|
+
if (prop && value !== void 0) first[prop] = value;
|
|
279
|
+
}
|
|
280
|
+
initial = first;
|
|
281
|
+
defaults.set(part, initial);
|
|
282
|
+
}
|
|
283
|
+
Object.assign(result, initial);
|
|
284
|
+
toReactAttrs(props, result);
|
|
285
|
+
for (const type of Object.keys(props.on ?? {})) {
|
|
286
|
+
const key = `${part}:${type}`;
|
|
287
|
+
let handler = handlers.get(key);
|
|
288
|
+
if (!handler) {
|
|
289
|
+
handler = (event) => {
|
|
290
|
+
instance.connect()[part]?.on?.[type]?.(event.nativeEvent);
|
|
291
|
+
};
|
|
292
|
+
handlers.set(key, handler);
|
|
293
|
+
}
|
|
294
|
+
result[reactEventName(type)] = handler;
|
|
295
|
+
}
|
|
296
|
+
let ref = refs.get(part);
|
|
297
|
+
if (!ref) {
|
|
298
|
+
ref = (element) => {
|
|
299
|
+
if (element && isDevelopment() && !internal.checked.has(element)) {
|
|
300
|
+
internal.checked.add(element);
|
|
301
|
+
checkOwnership(internal, part, element);
|
|
302
|
+
}
|
|
303
|
+
binder.attach(part, element);
|
|
304
|
+
};
|
|
305
|
+
refs.set(part, ref);
|
|
306
|
+
}
|
|
307
|
+
parts[part] = {
|
|
308
|
+
...result,
|
|
309
|
+
ref
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return parts;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Creates an instance of `definition` for this component and returns props
|
|
316
|
+
* for its parts. Props are stored silently during render and synced after
|
|
317
|
+
* commit; internal state changes never re-render this component.
|
|
318
|
+
*/
|
|
319
|
+
function useComponent(definition, props) {
|
|
320
|
+
const id = useId();
|
|
321
|
+
const ref = useRef(null);
|
|
322
|
+
if (ref.current?.instance.definition !== definition) {
|
|
323
|
+
const instance = createInstance(definition, {
|
|
324
|
+
props,
|
|
325
|
+
id
|
|
326
|
+
});
|
|
327
|
+
ref.current = {
|
|
328
|
+
instance,
|
|
329
|
+
binder: createBinder(instance),
|
|
330
|
+
handlers: /* @__PURE__ */ new Map(),
|
|
331
|
+
refs: /* @__PURE__ */ new Map(),
|
|
332
|
+
itemHandlers: /* @__PURE__ */ new Map(),
|
|
333
|
+
items: /* @__PURE__ */ new Map(),
|
|
334
|
+
getItemProps: createGetItemProps(() => ref.current),
|
|
335
|
+
attaching: /* @__PURE__ */ new Map(),
|
|
336
|
+
defaults: /* @__PURE__ */ new Map(),
|
|
337
|
+
checked: /* @__PURE__ */ new WeakSet(),
|
|
338
|
+
isLinkedByContext: false
|
|
339
|
+
};
|
|
340
|
+
} else ref.current.instance.setProps(props);
|
|
341
|
+
const internal = ref.current;
|
|
342
|
+
const { instance, binder } = internal;
|
|
343
|
+
const scope = useContext(OwnerContext);
|
|
344
|
+
const ownerLink = definition.owner;
|
|
345
|
+
const contextOwner = ownerLink ? findOwner(scope, ownerLink.definition) : void 0;
|
|
346
|
+
if (contextOwner) {
|
|
347
|
+
instance.link(contextOwner);
|
|
348
|
+
internal.isLinkedByContext = true;
|
|
349
|
+
} else if (internal.isLinkedByContext) {
|
|
350
|
+
instance.link(void 0);
|
|
351
|
+
internal.isLinkedByContext = false;
|
|
352
|
+
}
|
|
353
|
+
const isLinkedByDom = ownerLink !== void 0 && contextOwner === void 0 && props.owner === void 0;
|
|
354
|
+
useIsomorphicLayoutEffect(() => {
|
|
355
|
+
flushAttaching(internal);
|
|
356
|
+
instance.batch(() => {
|
|
357
|
+
instance.start();
|
|
358
|
+
binder.start();
|
|
359
|
+
});
|
|
360
|
+
const unprovide = isOwnerDefinition(definition) ? definition.parts.flatMap((part) => {
|
|
361
|
+
const element = instance.element(part);
|
|
362
|
+
return element ? [provideOwner(instance, element)] : [];
|
|
363
|
+
}) : [];
|
|
364
|
+
return () => {
|
|
365
|
+
for (const stop of unprovide) stop();
|
|
366
|
+
binder.stop();
|
|
367
|
+
instance.stop();
|
|
368
|
+
};
|
|
369
|
+
}, [instance, binder]);
|
|
370
|
+
useIsomorphicLayoutEffect(() => {
|
|
371
|
+
if (!isLinkedByDom) return;
|
|
372
|
+
const element = definition.parts.map((part) => instance.element(part)).find(Boolean);
|
|
373
|
+
if (!element) return;
|
|
374
|
+
const forget = requestOwner(element, ownerLink.definition, (owner) => {
|
|
375
|
+
instance.link(owner);
|
|
376
|
+
instance.sync();
|
|
377
|
+
});
|
|
378
|
+
return () => {
|
|
379
|
+
forget();
|
|
380
|
+
instance.link(void 0);
|
|
381
|
+
instance.sync();
|
|
382
|
+
};
|
|
383
|
+
}, [instance, isLinkedByDom]);
|
|
384
|
+
useIsomorphicLayoutEffect(() => {
|
|
385
|
+
flushAttaching(internal);
|
|
386
|
+
syncing.add(instance);
|
|
387
|
+
try {
|
|
388
|
+
instance.sync();
|
|
389
|
+
} finally {
|
|
390
|
+
syncing.delete(instance);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
const connected = instance.connect();
|
|
394
|
+
if (internal.rendered?.connected !== connected) internal.rendered = {
|
|
395
|
+
connected,
|
|
396
|
+
parts: toReactParts(internal, connected)
|
|
397
|
+
};
|
|
398
|
+
return {
|
|
399
|
+
instance,
|
|
400
|
+
actions: instance.actions,
|
|
401
|
+
parts: internal.rendered.parts,
|
|
402
|
+
getItemProps: internal.getItemProps
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Reads one value of an instance's public state and re-renders only when it
|
|
407
|
+
* changes (`Object.is`, or `isEqual`). Takes the instance or the handle
|
|
408
|
+
* `useComponent` returned.
|
|
409
|
+
*/
|
|
410
|
+
function useComponentState(source, selector, isEqual = Object.is) {
|
|
411
|
+
const instance = "instance" in source ? source.instance : source;
|
|
412
|
+
const cache = useRef(null);
|
|
413
|
+
const getSnapshot = () => {
|
|
414
|
+
const state = instance.getState();
|
|
415
|
+
const last = cache.current;
|
|
416
|
+
if (last?.state === state && last.selector === selector) return last.value;
|
|
417
|
+
const value = selector(state);
|
|
418
|
+
const stable = last && isEqual(last.value, value) ? last.value : value;
|
|
419
|
+
cache.current = {
|
|
420
|
+
state,
|
|
421
|
+
selector,
|
|
422
|
+
value: stable
|
|
423
|
+
};
|
|
424
|
+
return stable;
|
|
425
|
+
};
|
|
426
|
+
const committed = useRef(null);
|
|
427
|
+
const subscribe = useCallback((onChange) => instance.subscribe(() => {
|
|
428
|
+
const last = committed.current;
|
|
429
|
+
if (syncing.has(instance) && last && Object.is(last.value, last.getSnapshot())) return;
|
|
430
|
+
onChange();
|
|
431
|
+
}), [instance]);
|
|
432
|
+
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
433
|
+
useInsertionEffect(() => {
|
|
434
|
+
committed.current = {
|
|
435
|
+
value,
|
|
436
|
+
getSnapshot
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
return value;
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
export { OwnerProvider, useComponent, useComponentState };
|
|
443
|
+
|
|
444
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/owner.ts","../src/props.ts","../src/use-component.ts"],"sourcesContent":["/**\n * Owners in React (decision 11): `OwnerProvider` makes an instance the owner\n * of the UniAria components rendered inside it, through a React context that\n * holds only the stable instance, so it re-renders nothing. A component\n * finds its owner during render, which gives the first render and the server\n * HTML the owner's state. Contexts nest: each component takes the nearest\n * owner of the definition it names.\n */\n\nimport type { AnyDefinition, Instance } from '@uniaria/core';\nimport type { ReactNode } from 'react';\nimport { createContext, createElement, useContext, useMemo } from 'react';\n\ninterface Scope {\n readonly instance: Instance;\n readonly parent: Scope | null;\n}\n\nexport const OwnerContext = createContext<Scope | null>(null);\n\nexport interface OwnerProviderProps<D extends AnyDefinition = AnyDefinition> {\n /** The owner: an instance, or the handle `useComponent` returned. */\n readonly owner: Instance<D> | { readonly instance: Instance<D> };\n readonly children?: ReactNode;\n}\n\n// Generic, since an `Instance<D>` isn't an `Instance`: its typed callback\n// props don't take `unknown`.\nexport function OwnerProvider<D extends AnyDefinition>({\n owner,\n children,\n}: OwnerProviderProps<D>) {\n const parent = useContext(OwnerContext);\n const instance = ('instance' in owner ? owner.instance : owner) as Instance;\n const scope = useMemo(() => ({ instance, parent }), [instance, parent]);\n return createElement(OwnerContext.Provider, { value: scope }, children);\n}\n\n/** The nearest owner of `definition` in the context, if any. */\nexport function findOwner(\n scope: Scope | null,\n definition: AnyDefinition,\n): Instance | undefined {\n for (let current = scope; current; current = current.parent) {\n if (current.instance.definition === definition) return current.instance;\n }\n return undefined;\n}\n","/**\n * Neutral part props → React props (design decision 4): DOM attribute names\n * become React prop names, empty boolean attributes become `true`, and DOM\n * event names become React handler props.\n */\n\n/** DOM attribute → React prop, where they differ. */\nconst ATTRIBUTES: Readonly<Record<string, string>> = {\n class: 'className',\n for: 'htmlFor',\n tabindex: 'tabIndex',\n readonly: 'readOnly',\n autofocus: 'autoFocus',\n autocomplete: 'autoComplete',\n autocorrect: 'autoCorrect',\n contenteditable: 'contentEditable',\n enterkeyhint: 'enterKeyHint',\n inputmode: 'inputMode',\n maxlength: 'maxLength',\n minlength: 'minLength',\n spellcheck: 'spellCheck',\n colspan: 'colSpan',\n rowspan: 'rowSpan',\n formaction: 'formAction',\n formenctype: 'formEncType',\n formmethod: 'formMethod',\n formnovalidate: 'formNoValidate',\n formtarget: 'formTarget',\n novalidate: 'noValidate',\n};\n\n/** React props that take a boolean, so `''` must become `true`. */\nconst BOOLEANS = new Set([\n 'disabled',\n 'hidden',\n 'checked',\n 'selected',\n 'required',\n 'readOnly',\n 'multiple',\n 'open',\n 'inert',\n 'autoFocus',\n 'noValidate',\n 'formNoValidate',\n]);\n\n/** React's DOM event props; the map key is the DOM event name. */\nconst EVENTS = new Map(\n [\n 'onAnimationEnd',\n 'onAnimationIteration',\n 'onAnimationStart',\n 'onAuxClick',\n 'onBeforeInput',\n 'onBeforeToggle',\n 'onChange',\n 'onClick',\n 'onCompositionEnd',\n 'onCompositionStart',\n 'onCompositionUpdate',\n 'onContextMenu',\n 'onCopy',\n 'onCut',\n 'onDrag',\n 'onDragEnd',\n 'onDragEnter',\n 'onDragExit',\n 'onDragLeave',\n 'onDragOver',\n 'onDragStart',\n 'onDrop',\n 'onError',\n 'onGotPointerCapture',\n 'onInvalid',\n 'onKeyDown',\n 'onKeyUp',\n 'onLoad',\n 'onLostPointerCapture',\n 'onMouseDown',\n 'onMouseEnter',\n 'onMouseLeave',\n 'onMouseMove',\n 'onMouseOut',\n 'onMouseOver',\n 'onMouseUp',\n 'onPaste',\n 'onPointerCancel',\n 'onPointerDown',\n 'onPointerEnter',\n 'onPointerLeave',\n 'onPointerMove',\n 'onPointerOut',\n 'onPointerOver',\n 'onPointerUp',\n 'onReset',\n 'onScroll',\n 'onScrollEnd',\n 'onSelect',\n 'onSubmit',\n 'onToggle',\n 'onTouchCancel',\n 'onTouchEnd',\n 'onTouchMove',\n 'onTouchStart',\n 'onTransitionEnd',\n 'onWheel',\n ].map((name) => [name.slice(2).toLowerCase(), name]),\n);\n// Focus entering or leaving a subtree: React's onFocus and onBlur bubble and\n// are backed by focusin and focusout. The input event is React's onInput:\n// its onChange fires on click for a checkbox, before a cancelled click is\n// undone, and skips changes its value tracker missed.\nEVENTS.set('focusin', 'onFocus');\nEVENTS.set('focusout', 'onBlur');\nEVENTS.set('input', 'onInput');\nEVENTS.set('dblclick', 'onDoubleClick');\n\n/** The React prop for a DOM attribute. */\nexport function reactPropName(attribute: string): string {\n return ATTRIBUTES[attribute] ?? attribute;\n}\n\n/** The React value for an attribute value: `''` on a boolean prop is `true`. */\nexport function reactPropValue(name: string, value: string): string | boolean {\n return value === '' && BOOLEANS.has(name) ? true : value;\n}\n\n/** The React handler prop for a DOM event name. */\nexport function reactEventName(type: string): string {\n return (\n EVENTS.get(type) ?? `on${type.charAt(0).toUpperCase()}${type.slice(1)}`\n );\n}\n","/**\n * The React adapter (design decisions 2 and 9). Internal state never\n * re-renders: each part renders once with its current props and a ref, and\n * the DOM binder from `@uniaria/core/dom` writes later changes straight to the\n * element. A component re-renders for state only through\n * `useComponentState`, and only when the selected value changes.\n */\n\nimport type {\n ActionsOf,\n AnyDefinition,\n Instance,\n ItemPartOf,\n OwnerLink,\n PartOf,\n PartProps,\n PropsOf,\n StateOf,\n} from '@uniaria/core';\nimport { createInstance, isOwnerDefinition } from '@uniaria/core';\nimport type { Binder } from '@uniaria/core/dom';\nimport {\n createBinder,\n isDevelopment,\n provideOwner,\n requestOwner,\n warn,\n} from '@uniaria/core/dom';\nimport type { SyntheticEvent } from 'react';\nimport {\n useCallback,\n useContext,\n useEffect,\n useId,\n useInsertionEffect,\n useLayoutEffect,\n useRef,\n useSyncExternalStore,\n} from 'react';\n\nimport { OwnerContext, findOwner } from './owner';\nimport { reactEventName, reactPropName, reactPropValue } from './props';\n\n/** Props to spread onto a part element: attributes, stable handlers, a ref. */\nexport type ReactPartProps = Readonly<Record<string, unknown>> & {\n readonly ref: (element: Element | null) => void;\n};\n\nexport interface ComponentHandle<D extends AnyDefinition> {\n /** The stable instance: pass it to `useComponentState` or as an `owner`. */\n readonly instance: Instance<D>;\n /** The component's public actions, such as a popover's `open()` and `close()`. */\n readonly actions: ActionsOf<D>;\n /** Props for each part, from this render. */\n readonly parts: Readonly<Record<PartOf<D>, ReactPartProps>>;\n /**\n * Props for one item part of one item (Listbox spec, Item parts), from\n * this render. Later changes reach the element through the binder, so the\n * component rendering the rows doesn't render again for them. Item parts\n * take no live DOM properties as React defaults.\n */\n getItemProps(part: ItemPartOf<D>, key: string): ReactPartProps;\n}\n\n/**\n * Live DOM properties React renders once as defaults, so it never controls\n * the element; the binder sets them after that.\n */\nconst DEFAULT_PROPS: Readonly<Record<string, string>> = {\n value: 'defaultValue',\n checked: 'defaultChecked',\n};\n\ninterface Internal<D extends AnyDefinition> {\n readonly instance: Instance<D>;\n readonly binder: Binder;\n readonly handlers: Map<string, (event: SyntheticEvent) => void>;\n readonly refs: Map<string, (element: Element | null) => void>;\n /** Per item part and event type; the key comes from the element. */\n readonly itemHandlers: Map<string, (event: SyntheticEvent) => void>;\n /** Per item part and key: its ref, and the props it last rendered. */\n readonly items: Map<\n string,\n {\n readonly ref: (element: Element | null) => void;\n rendered?: { readonly props: PartProps; readonly react: ReactPartProps };\n }\n >;\n readonly getItemProps: (part: string, key: string) => ReactPartProps;\n /**\n * Item elements React attached or detached since the last flush, per part\n * and key: each row has its own ref, so a commit that swaps a range of\n * rows reaches the binder as one batch, in a microtask.\n */\n readonly attaching: Map<\n string,\n { readonly part: string; readonly key: string; element: Element | null }\n >;\n /** Default props from the first render, kept stable after it. */\n readonly defaults: Map<string, Readonly<Record<string, unknown>>>;\n /** Elements whose attributes were checked against the render. */\n readonly checked: WeakSet<Element>;\n /** Whether the instance's owner came from React context. */\n isLinkedByContext: boolean;\n rendered?: {\n readonly connected: unknown;\n readonly parts: Record<string, ReactPartProps>;\n };\n}\n\n/**\n * Instances whose new props `useComponent` is syncing after a commit. React's\n * `useSyncExternalStore` records the snapshot it rendered only in a passive\n * effect, so a notification from this layout-effect sync is compared with the\n * previous snapshot, and a component whose render already showed the new\n * props would render again with the same value.\n */\nconst syncing = new WeakSet<object>();\n\n// Layout effects attach the binder before paint; on the server they never\n// run, and React 18 would warn about them.\nconst useIsomorphicLayoutEffect =\n typeof document === 'undefined' ? useEffect : useLayoutEffect;\n\n/**\n * Warns when the element's attributes differ from the part props React just\n * rendered: the consumer wrote an attribute the binder owns, which the binder\n * would silently overwrite.\n */\nfunction checkOwnership<D extends AnyDefinition>(\n internal: Internal<D>,\n part: string,\n element: Element,\n rendered: PartProps | undefined = (\n internal.rendered?.connected as\n Readonly<Record<string, PartProps>> | undefined\n )?.[part],\n): void {\n // A named part keeps an id the JSX gives it after the spread, and the\n // binder points every reference at it; an item's id is made from its key.\n const isItem =\n internal.instance.definition.itemParts?.includes(part) ?? false;\n for (const [name, value] of Object.entries(rendered?.attrs ?? {})) {\n if (name === 'id' && !isItem) continue;\n const actual = element.getAttribute(name);\n if (actual === (value ?? null)) continue;\n warn(\n `${internal.instance.definition.name}: \"${name}\" on the ${part} part is ` +\n `written by UniAria, but the element has ${actual === null ? 'none' : `\"${actual}\"`}. ` +\n 'Pass it as a prop, or drop it from the JSX.',\n );\n }\n}\n\n/**\n * A style property as React names it: custom properties as they are,\n * others in camelCase (`inset-block-start` → `insetBlockStart`).\n */\nconst reactStyleName = (name: string): string =>\n name.startsWith('--')\n ? name\n : name.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());\n\n/** Attributes and style as React props. */\nfunction toReactAttrs(\n props: PartProps,\n result: Record<string, unknown>,\n): Record<string, unknown> {\n for (const [name, value] of Object.entries(props.attrs)) {\n if (value === undefined) continue;\n const prop = reactPropName(name);\n result[prop] = reactPropValue(prop, value);\n }\n if (props.style) {\n const style: Record<string, string> = {};\n for (const [name, value] of Object.entries(props.style)) {\n if (value !== undefined) style[reactStyleName(name)] = value;\n }\n result.style = style;\n }\n return result;\n}\n\n/**\n * Hands the binder every item element attached or detached since the last\n * flush: in a microtask, or before the owner's layout effects when the\n * owner rendered in the same commit, so its instance starts and syncs with\n * its items attached.\n */\nfunction flushAttaching<D extends AnyDefinition>(internal: Internal<D>): void {\n if (internal.attaching.size === 0) return;\n const list = Array.from(internal.attaching.values());\n internal.attaching.clear();\n internal.instance.batch(() => {\n for (const { part, key, element } of list) {\n internal.binder.attachItem(part, key, element);\n }\n });\n}\n\n/**\n * `getItemProps` for one instance: React props from the cached\n * `connectItem()`, reused while it returns the same object, with handlers\n * stable per part and event type and a ref stable per part and key.\n */\nfunction createGetItemProps<D extends AnyDefinition>(\n internal: () => Internal<D>,\n): (part: string, key: string) => ReactPartProps {\n return (part, key) => {\n const { instance, binder, itemHandlers, items } = internal();\n const props = instance.connectItem(part as never, key);\n const id = `${part}\\n${key}`;\n let item = items.get(id);\n if (!item) {\n const ref = (element: Element | null) => {\n const current = internal();\n const entry = current.items.get(id);\n if (element && isDevelopment() && !current.checked.has(element)) {\n current.checked.add(element);\n checkOwnership(current, part, element, entry?.rendered?.props);\n }\n const pending = current.attaching.get(id);\n if (pending) pending.element = element;\n else {\n if (current.attaching.size === 0) {\n queueMicrotask(() => flushAttaching(current));\n }\n current.attaching.set(id, { part, key, element });\n }\n // An item that unmounts takes its cache with it; a remount gets a\n // new ref.\n if (!element && entry?.ref === ref) current.items.delete(id);\n };\n item = { ref };\n items.set(id, item);\n }\n if (item.rendered?.props === props) return item.rendered.react;\n const result = toReactAttrs(props, {});\n for (const type of Object.keys(props.on ?? {})) {\n const handlerKey = `${part}:${type}`;\n let handler = itemHandlers.get(handlerKey);\n if (!handler) {\n handler = (event: SyntheticEvent) => {\n const target = binder.itemOf(event.currentTarget as Element);\n if (!target) return;\n instance\n .connectItem(target.part as never, target.key)\n .on?.[type]?.(event.nativeEvent as never);\n };\n itemHandlers.set(handlerKey, handler);\n }\n result[reactEventName(type)] = handler;\n }\n const react = { ...result, ref: item.ref } as ReactPartProps;\n item.rendered = { props, react };\n return react;\n };\n}\n\nfunction toReactParts<D extends AnyDefinition>(\n internal: Internal<D>,\n connected: Readonly<Record<string, PartProps>>,\n): Record<string, ReactPartProps> {\n const { instance, binder, handlers, refs, defaults } = internal;\n const parts: Record<string, ReactPartProps> = {};\n for (const [part, props] of Object.entries(connected)) {\n const result: Record<string, unknown> = {};\n // From the first render only: a later default would reset the value\n // attribute, which a form reset goes back to.\n let initial = defaults.get(part);\n if (!initial) {\n const first: Record<string, unknown> = {};\n for (const [name, value] of Object.entries(props.properties ?? {})) {\n const prop = DEFAULT_PROPS[name];\n if (prop && value !== undefined) first[prop] = value;\n }\n initial = first;\n defaults.set(part, initial);\n }\n Object.assign(result, initial);\n toReactAttrs(props, result);\n // Handlers keep their identity across renders and call the handler from\n // the latest connect() with the native event.\n for (const type of Object.keys(props.on ?? {})) {\n const key = `${part}:${type}`;\n let handler = handlers.get(key);\n if (!handler) {\n handler = (event: SyntheticEvent) => {\n const latest = instance.connect() as Readonly<\n Record<string, PartProps>\n >;\n latest[part]?.on?.[type]?.(event.nativeEvent as never);\n };\n handlers.set(key, handler);\n }\n result[reactEventName(type)] = handler;\n }\n let ref = refs.get(part);\n if (!ref) {\n ref = (element: Element | null) => {\n // Once per element: one attached again (StrictMode does) carries\n // what the binder wrote since the render.\n if (element && isDevelopment() && !internal.checked.has(element)) {\n internal.checked.add(element);\n checkOwnership(internal, part, element);\n }\n binder.attach(part, element);\n };\n refs.set(part, ref);\n }\n parts[part] = { ...result, ref };\n }\n return parts;\n}\n\n/**\n * Creates an instance of `definition` for this component and returns props\n * for its parts. Props are stored silently during render and synced after\n * commit; internal state changes never re-render this component.\n */\nexport function useComponent<D extends AnyDefinition>(\n definition: D,\n props: NoInfer<PropsOf<D>>,\n): ComponentHandle<D> {\n const id = useId();\n const ref = useRef<Internal<D> | null>(null);\n if (ref.current?.instance.definition !== definition) {\n const instance = createInstance(definition, { props, id });\n ref.current = {\n instance,\n binder: createBinder(instance),\n handlers: new Map(),\n refs: new Map(),\n itemHandlers: new Map(),\n items: new Map(),\n getItemProps: createGetItemProps(() => ref.current!),\n attaching: new Map(),\n defaults: new Map(),\n checked: new WeakSet(),\n isLinkedByContext: false,\n };\n } else {\n ref.current.instance.setProps(props);\n }\n const internal = ref.current;\n const { instance, binder } = internal;\n\n // Decision 11: the `owner` prop, else React context during render (so the\n // first render and SSR have it), else the DOM after mount.\n const scope = useContext(OwnerContext);\n const ownerLink = (definition as { owner?: OwnerLink }).owner;\n const contextOwner = ownerLink\n ? findOwner(scope, ownerLink.definition)\n : undefined;\n if (contextOwner) {\n instance.link(contextOwner);\n internal.isLinkedByContext = true;\n } else if (internal.isLinkedByContext) {\n instance.link(undefined);\n internal.isLinkedByContext = false;\n }\n const isLinkedByDom =\n ownerLink !== undefined &&\n contextOwner === undefined &&\n (props as { owner?: unknown }).owner === undefined;\n\n useIsomorphicLayoutEffect(() => {\n flushAttaching(internal);\n // In one batch, so the start's element effects run once the binder has\n // written the parts: an overlay open from the start is `data-entering`\n // as it shows.\n instance.batch(() => {\n instance.start();\n binder.start();\n });\n // Answers owner requests from custom elements and React components that\n // find their owner through the DOM.\n const unprovide = isOwnerDefinition(definition)\n ? definition.parts.flatMap((part: string) => {\n const element = instance.element(part as PartOf<D>);\n return element\n ? [\n provideOwner(\n instance as unknown as Instance,\n element as Element,\n ),\n ]\n : [];\n })\n : [];\n return () => {\n for (const stop of unprovide) stop();\n binder.stop();\n instance.stop();\n };\n }, [instance, binder]);\n\n useIsomorphicLayoutEffect(() => {\n if (!isLinkedByDom) return;\n const element = definition.parts\n .map((part: string) => instance.element(part as PartOf<D>))\n .find(Boolean) as Element | undefined;\n if (!element) return;\n const forget = requestOwner(element, ownerLink!.definition, (owner) => {\n instance.link(owner);\n instance.sync();\n });\n return () => {\n forget();\n instance.link(undefined);\n instance.sync();\n };\n }, [instance, isLinkedByDom]);\n\n useIsomorphicLayoutEffect(() => {\n flushAttaching(internal);\n syncing.add(instance);\n try {\n instance.sync();\n } finally {\n syncing.delete(instance);\n }\n });\n\n const connected = instance.connect();\n if (internal.rendered?.connected !== connected) {\n internal.rendered = {\n connected,\n parts: toReactParts(internal, connected),\n };\n }\n\n return {\n instance,\n actions: instance.actions,\n parts: internal.rendered.parts as ComponentHandle<D>['parts'],\n getItemProps: internal.getItemProps as ComponentHandle<D>['getItemProps'],\n };\n}\n\n/**\n * Reads one value of an instance's public state and re-renders only when it\n * changes (`Object.is`, or `isEqual`). Takes the instance or the handle\n * `useComponent` returned.\n */\nexport function useComponentState<D extends AnyDefinition, T>(\n source: Instance<D> | ComponentHandle<D>,\n selector: (state: StateOf<D>) => T,\n isEqual: (a: T, b: T) => boolean = Object.is,\n): T {\n const instance = 'instance' in source ? source.instance : source;\n const cache = useRef<{\n state: StateOf<D>;\n selector: (state: StateOf<D>) => T;\n value: T;\n } | null>(null);\n\n // The selected value is the snapshot, so it must be stable between\n // changes: reuse it while the state object is the same, or while the new\n // selection is equal to the last one.\n const getSnapshot = (): T => {\n const state = instance.getState();\n const last = cache.current;\n if (last?.state === state && last.selector === selector) return last.value;\n const value = selector(state);\n const stable = last && isEqual(last.value, value) ? last.value : value;\n cache.current = { state, selector, value: stable };\n return stable;\n };\n\n // The committed value and snapshot function. Insertion effects run before\n // every layout effect, so they are current when `useComponent` syncs.\n const committed = useRef<{ value: T; getSnapshot: () => T } | null>(null);\n\n // Skips the notification from the prop sync after a commit when this hook\n // already rendered the value it would bring (see `syncing`). A change the\n // sync itself makes, such as hover ending on disable, still re-renders.\n const subscribe = useCallback(\n (onChange: () => void) =>\n instance.subscribe(() => {\n const last = committed.current;\n if (\n syncing.has(instance) &&\n last &&\n Object.is(last.value, last.getSnapshot())\n ) {\n return;\n }\n onChange();\n }),\n [instance],\n );\n\n const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n useInsertionEffect(() => {\n committed.current = { value, getSnapshot };\n });\n return value;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniaria/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React adapter for UniAria: one generic useComponent hook over any UniAria component definition",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"uniaria-source": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/tenphi/uniaria.git",
|
|
21
|
+
"directory": "packages/react"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"headless",
|
|
25
|
+
"ui",
|
|
26
|
+
"react",
|
|
27
|
+
"accessibility",
|
|
28
|
+
"a11y",
|
|
29
|
+
"aria"
|
|
30
|
+
],
|
|
31
|
+
"author": "Andrey Yamanov",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/tenphi/uniaria/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/tenphi/uniaria#readme",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@uniaria/core": "0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@uniaria/conformance": "0.0.0",
|
|
51
|
+
"@uniaria/testing": "0.1.0",
|
|
52
|
+
"@types/react": "^19.3.0",
|
|
53
|
+
"@types/react-dom": "^19.3.0",
|
|
54
|
+
"react": "^19.3.0",
|
|
55
|
+
"react-aria": "^3.52.1",
|
|
56
|
+
"react-dom": "^19.3.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsdown",
|
|
60
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p bench/tsconfig.json",
|
|
61
|
+
"bench": "node bench/run.ts"
|
|
62
|
+
}
|
|
6
63
|
}
|