@arun-dev/headless 3.0.1 → 4.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/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/checkbox/index.cjs +302 -0
- package/dist/checkbox/index.d.cts +123 -0
- package/dist/checkbox/index.d.ts +123 -0
- package/dist/checkbox/index.js +138 -0
- package/dist/chunk-IWUVUY76.js +29 -0
- package/dist/switch/index.cjs +54 -51
- package/dist/switch/index.d.cts +2 -2
- package/dist/switch/index.d.ts +2 -2
- package/dist/switch/index.js +21 -41
- package/package.json +11 -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,
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/checkbox/index.ts
|
|
21
|
+
var checkbox_exports = {};
|
|
22
|
+
__export(checkbox_exports, {
|
|
23
|
+
Checkbox: () => index_parts_exports
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(checkbox_exports);
|
|
26
|
+
|
|
27
|
+
// src/checkbox/index.parts.ts
|
|
28
|
+
var index_parts_exports = {};
|
|
29
|
+
__export(index_parts_exports, {
|
|
30
|
+
Indicator: () => CheckboxIndicator,
|
|
31
|
+
Root: () => CheckboxRoot
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// src/checkbox/CheckboxRoot.tsx
|
|
35
|
+
var import_react5 = require("react");
|
|
36
|
+
|
|
37
|
+
// src/core/useControlled.ts
|
|
38
|
+
var import_react = require("react");
|
|
39
|
+
function useControlled({
|
|
40
|
+
controlled,
|
|
41
|
+
default: defaultValue,
|
|
42
|
+
name = "Component",
|
|
43
|
+
state = "value"
|
|
44
|
+
}) {
|
|
45
|
+
const { current: isControlled } = (0, import_react.useRef)(controlled !== void 0);
|
|
46
|
+
const [uncontrolled, setUncontrolled] = (0, import_react.useState)(defaultValue);
|
|
47
|
+
const defaultRef = (0, import_react.useRef)(defaultValue);
|
|
48
|
+
const value = isControlled ? controlled : uncontrolled;
|
|
49
|
+
if (process.env.NODE_ENV !== "production") {
|
|
50
|
+
if (isControlled !== (controlled !== void 0)) {
|
|
51
|
+
console.error(
|
|
52
|
+
`${name}: cannot switch between controlled and uncontrolled \`${state}\`. Decide which one this component is for the whole of its life.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (!isControlled && defaultRef.current !== defaultValue) {
|
|
56
|
+
console.error(
|
|
57
|
+
`${name}: cannot change the default \`${state}\` after mount. It is only read once, so later changes are silently ignored.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const setValue = (0, import_react.useCallback)(
|
|
62
|
+
(next) => {
|
|
63
|
+
if (!isControlled) setUncontrolled(next);
|
|
64
|
+
},
|
|
65
|
+
[isControlled]
|
|
66
|
+
);
|
|
67
|
+
return [value, setValue];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/core/useFormReset.ts
|
|
71
|
+
var import_react2 = require("react");
|
|
72
|
+
function useFormReset({
|
|
73
|
+
elementRef,
|
|
74
|
+
inputRef,
|
|
75
|
+
value,
|
|
76
|
+
inputChecked,
|
|
77
|
+
commit
|
|
78
|
+
}) {
|
|
79
|
+
const { current: initialValue } = (0, import_react2.useRef)(value);
|
|
80
|
+
(0, import_react2.useEffect)(() => {
|
|
81
|
+
const form = elementRef.current?.form;
|
|
82
|
+
if (!form) return;
|
|
83
|
+
function onReset(event) {
|
|
84
|
+
queueMicrotask(() => {
|
|
85
|
+
if (event.defaultPrevented) return;
|
|
86
|
+
if (inputRef.current) inputRef.current.checked = inputChecked;
|
|
87
|
+
if (value === initialValue) return;
|
|
88
|
+
commit(initialValue);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
form.addEventListener("reset", onReset);
|
|
92
|
+
return () => form.removeEventListener("reset", onReset);
|
|
93
|
+
}, [elementRef, inputRef, value, inputChecked, commit, initialValue]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/core/useRender.ts
|
|
97
|
+
var import_react3 = require("react");
|
|
98
|
+
|
|
99
|
+
// src/core/mergeProps.ts
|
|
100
|
+
function isEventHandler(key) {
|
|
101
|
+
return key.length > 2 && key.startsWith("on") && key[2] === key[2]?.toUpperCase();
|
|
102
|
+
}
|
|
103
|
+
function mergeRefs(...refs) {
|
|
104
|
+
return (node) => {
|
|
105
|
+
const cleanups = refs.map((ref) => {
|
|
106
|
+
if (typeof ref === "function") {
|
|
107
|
+
const result = ref(node);
|
|
108
|
+
return typeof result === "function" ? result : () => ref(null);
|
|
109
|
+
}
|
|
110
|
+
if (ref && typeof ref === "object") {
|
|
111
|
+
ref.current = node;
|
|
112
|
+
return () => {
|
|
113
|
+
ref.current = null;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return void 0;
|
|
117
|
+
});
|
|
118
|
+
return () => {
|
|
119
|
+
cleanups.forEach((cleanup) => {
|
|
120
|
+
if (typeof cleanup === "function") cleanup();
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function isSyntheticEvent(event) {
|
|
126
|
+
return typeof event === "object" && event !== null && "nativeEvent" in event;
|
|
127
|
+
}
|
|
128
|
+
function makeEventPreventable(event) {
|
|
129
|
+
if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
|
|
130
|
+
const target = event;
|
|
131
|
+
target.preventComponentHandler = () => {
|
|
132
|
+
target.componentHandlerPrevented = true;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function isComponentHandlerPrevented(event) {
|
|
136
|
+
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
137
|
+
}
|
|
138
|
+
function chainHandlers(earlier, value) {
|
|
139
|
+
return (...args) => {
|
|
140
|
+
const event = args[0];
|
|
141
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
142
|
+
value(...args);
|
|
143
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
144
|
+
earlier(...args);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function mergeProps(...objects) {
|
|
148
|
+
const merged = {};
|
|
149
|
+
for (const props of objects) {
|
|
150
|
+
if (!props) continue;
|
|
151
|
+
for (const key of Object.keys(props)) {
|
|
152
|
+
const value = props[key];
|
|
153
|
+
if (value === void 0) continue;
|
|
154
|
+
if (isEventHandler(key) && typeof value === "function") {
|
|
155
|
+
const earlier = merged[key];
|
|
156
|
+
merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
|
|
157
|
+
} else if (key === "className") {
|
|
158
|
+
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
159
|
+
} else if (key === "style") {
|
|
160
|
+
merged.style = { ...merged.style, ...value };
|
|
161
|
+
} else if (key === "ref") {
|
|
162
|
+
merged.ref = merged.ref ? mergeRefs(merged.ref, value) : value;
|
|
163
|
+
} else {
|
|
164
|
+
merged[key] = value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return merged;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/core/useRender.ts
|
|
172
|
+
function useRender({
|
|
173
|
+
render,
|
|
174
|
+
defaultTagName,
|
|
175
|
+
props,
|
|
176
|
+
consumerProps
|
|
177
|
+
}) {
|
|
178
|
+
const merged = mergeProps(props, consumerProps);
|
|
179
|
+
if ((0, import_react3.isValidElement)(render)) {
|
|
180
|
+
return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
|
|
181
|
+
}
|
|
182
|
+
return (0, import_react3.createElement)(defaultTagName, merged);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/checkbox/CheckboxRootContext.ts
|
|
186
|
+
var import_react4 = require("react");
|
|
187
|
+
var CheckboxRootContext = (0, import_react4.createContext)(null);
|
|
188
|
+
function useCheckboxRootContext() {
|
|
189
|
+
const context = (0, import_react4.useContext)(CheckboxRootContext);
|
|
190
|
+
if (context === null) {
|
|
191
|
+
throw new Error("<Checkbox.Indicator> must be rendered inside <Checkbox.Root>.");
|
|
192
|
+
}
|
|
193
|
+
return context;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/checkbox/checkboxDataAttributes.ts
|
|
197
|
+
function checkboxDataAttributes({ checked, disabled }) {
|
|
198
|
+
return {
|
|
199
|
+
"data-checked": checked === true ? "" : void 0,
|
|
200
|
+
"data-unchecked": checked === false ? "" : void 0,
|
|
201
|
+
"data-indeterminate": checked === "indeterminate" ? "" : void 0,
|
|
202
|
+
"data-disabled": disabled ? "" : void 0
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/checkbox/CheckboxRoot.tsx
|
|
207
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
208
|
+
function CheckboxRoot({
|
|
209
|
+
checked: checkedProp,
|
|
210
|
+
defaultChecked,
|
|
211
|
+
onCheckedChange,
|
|
212
|
+
disabled = false,
|
|
213
|
+
name,
|
|
214
|
+
value = "on",
|
|
215
|
+
className,
|
|
216
|
+
children,
|
|
217
|
+
render,
|
|
218
|
+
...rest
|
|
219
|
+
}) {
|
|
220
|
+
const [checked, setChecked] = useControlled({
|
|
221
|
+
controlled: checkedProp,
|
|
222
|
+
default: defaultChecked ?? false,
|
|
223
|
+
name: "Checkbox.Root",
|
|
224
|
+
state: "checked"
|
|
225
|
+
});
|
|
226
|
+
const commitChecked = (0, import_react5.useCallback)(
|
|
227
|
+
(next) => {
|
|
228
|
+
setChecked(next);
|
|
229
|
+
onCheckedChange?.(next);
|
|
230
|
+
},
|
|
231
|
+
[setChecked, onCheckedChange]
|
|
232
|
+
);
|
|
233
|
+
const state = (0, import_react5.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
234
|
+
const elementRef = (0, import_react5.useRef)(null);
|
|
235
|
+
const inputRef = (0, import_react5.useRef)(null);
|
|
236
|
+
const submitted = checked === true;
|
|
237
|
+
useFormReset({
|
|
238
|
+
elementRef,
|
|
239
|
+
inputRef,
|
|
240
|
+
value: checked,
|
|
241
|
+
inputChecked: submitted,
|
|
242
|
+
commit: commitChecked
|
|
243
|
+
});
|
|
244
|
+
const element = useRender({
|
|
245
|
+
render,
|
|
246
|
+
defaultTagName: "button",
|
|
247
|
+
props: {
|
|
248
|
+
// Without this a checkbox inside a form would submit it on every toggle.
|
|
249
|
+
type: "button",
|
|
250
|
+
role: "checkbox",
|
|
251
|
+
// The ARIA spelling of the third state is "mixed", not "indeterminate".
|
|
252
|
+
"aria-checked": checked === "indeterminate" ? "mixed" : checked,
|
|
253
|
+
// The platform suppresses activation and focus on a native button.
|
|
254
|
+
disabled: disabled || void 0,
|
|
255
|
+
...checkboxDataAttributes(state),
|
|
256
|
+
className,
|
|
257
|
+
children,
|
|
258
|
+
ref: elementRef,
|
|
259
|
+
onClick() {
|
|
260
|
+
if (disabled) return;
|
|
261
|
+
commitChecked(!submitted);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
consumerProps: rest
|
|
265
|
+
});
|
|
266
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CheckboxRootContext.Provider, { value: state, children: [
|
|
267
|
+
element,
|
|
268
|
+
name !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
269
|
+
"input",
|
|
270
|
+
{
|
|
271
|
+
ref: inputRef,
|
|
272
|
+
type: "checkbox",
|
|
273
|
+
hidden: true,
|
|
274
|
+
readOnly: true,
|
|
275
|
+
name,
|
|
276
|
+
value,
|
|
277
|
+
checked: submitted,
|
|
278
|
+
disabled
|
|
279
|
+
}
|
|
280
|
+
) : null
|
|
281
|
+
] });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/checkbox/CheckboxIndicator.tsx
|
|
285
|
+
function CheckboxIndicator({
|
|
286
|
+
className,
|
|
287
|
+
children,
|
|
288
|
+
render,
|
|
289
|
+
...rest
|
|
290
|
+
}) {
|
|
291
|
+
const state = useCheckboxRootContext();
|
|
292
|
+
return useRender({
|
|
293
|
+
render,
|
|
294
|
+
defaultTagName: "span",
|
|
295
|
+
props: { "aria-hidden": true, ...checkboxDataAttributes(state), className, children },
|
|
296
|
+
consumerProps: rest
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
300
|
+
0 && (module.exports = {
|
|
301
|
+
Checkbox
|
|
302
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A checkbox has three states, not two.
|
|
6
|
+
*
|
|
7
|
+
* `'indeterminate'` is a state of the checkbox itself, not a separate flag beside
|
|
8
|
+
* `checked` — a single union makes `checked && indeterminate` unrepresentable, and
|
|
9
|
+
* keeps one source of state to derive everything else from (decision 10).
|
|
10
|
+
*
|
|
11
|
+
* Native HTML splits them, but only because `.indeterminate` is a DOM property with no
|
|
12
|
+
* attribute behind it: it is purely visual there, is cleared by the first click, and
|
|
13
|
+
* survives `form.reset()`. None of that is worth reproducing.
|
|
14
|
+
*/
|
|
15
|
+
type CheckedState = boolean | 'indeterminate';
|
|
16
|
+
/** The state Checkbox.Root shares with its parts, and projects as `data-*` attributes. */
|
|
17
|
+
type CheckboxState = {
|
|
18
|
+
checked: CheckedState;
|
|
19
|
+
disabled: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checkbox.Root's own props. Everything else — `id`, `className`, `children`, `aria-*`,
|
|
24
|
+
* `data-*`, event handlers — comes from React's own `<button>` props, so it is typed
|
|
25
|
+
* and checked without being declared here.
|
|
26
|
+
*
|
|
27
|
+
* `id` in particular is how a checkbox gets an accessible name, paired with a
|
|
28
|
+
* `<label htmlFor>`. A wrapping `<label>` names the rendered `<button>` too, but
|
|
29
|
+
* `jsx-a11y/label-has-associated-control` rejects a button as a nested control, so the
|
|
30
|
+
* explicit pairing is the one that passes lint.
|
|
31
|
+
*/
|
|
32
|
+
type CheckboxRootOwnProps = {
|
|
33
|
+
/**
|
|
34
|
+
* Controlled state. Provide `onCheckedChange` alongside it.
|
|
35
|
+
*
|
|
36
|
+
* Never `undefined` once mounted. The mode is latched at mount, so an `undefined`
|
|
37
|
+
* first render makes the checkbox uncontrolled for good and every value passed later
|
|
38
|
+
* is ignored. Coalesce at the call site — `checked={x ?? false}`.
|
|
39
|
+
*/
|
|
40
|
+
checked?: CheckedState;
|
|
41
|
+
/** Initial state when uncontrolled. Read once, at mount. */
|
|
42
|
+
defaultChecked?: CheckedState;
|
|
43
|
+
onCheckedChange?: (checked: CheckedState) => void;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Submits with the enclosing form when checked, mirroring a native checkbox:
|
|
47
|
+
* an unchecked, indeterminate or disabled control contributes nothing.
|
|
48
|
+
*/
|
|
49
|
+
name?: string;
|
|
50
|
+
/** Value submitted when checked. Defaults to `"on"`, as a native checkbox does. */
|
|
51
|
+
value?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Component to render instead of the default `<button>`. Props, className, event
|
|
54
|
+
* handlers and ref are merged onto it.
|
|
55
|
+
*
|
|
56
|
+
* The default `<button>` is what supplies focus, Space and Enter activation and
|
|
57
|
+
* `disabled`; what `render` produces instead is the consumer's choice.
|
|
58
|
+
*/
|
|
59
|
+
render?: ReactElement;
|
|
60
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
61
|
+
ref?: Ref<HTMLElement>;
|
|
62
|
+
};
|
|
63
|
+
type CheckboxRootProps = CheckboxRootOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof CheckboxRootOwnProps>;
|
|
64
|
+
/**
|
|
65
|
+
* A checkbox — a three-state control whose value is read on submit, distinct from a
|
|
66
|
+
* switch in that it does not take effect at once.
|
|
67
|
+
*
|
|
68
|
+
* A `<button role="checkbox">` rather than an `<input type="checkbox">`, because an
|
|
69
|
+
* input is void and cannot hold an indicator — the content test in decision 7, the same
|
|
70
|
+
* one Switch.Root fails. Everything the input was still good for, the hidden one below
|
|
71
|
+
* keeps doing. The platform supplies focusability, Space and Enter activation and
|
|
72
|
+
* `disabled`, so none of it is synthesised here.
|
|
73
|
+
*
|
|
74
|
+
* Inside a form it behaves as a native checkbox would: `form.reset()` returns it to the
|
|
75
|
+
* state it mounted with, indeterminate included — which a native checkbox does not
|
|
76
|
+
* manage, since `.indeterminate` is a DOM property the platform never resets.
|
|
77
|
+
*
|
|
78
|
+
* It has no accessible name of its own — pair it with a `<label htmlFor>` by `id`, or
|
|
79
|
+
* pass `aria-label` or `aria-labelledby`. That is the consumer's decision, not one a
|
|
80
|
+
* headless component should guess.
|
|
81
|
+
*/
|
|
82
|
+
declare function CheckboxRoot({ checked: checkedProp, defaultChecked, onCheckedChange, disabled, name, value, className, children, render, ...rest }: CheckboxRootProps): react.JSX.Element;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Checkbox.Indicator's own props. Everything else — `id`, `className`, `children`,
|
|
86
|
+
* `aria-*`, `data-*` — comes from React's own `<span>` props.
|
|
87
|
+
*/
|
|
88
|
+
type CheckboxIndicatorOwnProps = {
|
|
89
|
+
/** Element to render instead of the default `<span>`. */
|
|
90
|
+
render?: ReactElement;
|
|
91
|
+
/** Ref to the rendered element, whatever `render` makes it. */
|
|
92
|
+
ref?: Ref<HTMLElement>;
|
|
93
|
+
};
|
|
94
|
+
type CheckboxIndicatorProps = CheckboxIndicatorOwnProps & Omit<ComponentPropsWithRef<'span'>, keyof CheckboxIndicatorOwnProps>;
|
|
95
|
+
/**
|
|
96
|
+
* Where the check or the dash goes.
|
|
97
|
+
*
|
|
98
|
+
* Ships neither — like every part here it has no glyph, no class and no colour, so a
|
|
99
|
+
* bare `<Checkbox.Indicator />` renders an empty `<span>`. Supply the mark as children,
|
|
100
|
+
* or draw it in your own CSS off the `data-*` attributes below. `@arun-dev/ui` is the
|
|
101
|
+
* layer that has an opinion about what a checkmark looks like.
|
|
102
|
+
*
|
|
103
|
+
* Reads state from the Root rather than taking props, so a consumer cannot get the two
|
|
104
|
+
* out of step. Carries the same `data-*` attributes as the Root, so it can be styled
|
|
105
|
+
* from either — on the indicator itself, or by descending from the Root:
|
|
106
|
+
*
|
|
107
|
+
* .indicator[data-checked] { opacity: 1; }
|
|
108
|
+
* .checkbox[data-checked] .indicator { opacity: 1; }
|
|
109
|
+
*
|
|
110
|
+
* Always rendered, in every state. The alternative — unmounting it when unchecked —
|
|
111
|
+
* would leave nothing for CSS to transition from, and nothing to hold an unchecked
|
|
112
|
+
* mark for the designs that want one.
|
|
113
|
+
*
|
|
114
|
+
* Purely presentational — hidden from assistive technology, since the Root already
|
|
115
|
+
* announces the state.
|
|
116
|
+
*/
|
|
117
|
+
declare function CheckboxIndicator({ className, children, render, ...rest }: CheckboxIndicatorProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
118
|
+
|
|
119
|
+
declare namespace index_parts {
|
|
120
|
+
export { CheckboxIndicator as Indicator, CheckboxRoot as Root };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { index_parts as Checkbox, type CheckboxIndicatorProps, type CheckboxRootProps, type CheckboxState, type CheckedState };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A checkbox has three states, not two.
|
|
6
|
+
*
|
|
7
|
+
* `'indeterminate'` is a state of the checkbox itself, not a separate flag beside
|
|
8
|
+
* `checked` — a single union makes `checked && indeterminate` unrepresentable, and
|
|
9
|
+
* keeps one source of state to derive everything else from (decision 10).
|
|
10
|
+
*
|
|
11
|
+
* Native HTML splits them, but only because `.indeterminate` is a DOM property with no
|
|
12
|
+
* attribute behind it: it is purely visual there, is cleared by the first click, and
|
|
13
|
+
* survives `form.reset()`. None of that is worth reproducing.
|
|
14
|
+
*/
|
|
15
|
+
type CheckedState = boolean | 'indeterminate';
|
|
16
|
+
/** The state Checkbox.Root shares with its parts, and projects as `data-*` attributes. */
|
|
17
|
+
type CheckboxState = {
|
|
18
|
+
checked: CheckedState;
|
|
19
|
+
disabled: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checkbox.Root's own props. Everything else — `id`, `className`, `children`, `aria-*`,
|
|
24
|
+
* `data-*`, event handlers — comes from React's own `<button>` props, so it is typed
|
|
25
|
+
* and checked without being declared here.
|
|
26
|
+
*
|
|
27
|
+
* `id` in particular is how a checkbox gets an accessible name, paired with a
|
|
28
|
+
* `<label htmlFor>`. A wrapping `<label>` names the rendered `<button>` too, but
|
|
29
|
+
* `jsx-a11y/label-has-associated-control` rejects a button as a nested control, so the
|
|
30
|
+
* explicit pairing is the one that passes lint.
|
|
31
|
+
*/
|
|
32
|
+
type CheckboxRootOwnProps = {
|
|
33
|
+
/**
|
|
34
|
+
* Controlled state. Provide `onCheckedChange` alongside it.
|
|
35
|
+
*
|
|
36
|
+
* Never `undefined` once mounted. The mode is latched at mount, so an `undefined`
|
|
37
|
+
* first render makes the checkbox uncontrolled for good and every value passed later
|
|
38
|
+
* is ignored. Coalesce at the call site — `checked={x ?? false}`.
|
|
39
|
+
*/
|
|
40
|
+
checked?: CheckedState;
|
|
41
|
+
/** Initial state when uncontrolled. Read once, at mount. */
|
|
42
|
+
defaultChecked?: CheckedState;
|
|
43
|
+
onCheckedChange?: (checked: CheckedState) => void;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Submits with the enclosing form when checked, mirroring a native checkbox:
|
|
47
|
+
* an unchecked, indeterminate or disabled control contributes nothing.
|
|
48
|
+
*/
|
|
49
|
+
name?: string;
|
|
50
|
+
/** Value submitted when checked. Defaults to `"on"`, as a native checkbox does. */
|
|
51
|
+
value?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Component to render instead of the default `<button>`. Props, className, event
|
|
54
|
+
* handlers and ref are merged onto it.
|
|
55
|
+
*
|
|
56
|
+
* The default `<button>` is what supplies focus, Space and Enter activation and
|
|
57
|
+
* `disabled`; what `render` produces instead is the consumer's choice.
|
|
58
|
+
*/
|
|
59
|
+
render?: ReactElement;
|
|
60
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
61
|
+
ref?: Ref<HTMLElement>;
|
|
62
|
+
};
|
|
63
|
+
type CheckboxRootProps = CheckboxRootOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof CheckboxRootOwnProps>;
|
|
64
|
+
/**
|
|
65
|
+
* A checkbox — a three-state control whose value is read on submit, distinct from a
|
|
66
|
+
* switch in that it does not take effect at once.
|
|
67
|
+
*
|
|
68
|
+
* A `<button role="checkbox">` rather than an `<input type="checkbox">`, because an
|
|
69
|
+
* input is void and cannot hold an indicator — the content test in decision 7, the same
|
|
70
|
+
* one Switch.Root fails. Everything the input was still good for, the hidden one below
|
|
71
|
+
* keeps doing. The platform supplies focusability, Space and Enter activation and
|
|
72
|
+
* `disabled`, so none of it is synthesised here.
|
|
73
|
+
*
|
|
74
|
+
* Inside a form it behaves as a native checkbox would: `form.reset()` returns it to the
|
|
75
|
+
* state it mounted with, indeterminate included — which a native checkbox does not
|
|
76
|
+
* manage, since `.indeterminate` is a DOM property the platform never resets.
|
|
77
|
+
*
|
|
78
|
+
* It has no accessible name of its own — pair it with a `<label htmlFor>` by `id`, or
|
|
79
|
+
* pass `aria-label` or `aria-labelledby`. That is the consumer's decision, not one a
|
|
80
|
+
* headless component should guess.
|
|
81
|
+
*/
|
|
82
|
+
declare function CheckboxRoot({ checked: checkedProp, defaultChecked, onCheckedChange, disabled, name, value, className, children, render, ...rest }: CheckboxRootProps): react.JSX.Element;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Checkbox.Indicator's own props. Everything else — `id`, `className`, `children`,
|
|
86
|
+
* `aria-*`, `data-*` — comes from React's own `<span>` props.
|
|
87
|
+
*/
|
|
88
|
+
type CheckboxIndicatorOwnProps = {
|
|
89
|
+
/** Element to render instead of the default `<span>`. */
|
|
90
|
+
render?: ReactElement;
|
|
91
|
+
/** Ref to the rendered element, whatever `render` makes it. */
|
|
92
|
+
ref?: Ref<HTMLElement>;
|
|
93
|
+
};
|
|
94
|
+
type CheckboxIndicatorProps = CheckboxIndicatorOwnProps & Omit<ComponentPropsWithRef<'span'>, keyof CheckboxIndicatorOwnProps>;
|
|
95
|
+
/**
|
|
96
|
+
* Where the check or the dash goes.
|
|
97
|
+
*
|
|
98
|
+
* Ships neither — like every part here it has no glyph, no class and no colour, so a
|
|
99
|
+
* bare `<Checkbox.Indicator />` renders an empty `<span>`. Supply the mark as children,
|
|
100
|
+
* or draw it in your own CSS off the `data-*` attributes below. `@arun-dev/ui` is the
|
|
101
|
+
* layer that has an opinion about what a checkmark looks like.
|
|
102
|
+
*
|
|
103
|
+
* Reads state from the Root rather than taking props, so a consumer cannot get the two
|
|
104
|
+
* out of step. Carries the same `data-*` attributes as the Root, so it can be styled
|
|
105
|
+
* from either — on the indicator itself, or by descending from the Root:
|
|
106
|
+
*
|
|
107
|
+
* .indicator[data-checked] { opacity: 1; }
|
|
108
|
+
* .checkbox[data-checked] .indicator { opacity: 1; }
|
|
109
|
+
*
|
|
110
|
+
* Always rendered, in every state. The alternative — unmounting it when unchecked —
|
|
111
|
+
* would leave nothing for CSS to transition from, and nothing to hold an unchecked
|
|
112
|
+
* mark for the designs that want one.
|
|
113
|
+
*
|
|
114
|
+
* Purely presentational — hidden from assistive technology, since the Root already
|
|
115
|
+
* announces the state.
|
|
116
|
+
*/
|
|
117
|
+
declare function CheckboxIndicator({ className, children, render, ...rest }: CheckboxIndicatorProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
118
|
+
|
|
119
|
+
declare namespace index_parts {
|
|
120
|
+
export { CheckboxIndicator as Indicator, CheckboxRoot as Root };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { index_parts as Checkbox, type CheckboxIndicatorProps, type CheckboxRootProps, type CheckboxState, type CheckedState };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useFormReset
|
|
3
|
+
} from "../chunk-IWUVUY76.js";
|
|
4
|
+
import {
|
|
5
|
+
useControlled
|
|
6
|
+
} from "../chunk-UEIB3I3T.js";
|
|
7
|
+
import {
|
|
8
|
+
__export,
|
|
9
|
+
useRender
|
|
10
|
+
} from "../chunk-OQTWWZ4B.js";
|
|
11
|
+
|
|
12
|
+
// src/checkbox/index.parts.ts
|
|
13
|
+
var index_parts_exports = {};
|
|
14
|
+
__export(index_parts_exports, {
|
|
15
|
+
Indicator: () => CheckboxIndicator,
|
|
16
|
+
Root: () => CheckboxRoot
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// src/checkbox/CheckboxRoot.tsx
|
|
20
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
21
|
+
|
|
22
|
+
// src/checkbox/CheckboxRootContext.ts
|
|
23
|
+
import { createContext, useContext } from "react";
|
|
24
|
+
var CheckboxRootContext = createContext(null);
|
|
25
|
+
function useCheckboxRootContext() {
|
|
26
|
+
const context = useContext(CheckboxRootContext);
|
|
27
|
+
if (context === null) {
|
|
28
|
+
throw new Error("<Checkbox.Indicator> must be rendered inside <Checkbox.Root>.");
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/checkbox/checkboxDataAttributes.ts
|
|
34
|
+
function checkboxDataAttributes({ checked, disabled }) {
|
|
35
|
+
return {
|
|
36
|
+
"data-checked": checked === true ? "" : void 0,
|
|
37
|
+
"data-unchecked": checked === false ? "" : void 0,
|
|
38
|
+
"data-indeterminate": checked === "indeterminate" ? "" : void 0,
|
|
39
|
+
"data-disabled": disabled ? "" : void 0
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/checkbox/CheckboxRoot.tsx
|
|
44
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
45
|
+
function CheckboxRoot({
|
|
46
|
+
checked: checkedProp,
|
|
47
|
+
defaultChecked,
|
|
48
|
+
onCheckedChange,
|
|
49
|
+
disabled = false,
|
|
50
|
+
name,
|
|
51
|
+
value = "on",
|
|
52
|
+
className,
|
|
53
|
+
children,
|
|
54
|
+
render,
|
|
55
|
+
...rest
|
|
56
|
+
}) {
|
|
57
|
+
const [checked, setChecked] = useControlled({
|
|
58
|
+
controlled: checkedProp,
|
|
59
|
+
default: defaultChecked ?? false,
|
|
60
|
+
name: "Checkbox.Root",
|
|
61
|
+
state: "checked"
|
|
62
|
+
});
|
|
63
|
+
const commitChecked = useCallback(
|
|
64
|
+
(next) => {
|
|
65
|
+
setChecked(next);
|
|
66
|
+
onCheckedChange?.(next);
|
|
67
|
+
},
|
|
68
|
+
[setChecked, onCheckedChange]
|
|
69
|
+
);
|
|
70
|
+
const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
|
|
71
|
+
const elementRef = useRef(null);
|
|
72
|
+
const inputRef = useRef(null);
|
|
73
|
+
const submitted = checked === true;
|
|
74
|
+
useFormReset({
|
|
75
|
+
elementRef,
|
|
76
|
+
inputRef,
|
|
77
|
+
value: checked,
|
|
78
|
+
inputChecked: submitted,
|
|
79
|
+
commit: commitChecked
|
|
80
|
+
});
|
|
81
|
+
const element = useRender({
|
|
82
|
+
render,
|
|
83
|
+
defaultTagName: "button",
|
|
84
|
+
props: {
|
|
85
|
+
// Without this a checkbox inside a form would submit it on every toggle.
|
|
86
|
+
type: "button",
|
|
87
|
+
role: "checkbox",
|
|
88
|
+
// The ARIA spelling of the third state is "mixed", not "indeterminate".
|
|
89
|
+
"aria-checked": checked === "indeterminate" ? "mixed" : checked,
|
|
90
|
+
// The platform suppresses activation and focus on a native button.
|
|
91
|
+
disabled: disabled || void 0,
|
|
92
|
+
...checkboxDataAttributes(state),
|
|
93
|
+
className,
|
|
94
|
+
children,
|
|
95
|
+
ref: elementRef,
|
|
96
|
+
onClick() {
|
|
97
|
+
if (disabled) return;
|
|
98
|
+
commitChecked(!submitted);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
consumerProps: rest
|
|
102
|
+
});
|
|
103
|
+
return /* @__PURE__ */ jsxs(CheckboxRootContext.Provider, { value: state, children: [
|
|
104
|
+
element,
|
|
105
|
+
name !== void 0 ? /* @__PURE__ */ jsx(
|
|
106
|
+
"input",
|
|
107
|
+
{
|
|
108
|
+
ref: inputRef,
|
|
109
|
+
type: "checkbox",
|
|
110
|
+
hidden: true,
|
|
111
|
+
readOnly: true,
|
|
112
|
+
name,
|
|
113
|
+
value,
|
|
114
|
+
checked: submitted,
|
|
115
|
+
disabled
|
|
116
|
+
}
|
|
117
|
+
) : null
|
|
118
|
+
] });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/checkbox/CheckboxIndicator.tsx
|
|
122
|
+
function CheckboxIndicator({
|
|
123
|
+
className,
|
|
124
|
+
children,
|
|
125
|
+
render,
|
|
126
|
+
...rest
|
|
127
|
+
}) {
|
|
128
|
+
const state = useCheckboxRootContext();
|
|
129
|
+
return useRender({
|
|
130
|
+
render,
|
|
131
|
+
defaultTagName: "span",
|
|
132
|
+
props: { "aria-hidden": true, ...checkboxDataAttributes(state), className, children },
|
|
133
|
+
consumerProps: rest
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
index_parts_exports as Checkbox
|
|
138
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/core/useFormReset.ts
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
function useFormReset({
|
|
4
|
+
elementRef,
|
|
5
|
+
inputRef,
|
|
6
|
+
value,
|
|
7
|
+
inputChecked,
|
|
8
|
+
commit
|
|
9
|
+
}) {
|
|
10
|
+
const { current: initialValue } = useRef(value);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const form = elementRef.current?.form;
|
|
13
|
+
if (!form) return;
|
|
14
|
+
function onReset(event) {
|
|
15
|
+
queueMicrotask(() => {
|
|
16
|
+
if (event.defaultPrevented) return;
|
|
17
|
+
if (inputRef.current) inputRef.current.checked = inputChecked;
|
|
18
|
+
if (value === initialValue) return;
|
|
19
|
+
commit(initialValue);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
form.addEventListener("reset", onReset);
|
|
23
|
+
return () => form.removeEventListener("reset", onReset);
|
|
24
|
+
}, [elementRef, inputRef, value, inputChecked, commit, initialValue]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
useFormReset
|
|
29
|
+
};
|
package/dist/switch/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ __export(index_parts_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// src/switch/SwitchRoot.tsx
|
|
35
|
-
var
|
|
35
|
+
var import_react5 = require("react");
|
|
36
36
|
|
|
37
37
|
// src/core/useControlled.ts
|
|
38
38
|
var import_react = require("react");
|
|
@@ -67,8 +67,34 @@ function useControlled({
|
|
|
67
67
|
return [value, setValue];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// src/core/
|
|
70
|
+
// src/core/useFormReset.ts
|
|
71
71
|
var import_react2 = require("react");
|
|
72
|
+
function useFormReset({
|
|
73
|
+
elementRef,
|
|
74
|
+
inputRef,
|
|
75
|
+
value,
|
|
76
|
+
inputChecked,
|
|
77
|
+
commit
|
|
78
|
+
}) {
|
|
79
|
+
const { current: initialValue } = (0, import_react2.useRef)(value);
|
|
80
|
+
(0, import_react2.useEffect)(() => {
|
|
81
|
+
const form = elementRef.current?.form;
|
|
82
|
+
if (!form) return;
|
|
83
|
+
function onReset(event) {
|
|
84
|
+
queueMicrotask(() => {
|
|
85
|
+
if (event.defaultPrevented) return;
|
|
86
|
+
if (inputRef.current) inputRef.current.checked = inputChecked;
|
|
87
|
+
if (value === initialValue) return;
|
|
88
|
+
commit(initialValue);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
form.addEventListener("reset", onReset);
|
|
92
|
+
return () => form.removeEventListener("reset", onReset);
|
|
93
|
+
}, [elementRef, inputRef, value, inputChecked, commit, initialValue]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/core/useRender.ts
|
|
97
|
+
var import_react3 = require("react");
|
|
72
98
|
|
|
73
99
|
// src/core/mergeProps.ts
|
|
74
100
|
function isEventHandler(key) {
|
|
@@ -150,17 +176,17 @@ function useRender({
|
|
|
150
176
|
consumerProps
|
|
151
177
|
}) {
|
|
152
178
|
const merged = mergeProps(props, consumerProps);
|
|
153
|
-
if ((0,
|
|
154
|
-
return (0,
|
|
179
|
+
if ((0, import_react3.isValidElement)(render)) {
|
|
180
|
+
return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
|
|
155
181
|
}
|
|
156
|
-
return (0,
|
|
182
|
+
return (0, import_react3.createElement)(defaultTagName, merged);
|
|
157
183
|
}
|
|
158
184
|
|
|
159
185
|
// src/switch/SwitchRootContext.ts
|
|
160
|
-
var
|
|
161
|
-
var SwitchRootContext = (0,
|
|
186
|
+
var import_react4 = require("react");
|
|
187
|
+
var SwitchRootContext = (0, import_react4.createContext)(null);
|
|
162
188
|
function useSwitchRootContext() {
|
|
163
|
-
const context = (0,
|
|
189
|
+
const context = (0, import_react4.useContext)(SwitchRootContext);
|
|
164
190
|
if (context === null) {
|
|
165
191
|
throw new Error("<Switch.Thumb> must be rendered inside <Switch.Root>.");
|
|
166
192
|
}
|
|
@@ -196,11 +222,23 @@ function SwitchRoot({
|
|
|
196
222
|
name: "Switch.Root",
|
|
197
223
|
state: "checked"
|
|
198
224
|
});
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
225
|
+
const commitChecked = (0, import_react5.useCallback)(
|
|
226
|
+
(next) => {
|
|
227
|
+
setChecked(next);
|
|
228
|
+
onCheckedChange?.(next);
|
|
229
|
+
},
|
|
230
|
+
[setChecked, onCheckedChange]
|
|
231
|
+
);
|
|
232
|
+
const state = (0, import_react5.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
233
|
+
const elementRef = (0, import_react5.useRef)(null);
|
|
234
|
+
const inputRef = (0, import_react5.useRef)(null);
|
|
235
|
+
useFormReset({
|
|
236
|
+
elementRef,
|
|
237
|
+
inputRef,
|
|
238
|
+
value: checked,
|
|
239
|
+
inputChecked: checked,
|
|
240
|
+
commit: commitChecked
|
|
241
|
+
});
|
|
204
242
|
const element = useRender({
|
|
205
243
|
render,
|
|
206
244
|
defaultTagName: "button",
|
|
@@ -209,16 +247,15 @@ function SwitchRoot({
|
|
|
209
247
|
type: "button",
|
|
210
248
|
role: "switch",
|
|
211
249
|
"aria-checked": checked,
|
|
212
|
-
// The platform suppresses activation
|
|
250
|
+
// The platform suppresses activation and focus on a native button.
|
|
213
251
|
disabled: disabled || void 0,
|
|
214
252
|
...switchDataAttributes(state),
|
|
215
253
|
className,
|
|
216
254
|
children,
|
|
217
255
|
ref: elementRef,
|
|
218
256
|
onClick() {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
onCheckedChange?.(next);
|
|
257
|
+
if (disabled) return;
|
|
258
|
+
commitChecked(!checked);
|
|
222
259
|
}
|
|
223
260
|
},
|
|
224
261
|
consumerProps: rest
|
|
@@ -240,40 +277,6 @@ function SwitchRoot({
|
|
|
240
277
|
) : null
|
|
241
278
|
] });
|
|
242
279
|
}
|
|
243
|
-
function useFormReset({
|
|
244
|
-
elementRef,
|
|
245
|
-
inputRef,
|
|
246
|
-
checked,
|
|
247
|
-
setChecked,
|
|
248
|
-
onCheckedChange
|
|
249
|
-
}) {
|
|
250
|
-
const { current: initialChecked } = (0, import_react4.useRef)(checked);
|
|
251
|
-
(0, import_react4.useEffect)(() => {
|
|
252
|
-
const form = elementRef.current?.form;
|
|
253
|
-
if (!form) return;
|
|
254
|
-
function onReset(event) {
|
|
255
|
-
queueMicrotask(() => {
|
|
256
|
-
if (event.defaultPrevented) return;
|
|
257
|
-
if (inputRef.current) inputRef.current.checked = checked;
|
|
258
|
-
if (checked === initialChecked) return;
|
|
259
|
-
setChecked(initialChecked);
|
|
260
|
-
onCheckedChange?.(initialChecked);
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
form.addEventListener("reset", onReset);
|
|
264
|
-
return () => form.removeEventListener("reset", onReset);
|
|
265
|
-
}, [elementRef, inputRef, checked, setChecked, onCheckedChange, initialChecked]);
|
|
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]);
|
|
276
|
-
}
|
|
277
280
|
|
|
278
281
|
// src/switch/SwitchThumb.tsx
|
|
279
282
|
function SwitchThumb({ className, children, render, ...rest }) {
|
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
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useFormReset
|
|
3
|
+
} from "../chunk-IWUVUY76.js";
|
|
1
4
|
import {
|
|
2
5
|
useControlled
|
|
3
6
|
} from "../chunk-UEIB3I3T.js";
|
|
@@ -14,7 +17,7 @@ __export(index_parts_exports, {
|
|
|
14
17
|
});
|
|
15
18
|
|
|
16
19
|
// src/switch/SwitchRoot.tsx
|
|
17
|
-
import {
|
|
20
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
18
21
|
|
|
19
22
|
// src/switch/SwitchRootContext.ts
|
|
20
23
|
import { createContext, useContext } from "react";
|
|
@@ -56,11 +59,23 @@ function SwitchRoot({
|
|
|
56
59
|
name: "Switch.Root",
|
|
57
60
|
state: "checked"
|
|
58
61
|
});
|
|
62
|
+
const commitChecked = useCallback(
|
|
63
|
+
(next) => {
|
|
64
|
+
setChecked(next);
|
|
65
|
+
onCheckedChange?.(next);
|
|
66
|
+
},
|
|
67
|
+
[setChecked, onCheckedChange]
|
|
68
|
+
);
|
|
59
69
|
const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
|
|
60
70
|
const elementRef = useRef(null);
|
|
61
71
|
const inputRef = useRef(null);
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
useFormReset({
|
|
73
|
+
elementRef,
|
|
74
|
+
inputRef,
|
|
75
|
+
value: checked,
|
|
76
|
+
inputChecked: checked,
|
|
77
|
+
commit: commitChecked
|
|
78
|
+
});
|
|
64
79
|
const element = useRender({
|
|
65
80
|
render,
|
|
66
81
|
defaultTagName: "button",
|
|
@@ -69,16 +84,15 @@ function SwitchRoot({
|
|
|
69
84
|
type: "button",
|
|
70
85
|
role: "switch",
|
|
71
86
|
"aria-checked": checked,
|
|
72
|
-
// The platform suppresses activation
|
|
87
|
+
// The platform suppresses activation and focus on a native button.
|
|
73
88
|
disabled: disabled || void 0,
|
|
74
89
|
...switchDataAttributes(state),
|
|
75
90
|
className,
|
|
76
91
|
children,
|
|
77
92
|
ref: elementRef,
|
|
78
93
|
onClick() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
onCheckedChange?.(next);
|
|
94
|
+
if (disabled) return;
|
|
95
|
+
commitChecked(!checked);
|
|
82
96
|
}
|
|
83
97
|
},
|
|
84
98
|
consumerProps: rest
|
|
@@ -100,40 +114,6 @@ function SwitchRoot({
|
|
|
100
114
|
) : null
|
|
101
115
|
] });
|
|
102
116
|
}
|
|
103
|
-
function useFormReset({
|
|
104
|
-
elementRef,
|
|
105
|
-
inputRef,
|
|
106
|
-
checked,
|
|
107
|
-
setChecked,
|
|
108
|
-
onCheckedChange
|
|
109
|
-
}) {
|
|
110
|
-
const { current: initialChecked } = useRef(checked);
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
const form = elementRef.current?.form;
|
|
113
|
-
if (!form) return;
|
|
114
|
-
function onReset(event) {
|
|
115
|
-
queueMicrotask(() => {
|
|
116
|
-
if (event.defaultPrevented) return;
|
|
117
|
-
if (inputRef.current) inputRef.current.checked = checked;
|
|
118
|
-
if (checked === initialChecked) return;
|
|
119
|
-
setChecked(initialChecked);
|
|
120
|
-
onCheckedChange?.(initialChecked);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
form.addEventListener("reset", onReset);
|
|
124
|
-
return () => form.removeEventListener("reset", onReset);
|
|
125
|
-
}, [elementRef, inputRef, checked, setChecked, onCheckedChange, initialChecked]);
|
|
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]);
|
|
136
|
-
}
|
|
137
117
|
|
|
138
118
|
// src/switch/SwitchThumb.tsx
|
|
139
119
|
function SwitchThumb({ className, children, render, ...rest }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.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",
|
|
@@ -44,6 +44,16 @@
|
|
|
44
44
|
"default": "./dist/button/index.cjs"
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
|
+
"./checkbox": {
|
|
48
|
+
"import": {
|
|
49
|
+
"types": "./dist/checkbox/index.d.ts",
|
|
50
|
+
"default": "./dist/checkbox/index.js"
|
|
51
|
+
},
|
|
52
|
+
"require": {
|
|
53
|
+
"types": "./dist/checkbox/index.d.cts",
|
|
54
|
+
"default": "./dist/checkbox/index.cjs"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
47
57
|
"./switch": {
|
|
48
58
|
"import": {
|
|
49
59
|
"types": "./dist/switch/index.d.ts",
|