@arun-dev/headless 2.1.0 → 3.0.1
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 +38 -22
- package/dist/button/index.cjs +25 -136
- package/dist/button/index.d.cts +34 -23
- package/dist/button/index.d.ts +34 -23
- package/dist/button/index.js +23 -16
- package/dist/{chunk-PYLPZVWQ.js → chunk-OQTWWZ4B.js} +3 -38
- package/dist/index.cjs +3 -43
- package/dist/index.d.cts +18 -61
- package/dist/index.d.ts +18 -61
- package/dist/index.js +1 -7
- package/dist/switch/index.cjs +117 -199
- package/dist/switch/index.d.cts +41 -42
- package/dist/switch/index.d.ts +41 -42
- package/dist/switch/index.js +74 -40
- package/package.json +2 -2
- package/dist/chunk-TNMGZWHA.js +0 -96
package/dist/switch/index.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useControlled
|
|
3
3
|
} from "../chunk-UEIB3I3T.js";
|
|
4
|
-
import {
|
|
5
|
-
retractActivationProps,
|
|
6
|
-
useButton
|
|
7
|
-
} from "../chunk-TNMGZWHA.js";
|
|
8
4
|
import {
|
|
9
5
|
__export,
|
|
10
|
-
booleanAttribute,
|
|
11
|
-
disabledAttribute,
|
|
12
6
|
useRender
|
|
13
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-OQTWWZ4B.js";
|
|
14
8
|
|
|
15
9
|
// src/switch/index.parts.ts
|
|
16
10
|
var index_parts_exports = {};
|
|
@@ -19,8 +13,8 @@ __export(index_parts_exports, {
|
|
|
19
13
|
Thumb: () => SwitchThumb
|
|
20
14
|
});
|
|
21
15
|
|
|
22
|
-
// src/switch/
|
|
23
|
-
import {
|
|
16
|
+
// src/switch/SwitchRoot.tsx
|
|
17
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
24
18
|
|
|
25
19
|
// src/switch/SwitchRootContext.ts
|
|
26
20
|
import { createContext, useContext } from "react";
|
|
@@ -33,18 +27,20 @@ function useSwitchRootContext() {
|
|
|
33
27
|
return context;
|
|
34
28
|
}
|
|
35
29
|
|
|
36
|
-
// src/switch/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
// src/switch/switchDataAttributes.ts
|
|
31
|
+
function switchDataAttributes({ checked, disabled }) {
|
|
32
|
+
return {
|
|
33
|
+
"data-checked": checked ? "" : void 0,
|
|
34
|
+
"data-unchecked": checked ? void 0 : "",
|
|
35
|
+
"data-disabled": disabled ? "" : void 0
|
|
36
|
+
};
|
|
37
|
+
}
|
|
41
38
|
|
|
42
|
-
// src/switch/
|
|
39
|
+
// src/switch/SwitchRoot.tsx
|
|
43
40
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
44
41
|
function SwitchRoot({
|
|
45
42
|
checked: checkedProp,
|
|
46
43
|
defaultChecked,
|
|
47
|
-
nativeButton,
|
|
48
44
|
onCheckedChange,
|
|
49
45
|
disabled = false,
|
|
50
46
|
name,
|
|
@@ -61,53 +57,91 @@ function SwitchRoot({
|
|
|
61
57
|
state: "checked"
|
|
62
58
|
});
|
|
63
59
|
const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
props: rest
|
|
69
|
-
});
|
|
70
|
-
const safeRender = disabled && !isNativeButton && render !== void 0 ? cloneElement(render, retractActivationProps(render.props)) : render;
|
|
60
|
+
const elementRef = useRef(null);
|
|
61
|
+
const inputRef = useRef(null);
|
|
62
|
+
useNativeButtonWarning(elementRef);
|
|
63
|
+
useFormReset({ elementRef, inputRef, checked, setChecked, onCheckedChange });
|
|
71
64
|
const element = useRender({
|
|
72
|
-
render
|
|
65
|
+
render,
|
|
73
66
|
defaultTagName: "button",
|
|
74
|
-
state,
|
|
75
|
-
stateAttributes: switchStateAttributes,
|
|
76
67
|
props: {
|
|
68
|
+
// Without this a switch inside a form would submit it on every toggle.
|
|
69
|
+
type: "button",
|
|
77
70
|
role: "switch",
|
|
78
71
|
"aria-checked": checked,
|
|
72
|
+
// The platform suppresses activation, focus and the click handler below.
|
|
73
|
+
disabled: disabled || void 0,
|
|
74
|
+
...switchDataAttributes(state),
|
|
79
75
|
className,
|
|
80
76
|
children,
|
|
81
|
-
ref:
|
|
77
|
+
ref: elementRef,
|
|
82
78
|
onClick() {
|
|
83
|
-
if (disabled) return;
|
|
84
79
|
const next = !checked;
|
|
85
80
|
setChecked(next);
|
|
86
81
|
onCheckedChange?.(next);
|
|
87
82
|
}
|
|
88
83
|
},
|
|
89
|
-
consumerProps
|
|
84
|
+
consumerProps: rest
|
|
90
85
|
});
|
|
91
86
|
return /* @__PURE__ */ jsxs(SwitchRootContext.Provider, { value: state, children: [
|
|
92
87
|
element,
|
|
93
|
-
name !== void 0
|
|
88
|
+
name !== void 0 ? /* @__PURE__ */ jsx(
|
|
89
|
+
"input",
|
|
90
|
+
{
|
|
91
|
+
ref: inputRef,
|
|
92
|
+
type: "checkbox",
|
|
93
|
+
hidden: true,
|
|
94
|
+
readOnly: true,
|
|
95
|
+
name,
|
|
96
|
+
value,
|
|
97
|
+
checked,
|
|
98
|
+
disabled
|
|
99
|
+
}
|
|
100
|
+
) : null
|
|
94
101
|
] });
|
|
95
102
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...rest
|
|
103
|
+
function useFormReset({
|
|
104
|
+
elementRef,
|
|
105
|
+
inputRef,
|
|
106
|
+
checked,
|
|
107
|
+
setChecked,
|
|
108
|
+
onCheckedChange
|
|
103
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
|
+
|
|
138
|
+
// src/switch/SwitchThumb.tsx
|
|
139
|
+
function SwitchThumb({ className, children, render, ...rest }) {
|
|
104
140
|
const state = useSwitchRootContext();
|
|
105
141
|
return useRender({
|
|
106
142
|
render,
|
|
107
143
|
defaultTagName: "span",
|
|
108
|
-
state,
|
|
109
|
-
stateAttributes: switchStateAttributes,
|
|
110
|
-
props: { "aria-hidden": true, className, children },
|
|
144
|
+
props: { "aria-hidden": true, ...switchDataAttributes(state), className, children },
|
|
111
145
|
consumerProps: rest
|
|
112
146
|
});
|
|
113
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
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",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"react-dom": "19.2.8",
|
|
80
80
|
"tsup": "8.5.1",
|
|
81
81
|
"typescript": "5.9.3",
|
|
82
|
-
"vitest": "
|
|
82
|
+
"vitest": "4.1.11",
|
|
83
83
|
"@arun-dev/config": "0.0.0"
|
|
84
84
|
},
|
|
85
85
|
"scripts": {
|
package/dist/chunk-TNMGZWHA.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
mergeProps
|
|
3
|
-
} from "./chunk-PYLPZVWQ.js";
|
|
4
|
-
|
|
5
|
-
// src/useButton.ts
|
|
6
|
-
import { useEffect, useRef } from "react";
|
|
7
|
-
var ACTIVATION_HANDLERS = [
|
|
8
|
-
"onClick",
|
|
9
|
-
"onDoubleClick",
|
|
10
|
-
"onMouseDown",
|
|
11
|
-
"onMouseUp",
|
|
12
|
-
"onPointerDown",
|
|
13
|
-
"onPointerUp",
|
|
14
|
-
"onTouchStart",
|
|
15
|
-
"onTouchEnd",
|
|
16
|
-
"onKeyDown",
|
|
17
|
-
"onKeyUp",
|
|
18
|
-
"onKeyPress"
|
|
19
|
-
];
|
|
20
|
-
function activationHandlers() {
|
|
21
|
-
return {
|
|
22
|
-
onKeyDown(event) {
|
|
23
|
-
if (event.target !== event.currentTarget || event.defaultPrevented) return;
|
|
24
|
-
if (event.key === " ") {
|
|
25
|
-
event.preventDefault();
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (event.key === "Enter") {
|
|
29
|
-
event.preventDefault();
|
|
30
|
-
click(event.currentTarget);
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
onKeyUp(event) {
|
|
34
|
-
if (event.target !== event.currentTarget || event.defaultPrevented) return;
|
|
35
|
-
if (event.key === " ") click(event.currentTarget);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function click(element) {
|
|
40
|
-
if (element && typeof element.click === "function") {
|
|
41
|
-
element.click();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function useButton({
|
|
45
|
-
disabled = false,
|
|
46
|
-
native,
|
|
47
|
-
props = {}
|
|
48
|
-
}) {
|
|
49
|
-
const elementRef = useRef(null);
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (process.env.NODE_ENV === "production") return;
|
|
52
|
-
const element = elementRef.current;
|
|
53
|
-
if (!element) return;
|
|
54
|
-
const isButtonTag = element.tagName === "BUTTON";
|
|
55
|
-
if (native && !isButtonTag) {
|
|
56
|
-
console.error(
|
|
57
|
-
`A component expected a native <button> but rendered <${element.tagName.toLowerCase()}>. Focus, keyboard activation and \`disabled\` will not behave natively.`
|
|
58
|
-
);
|
|
59
|
-
} else if (!native && isButtonTag) {
|
|
60
|
-
console.error(
|
|
61
|
-
"A component rendered a native <button> while treating it as a non-native element, so it carries synthesised attributes it does not need."
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
}, [native]);
|
|
65
|
-
return { props: buttonProps({ disabled, native, props }), ref: elementRef };
|
|
66
|
-
}
|
|
67
|
-
function buttonProps({ disabled, native, props }) {
|
|
68
|
-
if (!disabled) {
|
|
69
|
-
return native ? mergeProps({ type: "button" }, props) : mergeProps({ tabIndex: 0 }, activationHandlers(), props);
|
|
70
|
-
}
|
|
71
|
-
if (native) return mergeProps({ type: "button" }, props, { disabled: true, "data-disabled": "" });
|
|
72
|
-
const sanitised = {};
|
|
73
|
-
for (const key of Object.keys(props)) {
|
|
74
|
-
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) continue;
|
|
75
|
-
sanitised[key] = props[key];
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
...sanitised,
|
|
79
|
-
"aria-disabled": true,
|
|
80
|
-
"data-disabled": "",
|
|
81
|
-
// Mirrors a native disabled button, which is not focusable.
|
|
82
|
-
tabIndex: -1
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function retractActivationProps(props) {
|
|
86
|
-
const overrides = {};
|
|
87
|
-
for (const key of Object.keys(props)) {
|
|
88
|
-
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) overrides[key] = void 0;
|
|
89
|
-
}
|
|
90
|
-
return overrides;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export {
|
|
94
|
-
useButton,
|
|
95
|
-
retractActivationProps
|
|
96
|
-
};
|