@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/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" ? (...args) => {
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, ...props);
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
- /** Prop objects to merge, in precedence order (later wins). */
102
- props: (UnknownProps | undefined)[];
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
- 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 };
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
- /** Prop objects to merge, in precedence order (later wins). */
102
- props: (UnknownProps | undefined)[];
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
- 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 };
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-I477HMLD.js";
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
  };
@@ -32,74 +32,10 @@ __export(index_parts_exports, {
32
32
  });
33
33
 
34
34
  // src/switch/root/SwitchRoot.tsx
35
- var import_react4 = require("react");
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" ? (...args) => {
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, ...props);
202
- if ((0, import_react2.isValidElement)(render)) {
203
- return (0, import_react2.cloneElement)(render, mergeProps(merged, render.props));
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, import_react2.createElement)(defaultTagName, merged);
268
+ return (0, import_react3.createElement)(defaultTagName, merged);
206
269
  }
207
270
 
208
271
  // src/switch/SwitchRootContext.ts
209
- var import_react3 = require("react");
210
- var SwitchRootContext = (0, import_react3.createContext)(null);
272
+ var import_react4 = require("react");
273
+ var SwitchRootContext = (0, import_react4.createContext)(null);
211
274
  function useSwitchRootContext() {
212
- const context = (0, import_react3.useContext)(SwitchRootContext);
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, 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 });
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
- type: "button",
256
- role: "switch",
257
- "aria-checked": checked,
258
- className,
259
- children,
260
- onClick() {
261
- if (disabled) return;
262
- const next = !checked;
263
- setChecked(next);
264
- onCheckedChange?.(next);
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: [{ "aria-hidden": true, className, children }, rest]
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:
@@ -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, which is what
59
- * lets CSS move it:
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
- * .switch-thumb[data-checked] { transform: translateX(100%); }
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.
@@ -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, which is what
59
- * lets CSS move it:
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
- * .switch-thumb[data-checked] { transform: translateX(100%); }
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.