@arun-dev/headless 0.2.1 → 0.4.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 +11 -3
- package/dist/{chunk-DAO4JK6U.js → chunk-I477HMLD.js} +61 -4
- package/dist/index.cjs +63 -3
- package/dist/index.d.cts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +7 -1
- package/dist/switch/index.cjs +55 -6
- package/dist/switch/index.js +8 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,9 +72,17 @@ Every part takes a `render` prop to change the element, and spreads unrecognised
|
|
|
72
72
|
<Switch.Root render={<Tooltip.Trigger />} />
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
`className` is concatenated,
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
`className` is concatenated, `style` is merged, and refs are merged — so a `ref` on the `render`
|
|
76
|
+
element and a `ref` on the component both receive the node.
|
|
77
|
+
|
|
78
|
+
Event handlers are chained rather than replaced, and **your handler runs before the component's**,
|
|
79
|
+
so you can stop it:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<Switch.Root onClick={(event) => event.preventComponentHandler()} />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`preventDefault()` is left alone — it still means only "cancel the browser's default action".
|
|
78
86
|
|
|
79
87
|
## Engine
|
|
80
88
|
|
|
@@ -30,6 +30,18 @@ function mergeRefs(...refs) {
|
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
function isSyntheticEvent(event) {
|
|
34
|
+
return typeof event === "object" && event !== null && "nativeEvent" in event;
|
|
35
|
+
}
|
|
36
|
+
function makeEventPreventable(event) {
|
|
37
|
+
const target = event;
|
|
38
|
+
target.preventComponentHandler = () => {
|
|
39
|
+
target.componentHandlerPrevented = true;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function isComponentHandlerPrevented(event) {
|
|
43
|
+
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
44
|
+
}
|
|
33
45
|
function mergeProps(...objects) {
|
|
34
46
|
const merged = {};
|
|
35
47
|
for (const props of objects) {
|
|
@@ -38,10 +50,13 @@ function mergeProps(...objects) {
|
|
|
38
50
|
const value = props[key];
|
|
39
51
|
if (value === void 0) continue;
|
|
40
52
|
if (isEventHandler(key) && typeof value === "function") {
|
|
41
|
-
const
|
|
42
|
-
merged[key] = typeof
|
|
43
|
-
|
|
53
|
+
const earlier = merged[key];
|
|
54
|
+
merged[key] = typeof earlier === "function" ? (...args) => {
|
|
55
|
+
const event = args[0];
|
|
56
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
44
57
|
value(...args);
|
|
58
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
59
|
+
earlier(...args);
|
|
45
60
|
} : value;
|
|
46
61
|
} else if (key === "className") {
|
|
47
62
|
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
@@ -75,6 +90,7 @@ function booleanAttribute(whenTrue, whenFalse) {
|
|
|
75
90
|
return whenFalse ? { [whenFalse]: "" } : null;
|
|
76
91
|
};
|
|
77
92
|
}
|
|
93
|
+
var disabledAttribute = booleanAttribute("data-disabled");
|
|
78
94
|
|
|
79
95
|
// src/core/useRender.ts
|
|
80
96
|
import { cloneElement, createElement, isValidElement } from "react";
|
|
@@ -126,11 +142,52 @@ function useControlled({
|
|
|
126
142
|
return [value, setValue];
|
|
127
143
|
}
|
|
128
144
|
|
|
145
|
+
// src/useButton.ts
|
|
146
|
+
var ACTIVATION_HANDLERS = [
|
|
147
|
+
"onClick",
|
|
148
|
+
"onDoubleClick",
|
|
149
|
+
"onMouseDown",
|
|
150
|
+
"onMouseUp",
|
|
151
|
+
"onPointerDown",
|
|
152
|
+
"onPointerUp",
|
|
153
|
+
"onTouchStart",
|
|
154
|
+
"onTouchEnd",
|
|
155
|
+
"onKeyDown",
|
|
156
|
+
"onKeyUp",
|
|
157
|
+
"onKeyPress"
|
|
158
|
+
];
|
|
159
|
+
function useButton({ disabled = false, native, props = {} }) {
|
|
160
|
+
if (!disabled) return props;
|
|
161
|
+
if (native) return { ...props, disabled: true, "data-disabled": "" };
|
|
162
|
+
const sanitised = {};
|
|
163
|
+
for (const key of Object.keys(props)) {
|
|
164
|
+
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) continue;
|
|
165
|
+
sanitised[key] = props[key];
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
...sanitised,
|
|
169
|
+
"aria-disabled": true,
|
|
170
|
+
"data-disabled": "",
|
|
171
|
+
// Mirrors a native disabled button, which is not focusable.
|
|
172
|
+
tabIndex: -1
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function retractActivationProps(props) {
|
|
176
|
+
const overrides = {};
|
|
177
|
+
for (const key of Object.keys(props)) {
|
|
178
|
+
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) overrides[key] = void 0;
|
|
179
|
+
}
|
|
180
|
+
return overrides;
|
|
181
|
+
}
|
|
182
|
+
|
|
129
183
|
export {
|
|
130
184
|
__export,
|
|
131
185
|
mergeProps,
|
|
132
186
|
getStateAttributes,
|
|
133
187
|
booleanAttribute,
|
|
188
|
+
disabledAttribute,
|
|
134
189
|
useRender,
|
|
135
|
-
useControlled
|
|
190
|
+
useControlled,
|
|
191
|
+
useButton,
|
|
192
|
+
retractActivationProps
|
|
136
193
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -21,8 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
booleanAttribute: () => booleanAttribute,
|
|
24
|
+
disabledAttribute: () => disabledAttribute,
|
|
24
25
|
getStateAttributes: () => getStateAttributes,
|
|
25
26
|
mergeProps: () => mergeProps,
|
|
27
|
+
retractActivationProps: () => retractActivationProps,
|
|
28
|
+
useButton: () => useButton,
|
|
26
29
|
useControlled: () => useControlled,
|
|
27
30
|
useRender: () => useRender
|
|
28
31
|
});
|
|
@@ -54,6 +57,18 @@ function mergeRefs(...refs) {
|
|
|
54
57
|
};
|
|
55
58
|
};
|
|
56
59
|
}
|
|
60
|
+
function isSyntheticEvent(event) {
|
|
61
|
+
return typeof event === "object" && event !== null && "nativeEvent" in event;
|
|
62
|
+
}
|
|
63
|
+
function makeEventPreventable(event) {
|
|
64
|
+
const target = event;
|
|
65
|
+
target.preventComponentHandler = () => {
|
|
66
|
+
target.componentHandlerPrevented = true;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function isComponentHandlerPrevented(event) {
|
|
70
|
+
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
71
|
+
}
|
|
57
72
|
function mergeProps(...objects) {
|
|
58
73
|
const merged = {};
|
|
59
74
|
for (const props of objects) {
|
|
@@ -62,10 +77,13 @@ function mergeProps(...objects) {
|
|
|
62
77
|
const value = props[key];
|
|
63
78
|
if (value === void 0) continue;
|
|
64
79
|
if (isEventHandler(key) && typeof value === "function") {
|
|
65
|
-
const
|
|
66
|
-
merged[key] = typeof
|
|
67
|
-
|
|
80
|
+
const earlier = merged[key];
|
|
81
|
+
merged[key] = typeof earlier === "function" ? (...args) => {
|
|
82
|
+
const event = args[0];
|
|
83
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
68
84
|
value(...args);
|
|
85
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
86
|
+
earlier(...args);
|
|
69
87
|
} : value;
|
|
70
88
|
} else if (key === "className") {
|
|
71
89
|
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
@@ -102,6 +120,7 @@ function booleanAttribute(whenTrue, whenFalse) {
|
|
|
102
120
|
return whenFalse ? { [whenFalse]: "" } : null;
|
|
103
121
|
};
|
|
104
122
|
}
|
|
123
|
+
var disabledAttribute = booleanAttribute("data-disabled");
|
|
105
124
|
|
|
106
125
|
// src/core/useRender.ts
|
|
107
126
|
function useRender({
|
|
@@ -151,11 +170,52 @@ function useControlled({
|
|
|
151
170
|
);
|
|
152
171
|
return [value, setValue];
|
|
153
172
|
}
|
|
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
|
+
}
|
|
154
211
|
// Annotate the CommonJS export names for ESM import in node:
|
|
155
212
|
0 && (module.exports = {
|
|
156
213
|
booleanAttribute,
|
|
214
|
+
disabledAttribute,
|
|
157
215
|
getStateAttributes,
|
|
158
216
|
mergeProps,
|
|
217
|
+
retractActivationProps,
|
|
218
|
+
useButton,
|
|
159
219
|
useControlled,
|
|
160
220
|
useRender
|
|
161
221
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -8,6 +8,18 @@ import { ReactElement } from 'react';
|
|
|
8
8
|
* kind of prop is combined rather than replaced.
|
|
9
9
|
*/
|
|
10
10
|
type UnknownProps = Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* An event a consumer's handler can use to stop the component's own handler running.
|
|
13
|
+
*
|
|
14
|
+
* `preventDefault()` is deliberately not overloaded for this: it already means "cancel
|
|
15
|
+
* the browser's default action", which is a different statement. A link inside a
|
|
16
|
+
* component may well want one without the other.
|
|
17
|
+
*/
|
|
18
|
+
type ComponentEvent<E = unknown> = E & {
|
|
19
|
+
/** Stops the component's own handler for this event. */
|
|
20
|
+
preventComponentHandler(): void;
|
|
21
|
+
readonly componentHandlerPrevented?: boolean;
|
|
22
|
+
};
|
|
11
23
|
/**
|
|
12
24
|
* Merge prop objects left to right. Later objects win for plain values, but:
|
|
13
25
|
* - event handlers are chained rather than replaced
|
|
@@ -17,6 +29,17 @@ type UnknownProps = Record<string, unknown>;
|
|
|
17
29
|
*
|
|
18
30
|
* `undefined` values are skipped, so an absent key on a later object never
|
|
19
31
|
* clobbers a value set by an earlier one.
|
|
32
|
+
*
|
|
33
|
+
* **Event handlers run right to left** — the last object's handler first, the first
|
|
34
|
+
* object's last. Since a component passes its own props first and the consumer's last,
|
|
35
|
+
* the consumer's handler runs before the component's and can stop it:
|
|
36
|
+
*
|
|
37
|
+
* <Switch.Root onClick={(event) => event.preventComponentHandler()} />
|
|
38
|
+
*
|
|
39
|
+
* That is the opposite direction to plain values, where later objects win. The escape
|
|
40
|
+
* hatch belongs to the consumer: a component already decides which props reach the
|
|
41
|
+
* element, so it never needs the chain to suppress anything — it declines to attach the
|
|
42
|
+
* handler instead. See docs/architecture.md decision 8.
|
|
20
43
|
*/
|
|
21
44
|
declare function mergeProps(...objects: (UnknownProps | undefined)[]): UnknownProps;
|
|
22
45
|
|
|
@@ -51,6 +74,12 @@ declare function getStateAttributes<State extends Record<string, unknown>>(state
|
|
|
51
74
|
* checked: booleanAttribute('data-checked', 'data-unchecked')
|
|
52
75
|
*/
|
|
53
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;
|
|
54
83
|
|
|
55
84
|
/**
|
|
56
85
|
* Resolves what a component part actually renders.
|
|
@@ -96,4 +125,26 @@ declare function useControlled<T>({ controlled, default: defaultValue, name, sta
|
|
|
96
125
|
state?: string;
|
|
97
126
|
}): [T, (next: T) => void];
|
|
98
127
|
|
|
99
|
-
|
|
128
|
+
interface UseButtonParams {
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,18 @@ import { ReactElement } from 'react';
|
|
|
8
8
|
* kind of prop is combined rather than replaced.
|
|
9
9
|
*/
|
|
10
10
|
type UnknownProps = Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* An event a consumer's handler can use to stop the component's own handler running.
|
|
13
|
+
*
|
|
14
|
+
* `preventDefault()` is deliberately not overloaded for this: it already means "cancel
|
|
15
|
+
* the browser's default action", which is a different statement. A link inside a
|
|
16
|
+
* component may well want one without the other.
|
|
17
|
+
*/
|
|
18
|
+
type ComponentEvent<E = unknown> = E & {
|
|
19
|
+
/** Stops the component's own handler for this event. */
|
|
20
|
+
preventComponentHandler(): void;
|
|
21
|
+
readonly componentHandlerPrevented?: boolean;
|
|
22
|
+
};
|
|
11
23
|
/**
|
|
12
24
|
* Merge prop objects left to right. Later objects win for plain values, but:
|
|
13
25
|
* - event handlers are chained rather than replaced
|
|
@@ -17,6 +29,17 @@ type UnknownProps = Record<string, unknown>;
|
|
|
17
29
|
*
|
|
18
30
|
* `undefined` values are skipped, so an absent key on a later object never
|
|
19
31
|
* clobbers a value set by an earlier one.
|
|
32
|
+
*
|
|
33
|
+
* **Event handlers run right to left** — the last object's handler first, the first
|
|
34
|
+
* object's last. Since a component passes its own props first and the consumer's last,
|
|
35
|
+
* the consumer's handler runs before the component's and can stop it:
|
|
36
|
+
*
|
|
37
|
+
* <Switch.Root onClick={(event) => event.preventComponentHandler()} />
|
|
38
|
+
*
|
|
39
|
+
* That is the opposite direction to plain values, where later objects win. The escape
|
|
40
|
+
* hatch belongs to the consumer: a component already decides which props reach the
|
|
41
|
+
* element, so it never needs the chain to suppress anything — it declines to attach the
|
|
42
|
+
* handler instead. See docs/architecture.md decision 8.
|
|
20
43
|
*/
|
|
21
44
|
declare function mergeProps(...objects: (UnknownProps | undefined)[]): UnknownProps;
|
|
22
45
|
|
|
@@ -51,6 +74,12 @@ declare function getStateAttributes<State extends Record<string, unknown>>(state
|
|
|
51
74
|
* checked: booleanAttribute('data-checked', 'data-unchecked')
|
|
52
75
|
*/
|
|
53
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;
|
|
54
83
|
|
|
55
84
|
/**
|
|
56
85
|
* Resolves what a component part actually renders.
|
|
@@ -96,4 +125,26 @@ declare function useControlled<T>({ controlled, default: defaultValue, name, sta
|
|
|
96
125
|
state?: string;
|
|
97
126
|
}): [T, (next: T) => void];
|
|
98
127
|
|
|
99
|
-
|
|
128
|
+
interface UseButtonParams {
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
booleanAttribute,
|
|
3
|
+
disabledAttribute,
|
|
3
4
|
getStateAttributes,
|
|
4
5
|
mergeProps,
|
|
6
|
+
retractActivationProps,
|
|
7
|
+
useButton,
|
|
5
8
|
useControlled,
|
|
6
9
|
useRender
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-I477HMLD.js";
|
|
8
11
|
export {
|
|
9
12
|
booleanAttribute,
|
|
13
|
+
disabledAttribute,
|
|
10
14
|
getStateAttributes,
|
|
11
15
|
mergeProps,
|
|
16
|
+
retractActivationProps,
|
|
17
|
+
useButton,
|
|
12
18
|
useControlled,
|
|
13
19
|
useRender
|
|
14
20
|
};
|
package/dist/switch/index.cjs
CHANGED
|
@@ -34,6 +34,37 @@ __export(index_parts_exports, {
|
|
|
34
34
|
// src/switch/root/SwitchRoot.tsx
|
|
35
35
|
var import_react4 = require("react");
|
|
36
36
|
|
|
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
|
+
|
|
37
68
|
// src/core/useControlled.ts
|
|
38
69
|
var import_react = require("react");
|
|
39
70
|
function useControlled({
|
|
@@ -96,6 +127,18 @@ function mergeRefs(...refs) {
|
|
|
96
127
|
};
|
|
97
128
|
};
|
|
98
129
|
}
|
|
130
|
+
function isSyntheticEvent(event) {
|
|
131
|
+
return typeof event === "object" && event !== null && "nativeEvent" in event;
|
|
132
|
+
}
|
|
133
|
+
function makeEventPreventable(event) {
|
|
134
|
+
const target = event;
|
|
135
|
+
target.preventComponentHandler = () => {
|
|
136
|
+
target.componentHandlerPrevented = true;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function isComponentHandlerPrevented(event) {
|
|
140
|
+
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
141
|
+
}
|
|
99
142
|
function mergeProps(...objects) {
|
|
100
143
|
const merged = {};
|
|
101
144
|
for (const props of objects) {
|
|
@@ -104,10 +147,13 @@ function mergeProps(...objects) {
|
|
|
104
147
|
const value = props[key];
|
|
105
148
|
if (value === void 0) continue;
|
|
106
149
|
if (isEventHandler(key) && typeof value === "function") {
|
|
107
|
-
const
|
|
108
|
-
merged[key] = typeof
|
|
109
|
-
|
|
150
|
+
const earlier = merged[key];
|
|
151
|
+
merged[key] = typeof earlier === "function" ? (...args) => {
|
|
152
|
+
const event = args[0];
|
|
153
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
110
154
|
value(...args);
|
|
155
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
156
|
+
earlier(...args);
|
|
111
157
|
} : value;
|
|
112
158
|
} else if (key === "className") {
|
|
113
159
|
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
@@ -141,6 +187,7 @@ function booleanAttribute(whenTrue, whenFalse) {
|
|
|
141
187
|
return whenFalse ? { [whenFalse]: "" } : null;
|
|
142
188
|
};
|
|
143
189
|
}
|
|
190
|
+
var disabledAttribute = booleanAttribute("data-disabled");
|
|
144
191
|
|
|
145
192
|
// src/core/useRender.ts
|
|
146
193
|
function useRender({
|
|
@@ -172,7 +219,7 @@ function useSwitchRootContext() {
|
|
|
172
219
|
// src/switch/stateAttributes.ts
|
|
173
220
|
var switchStateAttributes = {
|
|
174
221
|
checked: booleanAttribute("data-checked", "data-unchecked"),
|
|
175
|
-
disabled:
|
|
222
|
+
disabled: disabledAttribute
|
|
176
223
|
};
|
|
177
224
|
|
|
178
225
|
// src/switch/root/SwitchRoot.tsx
|
|
@@ -196,6 +243,8 @@ function SwitchRoot({
|
|
|
196
243
|
state: "checked"
|
|
197
244
|
});
|
|
198
245
|
const state = (0, import_react4.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
246
|
+
const isNativeButton = render === void 0 || render.type === "button";
|
|
247
|
+
const consumerProps = useButton({ disabled, native: isNativeButton, props: rest });
|
|
199
248
|
const element = useRender({
|
|
200
249
|
render,
|
|
201
250
|
defaultTagName: "button",
|
|
@@ -206,16 +255,16 @@ function SwitchRoot({
|
|
|
206
255
|
type: "button",
|
|
207
256
|
role: "switch",
|
|
208
257
|
"aria-checked": checked,
|
|
209
|
-
disabled,
|
|
210
258
|
className,
|
|
211
259
|
children,
|
|
212
260
|
onClick() {
|
|
261
|
+
if (disabled) return;
|
|
213
262
|
const next = !checked;
|
|
214
263
|
setChecked(next);
|
|
215
264
|
onCheckedChange?.(next);
|
|
216
265
|
}
|
|
217
266
|
},
|
|
218
|
-
|
|
267
|
+
consumerProps
|
|
219
268
|
]
|
|
220
269
|
});
|
|
221
270
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SwitchRootContext.Provider, { value: state, children: [
|
package/dist/switch/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__export,
|
|
3
3
|
booleanAttribute,
|
|
4
|
+
disabledAttribute,
|
|
5
|
+
useButton,
|
|
4
6
|
useControlled,
|
|
5
7
|
useRender
|
|
6
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-I477HMLD.js";
|
|
7
9
|
|
|
8
10
|
// src/switch/index.parts.ts
|
|
9
11
|
var index_parts_exports = {};
|
|
@@ -29,7 +31,7 @@ function useSwitchRootContext() {
|
|
|
29
31
|
// src/switch/stateAttributes.ts
|
|
30
32
|
var switchStateAttributes = {
|
|
31
33
|
checked: booleanAttribute("data-checked", "data-unchecked"),
|
|
32
|
-
disabled:
|
|
34
|
+
disabled: disabledAttribute
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
// src/switch/root/SwitchRoot.tsx
|
|
@@ -53,6 +55,8 @@ function SwitchRoot({
|
|
|
53
55
|
state: "checked"
|
|
54
56
|
});
|
|
55
57
|
const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
|
|
58
|
+
const isNativeButton = render === void 0 || render.type === "button";
|
|
59
|
+
const consumerProps = useButton({ disabled, native: isNativeButton, props: rest });
|
|
56
60
|
const element = useRender({
|
|
57
61
|
render,
|
|
58
62
|
defaultTagName: "button",
|
|
@@ -63,16 +67,16 @@ function SwitchRoot({
|
|
|
63
67
|
type: "button",
|
|
64
68
|
role: "switch",
|
|
65
69
|
"aria-checked": checked,
|
|
66
|
-
disabled,
|
|
67
70
|
className,
|
|
68
71
|
children,
|
|
69
72
|
onClick() {
|
|
73
|
+
if (disabled) return;
|
|
70
74
|
const next = !checked;
|
|
71
75
|
setChecked(next);
|
|
72
76
|
onCheckedChange?.(next);
|
|
73
77
|
}
|
|
74
78
|
},
|
|
75
|
-
|
|
79
|
+
consumerProps
|
|
76
80
|
]
|
|
77
81
|
});
|
|
78
82
|
return /* @__PURE__ */ jsxs(SwitchRootContext.Provider, { value: state, children: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|