@arun-dev/headless 3.0.1 → 4.0.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 +9 -11
- package/dist/button/index.cjs +3 -13
- package/dist/button/index.d.cts +8 -22
- package/dist/button/index.d.ts +8 -22
- package/dist/button/index.js +3 -13
- package/dist/switch/index.cjs +14 -21
- package/dist/switch/index.d.cts +2 -2
- package/dist/switch/index.d.ts +2 -2
- package/dist/switch/index.js +15 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,21 +28,19 @@ import { Switch } from '@arun-dev/headless/switch';
|
|
|
28
28
|
|
|
29
29
|
| Component | Parts | Props |
|
|
30
30
|
| --------- | ----------------------- | --------------------------------------------------------------------------- |
|
|
31
|
-
| `Button` | — | `disabled`, `
|
|
31
|
+
| `Button` | — | `disabled`, `type` |
|
|
32
32
|
| `Switch` | `Switch.Root`, `.Thumb` | `checked`, `defaultChecked`, `onCheckedChange`, `disabled`, `name`, `value` |
|
|
33
33
|
|
|
34
|
-
`Button` renders a `<button
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
disabled
|
|
38
|
-
A `render` component cannot be inspected, so a disabled Button gives it `aria-disabled` and
|
|
39
|
-
`data-disabled` rather than `disabled` — actually disabling itself is up to the component.
|
|
34
|
+
`Button` renders a `<button>` and defaults `type="button"` so it never submits a form by accident.
|
|
35
|
+
It does not navigate — a link is a separate `Link` component. A `render` component cannot be
|
|
36
|
+
inspected, so a disabled Button gives it `aria-disabled` and `data-disabled` rather than
|
|
37
|
+
`disabled` — actually disabling itself is up to the component.
|
|
40
38
|
|
|
41
39
|
```tsx
|
|
42
40
|
import { Button } from '@arun-dev/headless/button';
|
|
43
41
|
|
|
44
|
-
<Button
|
|
45
|
-
|
|
42
|
+
<Button type="submit" disabled={saving}>
|
|
43
|
+
Save
|
|
46
44
|
</Button>;
|
|
47
45
|
```
|
|
48
46
|
|
|
@@ -100,8 +98,8 @@ come last and win. An element that declares no children inherits yours, one that
|
|
|
100
98
|
keeps them.
|
|
101
99
|
|
|
102
100
|
```tsx
|
|
103
|
-
<Button render={<
|
|
104
|
-
<Button render={<
|
|
101
|
+
<Button render={<span />}>Docs</Button> // renders "Docs"
|
|
102
|
+
<Button render={<span>Read the docs</span>}>Docs</Button> // renders "Read the docs"
|
|
105
103
|
```
|
|
106
104
|
|
|
107
105
|
Event handlers are chained rather than replaced, and **your handler runs before the component's**,
|
package/dist/button/index.cjs
CHANGED
|
@@ -116,27 +116,17 @@ function useRender({
|
|
|
116
116
|
// src/button/Button.tsx
|
|
117
117
|
function Button({
|
|
118
118
|
disabled = false,
|
|
119
|
-
href,
|
|
120
119
|
type = "button",
|
|
121
120
|
className,
|
|
122
121
|
children,
|
|
123
122
|
render,
|
|
124
123
|
...rest
|
|
125
124
|
}) {
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
if (process.env.NODE_ENV !== "production" && disabled && render !== void 0) {
|
|
129
|
-
const renderProps = render.props;
|
|
130
|
-
if (renderProps?.href !== void 0) {
|
|
131
|
-
console.error(
|
|
132
|
-
"Button: `disabled` cannot remove the `href` on a `render` element \u2014 its own props outrank the component. Pass the URL as `href` on Button instead."
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const elementProps = rendersButton ? { type, disabled: disabled || void 0 } : { href: linksOut ? href : void 0, "aria-disabled": disabled || void 0 };
|
|
125
|
+
const rendersButton = render === void 0 || render.type === "button";
|
|
126
|
+
const elementProps = rendersButton ? { type, disabled: disabled || void 0 } : { "aria-disabled": disabled || void 0 };
|
|
137
127
|
return useRender({
|
|
138
128
|
render,
|
|
139
|
-
defaultTagName:
|
|
129
|
+
defaultTagName: "button",
|
|
140
130
|
props: {
|
|
141
131
|
...elementProps,
|
|
142
132
|
"data-disabled": disabled ? "" : void 0,
|
package/dist/button/index.d.cts
CHANGED
|
@@ -8,48 +8,34 @@ import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
|
8
8
|
*/
|
|
9
9
|
type ButtonOwnProps = {
|
|
10
10
|
/**
|
|
11
|
-
* Prevents activation. On a `<button>` the platform does it
|
|
12
|
-
* `href` renders, dropping the `href` does it — the DOM leaves an anchor without one
|
|
13
|
-
* unfocusable, unactivatable and no longer a link.
|
|
11
|
+
* Prevents activation. On a `<button>` the platform does it.
|
|
14
12
|
*
|
|
15
13
|
* A `render` component cannot be inspected, so it gets `aria-disabled` and
|
|
16
14
|
* `data-disabled` rather than `disabled`: actually disabling itself is up to it.
|
|
17
15
|
*/
|
|
18
16
|
disabled?: boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Renders an `<a href>` instead of a `<button>`.
|
|
21
|
-
*
|
|
22
|
-
* A control that navigates should be an anchor, so middle-click, cmd-click, the
|
|
23
|
-
* status bar and "link" in assistive technology all work. It is a prop rather than
|
|
24
|
-
* something to write on a `render` element because `disabled` has to be able to take
|
|
25
|
-
* it away, and a `render` element's own props outrank everything.
|
|
26
|
-
*/
|
|
27
|
-
href?: string;
|
|
28
17
|
/**
|
|
29
18
|
* Element or component to render instead of the default. Props, className, event
|
|
30
19
|
* handlers and ref are merged onto it.
|
|
31
20
|
*
|
|
32
|
-
* @example <Button render={<
|
|
21
|
+
* @example <Button render={<motion.button />}>Save</Button>
|
|
33
22
|
*/
|
|
34
23
|
render?: ReactElement;
|
|
35
24
|
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
36
25
|
ref?: Ref<HTMLElement>;
|
|
37
26
|
};
|
|
38
|
-
type ButtonProps = ButtonOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ButtonOwnProps
|
|
27
|
+
type ButtonProps = ButtonOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ButtonOwnProps>;
|
|
39
28
|
/**
|
|
40
29
|
* A button — behaviour only, no styling.
|
|
41
30
|
*
|
|
42
|
-
* Renders a `<button>`,
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* and expresses `disabled` the way that element already understands.
|
|
31
|
+
* Renders a `<button>`, which is natively focusable and keyboard-activatable, so
|
|
32
|
+
* nothing here is synthesised: the component defaults `type` so it does not submit by
|
|
33
|
+
* accident, and expresses `disabled` the way the element already understands.
|
|
46
34
|
*
|
|
47
|
-
*
|
|
48
|
-
* platform then removes it from the tab order and suppresses activation, with no
|
|
49
|
-
* handler stripping or `tabindex` bookkeeping of our own.
|
|
35
|
+
* Navigation is not a button's job — a link belongs to a separate `Link` component.
|
|
50
36
|
*
|
|
51
37
|
* Emits `data-disabled` so one selector styles a disabled control either way.
|
|
52
38
|
*/
|
|
53
|
-
declare function Button({ disabled,
|
|
39
|
+
declare function Button({ disabled, type, className, children, render, ...rest }: ButtonProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
54
40
|
|
|
55
41
|
export { Button, type ButtonProps };
|
package/dist/button/index.d.ts
CHANGED
|
@@ -8,48 +8,34 @@ import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
|
8
8
|
*/
|
|
9
9
|
type ButtonOwnProps = {
|
|
10
10
|
/**
|
|
11
|
-
* Prevents activation. On a `<button>` the platform does it
|
|
12
|
-
* `href` renders, dropping the `href` does it — the DOM leaves an anchor without one
|
|
13
|
-
* unfocusable, unactivatable and no longer a link.
|
|
11
|
+
* Prevents activation. On a `<button>` the platform does it.
|
|
14
12
|
*
|
|
15
13
|
* A `render` component cannot be inspected, so it gets `aria-disabled` and
|
|
16
14
|
* `data-disabled` rather than `disabled`: actually disabling itself is up to it.
|
|
17
15
|
*/
|
|
18
16
|
disabled?: boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Renders an `<a href>` instead of a `<button>`.
|
|
21
|
-
*
|
|
22
|
-
* A control that navigates should be an anchor, so middle-click, cmd-click, the
|
|
23
|
-
* status bar and "link" in assistive technology all work. It is a prop rather than
|
|
24
|
-
* something to write on a `render` element because `disabled` has to be able to take
|
|
25
|
-
* it away, and a `render` element's own props outrank everything.
|
|
26
|
-
*/
|
|
27
|
-
href?: string;
|
|
28
17
|
/**
|
|
29
18
|
* Element or component to render instead of the default. Props, className, event
|
|
30
19
|
* handlers and ref are merged onto it.
|
|
31
20
|
*
|
|
32
|
-
* @example <Button render={<
|
|
21
|
+
* @example <Button render={<motion.button />}>Save</Button>
|
|
33
22
|
*/
|
|
34
23
|
render?: ReactElement;
|
|
35
24
|
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
36
25
|
ref?: Ref<HTMLElement>;
|
|
37
26
|
};
|
|
38
|
-
type ButtonProps = ButtonOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ButtonOwnProps
|
|
27
|
+
type ButtonProps = ButtonOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ButtonOwnProps>;
|
|
39
28
|
/**
|
|
40
29
|
* A button — behaviour only, no styling.
|
|
41
30
|
*
|
|
42
|
-
* Renders a `<button>`,
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* and expresses `disabled` the way that element already understands.
|
|
31
|
+
* Renders a `<button>`, which is natively focusable and keyboard-activatable, so
|
|
32
|
+
* nothing here is synthesised: the component defaults `type` so it does not submit by
|
|
33
|
+
* accident, and expresses `disabled` the way the element already understands.
|
|
46
34
|
*
|
|
47
|
-
*
|
|
48
|
-
* platform then removes it from the tab order and suppresses activation, with no
|
|
49
|
-
* handler stripping or `tabindex` bookkeeping of our own.
|
|
35
|
+
* Navigation is not a button's job — a link belongs to a separate `Link` component.
|
|
50
36
|
*
|
|
51
37
|
* Emits `data-disabled` so one selector styles a disabled control either way.
|
|
52
38
|
*/
|
|
53
|
-
declare function Button({ disabled,
|
|
39
|
+
declare function Button({ disabled, type, className, children, render, ...rest }: ButtonProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
54
40
|
|
|
55
41
|
export { Button, type ButtonProps };
|
package/dist/button/index.js
CHANGED
|
@@ -5,27 +5,17 @@ import {
|
|
|
5
5
|
// src/button/Button.tsx
|
|
6
6
|
function Button({
|
|
7
7
|
disabled = false,
|
|
8
|
-
href,
|
|
9
8
|
type = "button",
|
|
10
9
|
className,
|
|
11
10
|
children,
|
|
12
11
|
render,
|
|
13
12
|
...rest
|
|
14
13
|
}) {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
if (process.env.NODE_ENV !== "production" && disabled && render !== void 0) {
|
|
18
|
-
const renderProps = render.props;
|
|
19
|
-
if (renderProps?.href !== void 0) {
|
|
20
|
-
console.error(
|
|
21
|
-
"Button: `disabled` cannot remove the `href` on a `render` element \u2014 its own props outrank the component. Pass the URL as `href` on Button instead."
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const elementProps = rendersButton ? { type, disabled: disabled || void 0 } : { href: linksOut ? href : void 0, "aria-disabled": disabled || void 0 };
|
|
14
|
+
const rendersButton = render === void 0 || render.type === "button";
|
|
15
|
+
const elementProps = rendersButton ? { type, disabled: disabled || void 0 } : { "aria-disabled": disabled || void 0 };
|
|
26
16
|
return useRender({
|
|
27
17
|
render,
|
|
28
|
-
defaultTagName:
|
|
18
|
+
defaultTagName: "button",
|
|
29
19
|
props: {
|
|
30
20
|
...elementProps,
|
|
31
21
|
"data-disabled": disabled ? "" : void 0,
|
package/dist/switch/index.cjs
CHANGED
|
@@ -196,11 +196,17 @@ function SwitchRoot({
|
|
|
196
196
|
name: "Switch.Root",
|
|
197
197
|
state: "checked"
|
|
198
198
|
});
|
|
199
|
+
const commitChecked = (0, import_react4.useCallback)(
|
|
200
|
+
(next) => {
|
|
201
|
+
setChecked(next);
|
|
202
|
+
onCheckedChange?.(next);
|
|
203
|
+
},
|
|
204
|
+
[setChecked, onCheckedChange]
|
|
205
|
+
);
|
|
199
206
|
const state = (0, import_react4.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
200
207
|
const elementRef = (0, import_react4.useRef)(null);
|
|
201
208
|
const inputRef = (0, import_react4.useRef)(null);
|
|
202
|
-
|
|
203
|
-
useFormReset({ elementRef, inputRef, checked, setChecked, onCheckedChange });
|
|
209
|
+
useFormReset({ elementRef, inputRef, checked, commitChecked });
|
|
204
210
|
const element = useRender({
|
|
205
211
|
render,
|
|
206
212
|
defaultTagName: "button",
|
|
@@ -209,16 +215,15 @@ function SwitchRoot({
|
|
|
209
215
|
type: "button",
|
|
210
216
|
role: "switch",
|
|
211
217
|
"aria-checked": checked,
|
|
212
|
-
// The platform suppresses activation
|
|
218
|
+
// The platform suppresses activation and focus on a native button.
|
|
213
219
|
disabled: disabled || void 0,
|
|
214
220
|
...switchDataAttributes(state),
|
|
215
221
|
className,
|
|
216
222
|
children,
|
|
217
223
|
ref: elementRef,
|
|
218
224
|
onClick() {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
onCheckedChange?.(next);
|
|
225
|
+
if (disabled) return;
|
|
226
|
+
commitChecked(!checked);
|
|
222
227
|
}
|
|
223
228
|
},
|
|
224
229
|
consumerProps: rest
|
|
@@ -244,8 +249,7 @@ function useFormReset({
|
|
|
244
249
|
elementRef,
|
|
245
250
|
inputRef,
|
|
246
251
|
checked,
|
|
247
|
-
|
|
248
|
-
onCheckedChange
|
|
252
|
+
commitChecked
|
|
249
253
|
}) {
|
|
250
254
|
const { current: initialChecked } = (0, import_react4.useRef)(checked);
|
|
251
255
|
(0, import_react4.useEffect)(() => {
|
|
@@ -256,23 +260,12 @@ function useFormReset({
|
|
|
256
260
|
if (event.defaultPrevented) return;
|
|
257
261
|
if (inputRef.current) inputRef.current.checked = checked;
|
|
258
262
|
if (checked === initialChecked) return;
|
|
259
|
-
|
|
260
|
-
onCheckedChange?.(initialChecked);
|
|
263
|
+
commitChecked(initialChecked);
|
|
261
264
|
});
|
|
262
265
|
}
|
|
263
266
|
form.addEventListener("reset", onReset);
|
|
264
267
|
return () => form.removeEventListener("reset", onReset);
|
|
265
|
-
}, [elementRef, inputRef, checked,
|
|
266
|
-
}
|
|
267
|
-
function useNativeButtonWarning(elementRef) {
|
|
268
|
-
(0, import_react4.useEffect)(() => {
|
|
269
|
-
if (process.env.NODE_ENV === "production") return;
|
|
270
|
-
const element = elementRef.current;
|
|
271
|
-
if (!element || element.tagName === "BUTTON") return;
|
|
272
|
-
console.error(
|
|
273
|
-
`Switch.Root rendered <${element.tagName.toLowerCase()}> instead of <button>. Focus, Space and Enter activation and \`disabled\` all come from the button element; pass a \`render\` component that forwards its props to one.`
|
|
274
|
-
);
|
|
275
|
-
}, [elementRef]);
|
|
268
|
+
}, [elementRef, inputRef, checked, commitChecked, initialChecked]);
|
|
276
269
|
}
|
|
277
270
|
|
|
278
271
|
// src/switch/SwitchThumb.tsx
|
package/dist/switch/index.d.cts
CHANGED
|
@@ -35,8 +35,8 @@ type SwitchRootOwnProps = {
|
|
|
35
35
|
* Component to render instead of the default `<button>`. Props, className, event
|
|
36
36
|
* handlers and ref are merged onto it.
|
|
37
37
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* The default `<button>` is what supplies focus, Space and Enter activation and
|
|
39
|
+
* `disabled`; what `render` produces instead is the consumer's choice.
|
|
40
40
|
*/
|
|
41
41
|
render?: ReactElement;
|
|
42
42
|
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
package/dist/switch/index.d.ts
CHANGED
|
@@ -35,8 +35,8 @@ type SwitchRootOwnProps = {
|
|
|
35
35
|
* Component to render instead of the default `<button>`. Props, className, event
|
|
36
36
|
* handlers and ref are merged onto it.
|
|
37
37
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* The default `<button>` is what supplies focus, Space and Enter activation and
|
|
39
|
+
* `disabled`; what `render` produces instead is the consumer's choice.
|
|
40
40
|
*/
|
|
41
41
|
render?: ReactElement;
|
|
42
42
|
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
package/dist/switch/index.js
CHANGED
|
@@ -14,7 +14,7 @@ __export(index_parts_exports, {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
// src/switch/SwitchRoot.tsx
|
|
17
|
-
import { useEffect, useMemo, useRef } from "react";
|
|
17
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
18
18
|
|
|
19
19
|
// src/switch/SwitchRootContext.ts
|
|
20
20
|
import { createContext, useContext } from "react";
|
|
@@ -56,11 +56,17 @@ function SwitchRoot({
|
|
|
56
56
|
name: "Switch.Root",
|
|
57
57
|
state: "checked"
|
|
58
58
|
});
|
|
59
|
+
const commitChecked = useCallback(
|
|
60
|
+
(next) => {
|
|
61
|
+
setChecked(next);
|
|
62
|
+
onCheckedChange?.(next);
|
|
63
|
+
},
|
|
64
|
+
[setChecked, onCheckedChange]
|
|
65
|
+
);
|
|
59
66
|
const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
|
|
60
67
|
const elementRef = useRef(null);
|
|
61
68
|
const inputRef = useRef(null);
|
|
62
|
-
|
|
63
|
-
useFormReset({ elementRef, inputRef, checked, setChecked, onCheckedChange });
|
|
69
|
+
useFormReset({ elementRef, inputRef, checked, commitChecked });
|
|
64
70
|
const element = useRender({
|
|
65
71
|
render,
|
|
66
72
|
defaultTagName: "button",
|
|
@@ -69,16 +75,15 @@ function SwitchRoot({
|
|
|
69
75
|
type: "button",
|
|
70
76
|
role: "switch",
|
|
71
77
|
"aria-checked": checked,
|
|
72
|
-
// The platform suppresses activation
|
|
78
|
+
// The platform suppresses activation and focus on a native button.
|
|
73
79
|
disabled: disabled || void 0,
|
|
74
80
|
...switchDataAttributes(state),
|
|
75
81
|
className,
|
|
76
82
|
children,
|
|
77
83
|
ref: elementRef,
|
|
78
84
|
onClick() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
onCheckedChange?.(next);
|
|
85
|
+
if (disabled) return;
|
|
86
|
+
commitChecked(!checked);
|
|
82
87
|
}
|
|
83
88
|
},
|
|
84
89
|
consumerProps: rest
|
|
@@ -104,8 +109,7 @@ function useFormReset({
|
|
|
104
109
|
elementRef,
|
|
105
110
|
inputRef,
|
|
106
111
|
checked,
|
|
107
|
-
|
|
108
|
-
onCheckedChange
|
|
112
|
+
commitChecked
|
|
109
113
|
}) {
|
|
110
114
|
const { current: initialChecked } = useRef(checked);
|
|
111
115
|
useEffect(() => {
|
|
@@ -116,23 +120,12 @@ function useFormReset({
|
|
|
116
120
|
if (event.defaultPrevented) return;
|
|
117
121
|
if (inputRef.current) inputRef.current.checked = checked;
|
|
118
122
|
if (checked === initialChecked) return;
|
|
119
|
-
|
|
120
|
-
onCheckedChange?.(initialChecked);
|
|
123
|
+
commitChecked(initialChecked);
|
|
121
124
|
});
|
|
122
125
|
}
|
|
123
126
|
form.addEventListener("reset", onReset);
|
|
124
127
|
return () => form.removeEventListener("reset", onReset);
|
|
125
|
-
}, [elementRef, inputRef, checked,
|
|
126
|
-
}
|
|
127
|
-
function useNativeButtonWarning(elementRef) {
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
if (process.env.NODE_ENV === "production") return;
|
|
130
|
-
const element = elementRef.current;
|
|
131
|
-
if (!element || element.tagName === "BUTTON") return;
|
|
132
|
-
console.error(
|
|
133
|
-
`Switch.Root rendered <${element.tagName.toLowerCase()}> instead of <button>. Focus, Space and Enter activation and \`disabled\` all come from the button element; pass a \`render\` component that forwards its props to one.`
|
|
134
|
-
);
|
|
135
|
-
}, [elementRef]);
|
|
128
|
+
}, [elementRef, inputRef, checked, commitChecked, initialChecked]);
|
|
136
129
|
}
|
|
137
130
|
|
|
138
131
|
// src/switch/SwitchThumb.tsx
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Unstyled React behaviour primitives — render engine, controlled/uncontrolled state, and data-* state projection. Ships no CSS and no class names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|