@arun-dev/headless 0.4.0 → 2.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 +18 -0
- package/dist/button/index.cjs +253 -0
- package/dist/button/index.d.cts +44 -0
- package/dist/button/index.d.ts +44 -0
- package/dist/button/index.js +33 -0
- package/dist/chunk-ER7IAVR5.js +96 -0
- package/dist/{chunk-I477HMLD.js → chunk-IGKX2XWC.js} +13 -83
- package/dist/chunk-UEIB3I3T.js +36 -0
- package/dist/index.cjs +12 -50
- package/dist/index.d.cts +12 -26
- package/dist/index.d.ts +12 -26
- package/dist/index.js +4 -6
- package/dist/switch/index.cjs +168 -100
- package/dist/switch/index.d.cts +14 -4
- package/dist/switch/index.d.ts +14 -4
- package/dist/switch/index.js +34 -24
- package/package.json +11 -1
package/dist/index.cjs
CHANGED
|
@@ -24,8 +24,6 @@ __export(index_exports, {
|
|
|
24
24
|
disabledAttribute: () => disabledAttribute,
|
|
25
25
|
getStateAttributes: () => getStateAttributes,
|
|
26
26
|
mergeProps: () => mergeProps,
|
|
27
|
-
retractActivationProps: () => retractActivationProps,
|
|
28
|
-
useButton: () => useButton,
|
|
29
27
|
useControlled: () => useControlled,
|
|
30
28
|
useRender: () => useRender
|
|
31
29
|
});
|
|
@@ -69,6 +67,15 @@ function makeEventPreventable(event) {
|
|
|
69
67
|
function isComponentHandlerPrevented(event) {
|
|
70
68
|
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
71
69
|
}
|
|
70
|
+
function chainHandlers(earlier, value) {
|
|
71
|
+
return (...args) => {
|
|
72
|
+
const event = args[0];
|
|
73
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
74
|
+
value(...args);
|
|
75
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
76
|
+
earlier(...args);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
72
79
|
function mergeProps(...objects) {
|
|
73
80
|
const merged = {};
|
|
74
81
|
for (const props of objects) {
|
|
@@ -78,13 +85,7 @@ function mergeProps(...objects) {
|
|
|
78
85
|
if (value === void 0) continue;
|
|
79
86
|
if (isEventHandler(key) && typeof value === "function") {
|
|
80
87
|
const earlier = merged[key];
|
|
81
|
-
merged[key] = typeof earlier === "function" ? (
|
|
82
|
-
const event = args[0];
|
|
83
|
-
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
84
|
-
value(...args);
|
|
85
|
-
if (isComponentHandlerPrevented(event)) return;
|
|
86
|
-
earlier(...args);
|
|
87
|
-
} : value;
|
|
88
|
+
merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
|
|
88
89
|
} else if (key === "className") {
|
|
89
90
|
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
90
91
|
} else if (key === "style") {
|
|
@@ -127,11 +128,12 @@ function useRender({
|
|
|
127
128
|
render,
|
|
128
129
|
defaultTagName,
|
|
129
130
|
props,
|
|
131
|
+
consumerProps,
|
|
130
132
|
state,
|
|
131
133
|
stateAttributes
|
|
132
134
|
}) {
|
|
133
135
|
const attributes = state ? getStateAttributes(state, stateAttributes) : {};
|
|
134
|
-
const merged = mergeProps(attributes,
|
|
136
|
+
const merged = mergeProps(attributes, props, consumerProps);
|
|
135
137
|
if ((0, import_react.isValidElement)(render)) {
|
|
136
138
|
return (0, import_react.cloneElement)(render, mergeProps(merged, render.props));
|
|
137
139
|
}
|
|
@@ -170,52 +172,12 @@ function useControlled({
|
|
|
170
172
|
);
|
|
171
173
|
return [value, setValue];
|
|
172
174
|
}
|
|
173
|
-
|
|
174
|
-
// src/useButton.ts
|
|
175
|
-
var ACTIVATION_HANDLERS = [
|
|
176
|
-
"onClick",
|
|
177
|
-
"onDoubleClick",
|
|
178
|
-
"onMouseDown",
|
|
179
|
-
"onMouseUp",
|
|
180
|
-
"onPointerDown",
|
|
181
|
-
"onPointerUp",
|
|
182
|
-
"onTouchStart",
|
|
183
|
-
"onTouchEnd",
|
|
184
|
-
"onKeyDown",
|
|
185
|
-
"onKeyUp",
|
|
186
|
-
"onKeyPress"
|
|
187
|
-
];
|
|
188
|
-
function useButton({ disabled = false, native, props = {} }) {
|
|
189
|
-
if (!disabled) return props;
|
|
190
|
-
if (native) return { ...props, disabled: true, "data-disabled": "" };
|
|
191
|
-
const sanitised = {};
|
|
192
|
-
for (const key of Object.keys(props)) {
|
|
193
|
-
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) continue;
|
|
194
|
-
sanitised[key] = props[key];
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
...sanitised,
|
|
198
|
-
"aria-disabled": true,
|
|
199
|
-
"data-disabled": "",
|
|
200
|
-
// Mirrors a native disabled button, which is not focusable.
|
|
201
|
-
tabIndex: -1
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function retractActivationProps(props) {
|
|
205
|
-
const overrides = {};
|
|
206
|
-
for (const key of Object.keys(props)) {
|
|
207
|
-
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) overrides[key] = void 0;
|
|
208
|
-
}
|
|
209
|
-
return overrides;
|
|
210
|
-
}
|
|
211
175
|
// Annotate the CommonJS export names for ESM import in node:
|
|
212
176
|
0 && (module.exports = {
|
|
213
177
|
booleanAttribute,
|
|
214
178
|
disabledAttribute,
|
|
215
179
|
getStateAttributes,
|
|
216
180
|
mergeProps,
|
|
217
|
-
retractActivationProps,
|
|
218
|
-
useButton,
|
|
219
181
|
useControlled,
|
|
220
182
|
useRender
|
|
221
183
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -98,14 +98,22 @@ interface UseRenderParams<State extends Record<string, unknown>> {
|
|
|
98
98
|
render?: ReactElement | undefined;
|
|
99
99
|
/** Tag rendered when `render` is not supplied. */
|
|
100
100
|
defaultTagName: string;
|
|
101
|
-
/**
|
|
102
|
-
props
|
|
101
|
+
/** The component's own props. */
|
|
102
|
+
props?: UnknownProps;
|
|
103
|
+
/**
|
|
104
|
+
* The consumer's props, spread from `...rest`.
|
|
105
|
+
*
|
|
106
|
+
* A separate slot rather than a position in a list: precedence is then a property of
|
|
107
|
+
* this signature rather than a rule each component has to remember. A component
|
|
108
|
+
* cannot put a consumer's props before its own, because there is nowhere to put them.
|
|
109
|
+
*/
|
|
110
|
+
consumerProps?: UnknownProps;
|
|
103
111
|
/** The part's state, projected onto the DOM through `stateAttributes`. */
|
|
104
112
|
state?: State;
|
|
105
113
|
/** How each state field becomes `data-*` attributes. */
|
|
106
114
|
stateAttributes?: StateAttributeMapping<State>;
|
|
107
115
|
}
|
|
108
|
-
declare function useRender<State extends Record<string, unknown> = Record<string, never>>({ render, defaultTagName, props, state, stateAttributes, }: UseRenderParams<State>): ReactElement;
|
|
116
|
+
declare function useRender<State extends Record<string, unknown> = Record<string, never>>({ render, defaultTagName, props, consumerProps, state, stateAttributes, }: UseRenderParams<State>): ReactElement;
|
|
109
117
|
|
|
110
118
|
/**
|
|
111
119
|
* Supports both controlled and uncontrolled use of a single value.
|
|
@@ -125,26 +133,4 @@ declare function useControlled<T>({ controlled, default: defaultValue, name, sta
|
|
|
125
133
|
state?: string;
|
|
126
134
|
}): [T, (next: T) => void];
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
disabled?: boolean;
|
|
130
|
-
/** Whether the rendered element is a native `<button>`, which the platform disables for us. */
|
|
131
|
-
native: boolean;
|
|
132
|
-
/** Props destined for the element. Returned unchanged unless `disabled`. */
|
|
133
|
-
props?: UnknownProps;
|
|
134
|
-
}
|
|
135
|
-
declare function useButton({ disabled, native, props }: UseButtonParams): UnknownProps;
|
|
136
|
-
/**
|
|
137
|
-
* Overrides that neutralise a consumer-supplied `render` element.
|
|
138
|
-
*
|
|
139
|
-
* `useButton` sanitises the props a component passes, but a `render` element's own
|
|
140
|
-
* props merge at the highest precedence inside `useRender`, so a `href` written
|
|
141
|
-
* directly on it survives. Those have to be retracted on the element itself:
|
|
142
|
-
*
|
|
143
|
-
* cloneElement(render, retractActivationProps(render.props))
|
|
144
|
-
*
|
|
145
|
-
* `cloneElement` overwrites with `undefined`, which `mergeProps` cannot do — it skips
|
|
146
|
-
* `undefined` so an absent prop never clobbers a present one.
|
|
147
|
-
*/
|
|
148
|
-
declare function retractActivationProps(props: UnknownProps): UnknownProps;
|
|
149
|
-
|
|
150
|
-
export { type ComponentEvent, type StateAttributeMapping, type UnknownProps, type UseButtonParams, type UseRenderParams, booleanAttribute, disabledAttribute, getStateAttributes, mergeProps, retractActivationProps, useButton, useControlled, useRender };
|
|
136
|
+
export { type ComponentEvent, type StateAttributeMapping, type UnknownProps, type UseRenderParams, booleanAttribute, disabledAttribute, getStateAttributes, mergeProps, useControlled, useRender };
|
package/dist/index.d.ts
CHANGED
|
@@ -98,14 +98,22 @@ interface UseRenderParams<State extends Record<string, unknown>> {
|
|
|
98
98
|
render?: ReactElement | undefined;
|
|
99
99
|
/** Tag rendered when `render` is not supplied. */
|
|
100
100
|
defaultTagName: string;
|
|
101
|
-
/**
|
|
102
|
-
props
|
|
101
|
+
/** The component's own props. */
|
|
102
|
+
props?: UnknownProps;
|
|
103
|
+
/**
|
|
104
|
+
* The consumer's props, spread from `...rest`.
|
|
105
|
+
*
|
|
106
|
+
* A separate slot rather than a position in a list: precedence is then a property of
|
|
107
|
+
* this signature rather than a rule each component has to remember. A component
|
|
108
|
+
* cannot put a consumer's props before its own, because there is nowhere to put them.
|
|
109
|
+
*/
|
|
110
|
+
consumerProps?: UnknownProps;
|
|
103
111
|
/** The part's state, projected onto the DOM through `stateAttributes`. */
|
|
104
112
|
state?: State;
|
|
105
113
|
/** How each state field becomes `data-*` attributes. */
|
|
106
114
|
stateAttributes?: StateAttributeMapping<State>;
|
|
107
115
|
}
|
|
108
|
-
declare function useRender<State extends Record<string, unknown> = Record<string, never>>({ render, defaultTagName, props, state, stateAttributes, }: UseRenderParams<State>): ReactElement;
|
|
116
|
+
declare function useRender<State extends Record<string, unknown> = Record<string, never>>({ render, defaultTagName, props, consumerProps, state, stateAttributes, }: UseRenderParams<State>): ReactElement;
|
|
109
117
|
|
|
110
118
|
/**
|
|
111
119
|
* Supports both controlled and uncontrolled use of a single value.
|
|
@@ -125,26 +133,4 @@ declare function useControlled<T>({ controlled, default: defaultValue, name, sta
|
|
|
125
133
|
state?: string;
|
|
126
134
|
}): [T, (next: T) => void];
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
disabled?: boolean;
|
|
130
|
-
/** Whether the rendered element is a native `<button>`, which the platform disables for us. */
|
|
131
|
-
native: boolean;
|
|
132
|
-
/** Props destined for the element. Returned unchanged unless `disabled`. */
|
|
133
|
-
props?: UnknownProps;
|
|
134
|
-
}
|
|
135
|
-
declare function useButton({ disabled, native, props }: UseButtonParams): UnknownProps;
|
|
136
|
-
/**
|
|
137
|
-
* Overrides that neutralise a consumer-supplied `render` element.
|
|
138
|
-
*
|
|
139
|
-
* `useButton` sanitises the props a component passes, but a `render` element's own
|
|
140
|
-
* props merge at the highest precedence inside `useRender`, so a `href` written
|
|
141
|
-
* directly on it survives. Those have to be retracted on the element itself:
|
|
142
|
-
*
|
|
143
|
-
* cloneElement(render, retractActivationProps(render.props))
|
|
144
|
-
*
|
|
145
|
-
* `cloneElement` overwrites with `undefined`, which `mergeProps` cannot do — it skips
|
|
146
|
-
* `undefined` so an absent prop never clobbers a present one.
|
|
147
|
-
*/
|
|
148
|
-
declare function retractActivationProps(props: UnknownProps): UnknownProps;
|
|
149
|
-
|
|
150
|
-
export { type ComponentEvent, type StateAttributeMapping, type UnknownProps, type UseButtonParams, type UseRenderParams, booleanAttribute, disabledAttribute, getStateAttributes, mergeProps, retractActivationProps, useButton, useControlled, useRender };
|
|
136
|
+
export { type ComponentEvent, type StateAttributeMapping, type UnknownProps, type UseRenderParams, booleanAttribute, disabledAttribute, getStateAttributes, mergeProps, useControlled, useRender };
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useControlled
|
|
3
|
+
} from "./chunk-UEIB3I3T.js";
|
|
1
4
|
import {
|
|
2
5
|
booleanAttribute,
|
|
3
6
|
disabledAttribute,
|
|
4
7
|
getStateAttributes,
|
|
5
8
|
mergeProps,
|
|
6
|
-
retractActivationProps,
|
|
7
|
-
useButton,
|
|
8
|
-
useControlled,
|
|
9
9
|
useRender
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-IGKX2XWC.js";
|
|
11
11
|
export {
|
|
12
12
|
booleanAttribute,
|
|
13
13
|
disabledAttribute,
|
|
14
14
|
getStateAttributes,
|
|
15
15
|
mergeProps,
|
|
16
|
-
retractActivationProps,
|
|
17
|
-
useButton,
|
|
18
16
|
useControlled,
|
|
19
17
|
useRender
|
|
20
18
|
};
|
package/dist/switch/index.cjs
CHANGED
|
@@ -32,74 +32,10 @@ __export(index_parts_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// src/switch/root/SwitchRoot.tsx
|
|
35
|
-
var
|
|
35
|
+
var import_react5 = require("react");
|
|
36
36
|
|
|
37
37
|
// src/useButton.ts
|
|
38
|
-
var ACTIVATION_HANDLERS = [
|
|
39
|
-
"onClick",
|
|
40
|
-
"onDoubleClick",
|
|
41
|
-
"onMouseDown",
|
|
42
|
-
"onMouseUp",
|
|
43
|
-
"onPointerDown",
|
|
44
|
-
"onPointerUp",
|
|
45
|
-
"onTouchStart",
|
|
46
|
-
"onTouchEnd",
|
|
47
|
-
"onKeyDown",
|
|
48
|
-
"onKeyUp",
|
|
49
|
-
"onKeyPress"
|
|
50
|
-
];
|
|
51
|
-
function useButton({ disabled = false, native, props = {} }) {
|
|
52
|
-
if (!disabled) return props;
|
|
53
|
-
if (native) return { ...props, disabled: true, "data-disabled": "" };
|
|
54
|
-
const sanitised = {};
|
|
55
|
-
for (const key of Object.keys(props)) {
|
|
56
|
-
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) continue;
|
|
57
|
-
sanitised[key] = props[key];
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
...sanitised,
|
|
61
|
-
"aria-disabled": true,
|
|
62
|
-
"data-disabled": "",
|
|
63
|
-
// Mirrors a native disabled button, which is not focusable.
|
|
64
|
-
tabIndex: -1
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/core/useControlled.ts
|
|
69
38
|
var import_react = require("react");
|
|
70
|
-
function useControlled({
|
|
71
|
-
controlled,
|
|
72
|
-
default: defaultValue,
|
|
73
|
-
name = "Component",
|
|
74
|
-
state = "value"
|
|
75
|
-
}) {
|
|
76
|
-
const { current: isControlled } = (0, import_react.useRef)(controlled !== void 0);
|
|
77
|
-
const [uncontrolled, setUncontrolled] = (0, import_react.useState)(defaultValue);
|
|
78
|
-
const defaultRef = (0, import_react.useRef)(defaultValue);
|
|
79
|
-
const value = isControlled ? controlled : uncontrolled;
|
|
80
|
-
if (process.env.NODE_ENV !== "production") {
|
|
81
|
-
if (isControlled !== (controlled !== void 0)) {
|
|
82
|
-
console.error(
|
|
83
|
-
`${name}: cannot switch between controlled and uncontrolled \`${state}\`. Decide which one this component is for the whole of its life.`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
if (!isControlled && defaultRef.current !== defaultValue) {
|
|
87
|
-
console.error(
|
|
88
|
-
`${name}: cannot change the default \`${state}\` after mount. It is only read once, so later changes are silently ignored.`
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const setValue = (0, import_react.useCallback)(
|
|
93
|
-
(next) => {
|
|
94
|
-
if (!isControlled) setUncontrolled(next);
|
|
95
|
-
},
|
|
96
|
-
[isControlled]
|
|
97
|
-
);
|
|
98
|
-
return [value, setValue];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// src/core/useRender.ts
|
|
102
|
-
var import_react2 = require("react");
|
|
103
39
|
|
|
104
40
|
// src/core/mergeProps.ts
|
|
105
41
|
function isEventHandler(key) {
|
|
@@ -139,6 +75,15 @@ function makeEventPreventable(event) {
|
|
|
139
75
|
function isComponentHandlerPrevented(event) {
|
|
140
76
|
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
141
77
|
}
|
|
78
|
+
function chainHandlers(earlier, value) {
|
|
79
|
+
return (...args) => {
|
|
80
|
+
const event = args[0];
|
|
81
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
82
|
+
value(...args);
|
|
83
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
84
|
+
earlier(...args);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
142
87
|
function mergeProps(...objects) {
|
|
143
88
|
const merged = {};
|
|
144
89
|
for (const props of objects) {
|
|
@@ -148,13 +93,7 @@ function mergeProps(...objects) {
|
|
|
148
93
|
if (value === void 0) continue;
|
|
149
94
|
if (isEventHandler(key) && typeof value === "function") {
|
|
150
95
|
const earlier = merged[key];
|
|
151
|
-
merged[key] = typeof earlier === "function" ? (
|
|
152
|
-
const event = args[0];
|
|
153
|
-
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
154
|
-
value(...args);
|
|
155
|
-
if (isComponentHandlerPrevented(event)) return;
|
|
156
|
-
earlier(...args);
|
|
157
|
-
} : value;
|
|
96
|
+
merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
|
|
158
97
|
} else if (key === "className") {
|
|
159
98
|
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
160
99
|
} else if (key === "style") {
|
|
@@ -169,6 +108,129 @@ function mergeProps(...objects) {
|
|
|
169
108
|
return merged;
|
|
170
109
|
}
|
|
171
110
|
|
|
111
|
+
// src/useButton.ts
|
|
112
|
+
var ACTIVATION_HANDLERS = [
|
|
113
|
+
"onClick",
|
|
114
|
+
"onDoubleClick",
|
|
115
|
+
"onMouseDown",
|
|
116
|
+
"onMouseUp",
|
|
117
|
+
"onPointerDown",
|
|
118
|
+
"onPointerUp",
|
|
119
|
+
"onTouchStart",
|
|
120
|
+
"onTouchEnd",
|
|
121
|
+
"onKeyDown",
|
|
122
|
+
"onKeyUp",
|
|
123
|
+
"onKeyPress"
|
|
124
|
+
];
|
|
125
|
+
function activationHandlers() {
|
|
126
|
+
return {
|
|
127
|
+
onKeyDown(event) {
|
|
128
|
+
if (event.target !== event.currentTarget || event.defaultPrevented) return;
|
|
129
|
+
if (event.key === " ") {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (event.key === "Enter") {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
click(event.currentTarget);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
onKeyUp(event) {
|
|
139
|
+
if (event.target !== event.currentTarget || event.defaultPrevented) return;
|
|
140
|
+
if (event.key === " ") click(event.currentTarget);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function click(element) {
|
|
145
|
+
if (element && typeof element.click === "function") {
|
|
146
|
+
element.click();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function useButton({
|
|
150
|
+
disabled = false,
|
|
151
|
+
native,
|
|
152
|
+
props = {}
|
|
153
|
+
}) {
|
|
154
|
+
const elementRef = (0, import_react.useRef)(null);
|
|
155
|
+
(0, import_react.useEffect)(() => {
|
|
156
|
+
if (process.env.NODE_ENV === "production") return;
|
|
157
|
+
const element = elementRef.current;
|
|
158
|
+
if (!element) return;
|
|
159
|
+
const isButtonTag = element.tagName === "BUTTON";
|
|
160
|
+
if (native && !isButtonTag) {
|
|
161
|
+
console.error(
|
|
162
|
+
`A component expected a native <button> but rendered <${element.tagName.toLowerCase()}>. Focus, keyboard activation and \`disabled\` will not behave natively.`
|
|
163
|
+
);
|
|
164
|
+
} else if (!native && isButtonTag) {
|
|
165
|
+
console.error(
|
|
166
|
+
"A component rendered a native <button> while treating it as a non-native element, so it carries synthesised attributes it does not need."
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}, [native]);
|
|
170
|
+
return { props: buttonProps({ disabled, native, props }), ref: elementRef };
|
|
171
|
+
}
|
|
172
|
+
function buttonProps({ disabled, native, props }) {
|
|
173
|
+
if (!disabled) {
|
|
174
|
+
return native ? mergeProps({ type: "button" }, props) : mergeProps({ tabIndex: 0 }, activationHandlers(), props);
|
|
175
|
+
}
|
|
176
|
+
if (native) return mergeProps({ type: "button" }, props, { disabled: true, "data-disabled": "" });
|
|
177
|
+
const sanitised = {};
|
|
178
|
+
for (const key of Object.keys(props)) {
|
|
179
|
+
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) continue;
|
|
180
|
+
sanitised[key] = props[key];
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
...sanitised,
|
|
184
|
+
"aria-disabled": true,
|
|
185
|
+
"data-disabled": "",
|
|
186
|
+
// Mirrors a native disabled button, which is not focusable.
|
|
187
|
+
tabIndex: -1
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function retractActivationProps(props) {
|
|
191
|
+
const overrides = {};
|
|
192
|
+
for (const key of Object.keys(props)) {
|
|
193
|
+
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) overrides[key] = void 0;
|
|
194
|
+
}
|
|
195
|
+
return overrides;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/core/useControlled.ts
|
|
199
|
+
var import_react2 = require("react");
|
|
200
|
+
function useControlled({
|
|
201
|
+
controlled,
|
|
202
|
+
default: defaultValue,
|
|
203
|
+
name = "Component",
|
|
204
|
+
state = "value"
|
|
205
|
+
}) {
|
|
206
|
+
const { current: isControlled } = (0, import_react2.useRef)(controlled !== void 0);
|
|
207
|
+
const [uncontrolled, setUncontrolled] = (0, import_react2.useState)(defaultValue);
|
|
208
|
+
const defaultRef = (0, import_react2.useRef)(defaultValue);
|
|
209
|
+
const value = isControlled ? controlled : uncontrolled;
|
|
210
|
+
if (process.env.NODE_ENV !== "production") {
|
|
211
|
+
if (isControlled !== (controlled !== void 0)) {
|
|
212
|
+
console.error(
|
|
213
|
+
`${name}: cannot switch between controlled and uncontrolled \`${state}\`. Decide which one this component is for the whole of its life.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (!isControlled && defaultRef.current !== defaultValue) {
|
|
217
|
+
console.error(
|
|
218
|
+
`${name}: cannot change the default \`${state}\` after mount. It is only read once, so later changes are silently ignored.`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const setValue = (0, import_react2.useCallback)(
|
|
223
|
+
(next) => {
|
|
224
|
+
if (!isControlled) setUncontrolled(next);
|
|
225
|
+
},
|
|
226
|
+
[isControlled]
|
|
227
|
+
);
|
|
228
|
+
return [value, setValue];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/core/useRender.ts
|
|
232
|
+
var import_react3 = require("react");
|
|
233
|
+
|
|
172
234
|
// src/core/stateAttributes.ts
|
|
173
235
|
function getStateAttributes(state, mapping) {
|
|
174
236
|
if (!mapping) return {};
|
|
@@ -194,22 +256,23 @@ function useRender({
|
|
|
194
256
|
render,
|
|
195
257
|
defaultTagName,
|
|
196
258
|
props,
|
|
259
|
+
consumerProps,
|
|
197
260
|
state,
|
|
198
261
|
stateAttributes
|
|
199
262
|
}) {
|
|
200
263
|
const attributes = state ? getStateAttributes(state, stateAttributes) : {};
|
|
201
|
-
const merged = mergeProps(attributes,
|
|
202
|
-
if ((0,
|
|
203
|
-
return (0,
|
|
264
|
+
const merged = mergeProps(attributes, props, consumerProps);
|
|
265
|
+
if ((0, import_react3.isValidElement)(render)) {
|
|
266
|
+
return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
|
|
204
267
|
}
|
|
205
|
-
return (0,
|
|
268
|
+
return (0, import_react3.createElement)(defaultTagName, merged);
|
|
206
269
|
}
|
|
207
270
|
|
|
208
271
|
// src/switch/SwitchRootContext.ts
|
|
209
|
-
var
|
|
210
|
-
var SwitchRootContext = (0,
|
|
272
|
+
var import_react4 = require("react");
|
|
273
|
+
var SwitchRootContext = (0, import_react4.createContext)(null);
|
|
211
274
|
function useSwitchRootContext() {
|
|
212
|
-
const context = (0,
|
|
275
|
+
const context = (0, import_react4.useContext)(SwitchRootContext);
|
|
213
276
|
if (context === null) {
|
|
214
277
|
throw new Error("<Switch.Thumb> must be rendered inside <Switch.Root>.");
|
|
215
278
|
}
|
|
@@ -227,6 +290,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
227
290
|
function SwitchRoot({
|
|
228
291
|
checked: checkedProp,
|
|
229
292
|
defaultChecked,
|
|
293
|
+
nativeButton,
|
|
230
294
|
onCheckedChange,
|
|
231
295
|
disabled = false,
|
|
232
296
|
name,
|
|
@@ -242,30 +306,33 @@ function SwitchRoot({
|
|
|
242
306
|
name: "Switch.Root",
|
|
243
307
|
state: "checked"
|
|
244
308
|
});
|
|
245
|
-
const state = (0,
|
|
246
|
-
const isNativeButton = render === void 0 || render.type === "button";
|
|
247
|
-
const
|
|
309
|
+
const state = (0, import_react5.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
310
|
+
const isNativeButton = nativeButton ?? (render === void 0 || render.type === "button");
|
|
311
|
+
const { props: consumerProps, ref: buttonRef } = useButton({
|
|
312
|
+
disabled,
|
|
313
|
+
native: isNativeButton,
|
|
314
|
+
props: rest
|
|
315
|
+
});
|
|
316
|
+
const safeRender = disabled && !isNativeButton && render !== void 0 ? (0, import_react5.cloneElement)(render, retractActivationProps(render.props)) : render;
|
|
248
317
|
const element = useRender({
|
|
249
|
-
render,
|
|
318
|
+
render: safeRender,
|
|
250
319
|
defaultTagName: "button",
|
|
251
320
|
state,
|
|
252
321
|
stateAttributes: switchStateAttributes,
|
|
253
|
-
props:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
consumerProps
|
|
268
|
-
]
|
|
322
|
+
props: {
|
|
323
|
+
role: "switch",
|
|
324
|
+
"aria-checked": checked,
|
|
325
|
+
className,
|
|
326
|
+
children,
|
|
327
|
+
ref: buttonRef,
|
|
328
|
+
onClick() {
|
|
329
|
+
if (disabled) return;
|
|
330
|
+
const next = !checked;
|
|
331
|
+
setChecked(next);
|
|
332
|
+
onCheckedChange?.(next);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
consumerProps
|
|
269
336
|
});
|
|
270
337
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SwitchRootContext.Provider, { value: state, children: [
|
|
271
338
|
element,
|
|
@@ -286,7 +353,8 @@ function SwitchThumb({
|
|
|
286
353
|
defaultTagName: "span",
|
|
287
354
|
state,
|
|
288
355
|
stateAttributes: switchStateAttributes,
|
|
289
|
-
props:
|
|
356
|
+
props: { "aria-hidden": true, className, children },
|
|
357
|
+
consumerProps: rest
|
|
290
358
|
});
|
|
291
359
|
}
|
|
292
360
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/switch/index.d.cts
CHANGED
|
@@ -12,6 +12,15 @@ interface SwitchRootProps {
|
|
|
12
12
|
checked?: boolean;
|
|
13
13
|
/** Initial state when uncontrolled. Read once, at mount. */
|
|
14
14
|
defaultChecked?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the rendered element is a native `<button>`.
|
|
17
|
+
*
|
|
18
|
+
* Inferred from `render`, which is right for an element literal. Set it explicitly
|
|
19
|
+
* when rendering a *component* — `render={<Tooltip.Trigger />}` cannot be inspected,
|
|
20
|
+
* so it is treated as non-native and picks up attributes it does not need. A mismatch
|
|
21
|
+
* logs a development warning.
|
|
22
|
+
*/
|
|
23
|
+
nativeButton?: boolean;
|
|
15
24
|
onCheckedChange?: (checked: boolean) => void;
|
|
16
25
|
disabled?: boolean;
|
|
17
26
|
/**
|
|
@@ -42,7 +51,7 @@ interface SwitchRootProps {
|
|
|
42
51
|
* or `aria-labelledby`. That is the consumer's decision, not something a headless
|
|
43
52
|
* component should guess.
|
|
44
53
|
*/
|
|
45
|
-
declare function SwitchRoot({ checked: checkedProp, defaultChecked, onCheckedChange, disabled, name, value, className, children, render, ...rest }: SwitchRootProps & Record<string, unknown>): react.JSX.Element;
|
|
54
|
+
declare function SwitchRoot({ checked: checkedProp, defaultChecked, nativeButton, onCheckedChange, disabled, name, value, className, children, render, ...rest }: SwitchRootProps & Record<string, unknown>): react.JSX.Element;
|
|
46
55
|
|
|
47
56
|
interface SwitchThumbProps {
|
|
48
57
|
className?: string;
|
|
@@ -55,10 +64,11 @@ interface SwitchThumbProps {
|
|
|
55
64
|
* The moving part of the switch.
|
|
56
65
|
*
|
|
57
66
|
* Reads state from the Root rather than taking props, so a consumer cannot get the
|
|
58
|
-
* two out of step. Carries the same `data-*` attributes as the Root,
|
|
59
|
-
*
|
|
67
|
+
* two out of step. Carries the same `data-*` attributes as the Root, so it can be
|
|
68
|
+
* styled from either — on the thumb itself, or by descending from the Root:
|
|
60
69
|
*
|
|
61
|
-
* .
|
|
70
|
+
* .thumb[data-checked] { transform: translateX(100%); }
|
|
71
|
+
* .switch[data-checked] .thumb { transform: translateX(100%); }
|
|
62
72
|
*
|
|
63
73
|
* Purely presentational — hidden from assistive technology, since the Root already
|
|
64
74
|
* announces the state.
|
package/dist/switch/index.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ interface SwitchRootProps {
|
|
|
12
12
|
checked?: boolean;
|
|
13
13
|
/** Initial state when uncontrolled. Read once, at mount. */
|
|
14
14
|
defaultChecked?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the rendered element is a native `<button>`.
|
|
17
|
+
*
|
|
18
|
+
* Inferred from `render`, which is right for an element literal. Set it explicitly
|
|
19
|
+
* when rendering a *component* — `render={<Tooltip.Trigger />}` cannot be inspected,
|
|
20
|
+
* so it is treated as non-native and picks up attributes it does not need. A mismatch
|
|
21
|
+
* logs a development warning.
|
|
22
|
+
*/
|
|
23
|
+
nativeButton?: boolean;
|
|
15
24
|
onCheckedChange?: (checked: boolean) => void;
|
|
16
25
|
disabled?: boolean;
|
|
17
26
|
/**
|
|
@@ -42,7 +51,7 @@ interface SwitchRootProps {
|
|
|
42
51
|
* or `aria-labelledby`. That is the consumer's decision, not something a headless
|
|
43
52
|
* component should guess.
|
|
44
53
|
*/
|
|
45
|
-
declare function SwitchRoot({ checked: checkedProp, defaultChecked, onCheckedChange, disabled, name, value, className, children, render, ...rest }: SwitchRootProps & Record<string, unknown>): react.JSX.Element;
|
|
54
|
+
declare function SwitchRoot({ checked: checkedProp, defaultChecked, nativeButton, onCheckedChange, disabled, name, value, className, children, render, ...rest }: SwitchRootProps & Record<string, unknown>): react.JSX.Element;
|
|
46
55
|
|
|
47
56
|
interface SwitchThumbProps {
|
|
48
57
|
className?: string;
|
|
@@ -55,10 +64,11 @@ interface SwitchThumbProps {
|
|
|
55
64
|
* The moving part of the switch.
|
|
56
65
|
*
|
|
57
66
|
* Reads state from the Root rather than taking props, so a consumer cannot get the
|
|
58
|
-
* two out of step. Carries the same `data-*` attributes as the Root,
|
|
59
|
-
*
|
|
67
|
+
* two out of step. Carries the same `data-*` attributes as the Root, so it can be
|
|
68
|
+
* styled from either — on the thumb itself, or by descending from the Root:
|
|
60
69
|
*
|
|
61
|
-
* .
|
|
70
|
+
* .thumb[data-checked] { transform: translateX(100%); }
|
|
71
|
+
* .switch[data-checked] .thumb { transform: translateX(100%); }
|
|
62
72
|
*
|
|
63
73
|
* Purely presentational — hidden from assistive technology, since the Root already
|
|
64
74
|
* announces the state.
|