@unofficialbox/box-open-elements-react 0.7.0 → 0.9.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/README.md +25 -11
- package/dist/button.d.ts +24 -8
- package/dist/button.js +8 -0
- package/dist/create-web-component.d.ts +38 -2
- package/dist/events.d.ts +16 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,21 @@ The core package stays framework-agnostic. This adapter is a thin layer that:
|
|
|
6
6
|
|
|
7
7
|
1. imports the automatically registered custom element
|
|
8
8
|
2. syncs React props onto element **properties** (not fragile attribute stringification)
|
|
9
|
-
3. forwards refs and
|
|
9
|
+
3. forwards refs, and binds event callbacks directly to the element
|
|
10
|
+
|
|
11
|
+
Callbacks an adapter **declares** receive the **native** DOM event, not a React
|
|
12
|
+
`SyntheticEvent`. They are registered with `addEventListener` on the custom
|
|
13
|
+
element so the listener travels with it: React delegates from its root
|
|
14
|
+
container, so a node relocated out of that container silently stops receiving
|
|
15
|
+
delegated handlers. `Button` declares its own `onClick`, typed as
|
|
16
|
+
`(event: MouseEvent) => void`, for exactly that reason.
|
|
17
|
+
|
|
18
|
+
Props an adapter does not declare — including React's `onClick` on `Select`,
|
|
19
|
+
`TextField` and `Dialog` — are forwarded as ordinary host props and stay
|
|
20
|
+
delegated. That is fine unless something moves the node out of the React root,
|
|
21
|
+
and it is React's delegation model rather than these components': a plain
|
|
22
|
+
`<div onClick>` in a relocated subtree behaves the same. (`box-drawer` used to
|
|
23
|
+
relocate its subtree and no longer does — it uses the top layer.)
|
|
10
24
|
|
|
11
25
|
See [`docs/integration/react.md`](../../docs/integration/react.md) for the React
|
|
12
26
|
boundary and the [framework adapter tracker](../../docs/integration/framework-adapters.md)
|
|
@@ -43,27 +57,27 @@ export function SaveAction() {
|
|
|
43
57
|
}
|
|
44
58
|
```
|
|
45
59
|
|
|
46
|
-
The
|
|
60
|
+
The supported surface also includes a controlled `Dialog` wrapper and
|
|
47
61
|
`useExplorerSelectionController`, which subscribes React to the existing
|
|
48
62
|
headless selection controller without duplicating its state.
|
|
49
63
|
|
|
50
64
|
## Status
|
|
51
65
|
|
|
52
|
-
**
|
|
53
|
-
composed events, value and structured property synchronization, latest
|
|
54
|
-
routing, forwarded refs, controlled overlay focus behavior, and
|
|
55
|
-
rendering. `useExplorerSelectionController` proves headless
|
|
56
|
-
composition.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
**Supported** as of `0.7.0` — `Button`, `TextField`, `Select`, and `Dialog` prove
|
|
67
|
+
native and composed events, value and structured property synchronization, latest
|
|
68
|
+
callback routing, forwarded refs, controlled overlay focus behavior, and
|
|
69
|
+
server-safe host rendering. `useExplorerSelectionController` proves headless
|
|
70
|
+
controller composition. The two conditions this section used to defer on are met:
|
|
71
|
+
the first public npm publication happened at `0.7.0`, and a clean registry install
|
|
72
|
+
resolves the package and loads its exports. React releases in lockstep with the
|
|
73
|
+
Angular, Vue, and Svelte adapters under `adapters-vX.Y.Z`, at the core's version.
|
|
60
74
|
|
|
61
75
|
## Supported versions
|
|
62
76
|
|
|
63
77
|
| Dependency | Contract |
|
|
64
78
|
| --- | --- |
|
|
65
79
|
| React / React DOM | `^19.0.0` |
|
|
66
|
-
| `@unofficialbox/box-open-elements` | `^0.
|
|
80
|
+
| `@unofficialbox/box-open-elements` | `^0.9.0` |
|
|
67
81
|
| Node.js for SSR | `>=20.9.0` |
|
|
68
82
|
| Next.js validation host | `16.2.12` |
|
|
69
83
|
|
package/dist/button.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { MouseEventHandler } from "react";
|
|
2
1
|
import { Button as ButtonElement } from "@unofficialbox/box-open-elements/button";
|
|
3
2
|
import { type WebComponentProps } from "./create-web-component.js";
|
|
4
|
-
|
|
3
|
+
import type { NativeEventHandler } from "./events.js";
|
|
4
|
+
export type ButtonProps = Omit<WebComponentProps, "onClick"> & {
|
|
5
5
|
/** Button label text (maps to the `label` property / attribute). */
|
|
6
6
|
label?: string;
|
|
7
7
|
/** Visual tone: `primary` (default), `neutral`, `danger`. */
|
|
@@ -9,16 +9,26 @@ export type ButtonProps = WebComponentProps & {
|
|
|
9
9
|
/** Control size: `small`, `medium` (default), `large`. */
|
|
10
10
|
size?: string;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Click callback, bound directly to the element.
|
|
14
|
+
*
|
|
15
|
+
* Receives a **native** `MouseEvent`, not a React `SyntheticEvent`. See
|
|
16
|
+
* `NativeEventHandler` for why the binding is direct.
|
|
17
|
+
*/
|
|
18
|
+
onClick?: NativeEventHandler<ButtonElement, MouseEvent>;
|
|
13
19
|
};
|
|
14
20
|
/**
|
|
15
21
|
* React wrapper for `<box-button>`. Registers the custom element on first render
|
|
16
22
|
* and syncs props as element properties for the supported React 19 contract.
|
|
23
|
+
*
|
|
24
|
+
* `click` is bound through the factory's `events` map rather than left to
|
|
25
|
+
* React's `onClick`. React delegates from its root container, so a button that
|
|
26
|
+
* has been relocated out of it — every button inside a `box-drawer`, which
|
|
27
|
+
* portals to `document.body` on open — never sees a delegated event and the
|
|
28
|
+
* callback silently does nothing. A listener on the element travels with it.
|
|
29
|
+
* The other adapters already bind this way; Button was the one that didn't.
|
|
17
30
|
*/
|
|
18
|
-
export declare const Button: import("react").ForwardRefExoticComponent<{
|
|
19
|
-
className?: string;
|
|
20
|
-
style?: import("react").CSSProperties;
|
|
21
|
-
} & Omit<import("react").HTMLAttributes<HTMLElement>, "className" | "style" | "children" | "onCancel"> & {
|
|
31
|
+
export declare const Button: import("react").ForwardRefExoticComponent<Omit<WebComponentProps, "onClick"> & {
|
|
22
32
|
/** Button label text (maps to the `label` property / attribute). */
|
|
23
33
|
label?: string;
|
|
24
34
|
/** Visual tone: `primary` (default), `neutral`, `danger`. */
|
|
@@ -26,5 +36,11 @@ export declare const Button: import("react").ForwardRefExoticComponent<{
|
|
|
26
36
|
/** Control size: `small`, `medium` (default), `large`. */
|
|
27
37
|
size?: string;
|
|
28
38
|
disabled?: boolean;
|
|
29
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Click callback, bound directly to the element.
|
|
41
|
+
*
|
|
42
|
+
* Receives a **native** `MouseEvent`, not a React `SyntheticEvent`. See
|
|
43
|
+
* `NativeEventHandler` for why the binding is direct.
|
|
44
|
+
*/
|
|
45
|
+
onClick?: NativeEventHandler<ButtonElement, MouseEvent>;
|
|
30
46
|
} & import("react").RefAttributes<ButtonElement>>;
|
package/dist/button.js
CHANGED
|
@@ -4,11 +4,19 @@ ButtonElement.register();
|
|
|
4
4
|
/**
|
|
5
5
|
* React wrapper for `<box-button>`. Registers the custom element on first render
|
|
6
6
|
* and syncs props as element properties for the supported React 19 contract.
|
|
7
|
+
*
|
|
8
|
+
* `click` is bound through the factory's `events` map rather than left to
|
|
9
|
+
* React's `onClick`. React delegates from its root container, so a button that
|
|
10
|
+
* has been relocated out of it — every button inside a `box-drawer`, which
|
|
11
|
+
* portals to `document.body` on open — never sees a delegated event and the
|
|
12
|
+
* callback silently does nothing. A listener on the element travels with it.
|
|
13
|
+
* The other adapters already bind this way; Button was the one that didn't.
|
|
7
14
|
*/
|
|
8
15
|
export const Button = createWebComponent({
|
|
9
16
|
tagName: "box-button",
|
|
10
17
|
displayName: "Button",
|
|
11
18
|
propertyNames: ["label", "tone", "size", "disabled"],
|
|
19
|
+
events: [{ propName: "onClick", eventName: "click" }],
|
|
12
20
|
sync: (element, props) => {
|
|
13
21
|
if (props.label !== undefined) {
|
|
14
22
|
element.label = props.label;
|
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
import { type CSSProperties, type HTMLAttributes } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Host props an adapter accepts.
|
|
4
|
+
*
|
|
5
|
+
* `onCancel` is omitted because `Dialog` redeclares it with a native-event
|
|
6
|
+
* signature, and an intersection of two function types is an overload that no
|
|
7
|
+
* single handler satisfies.
|
|
8
|
+
*
|
|
9
|
+
* React's `onClick` stays. It is delegated from the React root container, which
|
|
10
|
+
* means it does **not** fire for an element that has relocated outside that
|
|
11
|
+
* container — every host inside an open `box-drawer`, which portals its subtree
|
|
12
|
+
* to `document.body`. That is a real trap, but it is React's trap and it catches
|
|
13
|
+
* a plain `<div onClick>` in a drawer just the same; removing the prop from
|
|
14
|
+
* three adapters would not fix the class of bug, only make those three behave
|
|
15
|
+
* unlike every other element in the tree while breaking handlers that work
|
|
16
|
+
* perfectly well outside overlays. An adapter that needs a click callback to
|
|
17
|
+
* survive relocation declares its own and binds through `events` below, as
|
|
18
|
+
* `Button` does.
|
|
19
|
+
*/
|
|
2
20
|
export type WebComponentProps = {
|
|
3
21
|
className?: string;
|
|
4
22
|
style?: CSSProperties;
|
|
5
23
|
} & Omit<HTMLAttributes<HTMLElement>, "className" | "style" | "children" | "onCancel">;
|
|
6
|
-
|
|
24
|
+
/**
|
|
25
|
+
* What the factory itself requires of an adapter's props.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately narrower than `WebComponentProps`: the factory reads `className`
|
|
28
|
+
* and `style` and spreads the rest, so those two are the whole requirement.
|
|
29
|
+
* Constraining to the full host-prop type instead would forbid an adapter from
|
|
30
|
+
* *narrowing* a host prop — `Button` redeclaring `onClick` as a native-event
|
|
31
|
+
* handler is not assignable to React's `MouseEventHandler` (parameters are
|
|
32
|
+
* contravariant, and a `SyntheticEvent` is not a `MouseEvent`), so the whole
|
|
33
|
+
* prop would have to come off the base type for every adapter to keep one
|
|
34
|
+
* adapter's narrowing legal. Each adapter still builds its props from
|
|
35
|
+
* `WebComponentProps`; the constraint just stops one adapter's choice from
|
|
36
|
+
* dictating the shared type.
|
|
37
|
+
*/
|
|
38
|
+
type AdapterHostProps = {
|
|
39
|
+
className?: string;
|
|
40
|
+
style?: CSSProperties;
|
|
41
|
+
};
|
|
42
|
+
type CreateWebComponentOptions<E extends HTMLElement, P extends AdapterHostProps> = {
|
|
7
43
|
tagName: string;
|
|
8
44
|
/** Sync React props onto the custom element instance (prefer properties over attributes). */
|
|
9
45
|
sync: (element: E, props: P) => void;
|
|
@@ -20,5 +56,5 @@ type CreateWebComponentOptions<E extends HTMLElement, P extends WebComponentProp
|
|
|
20
56
|
* Thin React adapter factory for a box-open-elements custom element.
|
|
21
57
|
* Defines the element once, syncs props via properties, and forwards refs/events.
|
|
22
58
|
*/
|
|
23
|
-
export declare const createWebComponent: <E extends HTMLElement, P extends
|
|
59
|
+
export declare const createWebComponent: <E extends HTMLElement, P extends AdapterHostProps>(options: CreateWebComponentOptions<E, P>) => import("react").ForwardRefExoticComponent<import("react").PropsWithoutRef<P> & import("react").RefAttributes<E>>;
|
|
24
60
|
export {};
|
package/dist/events.d.ts
CHANGED
|
@@ -4,3 +4,19 @@ export type CustomEventHandler<E extends HTMLElement, Detail> = (event: CustomEv
|
|
|
4
4
|
export type ValueChangedDetail = {
|
|
5
5
|
value: string;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* A callback bound with `addEventListener`, receiving the **native** event.
|
|
9
|
+
*
|
|
10
|
+
* Not a React `SyntheticEvent`. Adapter callbacks are registered directly on
|
|
11
|
+
* the custom element rather than routed through React's delegation, so what
|
|
12
|
+
* arrives is the real DOM event — `event.nativeEvent` does not exist on it, and
|
|
13
|
+
* `stopPropagation` acts on the real tree.
|
|
14
|
+
*
|
|
15
|
+
* That indirection is not a style choice. React delegates from the root
|
|
16
|
+
* container, and an element that relocates itself outside that container —
|
|
17
|
+
* `box-drawer` portals to `document.body` when it opens — stops receiving
|
|
18
|
+
* delegated events entirely. A listener on the element itself travels with it.
|
|
19
|
+
*/
|
|
20
|
+
export type NativeEventHandler<E extends HTMLElement, Ev extends Event = Event> = (event: Ev & {
|
|
21
|
+
currentTarget: E;
|
|
22
|
+
}) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unofficialbox/box-open-elements-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Optional React wrappers for box-open-elements Web Components. Thin adapters — core stays framework-agnostic.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://unofficialbox.github.io/box-open-elements",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"node": ">=20.9.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@unofficialbox/box-open-elements": "^0.
|
|
40
|
+
"@unofficialbox/box-open-elements": "^0.9.0",
|
|
41
41
|
"react": "^19.0.0",
|
|
42
42
|
"react-dom": "^19.0.0"
|
|
43
43
|
},
|