@arun-dev/headless 4.0.0 → 4.1.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.
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/checkbox/index.ts
21
+ var checkbox_exports = {};
22
+ __export(checkbox_exports, {
23
+ Checkbox: () => index_parts_exports
24
+ });
25
+ module.exports = __toCommonJS(checkbox_exports);
26
+
27
+ // src/checkbox/index.parts.ts
28
+ var index_parts_exports = {};
29
+ __export(index_parts_exports, {
30
+ Indicator: () => CheckboxIndicator,
31
+ Root: () => CheckboxRoot
32
+ });
33
+
34
+ // src/checkbox/CheckboxRoot.tsx
35
+ var import_react5 = require("react");
36
+
37
+ // src/core/useControlled.ts
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/useFormReset.ts
71
+ var import_react2 = require("react");
72
+ function useFormReset({
73
+ elementRef,
74
+ inputRef,
75
+ value,
76
+ inputChecked,
77
+ commit
78
+ }) {
79
+ const { current: initialValue } = (0, import_react2.useRef)(value);
80
+ (0, import_react2.useEffect)(() => {
81
+ const form = elementRef.current?.form;
82
+ if (!form) return;
83
+ function onReset(event) {
84
+ queueMicrotask(() => {
85
+ if (event.defaultPrevented) return;
86
+ if (inputRef.current) inputRef.current.checked = inputChecked;
87
+ if (value === initialValue) return;
88
+ commit(initialValue);
89
+ });
90
+ }
91
+ form.addEventListener("reset", onReset);
92
+ return () => form.removeEventListener("reset", onReset);
93
+ }, [elementRef, inputRef, value, inputChecked, commit, initialValue]);
94
+ }
95
+
96
+ // src/core/useRender.ts
97
+ var import_react3 = require("react");
98
+
99
+ // src/core/mergeProps.ts
100
+ function isEventHandler(key) {
101
+ return key.length > 2 && key.startsWith("on") && key[2] === key[2]?.toUpperCase();
102
+ }
103
+ function mergeRefs(...refs) {
104
+ return (node) => {
105
+ const cleanups = refs.map((ref) => {
106
+ if (typeof ref === "function") {
107
+ const result = ref(node);
108
+ return typeof result === "function" ? result : () => ref(null);
109
+ }
110
+ if (ref && typeof ref === "object") {
111
+ ref.current = node;
112
+ return () => {
113
+ ref.current = null;
114
+ };
115
+ }
116
+ return void 0;
117
+ });
118
+ return () => {
119
+ cleanups.forEach((cleanup) => {
120
+ if (typeof cleanup === "function") cleanup();
121
+ });
122
+ };
123
+ };
124
+ }
125
+ function isSyntheticEvent(event) {
126
+ return typeof event === "object" && event !== null && "nativeEvent" in event;
127
+ }
128
+ function makeEventPreventable(event) {
129
+ if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
130
+ const target = event;
131
+ target.preventComponentHandler = () => {
132
+ target.componentHandlerPrevented = true;
133
+ };
134
+ }
135
+ function isComponentHandlerPrevented(event) {
136
+ return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
137
+ }
138
+ function chainHandlers(earlier, value) {
139
+ return (...args) => {
140
+ const event = args[0];
141
+ if (isSyntheticEvent(event)) makeEventPreventable(event);
142
+ value(...args);
143
+ if (isComponentHandlerPrevented(event)) return;
144
+ earlier(...args);
145
+ };
146
+ }
147
+ function mergeProps(...objects) {
148
+ const merged = {};
149
+ for (const props of objects) {
150
+ if (!props) continue;
151
+ for (const key of Object.keys(props)) {
152
+ const value = props[key];
153
+ if (value === void 0) continue;
154
+ if (isEventHandler(key) && typeof value === "function") {
155
+ const earlier = merged[key];
156
+ merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
157
+ } else if (key === "className") {
158
+ merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
159
+ } else if (key === "style") {
160
+ merged.style = { ...merged.style, ...value };
161
+ } else if (key === "ref") {
162
+ merged.ref = merged.ref ? mergeRefs(merged.ref, value) : value;
163
+ } else {
164
+ merged[key] = value;
165
+ }
166
+ }
167
+ }
168
+ return merged;
169
+ }
170
+
171
+ // src/core/useRender.ts
172
+ function useRender({
173
+ render,
174
+ defaultTagName,
175
+ props,
176
+ consumerProps
177
+ }) {
178
+ const merged = mergeProps(props, consumerProps);
179
+ if ((0, import_react3.isValidElement)(render)) {
180
+ return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
181
+ }
182
+ return (0, import_react3.createElement)(defaultTagName, merged);
183
+ }
184
+
185
+ // src/checkbox/CheckboxRootContext.ts
186
+ var import_react4 = require("react");
187
+ var CheckboxRootContext = (0, import_react4.createContext)(null);
188
+ function useCheckboxRootContext() {
189
+ const context = (0, import_react4.useContext)(CheckboxRootContext);
190
+ if (context === null) {
191
+ throw new Error("<Checkbox.Indicator> must be rendered inside <Checkbox.Root>.");
192
+ }
193
+ return context;
194
+ }
195
+
196
+ // src/checkbox/checkboxDataAttributes.ts
197
+ function checkboxDataAttributes({ checked, disabled }) {
198
+ return {
199
+ "data-checked": checked === true ? "" : void 0,
200
+ "data-unchecked": checked === false ? "" : void 0,
201
+ "data-indeterminate": checked === "indeterminate" ? "" : void 0,
202
+ "data-disabled": disabled ? "" : void 0
203
+ };
204
+ }
205
+
206
+ // src/checkbox/CheckboxRoot.tsx
207
+ var import_jsx_runtime = require("react/jsx-runtime");
208
+ function CheckboxRoot({
209
+ checked: checkedProp,
210
+ defaultChecked,
211
+ onCheckedChange,
212
+ disabled = false,
213
+ name,
214
+ value = "on",
215
+ className,
216
+ children,
217
+ render,
218
+ ...rest
219
+ }) {
220
+ const [checked, setChecked] = useControlled({
221
+ controlled: checkedProp,
222
+ default: defaultChecked ?? false,
223
+ name: "Checkbox.Root",
224
+ state: "checked"
225
+ });
226
+ const commitChecked = (0, import_react5.useCallback)(
227
+ (next) => {
228
+ setChecked(next);
229
+ onCheckedChange?.(next);
230
+ },
231
+ [setChecked, onCheckedChange]
232
+ );
233
+ const state = (0, import_react5.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
234
+ const elementRef = (0, import_react5.useRef)(null);
235
+ const inputRef = (0, import_react5.useRef)(null);
236
+ const submitted = checked === true;
237
+ useFormReset({
238
+ elementRef,
239
+ inputRef,
240
+ value: checked,
241
+ inputChecked: submitted,
242
+ commit: commitChecked
243
+ });
244
+ const element = useRender({
245
+ render,
246
+ defaultTagName: "button",
247
+ props: {
248
+ // Without this a checkbox inside a form would submit it on every toggle.
249
+ type: "button",
250
+ role: "checkbox",
251
+ // The ARIA spelling of the third state is "mixed", not "indeterminate".
252
+ "aria-checked": checked === "indeterminate" ? "mixed" : checked,
253
+ // The platform suppresses activation and focus on a native button.
254
+ disabled: disabled || void 0,
255
+ ...checkboxDataAttributes(state),
256
+ className,
257
+ children,
258
+ ref: elementRef,
259
+ onClick() {
260
+ if (disabled) return;
261
+ commitChecked(!submitted);
262
+ }
263
+ },
264
+ consumerProps: rest
265
+ });
266
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CheckboxRootContext.Provider, { value: state, children: [
267
+ element,
268
+ name !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
269
+ "input",
270
+ {
271
+ ref: inputRef,
272
+ type: "checkbox",
273
+ hidden: true,
274
+ readOnly: true,
275
+ name,
276
+ value,
277
+ checked: submitted,
278
+ disabled
279
+ }
280
+ ) : null
281
+ ] });
282
+ }
283
+
284
+ // src/checkbox/CheckboxIndicator.tsx
285
+ function CheckboxIndicator({
286
+ className,
287
+ children,
288
+ render,
289
+ ...rest
290
+ }) {
291
+ const state = useCheckboxRootContext();
292
+ return useRender({
293
+ render,
294
+ defaultTagName: "span",
295
+ props: { "aria-hidden": true, ...checkboxDataAttributes(state), className, children },
296
+ consumerProps: rest
297
+ });
298
+ }
299
+ // Annotate the CommonJS export names for ESM import in node:
300
+ 0 && (module.exports = {
301
+ Checkbox
302
+ });
@@ -0,0 +1,123 @@
1
+ import * as react from 'react';
2
+ import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
3
+
4
+ /**
5
+ * A checkbox has three states, not two.
6
+ *
7
+ * `'indeterminate'` is a state of the checkbox itself, not a separate flag beside
8
+ * `checked` — a single union makes `checked && indeterminate` unrepresentable, and
9
+ * keeps one source of state to derive everything else from (decision 10).
10
+ *
11
+ * Native HTML splits them, but only because `.indeterminate` is a DOM property with no
12
+ * attribute behind it: it is purely visual there, is cleared by the first click, and
13
+ * survives `form.reset()`. None of that is worth reproducing.
14
+ */
15
+ type CheckedState = boolean | 'indeterminate';
16
+ /** The state Checkbox.Root shares with its parts, and projects as `data-*` attributes. */
17
+ type CheckboxState = {
18
+ checked: CheckedState;
19
+ disabled: boolean;
20
+ };
21
+
22
+ /**
23
+ * Checkbox.Root's own props. Everything else — `id`, `className`, `children`, `aria-*`,
24
+ * `data-*`, event handlers — comes from React's own `<button>` props, so it is typed
25
+ * and checked without being declared here.
26
+ *
27
+ * `id` in particular is how a checkbox gets an accessible name, paired with a
28
+ * `<label htmlFor>`. A wrapping `<label>` names the rendered `<button>` too, but
29
+ * `jsx-a11y/label-has-associated-control` rejects a button as a nested control, so the
30
+ * explicit pairing is the one that passes lint.
31
+ */
32
+ type CheckboxRootOwnProps = {
33
+ /**
34
+ * Controlled state. Provide `onCheckedChange` alongside it.
35
+ *
36
+ * Never `undefined` once mounted. The mode is latched at mount, so an `undefined`
37
+ * first render makes the checkbox uncontrolled for good and every value passed later
38
+ * is ignored. Coalesce at the call site — `checked={x ?? false}`.
39
+ */
40
+ checked?: CheckedState;
41
+ /** Initial state when uncontrolled. Read once, at mount. */
42
+ defaultChecked?: CheckedState;
43
+ onCheckedChange?: (checked: CheckedState) => void;
44
+ disabled?: boolean;
45
+ /**
46
+ * Submits with the enclosing form when checked, mirroring a native checkbox:
47
+ * an unchecked, indeterminate or disabled control contributes nothing.
48
+ */
49
+ name?: string;
50
+ /** Value submitted when checked. Defaults to `"on"`, as a native checkbox does. */
51
+ value?: string;
52
+ /**
53
+ * Component to render instead of the default `<button>`. Props, className, event
54
+ * handlers and ref are merged onto it.
55
+ *
56
+ * The default `<button>` is what supplies focus, Space and Enter activation and
57
+ * `disabled`; what `render` produces instead is the consumer's choice.
58
+ */
59
+ render?: ReactElement;
60
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
61
+ ref?: Ref<HTMLElement>;
62
+ };
63
+ type CheckboxRootProps = CheckboxRootOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof CheckboxRootOwnProps>;
64
+ /**
65
+ * A checkbox — a three-state control whose value is read on submit, distinct from a
66
+ * switch in that it does not take effect at once.
67
+ *
68
+ * A `<button role="checkbox">` rather than an `<input type="checkbox">`, because an
69
+ * input is void and cannot hold an indicator — the content test in decision 7, the same
70
+ * one Switch.Root fails. Everything the input was still good for, the hidden one below
71
+ * keeps doing. The platform supplies focusability, Space and Enter activation and
72
+ * `disabled`, so none of it is synthesised here.
73
+ *
74
+ * Inside a form it behaves as a native checkbox would: `form.reset()` returns it to the
75
+ * state it mounted with, indeterminate included — which a native checkbox does not
76
+ * manage, since `.indeterminate` is a DOM property the platform never resets.
77
+ *
78
+ * It has no accessible name of its own — pair it with a `<label htmlFor>` by `id`, or
79
+ * pass `aria-label` or `aria-labelledby`. That is the consumer's decision, not one a
80
+ * headless component should guess.
81
+ */
82
+ declare function CheckboxRoot({ checked: checkedProp, defaultChecked, onCheckedChange, disabled, name, value, className, children, render, ...rest }: CheckboxRootProps): react.JSX.Element;
83
+
84
+ /**
85
+ * Checkbox.Indicator's own props. Everything else — `id`, `className`, `children`,
86
+ * `aria-*`, `data-*` — comes from React's own `<span>` props.
87
+ */
88
+ type CheckboxIndicatorOwnProps = {
89
+ /** Element to render instead of the default `<span>`. */
90
+ render?: ReactElement;
91
+ /** Ref to the rendered element, whatever `render` makes it. */
92
+ ref?: Ref<HTMLElement>;
93
+ };
94
+ type CheckboxIndicatorProps = CheckboxIndicatorOwnProps & Omit<ComponentPropsWithRef<'span'>, keyof CheckboxIndicatorOwnProps>;
95
+ /**
96
+ * Where the check or the dash goes.
97
+ *
98
+ * Ships neither — like every part here it has no glyph, no class and no colour, so a
99
+ * bare `<Checkbox.Indicator />` renders an empty `<span>`. Supply the mark as children,
100
+ * or draw it in your own CSS off the `data-*` attributes below. `@arun-dev/ui` is the
101
+ * layer that has an opinion about what a checkmark looks like.
102
+ *
103
+ * Reads state from the Root rather than taking props, so a consumer cannot get the two
104
+ * out of step. Carries the same `data-*` attributes as the Root, so it can be styled
105
+ * from either — on the indicator itself, or by descending from the Root:
106
+ *
107
+ * .indicator[data-checked] { opacity: 1; }
108
+ * .checkbox[data-checked] .indicator { opacity: 1; }
109
+ *
110
+ * Always rendered, in every state. The alternative — unmounting it when unchecked —
111
+ * would leave nothing for CSS to transition from, and nothing to hold an unchecked
112
+ * mark for the designs that want one.
113
+ *
114
+ * Purely presentational — hidden from assistive technology, since the Root already
115
+ * announces the state.
116
+ */
117
+ declare function CheckboxIndicator({ className, children, render, ...rest }: CheckboxIndicatorProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
118
+
119
+ declare namespace index_parts {
120
+ export { CheckboxIndicator as Indicator, CheckboxRoot as Root };
121
+ }
122
+
123
+ export { index_parts as Checkbox, type CheckboxIndicatorProps, type CheckboxRootProps, type CheckboxState, type CheckedState };
@@ -0,0 +1,123 @@
1
+ import * as react from 'react';
2
+ import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
3
+
4
+ /**
5
+ * A checkbox has three states, not two.
6
+ *
7
+ * `'indeterminate'` is a state of the checkbox itself, not a separate flag beside
8
+ * `checked` — a single union makes `checked && indeterminate` unrepresentable, and
9
+ * keeps one source of state to derive everything else from (decision 10).
10
+ *
11
+ * Native HTML splits them, but only because `.indeterminate` is a DOM property with no
12
+ * attribute behind it: it is purely visual there, is cleared by the first click, and
13
+ * survives `form.reset()`. None of that is worth reproducing.
14
+ */
15
+ type CheckedState = boolean | 'indeterminate';
16
+ /** The state Checkbox.Root shares with its parts, and projects as `data-*` attributes. */
17
+ type CheckboxState = {
18
+ checked: CheckedState;
19
+ disabled: boolean;
20
+ };
21
+
22
+ /**
23
+ * Checkbox.Root's own props. Everything else — `id`, `className`, `children`, `aria-*`,
24
+ * `data-*`, event handlers — comes from React's own `<button>` props, so it is typed
25
+ * and checked without being declared here.
26
+ *
27
+ * `id` in particular is how a checkbox gets an accessible name, paired with a
28
+ * `<label htmlFor>`. A wrapping `<label>` names the rendered `<button>` too, but
29
+ * `jsx-a11y/label-has-associated-control` rejects a button as a nested control, so the
30
+ * explicit pairing is the one that passes lint.
31
+ */
32
+ type CheckboxRootOwnProps = {
33
+ /**
34
+ * Controlled state. Provide `onCheckedChange` alongside it.
35
+ *
36
+ * Never `undefined` once mounted. The mode is latched at mount, so an `undefined`
37
+ * first render makes the checkbox uncontrolled for good and every value passed later
38
+ * is ignored. Coalesce at the call site — `checked={x ?? false}`.
39
+ */
40
+ checked?: CheckedState;
41
+ /** Initial state when uncontrolled. Read once, at mount. */
42
+ defaultChecked?: CheckedState;
43
+ onCheckedChange?: (checked: CheckedState) => void;
44
+ disabled?: boolean;
45
+ /**
46
+ * Submits with the enclosing form when checked, mirroring a native checkbox:
47
+ * an unchecked, indeterminate or disabled control contributes nothing.
48
+ */
49
+ name?: string;
50
+ /** Value submitted when checked. Defaults to `"on"`, as a native checkbox does. */
51
+ value?: string;
52
+ /**
53
+ * Component to render instead of the default `<button>`. Props, className, event
54
+ * handlers and ref are merged onto it.
55
+ *
56
+ * The default `<button>` is what supplies focus, Space and Enter activation and
57
+ * `disabled`; what `render` produces instead is the consumer's choice.
58
+ */
59
+ render?: ReactElement;
60
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
61
+ ref?: Ref<HTMLElement>;
62
+ };
63
+ type CheckboxRootProps = CheckboxRootOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof CheckboxRootOwnProps>;
64
+ /**
65
+ * A checkbox — a three-state control whose value is read on submit, distinct from a
66
+ * switch in that it does not take effect at once.
67
+ *
68
+ * A `<button role="checkbox">` rather than an `<input type="checkbox">`, because an
69
+ * input is void and cannot hold an indicator — the content test in decision 7, the same
70
+ * one Switch.Root fails. Everything the input was still good for, the hidden one below
71
+ * keeps doing. The platform supplies focusability, Space and Enter activation and
72
+ * `disabled`, so none of it is synthesised here.
73
+ *
74
+ * Inside a form it behaves as a native checkbox would: `form.reset()` returns it to the
75
+ * state it mounted with, indeterminate included — which a native checkbox does not
76
+ * manage, since `.indeterminate` is a DOM property the platform never resets.
77
+ *
78
+ * It has no accessible name of its own — pair it with a `<label htmlFor>` by `id`, or
79
+ * pass `aria-label` or `aria-labelledby`. That is the consumer's decision, not one a
80
+ * headless component should guess.
81
+ */
82
+ declare function CheckboxRoot({ checked: checkedProp, defaultChecked, onCheckedChange, disabled, name, value, className, children, render, ...rest }: CheckboxRootProps): react.JSX.Element;
83
+
84
+ /**
85
+ * Checkbox.Indicator's own props. Everything else — `id`, `className`, `children`,
86
+ * `aria-*`, `data-*` — comes from React's own `<span>` props.
87
+ */
88
+ type CheckboxIndicatorOwnProps = {
89
+ /** Element to render instead of the default `<span>`. */
90
+ render?: ReactElement;
91
+ /** Ref to the rendered element, whatever `render` makes it. */
92
+ ref?: Ref<HTMLElement>;
93
+ };
94
+ type CheckboxIndicatorProps = CheckboxIndicatorOwnProps & Omit<ComponentPropsWithRef<'span'>, keyof CheckboxIndicatorOwnProps>;
95
+ /**
96
+ * Where the check or the dash goes.
97
+ *
98
+ * Ships neither — like every part here it has no glyph, no class and no colour, so a
99
+ * bare `<Checkbox.Indicator />` renders an empty `<span>`. Supply the mark as children,
100
+ * or draw it in your own CSS off the `data-*` attributes below. `@arun-dev/ui` is the
101
+ * layer that has an opinion about what a checkmark looks like.
102
+ *
103
+ * Reads state from the Root rather than taking props, so a consumer cannot get the two
104
+ * out of step. Carries the same `data-*` attributes as the Root, so it can be styled
105
+ * from either — on the indicator itself, or by descending from the Root:
106
+ *
107
+ * .indicator[data-checked] { opacity: 1; }
108
+ * .checkbox[data-checked] .indicator { opacity: 1; }
109
+ *
110
+ * Always rendered, in every state. The alternative — unmounting it when unchecked —
111
+ * would leave nothing for CSS to transition from, and nothing to hold an unchecked
112
+ * mark for the designs that want one.
113
+ *
114
+ * Purely presentational — hidden from assistive technology, since the Root already
115
+ * announces the state.
116
+ */
117
+ declare function CheckboxIndicator({ className, children, render, ...rest }: CheckboxIndicatorProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
118
+
119
+ declare namespace index_parts {
120
+ export { CheckboxIndicator as Indicator, CheckboxRoot as Root };
121
+ }
122
+
123
+ export { index_parts as Checkbox, type CheckboxIndicatorProps, type CheckboxRootProps, type CheckboxState, type CheckedState };
@@ -0,0 +1,138 @@
1
+ import {
2
+ useFormReset
3
+ } from "../chunk-IWUVUY76.js";
4
+ import {
5
+ useControlled
6
+ } from "../chunk-UEIB3I3T.js";
7
+ import {
8
+ __export,
9
+ useRender
10
+ } from "../chunk-OQTWWZ4B.js";
11
+
12
+ // src/checkbox/index.parts.ts
13
+ var index_parts_exports = {};
14
+ __export(index_parts_exports, {
15
+ Indicator: () => CheckboxIndicator,
16
+ Root: () => CheckboxRoot
17
+ });
18
+
19
+ // src/checkbox/CheckboxRoot.tsx
20
+ import { useCallback, useMemo, useRef } from "react";
21
+
22
+ // src/checkbox/CheckboxRootContext.ts
23
+ import { createContext, useContext } from "react";
24
+ var CheckboxRootContext = createContext(null);
25
+ function useCheckboxRootContext() {
26
+ const context = useContext(CheckboxRootContext);
27
+ if (context === null) {
28
+ throw new Error("<Checkbox.Indicator> must be rendered inside <Checkbox.Root>.");
29
+ }
30
+ return context;
31
+ }
32
+
33
+ // src/checkbox/checkboxDataAttributes.ts
34
+ function checkboxDataAttributes({ checked, disabled }) {
35
+ return {
36
+ "data-checked": checked === true ? "" : void 0,
37
+ "data-unchecked": checked === false ? "" : void 0,
38
+ "data-indeterminate": checked === "indeterminate" ? "" : void 0,
39
+ "data-disabled": disabled ? "" : void 0
40
+ };
41
+ }
42
+
43
+ // src/checkbox/CheckboxRoot.tsx
44
+ import { jsx, jsxs } from "react/jsx-runtime";
45
+ function CheckboxRoot({
46
+ checked: checkedProp,
47
+ defaultChecked,
48
+ onCheckedChange,
49
+ disabled = false,
50
+ name,
51
+ value = "on",
52
+ className,
53
+ children,
54
+ render,
55
+ ...rest
56
+ }) {
57
+ const [checked, setChecked] = useControlled({
58
+ controlled: checkedProp,
59
+ default: defaultChecked ?? false,
60
+ name: "Checkbox.Root",
61
+ state: "checked"
62
+ });
63
+ const commitChecked = useCallback(
64
+ (next) => {
65
+ setChecked(next);
66
+ onCheckedChange?.(next);
67
+ },
68
+ [setChecked, onCheckedChange]
69
+ );
70
+ const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
71
+ const elementRef = useRef(null);
72
+ const inputRef = useRef(null);
73
+ const submitted = checked === true;
74
+ useFormReset({
75
+ elementRef,
76
+ inputRef,
77
+ value: checked,
78
+ inputChecked: submitted,
79
+ commit: commitChecked
80
+ });
81
+ const element = useRender({
82
+ render,
83
+ defaultTagName: "button",
84
+ props: {
85
+ // Without this a checkbox inside a form would submit it on every toggle.
86
+ type: "button",
87
+ role: "checkbox",
88
+ // The ARIA spelling of the third state is "mixed", not "indeterminate".
89
+ "aria-checked": checked === "indeterminate" ? "mixed" : checked,
90
+ // The platform suppresses activation and focus on a native button.
91
+ disabled: disabled || void 0,
92
+ ...checkboxDataAttributes(state),
93
+ className,
94
+ children,
95
+ ref: elementRef,
96
+ onClick() {
97
+ if (disabled) return;
98
+ commitChecked(!submitted);
99
+ }
100
+ },
101
+ consumerProps: rest
102
+ });
103
+ return /* @__PURE__ */ jsxs(CheckboxRootContext.Provider, { value: state, children: [
104
+ element,
105
+ name !== void 0 ? /* @__PURE__ */ jsx(
106
+ "input",
107
+ {
108
+ ref: inputRef,
109
+ type: "checkbox",
110
+ hidden: true,
111
+ readOnly: true,
112
+ name,
113
+ value,
114
+ checked: submitted,
115
+ disabled
116
+ }
117
+ ) : null
118
+ ] });
119
+ }
120
+
121
+ // src/checkbox/CheckboxIndicator.tsx
122
+ function CheckboxIndicator({
123
+ className,
124
+ children,
125
+ render,
126
+ ...rest
127
+ }) {
128
+ const state = useCheckboxRootContext();
129
+ return useRender({
130
+ render,
131
+ defaultTagName: "span",
132
+ props: { "aria-hidden": true, ...checkboxDataAttributes(state), className, children },
133
+ consumerProps: rest
134
+ });
135
+ }
136
+ export {
137
+ index_parts_exports as Checkbox
138
+ };
@@ -0,0 +1,29 @@
1
+ // src/core/useFormReset.ts
2
+ import { useEffect, useRef } from "react";
3
+ function useFormReset({
4
+ elementRef,
5
+ inputRef,
6
+ value,
7
+ inputChecked,
8
+ commit
9
+ }) {
10
+ const { current: initialValue } = useRef(value);
11
+ useEffect(() => {
12
+ const form = elementRef.current?.form;
13
+ if (!form) return;
14
+ function onReset(event) {
15
+ queueMicrotask(() => {
16
+ if (event.defaultPrevented) return;
17
+ if (inputRef.current) inputRef.current.checked = inputChecked;
18
+ if (value === initialValue) return;
19
+ commit(initialValue);
20
+ });
21
+ }
22
+ form.addEventListener("reset", onReset);
23
+ return () => form.removeEventListener("reset", onReset);
24
+ }, [elementRef, inputRef, value, inputChecked, commit, initialValue]);
25
+ }
26
+
27
+ export {
28
+ useFormReset
29
+ };
@@ -32,7 +32,7 @@ __export(index_parts_exports, {
32
32
  });
33
33
 
34
34
  // src/switch/SwitchRoot.tsx
35
- var import_react4 = require("react");
35
+ var import_react5 = require("react");
36
36
 
37
37
  // src/core/useControlled.ts
38
38
  var import_react = require("react");
@@ -67,8 +67,34 @@ function useControlled({
67
67
  return [value, setValue];
68
68
  }
69
69
 
70
- // src/core/useRender.ts
70
+ // src/core/useFormReset.ts
71
71
  var import_react2 = require("react");
72
+ function useFormReset({
73
+ elementRef,
74
+ inputRef,
75
+ value,
76
+ inputChecked,
77
+ commit
78
+ }) {
79
+ const { current: initialValue } = (0, import_react2.useRef)(value);
80
+ (0, import_react2.useEffect)(() => {
81
+ const form = elementRef.current?.form;
82
+ if (!form) return;
83
+ function onReset(event) {
84
+ queueMicrotask(() => {
85
+ if (event.defaultPrevented) return;
86
+ if (inputRef.current) inputRef.current.checked = inputChecked;
87
+ if (value === initialValue) return;
88
+ commit(initialValue);
89
+ });
90
+ }
91
+ form.addEventListener("reset", onReset);
92
+ return () => form.removeEventListener("reset", onReset);
93
+ }, [elementRef, inputRef, value, inputChecked, commit, initialValue]);
94
+ }
95
+
96
+ // src/core/useRender.ts
97
+ var import_react3 = require("react");
72
98
 
73
99
  // src/core/mergeProps.ts
74
100
  function isEventHandler(key) {
@@ -150,17 +176,17 @@ function useRender({
150
176
  consumerProps
151
177
  }) {
152
178
  const merged = mergeProps(props, consumerProps);
153
- if ((0, import_react2.isValidElement)(render)) {
154
- return (0, import_react2.cloneElement)(render, mergeProps(merged, render.props));
179
+ if ((0, import_react3.isValidElement)(render)) {
180
+ return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
155
181
  }
156
- return (0, import_react2.createElement)(defaultTagName, merged);
182
+ return (0, import_react3.createElement)(defaultTagName, merged);
157
183
  }
158
184
 
159
185
  // src/switch/SwitchRootContext.ts
160
- var import_react3 = require("react");
161
- var SwitchRootContext = (0, import_react3.createContext)(null);
186
+ var import_react4 = require("react");
187
+ var SwitchRootContext = (0, import_react4.createContext)(null);
162
188
  function useSwitchRootContext() {
163
- const context = (0, import_react3.useContext)(SwitchRootContext);
189
+ const context = (0, import_react4.useContext)(SwitchRootContext);
164
190
  if (context === null) {
165
191
  throw new Error("<Switch.Thumb> must be rendered inside <Switch.Root>.");
166
192
  }
@@ -196,17 +222,23 @@ function SwitchRoot({
196
222
  name: "Switch.Root",
197
223
  state: "checked"
198
224
  });
199
- const commitChecked = (0, import_react4.useCallback)(
225
+ const commitChecked = (0, import_react5.useCallback)(
200
226
  (next) => {
201
227
  setChecked(next);
202
228
  onCheckedChange?.(next);
203
229
  },
204
230
  [setChecked, onCheckedChange]
205
231
  );
206
- const state = (0, import_react4.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
207
- const elementRef = (0, import_react4.useRef)(null);
208
- const inputRef = (0, import_react4.useRef)(null);
209
- useFormReset({ elementRef, inputRef, checked, commitChecked });
232
+ const state = (0, import_react5.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
233
+ const elementRef = (0, import_react5.useRef)(null);
234
+ const inputRef = (0, import_react5.useRef)(null);
235
+ useFormReset({
236
+ elementRef,
237
+ inputRef,
238
+ value: checked,
239
+ inputChecked: checked,
240
+ commit: commitChecked
241
+ });
210
242
  const element = useRender({
211
243
  render,
212
244
  defaultTagName: "button",
@@ -245,28 +277,6 @@ function SwitchRoot({
245
277
  ) : null
246
278
  ] });
247
279
  }
248
- function useFormReset({
249
- elementRef,
250
- inputRef,
251
- checked,
252
- commitChecked
253
- }) {
254
- const { current: initialChecked } = (0, import_react4.useRef)(checked);
255
- (0, import_react4.useEffect)(() => {
256
- const form = elementRef.current?.form;
257
- if (!form) return;
258
- function onReset(event) {
259
- queueMicrotask(() => {
260
- if (event.defaultPrevented) return;
261
- if (inputRef.current) inputRef.current.checked = checked;
262
- if (checked === initialChecked) return;
263
- commitChecked(initialChecked);
264
- });
265
- }
266
- form.addEventListener("reset", onReset);
267
- return () => form.removeEventListener("reset", onReset);
268
- }, [elementRef, inputRef, checked, commitChecked, initialChecked]);
269
- }
270
280
 
271
281
  // src/switch/SwitchThumb.tsx
272
282
  function SwitchThumb({ className, children, render, ...rest }) {
@@ -1,3 +1,6 @@
1
+ import {
2
+ useFormReset
3
+ } from "../chunk-IWUVUY76.js";
1
4
  import {
2
5
  useControlled
3
6
  } from "../chunk-UEIB3I3T.js";
@@ -14,7 +17,7 @@ __export(index_parts_exports, {
14
17
  });
15
18
 
16
19
  // src/switch/SwitchRoot.tsx
17
- import { useCallback, useEffect, useMemo, useRef } from "react";
20
+ import { useCallback, useMemo, useRef } from "react";
18
21
 
19
22
  // src/switch/SwitchRootContext.ts
20
23
  import { createContext, useContext } from "react";
@@ -66,7 +69,13 @@ function SwitchRoot({
66
69
  const state = useMemo(() => ({ checked, disabled }), [checked, disabled]);
67
70
  const elementRef = useRef(null);
68
71
  const inputRef = useRef(null);
69
- useFormReset({ elementRef, inputRef, checked, commitChecked });
72
+ useFormReset({
73
+ elementRef,
74
+ inputRef,
75
+ value: checked,
76
+ inputChecked: checked,
77
+ commit: commitChecked
78
+ });
70
79
  const element = useRender({
71
80
  render,
72
81
  defaultTagName: "button",
@@ -105,28 +114,6 @@ function SwitchRoot({
105
114
  ) : null
106
115
  ] });
107
116
  }
108
- function useFormReset({
109
- elementRef,
110
- inputRef,
111
- checked,
112
- commitChecked
113
- }) {
114
- const { current: initialChecked } = useRef(checked);
115
- useEffect(() => {
116
- const form = elementRef.current?.form;
117
- if (!form) return;
118
- function onReset(event) {
119
- queueMicrotask(() => {
120
- if (event.defaultPrevented) return;
121
- if (inputRef.current) inputRef.current.checked = checked;
122
- if (checked === initialChecked) return;
123
- commitChecked(initialChecked);
124
- });
125
- }
126
- form.addEventListener("reset", onReset);
127
- return () => form.removeEventListener("reset", onReset);
128
- }, [elementRef, inputRef, checked, commitChecked, initialChecked]);
129
- }
130
117
 
131
118
  // src/switch/SwitchThumb.tsx
132
119
  function SwitchThumb({ className, children, render, ...rest }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arun-dev/headless",
3
- "version": "4.0.0",
3
+ "version": "4.1.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",
@@ -44,6 +44,16 @@
44
44
  "default": "./dist/button/index.cjs"
45
45
  }
46
46
  },
47
+ "./checkbox": {
48
+ "import": {
49
+ "types": "./dist/checkbox/index.d.ts",
50
+ "default": "./dist/checkbox/index.js"
51
+ },
52
+ "require": {
53
+ "types": "./dist/checkbox/index.d.cts",
54
+ "default": "./dist/checkbox/index.cjs"
55
+ }
56
+ },
47
57
  "./switch": {
48
58
  "import": {
49
59
  "types": "./dist/switch/index.d.ts",