@arun-dev/headless 2.0.0 → 3.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 +32 -22
- package/dist/button/index.cjs +25 -126
- package/dist/button/index.d.cts +31 -23
- package/dist/button/index.d.ts +31 -23
- package/dist/button/index.js +23 -16
- package/dist/{chunk-IGKX2XWC.js → chunk-OQTWWZ4B.js} +3 -28
- package/dist/index.cjs +3 -33
- package/dist/index.d.cts +19 -57
- package/dist/index.d.ts +19 -57
- package/dist/index.js +1 -7
- package/dist/switch/index.cjs +80 -189
- package/dist/switch/index.d.cts +35 -27
- package/dist/switch/index.d.ts +35 -27
- package/dist/switch/index.js +37 -40
- package/package.json +1 -1
- package/dist/chunk-ER7IAVR5.js +0 -96
package/dist/index.d.cts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Prop merging for headless components.
|
|
5
|
-
*
|
|
6
|
-
* A component and its consumer both want to put things on the same element. Naive
|
|
7
|
-
* spreading lets whichever runs last silently destroy the other's behaviour, so each
|
|
8
|
-
* kind of prop is combined rather than replaced.
|
|
9
|
-
*/
|
|
3
|
+
/** The shape prop objects are handled as here: unknown keys, unknown values. */
|
|
10
4
|
type UnknownProps = Record<string, unknown>;
|
|
11
5
|
/**
|
|
12
6
|
* An event a consumer's handler can use to stop the component's own handler running.
|
|
@@ -30,6 +24,11 @@ type ComponentEvent<E = unknown> = E & {
|
|
|
30
24
|
* `undefined` values are skipped, so an absent key on a later object never
|
|
31
25
|
* clobbers a value set by an earlier one.
|
|
32
26
|
*
|
|
27
|
+
* `children` is deliberately not special-cased: it is a plain value, so the last
|
|
28
|
+
* object to declare it wins. That is what lets a `render` element either inherit the
|
|
29
|
+
* component's children (it declares none, so the key is absent and skipped) or supply
|
|
30
|
+
* its own (it declares them, and they win like any other value).
|
|
31
|
+
*
|
|
33
32
|
* **Event handlers run right to left** — the last object's handler first, the first
|
|
34
33
|
* object's last. Since a component passes its own props first and the consumer's last,
|
|
35
34
|
* the consumer's handler runs before the component's and can stop it:
|
|
@@ -43,62 +42,29 @@ type ComponentEvent<E = unknown> = E & {
|
|
|
43
42
|
*/
|
|
44
43
|
declare function mergeProps(...objects: (UnknownProps | undefined)[]): UnknownProps;
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
* Projects a component's state onto the DOM as `data-*` attributes.
|
|
48
|
-
*
|
|
49
|
-
* This is the contract between behaviour and styling. A headless component owns no
|
|
50
|
-
* class names, so the only way CSS can react to `checked` or `disabled` is for the
|
|
51
|
-
* state to be visible in the DOM:
|
|
52
|
-
*
|
|
53
|
-
* .switch[data-checked] { background: var(--switch-track-bg-checked); }
|
|
54
|
-
*
|
|
55
|
-
* Doing it through a declared mapping rather than by hand keeps the attributes
|
|
56
|
-
* consistent across components — Base UI's Checkbox emits eleven of them, which is
|
|
57
|
-
* not something anyone maintains by hand — and makes the state a component exposes
|
|
58
|
-
* a single readable object rather than scattered JSX.
|
|
59
|
-
*/
|
|
60
|
-
/**
|
|
61
|
-
* Maps one state value to the attributes it should produce. Return `null` for
|
|
62
|
-
* "no attribute". A single value may produce several, and mutually exclusive
|
|
63
|
-
* attributes are how a third state stays addressable: with only `data-checked`,
|
|
64
|
-
* `:not([data-checked])` would match both unchecked *and* indeterminate.
|
|
65
|
-
*/
|
|
66
|
-
type StateAttributeMapping<State> = {
|
|
67
|
-
[Key in keyof State]?: (value: State[Key]) => Record<string, string> | null;
|
|
68
|
-
};
|
|
69
|
-
declare function getStateAttributes<State extends Record<string, unknown>>(state: State, mapping: StateAttributeMapping<State> | undefined): UnknownProps;
|
|
70
|
-
/**
|
|
71
|
-
* The common case: a boolean that emits one attribute when true and another when
|
|
72
|
-
* false, so both sides are addressable at equal specificity.
|
|
73
|
-
*
|
|
74
|
-
* checked: booleanAttribute('data-checked', 'data-unchecked')
|
|
75
|
-
*/
|
|
76
|
-
declare function booleanAttribute(whenTrue: string, whenFalse?: string): (value: unknown) => Record<string, string> | null;
|
|
77
|
-
/**
|
|
78
|
-
* The shared spelling of the disabled state. Every component uses this rather than
|
|
79
|
-
* writing the string again, so `[data-disabled]` means the same thing system-wide and
|
|
80
|
-
* a typo cannot silently split the CSS contract.
|
|
81
|
-
*/
|
|
82
|
-
declare const disabledAttribute: (value: unknown) => Record<string, string> | null;
|
|
83
|
-
|
|
84
45
|
/**
|
|
85
46
|
* Resolves what a component part actually renders.
|
|
86
47
|
*
|
|
87
48
|
* Every part goes through here, which is what makes the whole library consistent:
|
|
88
|
-
* one place decides how props merge
|
|
89
|
-
*
|
|
90
|
-
*
|
|
49
|
+
* one place decides how props merge and what element comes out. A part supplies its
|
|
50
|
+
* default element and the props it wants; the consumer can replace the element
|
|
51
|
+
* entirely via `render` without losing any of it.
|
|
91
52
|
*
|
|
92
53
|
* Currently uses no hooks. The `use` prefix matches the API it will grow into
|
|
93
54
|
* (memoised merging, so parts stop re-rendering their children needlessly) and keeps
|
|
94
55
|
* call sites stable when that lands.
|
|
95
56
|
*/
|
|
96
|
-
interface UseRenderParams
|
|
97
|
-
/**
|
|
57
|
+
interface UseRenderParams {
|
|
58
|
+
/**
|
|
59
|
+
* Element to render instead of the default. Props, className and ref are merged onto it.
|
|
60
|
+
*
|
|
61
|
+
* Its own props are the last word, `children` included: an element with no children of
|
|
62
|
+
* its own takes the component's, and one with children keeps them.
|
|
63
|
+
*/
|
|
98
64
|
render?: ReactElement | undefined;
|
|
99
65
|
/** Tag rendered when `render` is not supplied. */
|
|
100
66
|
defaultTagName: string;
|
|
101
|
-
/** The component's own props. */
|
|
67
|
+
/** The component's own props, including any `data-*` state attributes it emits. */
|
|
102
68
|
props?: UnknownProps;
|
|
103
69
|
/**
|
|
104
70
|
* The consumer's props, spread from `...rest`.
|
|
@@ -108,12 +74,8 @@ interface UseRenderParams<State extends Record<string, unknown>> {
|
|
|
108
74
|
* cannot put a consumer's props before its own, because there is nowhere to put them.
|
|
109
75
|
*/
|
|
110
76
|
consumerProps?: UnknownProps;
|
|
111
|
-
/** The part's state, projected onto the DOM through `stateAttributes`. */
|
|
112
|
-
state?: State;
|
|
113
|
-
/** How each state field becomes `data-*` attributes. */
|
|
114
|
-
stateAttributes?: StateAttributeMapping<State>;
|
|
115
77
|
}
|
|
116
|
-
declare function useRender
|
|
78
|
+
declare function useRender({ render, defaultTagName, props, consumerProps, }: UseRenderParams): ReactElement;
|
|
117
79
|
|
|
118
80
|
/**
|
|
119
81
|
* Supports both controlled and uncontrolled use of a single value.
|
|
@@ -133,4 +95,4 @@ declare function useControlled<T>({ controlled, default: defaultValue, name, sta
|
|
|
133
95
|
state?: string;
|
|
134
96
|
}): [T, (next: T) => void];
|
|
135
97
|
|
|
136
|
-
export { type ComponentEvent, type
|
|
98
|
+
export { type ComponentEvent, type UnknownProps, type UseRenderParams, mergeProps, useControlled, useRender };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Prop merging for headless components.
|
|
5
|
-
*
|
|
6
|
-
* A component and its consumer both want to put things on the same element. Naive
|
|
7
|
-
* spreading lets whichever runs last silently destroy the other's behaviour, so each
|
|
8
|
-
* kind of prop is combined rather than replaced.
|
|
9
|
-
*/
|
|
3
|
+
/** The shape prop objects are handled as here: unknown keys, unknown values. */
|
|
10
4
|
type UnknownProps = Record<string, unknown>;
|
|
11
5
|
/**
|
|
12
6
|
* An event a consumer's handler can use to stop the component's own handler running.
|
|
@@ -30,6 +24,11 @@ type ComponentEvent<E = unknown> = E & {
|
|
|
30
24
|
* `undefined` values are skipped, so an absent key on a later object never
|
|
31
25
|
* clobbers a value set by an earlier one.
|
|
32
26
|
*
|
|
27
|
+
* `children` is deliberately not special-cased: it is a plain value, so the last
|
|
28
|
+
* object to declare it wins. That is what lets a `render` element either inherit the
|
|
29
|
+
* component's children (it declares none, so the key is absent and skipped) or supply
|
|
30
|
+
* its own (it declares them, and they win like any other value).
|
|
31
|
+
*
|
|
33
32
|
* **Event handlers run right to left** — the last object's handler first, the first
|
|
34
33
|
* object's last. Since a component passes its own props first and the consumer's last,
|
|
35
34
|
* the consumer's handler runs before the component's and can stop it:
|
|
@@ -43,62 +42,29 @@ type ComponentEvent<E = unknown> = E & {
|
|
|
43
42
|
*/
|
|
44
43
|
declare function mergeProps(...objects: (UnknownProps | undefined)[]): UnknownProps;
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
* Projects a component's state onto the DOM as `data-*` attributes.
|
|
48
|
-
*
|
|
49
|
-
* This is the contract between behaviour and styling. A headless component owns no
|
|
50
|
-
* class names, so the only way CSS can react to `checked` or `disabled` is for the
|
|
51
|
-
* state to be visible in the DOM:
|
|
52
|
-
*
|
|
53
|
-
* .switch[data-checked] { background: var(--switch-track-bg-checked); }
|
|
54
|
-
*
|
|
55
|
-
* Doing it through a declared mapping rather than by hand keeps the attributes
|
|
56
|
-
* consistent across components — Base UI's Checkbox emits eleven of them, which is
|
|
57
|
-
* not something anyone maintains by hand — and makes the state a component exposes
|
|
58
|
-
* a single readable object rather than scattered JSX.
|
|
59
|
-
*/
|
|
60
|
-
/**
|
|
61
|
-
* Maps one state value to the attributes it should produce. Return `null` for
|
|
62
|
-
* "no attribute". A single value may produce several, and mutually exclusive
|
|
63
|
-
* attributes are how a third state stays addressable: with only `data-checked`,
|
|
64
|
-
* `:not([data-checked])` would match both unchecked *and* indeterminate.
|
|
65
|
-
*/
|
|
66
|
-
type StateAttributeMapping<State> = {
|
|
67
|
-
[Key in keyof State]?: (value: State[Key]) => Record<string, string> | null;
|
|
68
|
-
};
|
|
69
|
-
declare function getStateAttributes<State extends Record<string, unknown>>(state: State, mapping: StateAttributeMapping<State> | undefined): UnknownProps;
|
|
70
|
-
/**
|
|
71
|
-
* The common case: a boolean that emits one attribute when true and another when
|
|
72
|
-
* false, so both sides are addressable at equal specificity.
|
|
73
|
-
*
|
|
74
|
-
* checked: booleanAttribute('data-checked', 'data-unchecked')
|
|
75
|
-
*/
|
|
76
|
-
declare function booleanAttribute(whenTrue: string, whenFalse?: string): (value: unknown) => Record<string, string> | null;
|
|
77
|
-
/**
|
|
78
|
-
* The shared spelling of the disabled state. Every component uses this rather than
|
|
79
|
-
* writing the string again, so `[data-disabled]` means the same thing system-wide and
|
|
80
|
-
* a typo cannot silently split the CSS contract.
|
|
81
|
-
*/
|
|
82
|
-
declare const disabledAttribute: (value: unknown) => Record<string, string> | null;
|
|
83
|
-
|
|
84
45
|
/**
|
|
85
46
|
* Resolves what a component part actually renders.
|
|
86
47
|
*
|
|
87
48
|
* Every part goes through here, which is what makes the whole library consistent:
|
|
88
|
-
* one place decides how props merge
|
|
89
|
-
*
|
|
90
|
-
*
|
|
49
|
+
* one place decides how props merge and what element comes out. A part supplies its
|
|
50
|
+
* default element and the props it wants; the consumer can replace the element
|
|
51
|
+
* entirely via `render` without losing any of it.
|
|
91
52
|
*
|
|
92
53
|
* Currently uses no hooks. The `use` prefix matches the API it will grow into
|
|
93
54
|
* (memoised merging, so parts stop re-rendering their children needlessly) and keeps
|
|
94
55
|
* call sites stable when that lands.
|
|
95
56
|
*/
|
|
96
|
-
interface UseRenderParams
|
|
97
|
-
/**
|
|
57
|
+
interface UseRenderParams {
|
|
58
|
+
/**
|
|
59
|
+
* Element to render instead of the default. Props, className and ref are merged onto it.
|
|
60
|
+
*
|
|
61
|
+
* Its own props are the last word, `children` included: an element with no children of
|
|
62
|
+
* its own takes the component's, and one with children keeps them.
|
|
63
|
+
*/
|
|
98
64
|
render?: ReactElement | undefined;
|
|
99
65
|
/** Tag rendered when `render` is not supplied. */
|
|
100
66
|
defaultTagName: string;
|
|
101
|
-
/** The component's own props. */
|
|
67
|
+
/** The component's own props, including any `data-*` state attributes it emits. */
|
|
102
68
|
props?: UnknownProps;
|
|
103
69
|
/**
|
|
104
70
|
* The consumer's props, spread from `...rest`.
|
|
@@ -108,12 +74,8 @@ interface UseRenderParams<State extends Record<string, unknown>> {
|
|
|
108
74
|
* cannot put a consumer's props before its own, because there is nowhere to put them.
|
|
109
75
|
*/
|
|
110
76
|
consumerProps?: UnknownProps;
|
|
111
|
-
/** The part's state, projected onto the DOM through `stateAttributes`. */
|
|
112
|
-
state?: State;
|
|
113
|
-
/** How each state field becomes `data-*` attributes. */
|
|
114
|
-
stateAttributes?: StateAttributeMapping<State>;
|
|
115
77
|
}
|
|
116
|
-
declare function useRender
|
|
78
|
+
declare function useRender({ render, defaultTagName, props, consumerProps, }: UseRenderParams): ReactElement;
|
|
117
79
|
|
|
118
80
|
/**
|
|
119
81
|
* Supports both controlled and uncontrolled use of a single value.
|
|
@@ -133,4 +95,4 @@ declare function useControlled<T>({ controlled, default: defaultValue, name, sta
|
|
|
133
95
|
state?: string;
|
|
134
96
|
}): [T, (next: T) => void];
|
|
135
97
|
|
|
136
|
-
export { type ComponentEvent, type
|
|
98
|
+
export { type ComponentEvent, type UnknownProps, type UseRenderParams, mergeProps, useControlled, useRender };
|
package/dist/index.js
CHANGED
|
@@ -2,16 +2,10 @@ import {
|
|
|
2
2
|
useControlled
|
|
3
3
|
} from "./chunk-UEIB3I3T.js";
|
|
4
4
|
import {
|
|
5
|
-
booleanAttribute,
|
|
6
|
-
disabledAttribute,
|
|
7
|
-
getStateAttributes,
|
|
8
5
|
mergeProps,
|
|
9
6
|
useRender
|
|
10
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OQTWWZ4B.js";
|
|
11
8
|
export {
|
|
12
|
-
booleanAttribute,
|
|
13
|
-
disabledAttribute,
|
|
14
|
-
getStateAttributes,
|
|
15
9
|
mergeProps,
|
|
16
10
|
useControlled,
|
|
17
11
|
useRender
|
package/dist/switch/index.cjs
CHANGED
|
@@ -31,11 +31,44 @@ __export(index_parts_exports, {
|
|
|
31
31
|
Thumb: () => SwitchThumb
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
// src/switch/
|
|
35
|
-
var
|
|
34
|
+
// src/switch/SwitchRoot.tsx
|
|
35
|
+
var import_react4 = require("react");
|
|
36
36
|
|
|
37
|
-
// src/
|
|
37
|
+
// src/core/useControlled.ts
|
|
38
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/useRender.ts
|
|
71
|
+
var import_react2 = require("react");
|
|
39
72
|
|
|
40
73
|
// src/core/mergeProps.ts
|
|
41
74
|
function isEventHandler(key) {
|
|
@@ -67,6 +100,7 @@ function isSyntheticEvent(event) {
|
|
|
67
100
|
return typeof event === "object" && event !== null && "nativeEvent" in event;
|
|
68
101
|
}
|
|
69
102
|
function makeEventPreventable(event) {
|
|
103
|
+
if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
|
|
70
104
|
const target = event;
|
|
71
105
|
target.preventComponentHandler = () => {
|
|
72
106
|
target.componentHandlerPrevented = true;
|
|
@@ -108,189 +142,45 @@ function mergeProps(...objects) {
|
|
|
108
142
|
return merged;
|
|
109
143
|
}
|
|
110
144
|
|
|
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
|
-
|
|
234
|
-
// src/core/stateAttributes.ts
|
|
235
|
-
function getStateAttributes(state, mapping) {
|
|
236
|
-
if (!mapping) return {};
|
|
237
|
-
const attributes = {};
|
|
238
|
-
for (const key of Object.keys(state)) {
|
|
239
|
-
const toAttributes = mapping[key];
|
|
240
|
-
if (!toAttributes) continue;
|
|
241
|
-
const result = toAttributes(state[key]);
|
|
242
|
-
if (result) Object.assign(attributes, result);
|
|
243
|
-
}
|
|
244
|
-
return attributes;
|
|
245
|
-
}
|
|
246
|
-
function booleanAttribute(whenTrue, whenFalse) {
|
|
247
|
-
return (value) => {
|
|
248
|
-
if (value) return { [whenTrue]: "" };
|
|
249
|
-
return whenFalse ? { [whenFalse]: "" } : null;
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
var disabledAttribute = booleanAttribute("data-disabled");
|
|
253
|
-
|
|
254
145
|
// src/core/useRender.ts
|
|
255
146
|
function useRender({
|
|
256
147
|
render,
|
|
257
148
|
defaultTagName,
|
|
258
149
|
props,
|
|
259
|
-
consumerProps
|
|
260
|
-
state,
|
|
261
|
-
stateAttributes
|
|
150
|
+
consumerProps
|
|
262
151
|
}) {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
|
|
152
|
+
const merged = mergeProps(props, consumerProps);
|
|
153
|
+
if ((0, import_react2.isValidElement)(render)) {
|
|
154
|
+
return (0, import_react2.cloneElement)(render, mergeProps(merged, render.props));
|
|
267
155
|
}
|
|
268
|
-
return (0,
|
|
156
|
+
return (0, import_react2.createElement)(defaultTagName, merged);
|
|
269
157
|
}
|
|
270
158
|
|
|
271
159
|
// src/switch/SwitchRootContext.ts
|
|
272
|
-
var
|
|
273
|
-
var SwitchRootContext = (0,
|
|
160
|
+
var import_react3 = require("react");
|
|
161
|
+
var SwitchRootContext = (0, import_react3.createContext)(null);
|
|
274
162
|
function useSwitchRootContext() {
|
|
275
|
-
const context = (0,
|
|
163
|
+
const context = (0, import_react3.useContext)(SwitchRootContext);
|
|
276
164
|
if (context === null) {
|
|
277
165
|
throw new Error("<Switch.Thumb> must be rendered inside <Switch.Root>.");
|
|
278
166
|
}
|
|
279
167
|
return context;
|
|
280
168
|
}
|
|
281
169
|
|
|
282
|
-
// src/switch/
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
170
|
+
// src/switch/switchDataAttributes.ts
|
|
171
|
+
function switchDataAttributes({ checked, disabled }) {
|
|
172
|
+
return {
|
|
173
|
+
"data-checked": checked ? "" : void 0,
|
|
174
|
+
"data-unchecked": checked ? void 0 : "",
|
|
175
|
+
"data-disabled": disabled ? "" : void 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
287
178
|
|
|
288
|
-
// src/switch/
|
|
179
|
+
// src/switch/SwitchRoot.tsx
|
|
289
180
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
290
181
|
function SwitchRoot({
|
|
291
182
|
checked: checkedProp,
|
|
292
183
|
defaultChecked,
|
|
293
|
-
nativeButton,
|
|
294
184
|
onCheckedChange,
|
|
295
185
|
disabled = false,
|
|
296
186
|
name,
|
|
@@ -306,54 +196,55 @@ function SwitchRoot({
|
|
|
306
196
|
name: "Switch.Root",
|
|
307
197
|
state: "checked"
|
|
308
198
|
});
|
|
309
|
-
const state = (0,
|
|
310
|
-
const
|
|
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;
|
|
199
|
+
const state = (0, import_react4.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
200
|
+
const elementRef = useNativeButtonWarning();
|
|
317
201
|
const element = useRender({
|
|
318
|
-
render
|
|
202
|
+
render,
|
|
319
203
|
defaultTagName: "button",
|
|
320
|
-
state,
|
|
321
|
-
stateAttributes: switchStateAttributes,
|
|
322
204
|
props: {
|
|
205
|
+
// Without this a switch inside a form would submit it on every toggle.
|
|
206
|
+
type: "button",
|
|
323
207
|
role: "switch",
|
|
324
208
|
"aria-checked": checked,
|
|
209
|
+
// The platform suppresses activation, focus and the click handler below.
|
|
210
|
+
disabled: disabled || void 0,
|
|
211
|
+
...switchDataAttributes(state),
|
|
325
212
|
className,
|
|
326
213
|
children,
|
|
327
|
-
ref:
|
|
214
|
+
ref: elementRef,
|
|
328
215
|
onClick() {
|
|
329
|
-
if (disabled) return;
|
|
330
216
|
const next = !checked;
|
|
331
217
|
setChecked(next);
|
|
332
218
|
onCheckedChange?.(next);
|
|
333
219
|
}
|
|
334
220
|
},
|
|
335
|
-
consumerProps
|
|
221
|
+
consumerProps: rest
|
|
336
222
|
});
|
|
337
223
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SwitchRootContext.Provider, { value: state, children: [
|
|
338
224
|
element,
|
|
339
|
-
name !== void 0
|
|
225
|
+
name !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", hidden: true, readOnly: true, name, value, checked }) : null
|
|
340
226
|
] });
|
|
341
227
|
}
|
|
228
|
+
function useNativeButtonWarning() {
|
|
229
|
+
const elementRef = (0, import_react4.useRef)(null);
|
|
230
|
+
(0, import_react4.useEffect)(() => {
|
|
231
|
+
if (process.env.NODE_ENV === "production") return;
|
|
232
|
+
const element = elementRef.current;
|
|
233
|
+
if (!element || element.tagName === "BUTTON") return;
|
|
234
|
+
console.error(
|
|
235
|
+
`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.`
|
|
236
|
+
);
|
|
237
|
+
}, []);
|
|
238
|
+
return elementRef;
|
|
239
|
+
}
|
|
342
240
|
|
|
343
|
-
// src/switch/
|
|
344
|
-
function SwitchThumb({
|
|
345
|
-
className,
|
|
346
|
-
children,
|
|
347
|
-
render,
|
|
348
|
-
...rest
|
|
349
|
-
}) {
|
|
241
|
+
// src/switch/SwitchThumb.tsx
|
|
242
|
+
function SwitchThumb({ className, children, render, ...rest }) {
|
|
350
243
|
const state = useSwitchRootContext();
|
|
351
244
|
return useRender({
|
|
352
245
|
render,
|
|
353
246
|
defaultTagName: "span",
|
|
354
|
-
state,
|
|
355
|
-
stateAttributes: switchStateAttributes,
|
|
356
|
-
props: { "aria-hidden": true, className, children },
|
|
247
|
+
props: { "aria-hidden": true, ...switchDataAttributes(state), className, children },
|
|
357
248
|
consumerProps: rest
|
|
358
249
|
});
|
|
359
250
|
}
|