@arun-dev/headless 1.0.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.
@@ -68,6 +68,15 @@ function makeEventPreventable(event) {
68
68
  function isComponentHandlerPrevented(event) {
69
69
  return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
70
70
  }
71
+ function chainHandlers(earlier, value) {
72
+ return (...args) => {
73
+ const event = args[0];
74
+ if (isSyntheticEvent(event)) makeEventPreventable(event);
75
+ value(...args);
76
+ if (isComponentHandlerPrevented(event)) return;
77
+ earlier(...args);
78
+ };
79
+ }
71
80
  function mergeProps(...objects) {
72
81
  const merged = {};
73
82
  for (const props of objects) {
@@ -77,13 +86,7 @@ function mergeProps(...objects) {
77
86
  if (value === void 0) continue;
78
87
  if (isEventHandler(key) && typeof value === "function") {
79
88
  const earlier = merged[key];
80
- merged[key] = typeof earlier === "function" ? (...args) => {
81
- const event = args[0];
82
- if (isSyntheticEvent(event)) makeEventPreventable(event);
83
- value(...args);
84
- if (isComponentHandlerPrevented(event)) return;
85
- earlier(...args);
86
- } : value;
89
+ merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
87
90
  } else if (key === "className") {
88
91
  merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
89
92
  } else if (key === "style") {
@@ -123,11 +126,12 @@ function useRender({
123
126
  render,
124
127
  defaultTagName,
125
128
  props,
129
+ consumerProps,
126
130
  state,
127
131
  stateAttributes
128
132
  }) {
129
133
  const attributes = state ? getStateAttributes(state, stateAttributes) : {};
130
- const merged = mergeProps(attributes, ...props);
134
+ const merged = mergeProps(attributes, props, consumerProps);
131
135
  if ((0, import_react.isValidElement)(render)) {
132
136
  return (0, import_react.cloneElement)(render, mergeProps(merged, render.props));
133
137
  }
@@ -239,7 +243,8 @@ function Button({
239
243
  return useRender({
240
244
  render: safeRender,
241
245
  defaultTagName: "button",
242
- props: [{ role, className, children }, elementProps, { ref: buttonRef }]
246
+ props: { role, className, children, ref: buttonRef },
247
+ consumerProps: elementProps
243
248
  });
244
249
  }
245
250
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  retractActivationProps,
3
3
  useButton
4
- } from "../chunk-2XSRAJHB.js";
4
+ } from "../chunk-ER7IAVR5.js";
5
5
  import {
6
6
  useRender
7
- } from "../chunk-HWG3GMTO.js";
7
+ } from "../chunk-IGKX2XWC.js";
8
8
 
9
9
  // src/button/Button.tsx
10
10
  import { cloneElement } from "react";
@@ -24,7 +24,8 @@ function Button({
24
24
  return useRender({
25
25
  render: safeRender,
26
26
  defaultTagName: "button",
27
- props: [{ role, className, children }, elementProps, { ref: buttonRef }]
27
+ props: { role, className, children, ref: buttonRef },
28
+ consumerProps: elementProps
28
29
  });
29
30
  }
30
31
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  mergeProps
3
- } from "./chunk-HWG3GMTO.js";
3
+ } from "./chunk-IGKX2XWC.js";
4
4
 
5
5
  // src/useButton.ts
6
6
  import { useEffect, useRef } from "react";
@@ -42,6 +42,15 @@ function makeEventPreventable(event) {
42
42
  function isComponentHandlerPrevented(event) {
43
43
  return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
44
44
  }
45
+ function chainHandlers(earlier, value) {
46
+ return (...args) => {
47
+ const event = args[0];
48
+ if (isSyntheticEvent(event)) makeEventPreventable(event);
49
+ value(...args);
50
+ if (isComponentHandlerPrevented(event)) return;
51
+ earlier(...args);
52
+ };
53
+ }
45
54
  function mergeProps(...objects) {
46
55
  const merged = {};
47
56
  for (const props of objects) {
@@ -51,13 +60,7 @@ function mergeProps(...objects) {
51
60
  if (value === void 0) continue;
52
61
  if (isEventHandler(key) && typeof value === "function") {
53
62
  const earlier = merged[key];
54
- merged[key] = typeof earlier === "function" ? (...args) => {
55
- const event = args[0];
56
- if (isSyntheticEvent(event)) makeEventPreventable(event);
57
- value(...args);
58
- if (isComponentHandlerPrevented(event)) return;
59
- earlier(...args);
60
- } : value;
63
+ merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
61
64
  } else if (key === "className") {
62
65
  merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
63
66
  } else if (key === "style") {
@@ -98,11 +101,12 @@ function useRender({
98
101
  render,
99
102
  defaultTagName,
100
103
  props,
104
+ consumerProps,
101
105
  state,
102
106
  stateAttributes
103
107
  }) {
104
108
  const attributes = state ? getStateAttributes(state, stateAttributes) : {};
105
- const merged = mergeProps(attributes, ...props);
109
+ const merged = mergeProps(attributes, props, consumerProps);
106
110
  if (isValidElement(render)) {
107
111
  return cloneElement(render, mergeProps(merged, render.props));
108
112
  }
package/dist/index.cjs CHANGED
@@ -67,6 +67,15 @@ function makeEventPreventable(event) {
67
67
  function isComponentHandlerPrevented(event) {
68
68
  return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
69
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
+ }
70
79
  function mergeProps(...objects) {
71
80
  const merged = {};
72
81
  for (const props of objects) {
@@ -76,13 +85,7 @@ function mergeProps(...objects) {
76
85
  if (value === void 0) continue;
77
86
  if (isEventHandler(key) && typeof value === "function") {
78
87
  const earlier = merged[key];
79
- merged[key] = typeof earlier === "function" ? (...args) => {
80
- const event = args[0];
81
- if (isSyntheticEvent(event)) makeEventPreventable(event);
82
- value(...args);
83
- if (isComponentHandlerPrevented(event)) return;
84
- earlier(...args);
85
- } : value;
88
+ merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
86
89
  } else if (key === "className") {
87
90
  merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
88
91
  } else if (key === "style") {
@@ -125,11 +128,12 @@ function useRender({
125
128
  render,
126
129
  defaultTagName,
127
130
  props,
131
+ consumerProps,
128
132
  state,
129
133
  stateAttributes
130
134
  }) {
131
135
  const attributes = state ? getStateAttributes(state, stateAttributes) : {};
132
- const merged = mergeProps(attributes, ...props);
136
+ const merged = mergeProps(attributes, props, consumerProps);
133
137
  if ((0, import_react.isValidElement)(render)) {
134
138
  return (0, import_react.cloneElement)(render, mergeProps(merged, render.props));
135
139
  }
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.
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.
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getStateAttributes,
8
8
  mergeProps,
9
9
  useRender
10
- } from "./chunk-HWG3GMTO.js";
10
+ } from "./chunk-IGKX2XWC.js";
11
11
  export {
12
12
  booleanAttribute,
13
13
  disabledAttribute,
@@ -75,6 +75,15 @@ function makeEventPreventable(event) {
75
75
  function isComponentHandlerPrevented(event) {
76
76
  return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
77
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
+ }
78
87
  function mergeProps(...objects) {
79
88
  const merged = {};
80
89
  for (const props of objects) {
@@ -84,13 +93,7 @@ function mergeProps(...objects) {
84
93
  if (value === void 0) continue;
85
94
  if (isEventHandler(key) && typeof value === "function") {
86
95
  const earlier = merged[key];
87
- merged[key] = typeof earlier === "function" ? (...args) => {
88
- const event = args[0];
89
- if (isSyntheticEvent(event)) makeEventPreventable(event);
90
- value(...args);
91
- if (isComponentHandlerPrevented(event)) return;
92
- earlier(...args);
93
- } : value;
96
+ merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
94
97
  } else if (key === "className") {
95
98
  merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
96
99
  } else if (key === "style") {
@@ -253,11 +256,12 @@ function useRender({
253
256
  render,
254
257
  defaultTagName,
255
258
  props,
259
+ consumerProps,
256
260
  state,
257
261
  stateAttributes
258
262
  }) {
259
263
  const attributes = state ? getStateAttributes(state, stateAttributes) : {};
260
- const merged = mergeProps(attributes, ...props);
264
+ const merged = mergeProps(attributes, props, consumerProps);
261
265
  if ((0, import_react3.isValidElement)(render)) {
262
266
  return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
263
267
  }
@@ -286,6 +290,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
286
290
  function SwitchRoot({
287
291
  checked: checkedProp,
288
292
  defaultChecked,
293
+ nativeButton,
289
294
  onCheckedChange,
290
295
  disabled = false,
291
296
  name,
@@ -302,7 +307,7 @@ function SwitchRoot({
302
307
  state: "checked"
303
308
  });
304
309
  const state = (0, import_react5.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
305
- const isNativeButton = render === void 0 || render.type === "button";
310
+ const isNativeButton = nativeButton ?? (render === void 0 || render.type === "button");
306
311
  const { props: consumerProps, ref: buttonRef } = useButton({
307
312
  disabled,
308
313
  native: isNativeButton,
@@ -314,22 +319,20 @@ function SwitchRoot({
314
319
  defaultTagName: "button",
315
320
  state,
316
321
  stateAttributes: switchStateAttributes,
317
- props: [
318
- {
319
- role: "switch",
320
- "aria-checked": checked,
321
- className,
322
- children,
323
- onClick() {
324
- if (disabled) return;
325
- const next = !checked;
326
- setChecked(next);
327
- onCheckedChange?.(next);
328
- }
329
- },
330
- consumerProps,
331
- { ref: buttonRef }
332
- ]
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
333
336
  });
334
337
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SwitchRootContext.Provider, { value: state, children: [
335
338
  element,
@@ -350,7 +353,8 @@ function SwitchThumb({
350
353
  defaultTagName: "span",
351
354
  state,
352
355
  stateAttributes: switchStateAttributes,
353
- props: [{ "aria-hidden": true, className, children }, rest]
356
+ props: { "aria-hidden": true, className, children },
357
+ consumerProps: rest
354
358
  });
355
359
  }
356
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;
@@ -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;
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  retractActivationProps,
6
6
  useButton
7
- } from "../chunk-2XSRAJHB.js";
7
+ } from "../chunk-ER7IAVR5.js";
8
8
  import {
9
9
  __export,
10
10
  booleanAttribute,
11
11
  disabledAttribute,
12
12
  useRender
13
- } from "../chunk-HWG3GMTO.js";
13
+ } from "../chunk-IGKX2XWC.js";
14
14
 
15
15
  // src/switch/index.parts.ts
16
16
  var index_parts_exports = {};
@@ -44,6 +44,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
44
44
  function SwitchRoot({
45
45
  checked: checkedProp,
46
46
  defaultChecked,
47
+ nativeButton,
47
48
  onCheckedChange,
48
49
  disabled = false,
49
50
  name,
@@ -60,7 +61,7 @@ function SwitchRoot({
60
61
  state: "checked"
61
62
  });
62
63
  const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
63
- const isNativeButton = render === void 0 || render.type === "button";
64
+ const isNativeButton = nativeButton ?? (render === void 0 || render.type === "button");
64
65
  const { props: consumerProps, ref: buttonRef } = useButton({
65
66
  disabled,
66
67
  native: isNativeButton,
@@ -72,22 +73,20 @@ function SwitchRoot({
72
73
  defaultTagName: "button",
73
74
  state,
74
75
  stateAttributes: switchStateAttributes,
75
- props: [
76
- {
77
- role: "switch",
78
- "aria-checked": checked,
79
- className,
80
- children,
81
- onClick() {
82
- if (disabled) return;
83
- const next = !checked;
84
- setChecked(next);
85
- onCheckedChange?.(next);
86
- }
87
- },
88
- consumerProps,
89
- { ref: buttonRef }
90
- ]
76
+ props: {
77
+ role: "switch",
78
+ "aria-checked": checked,
79
+ className,
80
+ children,
81
+ ref: buttonRef,
82
+ onClick() {
83
+ if (disabled) return;
84
+ const next = !checked;
85
+ setChecked(next);
86
+ onCheckedChange?.(next);
87
+ }
88
+ },
89
+ consumerProps
91
90
  });
92
91
  return /* @__PURE__ */ jsxs(SwitchRootContext.Provider, { value: state, children: [
93
92
  element,
@@ -108,7 +107,8 @@ function SwitchThumb({
108
107
  defaultTagName: "span",
109
108
  state,
110
109
  stateAttributes: switchStateAttributes,
111
- props: [{ "aria-hidden": true, className, children }, rest]
110
+ props: { "aria-hidden": true, className, children },
111
+ consumerProps: rest
112
112
  });
113
113
  }
114
114
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arun-dev/headless",
3
- "version": "1.0.0",
3
+ "version": "2.0.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",