@funstack/ui 0.1.0-alpha.1 → 0.1.0-alpha.3
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/accordion/accordion-context.mjs.map +1 -1
- package/dist/accordion/accordion-item.mjs +18 -4
- package/dist/accordion/accordion-item.mjs.map +1 -1
- package/dist/accordion/accordion-panel.mjs +2 -3
- package/dist/accordion/accordion-panel.mjs.map +1 -1
- package/dist/accordion/accordion-root.d.mts.map +1 -1
- package/dist/accordion/accordion-root.mjs +32 -14
- package/dist/accordion/accordion-root.mjs.map +1 -1
- package/dist/accordion/accordion-trigger.mjs +4 -5
- package/dist/accordion/accordion-trigger.mjs.map +1 -1
- package/dist/checkbox/checkbox.d.mts +5 -3
- package/dist/checkbox/checkbox.d.mts.map +1 -1
- package/dist/checkbox/checkbox.mjs +25 -9
- package/dist/checkbox/checkbox.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-item.d.mts +2 -1
- package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -1
- package/dist/checkbox-group/checkbox-group-item.mjs +3 -1
- package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-root.d.mts +5 -2
- package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -1
- package/dist/checkbox-group/checkbox-group-root.mjs +24 -5
- package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -1
- package/dist/combobox/combobox-clear.d.mts +34 -0
- package/dist/combobox/combobox-clear.d.mts.map +1 -0
- package/dist/combobox/combobox-clear.mjs +58 -0
- package/dist/combobox/combobox-clear.mjs.map +1 -0
- package/dist/combobox/combobox-context.mjs +23 -0
- package/dist/combobox/combobox-context.mjs.map +1 -0
- package/dist/combobox/combobox-group.d.mts +72 -0
- package/dist/combobox/combobox-group.d.mts.map +1 -0
- package/dist/combobox/combobox-group.mjs +104 -0
- package/dist/combobox/combobox-group.mjs.map +1 -0
- package/dist/combobox/combobox-input.d.mts +42 -0
- package/dist/combobox/combobox-input.d.mts.map +1 -0
- package/dist/combobox/combobox-input.mjs +102 -0
- package/dist/combobox/combobox-input.mjs.map +1 -0
- package/dist/combobox/combobox-label.d.mts +22 -0
- package/dist/combobox/combobox-label.d.mts.map +1 -0
- package/dist/combobox/combobox-label.mjs +32 -0
- package/dist/combobox/combobox-label.mjs.map +1 -0
- package/dist/combobox/combobox-list.d.mts +37 -0
- package/dist/combobox/combobox-list.d.mts.map +1 -0
- package/dist/combobox/combobox-list.mjs +70 -0
- package/dist/combobox/combobox-list.mjs.map +1 -0
- package/dist/combobox/combobox-option.d.mts +45 -0
- package/dist/combobox/combobox-option.d.mts.map +1 -0
- package/dist/combobox/combobox-option.mjs +88 -0
- package/dist/combobox/combobox-option.mjs.map +1 -0
- package/dist/combobox/combobox-popup.d.mts +49 -0
- package/dist/combobox/combobox-popup.d.mts.map +1 -0
- package/dist/combobox/combobox-popup.mjs +86 -0
- package/dist/combobox/combobox-popup.mjs.map +1 -0
- package/dist/combobox/combobox-root.d.mts +135 -0
- package/dist/combobox/combobox-root.d.mts.map +1 -0
- package/dist/combobox/combobox-root.mjs +523 -0
- package/dist/combobox/combobox-root.mjs.map +1 -0
- package/dist/combobox/combobox-trigger.d.mts +36 -0
- package/dist/combobox/combobox-trigger.d.mts.map +1 -0
- package/dist/combobox/combobox-trigger.mjs +92 -0
- package/dist/combobox/combobox-trigger.mjs.map +1 -0
- package/dist/combobox/index.d.mts +36 -0
- package/dist/combobox/index.d.mts.map +1 -0
- package/dist/combobox/index.mjs +36 -0
- package/dist/combobox/index.mjs.map +1 -0
- package/dist/dialog/dialog-close.d.mts +17 -10
- package/dist/dialog/dialog-close.d.mts.map +1 -1
- package/dist/dialog/dialog-close.mjs +31 -20
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-context.mjs.map +1 -1
- package/dist/dialog/dialog-form.d.mts +12 -5
- package/dist/dialog/dialog-form.d.mts.map +1 -1
- package/dist/dialog/dialog-form.mjs +16 -28
- package/dist/dialog/dialog-form.mjs.map +1 -1
- package/dist/dialog/dialog-popup.d.mts +10 -4
- package/dist/dialog/dialog-popup.d.mts.map +1 -1
- package/dist/dialog/dialog-popup.mjs +106 -41
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.d.mts +14 -8
- package/dist/dialog/dialog-root.d.mts.map +1 -1
- package/dist/dialog/dialog-root.mjs +66 -11
- package/dist/dialog/dialog-root.mjs.map +1 -1
- package/dist/dialog/dialog-trigger.d.mts +3 -2
- package/dist/dialog/dialog-trigger.d.mts.map +1 -1
- package/dist/dialog/dialog-trigger.mjs.map +1 -1
- package/dist/dialog/index.d.mts +4 -2
- package/dist/dialog/index.d.mts.map +1 -1
- package/dist/dialog/index.mjs +4 -2
- package/dist/dialog/index.mjs.map +1 -1
- package/dist/field/field-control.d.mts +33 -0
- package/dist/field/field-control.d.mts.map +1 -0
- package/dist/field/field-control.mjs +68 -0
- package/dist/field/field-control.mjs.map +1 -0
- package/dist/field/field-description.d.mts +24 -0
- package/dist/field/field-description.d.mts.map +1 -0
- package/dist/field/field-description.mjs +34 -0
- package/dist/field/field-description.mjs.map +1 -0
- package/dist/field/field-error.d.mts +30 -0
- package/dist/field/field-error.d.mts.map +1 -0
- package/dist/field/field-error.mjs +42 -0
- package/dist/field/field-error.mjs.map +1 -0
- package/dist/field/field-label.d.mts +25 -0
- package/dist/field/field-label.d.mts.map +1 -0
- package/dist/field/field-label.mjs +46 -0
- package/dist/field/field-label.mjs.map +1 -0
- package/dist/field/field-root.d.mts +53 -0
- package/dist/field/field-root.d.mts.map +1 -0
- package/dist/field/field-root.mjs +317 -0
- package/dist/field/field-root.mjs.map +1 -0
- package/dist/field/index.d.mts +18 -0
- package/dist/field/index.d.mts.map +1 -0
- package/dist/field/index.mjs +17 -0
- package/dist/field/index.mjs.map +1 -0
- package/dist/form/form-root.d.mts +58 -0
- package/dist/form/form-root.d.mts.map +1 -0
- package/dist/form/form-root.mjs +146 -0
- package/dist/form/form-root.mjs.map +1 -0
- package/dist/form/form-submit.d.mts +31 -0
- package/dist/form/form-submit.d.mts.map +1 -0
- package/dist/form/form-submit.mjs +52 -0
- package/dist/form/form-submit.mjs.map +1 -0
- package/dist/form/index.d.mts +11 -0
- package/dist/form/index.d.mts.map +1 -0
- package/dist/form/index.mjs +11 -0
- package/dist/form/index.mjs.map +1 -0
- package/dist/index.d.mts +46 -2
- package/dist/index.mjs +8 -1
- package/dist/internal/content-focus-probe.mjs +42 -0
- package/dist/internal/content-focus-probe.mjs.map +1 -0
- package/dist/internal/field-context.d.mts +19 -0
- package/dist/internal/field-context.d.mts.map +1 -0
- package/dist/internal/field-context.mjs +14 -0
- package/dist/internal/field-context.mjs.map +1 -0
- package/dist/internal/focus-first-invalid.mjs +67 -0
- package/dist/internal/focus-first-invalid.mjs.map +1 -0
- package/dist/internal/form-status-watcher.mjs +31 -0
- package/dist/internal/form-status-watcher.mjs.map +1 -0
- package/dist/internal/modal-registry.mjs +39 -0
- package/dist/internal/modal-registry.mjs.map +1 -0
- package/dist/internal/platform-attributes.d.mts +12 -1
- package/dist/internal/platform-attributes.d.mts.map +1 -1
- package/dist/internal/types.d.mts +11 -1
- package/dist/internal/types.d.mts.map +1 -1
- package/dist/internal/use-controllable-action.mjs +35 -13
- package/dist/internal/use-controllable-action.mjs.map +1 -1
- package/dist/internal/use-field-control.mjs +107 -0
- package/dist/internal/use-field-control.mjs.map +1 -0
- package/dist/internal/use-form-owner-event.mjs +44 -0
- package/dist/internal/use-form-owner-event.mjs.map +1 -0
- package/dist/internal/use-form-reset.mjs +12 -31
- package/dist/internal/use-form-reset.mjs.map +1 -1
- package/dist/internal/use-hydrated-mount.mjs +21 -0
- package/dist/internal/use-hydrated-mount.mjs.map +1 -0
- package/dist/internal/use-latest.mjs +32 -0
- package/dist/internal/use-latest.mjs.map +1 -0
- package/dist/internal/use-modal-registration.mjs +47 -0
- package/dist/internal/use-modal-registration.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +6 -5
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
- package/dist/internal/use-native-select-value.mjs +184 -0
- package/dist/internal/use-native-select-value.mjs.map +1 -0
- package/dist/internal/use-popover-popup.mjs +8 -22
- package/dist/internal/use-popover-popup.mjs.map +1 -1
- package/dist/internal/use-popover-root.mjs +58 -23
- package/dist/internal/use-popover-root.mjs.map +1 -1
- package/dist/internal/validity.d.mts +24 -0
- package/dist/internal/validity.d.mts.map +1 -0
- package/dist/internal/validity.mjs +108 -0
- package/dist/internal/validity.mjs.map +1 -0
- package/dist/menu/menu-group.d.mts.map +1 -1
- package/dist/menu/menu-group.mjs +3 -2
- package/dist/menu/menu-group.mjs.map +1 -1
- package/dist/menu/menu-radio-group.mjs +9 -4
- package/dist/menu/menu-radio-group.mjs.map +1 -1
- package/dist/menu/menu-root.mjs.map +1 -1
- package/dist/radio-group/radio-group-root.d.mts +4 -2
- package/dist/radio-group/radio-group-root.d.mts.map +1 -1
- package/dist/radio-group/radio-group-root.mjs +34 -8
- package/dist/radio-group/radio-group-root.mjs.map +1 -1
- package/dist/select/index.d.mts +30 -0
- package/dist/select/index.d.mts.map +1 -0
- package/dist/select/index.mjs +30 -0
- package/dist/select/index.mjs.map +1 -0
- package/dist/select/select-context.mjs +13 -0
- package/dist/select/select-context.mjs.map +1 -0
- package/dist/select/select-dom.mjs +44 -0
- package/dist/select/select-dom.mjs.map +1 -0
- package/dist/select/select-group-label.d.mts +22 -0
- package/dist/select/select-group-label.d.mts.map +1 -0
- package/dist/select/select-group-label.mjs +39 -0
- package/dist/select/select-group-label.mjs.map +1 -0
- package/dist/select/select-group.d.mts +28 -0
- package/dist/select/select-group.d.mts.map +1 -0
- package/dist/select/select-group.mjs +47 -0
- package/dist/select/select-group.mjs.map +1 -0
- package/dist/select/select-option.d.mts +42 -0
- package/dist/select/select-option.d.mts.map +1 -0
- package/dist/select/select-option.mjs +65 -0
- package/dist/select/select-option.mjs.map +1 -0
- package/dist/select/select-root.d.mts +68 -0
- package/dist/select/select-root.d.mts.map +1 -0
- package/dist/select/select-root.mjs +114 -0
- package/dist/select/select-root.mjs.map +1 -0
- package/dist/select/select-separator.d.mts +20 -0
- package/dist/select/select-separator.d.mts.map +1 -0
- package/dist/select/select-separator.mjs +30 -0
- package/dist/select/select-separator.mjs.map +1 -0
- package/dist/select/select-trigger.d.mts +29 -0
- package/dist/select/select-trigger.d.mts.map +1 -0
- package/dist/select/select-trigger.mjs +48 -0
- package/dist/select/select-trigger.mjs.map +1 -0
- package/dist/select/select-value.d.mts +31 -0
- package/dist/select/select-value.d.mts.map +1 -0
- package/dist/select/select-value.mjs +101 -0
- package/dist/select/select-value.mjs.map +1 -0
- package/dist/slider/index.d.mts +32 -0
- package/dist/slider/index.d.mts.map +1 -0
- package/dist/slider/index.mjs +27 -0
- package/dist/slider/index.mjs.map +1 -0
- package/dist/slider/slider-arithmetic.mjs +120 -0
- package/dist/slider/slider-arithmetic.mjs.map +1 -0
- package/dist/slider/slider-context.mjs +13 -0
- package/dist/slider/slider-context.mjs.map +1 -0
- package/dist/slider/slider-indicator.d.mts +22 -0
- package/dist/slider/slider-indicator.d.mts.map +1 -0
- package/dist/slider/slider-indicator.mjs +36 -0
- package/dist/slider/slider-indicator.mjs.map +1 -0
- package/dist/slider/slider-label.d.mts +23 -0
- package/dist/slider/slider-label.d.mts.map +1 -0
- package/dist/slider/slider-label.mjs +44 -0
- package/dist/slider/slider-label.mjs.map +1 -0
- package/dist/slider/slider-root.d.mts +111 -0
- package/dist/slider/slider-root.d.mts.map +1 -0
- package/dist/slider/slider-root.mjs +582 -0
- package/dist/slider/slider-root.mjs.map +1 -0
- package/dist/slider/slider-thumb.d.mts +58 -0
- package/dist/slider/slider-thumb.d.mts.map +1 -0
- package/dist/slider/slider-thumb.mjs +145 -0
- package/dist/slider/slider-thumb.mjs.map +1 -0
- package/dist/slider/slider-track.d.mts +29 -0
- package/dist/slider/slider-track.d.mts.map +1 -0
- package/dist/slider/slider-track.mjs +54 -0
- package/dist/slider/slider-track.mjs.map +1 -0
- package/dist/slider/slider-value.d.mts +34 -0
- package/dist/slider/slider-value.d.mts.map +1 -0
- package/dist/slider/slider-value.mjs +38 -0
- package/dist/slider/slider-value.mjs.map +1 -0
- package/dist/switch/switch.d.mts +5 -3
- package/dist/switch/switch.d.mts.map +1 -1
- package/dist/switch/switch.mjs +24 -8
- package/dist/switch/switch.mjs.map +1 -1
- package/dist/tabs/tabs-root.mjs +13 -3
- package/dist/tabs/tabs-root.mjs.map +1 -1
- package/dist/toast/index.d.mts +25 -0
- package/dist/toast/index.d.mts.map +1 -0
- package/dist/toast/index.mjs +26 -0
- package/dist/toast/index.mjs.map +1 -0
- package/dist/toast/toast-action.d.mts +39 -0
- package/dist/toast/toast-action.d.mts.map +1 -0
- package/dist/toast/toast-action.mjs +64 -0
- package/dist/toast/toast-action.mjs.map +1 -0
- package/dist/toast/toast-close.d.mts +25 -0
- package/dist/toast/toast-close.d.mts.map +1 -0
- package/dist/toast/toast-close.mjs +46 -0
- package/dist/toast/toast-close.mjs.map +1 -0
- package/dist/toast/toast-context.mjs +19 -0
- package/dist/toast/toast-context.mjs.map +1 -0
- package/dist/toast/toast-description.d.mts +24 -0
- package/dist/toast/toast-description.d.mts.map +1 -0
- package/dist/toast/toast-description.mjs +35 -0
- package/dist/toast/toast-description.mjs.map +1 -0
- package/dist/toast/toast-root.d.mts +43 -0
- package/dist/toast/toast-root.d.mts.map +1 -0
- package/dist/toast/toast-root.mjs +204 -0
- package/dist/toast/toast-root.mjs.map +1 -0
- package/dist/toast/toast-title.d.mts +24 -0
- package/dist/toast/toast-title.d.mts.map +1 -0
- package/dist/toast/toast-title.mjs +33 -0
- package/dist/toast/toast-title.mjs.map +1 -0
- package/dist/toast/toast-viewport.d.mts +52 -0
- package/dist/toast/toast-viewport.d.mts.map +1 -0
- package/dist/toast/toast-viewport.mjs +621 -0
- package/dist/toast/toast-viewport.mjs.map +1 -0
- package/dist/toast/toast.d.mts +119 -0
- package/dist/toast/toast.d.mts.map +1 -0
- package/dist/toast/toast.mjs +153 -0
- package/dist/toast/toast.mjs.map +1 -0
- package/package.json +7 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-context.mjs","names":[],"sources":["../../src/accordion/accordion-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\n\n/** Why a change was dispatched (design 2.2 / 2.3). */\nexport type AccordionChangeReason = \"activate\" | \"platform\";\n\n/**\n * \"static\": JS-free mode, the browser owns `open`. \"library\": `open` is driven by React\n * committed state inside the Root's transition (design section 2).\n */\nexport type AccordionMode = \"static\" | \"library\";\n\nexport interface AccordionRootContextValue {\n readonly mode: AccordionMode;\n readonly exclusive: boolean;\n /** `name` for every `<details>`; set only in static exclusive mode. */\n readonly name: string | undefined;\n readonly disabled: boolean;\n readonly pending: boolean;\n /** Committed open set (library mode). Empty in static mode. */\n readonly committed: ReadonlySet<string>;\n /** Optimistic open set (library mode). Empty in static mode. */\n readonly optimistic: ReadonlySet<string>;\n /** A Trigger was activated: compute `next` from the optimistic set and dispatch. */\n readonly dispatch: (item: string, open: boolean, event: Event | undefined) => void;\n /** The browser changed `details.open`: compute `next` from the DOM and dispatch (design 2.3). */\n readonly dispatchFromDom: (item: string, open: boolean, event: Event | undefined) => void;\n /** Items register their `<details>` so `dispatchFromDom` can read every item's DOM state. */\n readonly registerItem: (value: string, element: HTMLDetailsElement) => () => void;\n /** Triggers register their `<summary>` for arrow-key navigation (design section 5). */\n readonly registerTrigger: (element: HTMLElement) => () => void;\n}\n\nexport const AccordionRootContext = createContext<AccordionRootContextValue | null>(null);\n\nexport interface AccordionItemContextValue {\n readonly value: string;\n readonly disabled: boolean;\n /** `undefined` in static mode (browser-owned, unknown to React). */\n readonly committedOpen: boolean | undefined;\n /** `undefined` in static mode. */\n readonly optimisticOpen: boolean | undefined;\n readonly pending: boolean;\n}\n\nexport const AccordionItemContext = createContext<AccordionItemContextValue | null>(null);\n"],"mappings":";;;AAiCA,MAAa,uBAAuB,cAAgD,IAAI;
|
|
1
|
+
{"version":3,"file":"accordion-context.mjs","names":[],"sources":["../../src/accordion/accordion-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\n\n/** Why a change was dispatched (design 2.2 / 2.3). */\nexport type AccordionChangeReason = \"activate\" | \"platform\";\n\n/**\n * \"static\": JS-free mode, the browser owns `open`. \"library\": `open` is driven by React\n * committed state inside the Root's transition (design section 2).\n */\nexport type AccordionMode = \"static\" | \"library\";\n\nexport interface AccordionRootContextValue {\n readonly mode: AccordionMode;\n readonly exclusive: boolean;\n /** `name` for every `<details>`; set only in static exclusive mode. */\n readonly name: string | undefined;\n readonly disabled: boolean;\n readonly pending: boolean;\n /** Committed open set (library mode). Empty in static mode. */\n readonly committed: ReadonlySet<string>;\n /** Optimistic open set (library mode). Empty in static mode. */\n readonly optimistic: ReadonlySet<string>;\n /** A Trigger was activated: compute `next` from the optimistic set and dispatch. */\n readonly dispatch: (item: string, open: boolean, event: Event | undefined) => void;\n /** The browser changed `details.open`: compute `next` from the DOM and dispatch (design 2.3). */\n readonly dispatchFromDom: (item: string, open: boolean, event: Event | undefined) => void;\n /** Items register their `<details>` so `dispatchFromDom` can read every item's DOM state. */\n readonly registerItem: (value: string, element: HTMLDetailsElement) => () => void;\n /** Triggers register their `<summary>` for arrow-key navigation (design section 5). */\n readonly registerTrigger: (element: HTMLElement) => () => void;\n}\n\nexport const AccordionRootContext = createContext<AccordionRootContextValue | null>(null);\n\nexport interface AccordionItemContextValue {\n readonly value: string;\n readonly disabled: boolean;\n /** `undefined` in static mode (browser-owned, unknown to React). */\n readonly committedOpen: boolean | undefined;\n /** `undefined` in static mode. */\n readonly optimisticOpen: boolean | undefined;\n readonly pending: boolean;\n /**\n * The Root's mode is `\"library\"`. The Root members the Trigger and Panel need are copied here\n * so they read only this context, which is memoised per item: opening another item re-renders\n * every Item (they read the Root's sets) but not their Triggers and Panels (#48).\n */\n readonly library: boolean;\n /** The Root's `dispatch` (stable); `undefined` outside a Root. */\n readonly dispatch: AccordionRootContextValue[\"dispatch\"] | undefined;\n /** The Root's `registerTrigger` (stable); `undefined` outside a Root. */\n readonly registerTrigger: AccordionRootContextValue[\"registerTrigger\"] | undefined;\n}\n\nexport const AccordionItemContext = createContext<AccordionItemContextValue | null>(null);\n"],"mappings":";;;AAiCA,MAAa,uBAAuB,cAAgD,IAAI;AAsBxF,MAAa,uBAAuB,cAAgD,IAAI"}
|
|
@@ -4,7 +4,7 @@ import { dataAttr } from "../internal/data-attr.mjs";
|
|
|
4
4
|
import { useRender } from "../internal/use-render.mjs";
|
|
5
5
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
6
6
|
import { AccordionItemContext, AccordionRootContext } from "./accordion-context.mjs";
|
|
7
|
-
import { use, useCallback, useState } from "react";
|
|
7
|
+
import { use, useCallback, useMemo, useState } from "react";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
9
9
|
//#region src/accordion/accordion-item.tsx
|
|
10
10
|
const isDev = process.env.NODE_ENV !== "production";
|
|
@@ -47,13 +47,27 @@ function AccordionItem({ value, defaultOpen = false, disabled: disabledProp = fa
|
|
|
47
47
|
pending,
|
|
48
48
|
disabled
|
|
49
49
|
};
|
|
50
|
-
const
|
|
50
|
+
const dispatch = root?.dispatch;
|
|
51
|
+
const registerTrigger = root?.registerTrigger;
|
|
52
|
+
const itemContext = useMemo(() => ({
|
|
51
53
|
value,
|
|
52
54
|
disabled,
|
|
53
55
|
committedOpen,
|
|
54
56
|
optimisticOpen,
|
|
55
|
-
pending
|
|
56
|
-
|
|
57
|
+
pending,
|
|
58
|
+
library: isLibrary,
|
|
59
|
+
dispatch,
|
|
60
|
+
registerTrigger
|
|
61
|
+
}), [
|
|
62
|
+
value,
|
|
63
|
+
disabled,
|
|
64
|
+
committedOpen,
|
|
65
|
+
optimisticOpen,
|
|
66
|
+
pending,
|
|
67
|
+
isLibrary,
|
|
68
|
+
dispatch,
|
|
69
|
+
registerTrigger
|
|
70
|
+
]);
|
|
57
71
|
const element = useRender({
|
|
58
72
|
defaultElement: "details",
|
|
59
73
|
render,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-item.mjs","names":[],"sources":["../../src/accordion/accordion-item.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useState } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n SyntheticEvent,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext, AccordionRootContext } from \"./accordion-context.js\";\nimport type { AccordionItemContextValue } from \"./accordion-context.js\";\n\nexport interface AccordionItemState {\n readonly value: string;\n /** Optimistic open state. `undefined` in JS-free mode (browser-owned, unknown to React). */\n readonly open: boolean | undefined;\n /** This item's open state is changing through an in-flight action. */\n readonly pending: boolean;\n /** Disabled by its own prop or by the Root. */\n readonly disabled: boolean;\n}\n\nexport interface AccordionItemProps extends Omit<\n ComponentPropsWithRef<\"details\">,\n \"open\" | \"name\" | \"onToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /** Identifies the item in the Root's `value`. Required in both modes. */\n value: string;\n /**\n * JS-free mode only: renders the initial `open` attribute; the browser owns it from then on.\n * Ignored (dev warning) in library mode, where the Root's `defaultValue` / `value` decide.\n */\n defaultOpen?: boolean;\n /**\n * Disables this item: the Trigger renders `aria-disabled` and never toggles. Enforced by a\n * click handler, so in JS-free mode it does not hold before hydration or with JS off.\n */\n disabled?: boolean;\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the `<details>`;\n * animates the commit that ends an action (the `open` attribute flip), not the optimistic\n * `data-open` flip. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionItemState>;\n style?: StateValue<CSSProperties | undefined, AccordionItemState>;\n children?: StateValue<ReactNode, AccordionItemState>;\n /** Must render a `<details>` element. */\n render?: RenderProp<ComponentPropsWithRef<\"details\">, AccordionItemState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * One accordion entry: the `<details>` element. In library mode its `open` attribute follows\n * committed state and `data-open` / `data-pending` follow optimistic state; in JS-free mode (or\n * outside any Root, as a standalone disclosure) the browser owns `open`.\n */\nexport function AccordionItem({\n value,\n defaultOpen = false,\n disabled: disabledProp = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionItemProps) {\n const root = use(AccordionRootContext);\n const isLibrary = root?.mode === \"library\";\n const disabled = disabledProp || (root?.disabled ?? false);\n const committedOpen = isLibrary ? root.committed.has(value) : undefined;\n const optimisticOpen = isLibrary ? root.optimistic.has(value) : undefined;\n const rootPending = root?.pending ?? false;\n const pending = isLibrary && rootPending && committedOpen !== optimisticOpen;\n // JS-free mode: the prop React writes never changes after the first render, so React never\n // rewrites the attribute the browser now owns.\n const [staticOpen] = useState(defaultOpen);\n\n useDevWarning(\n isLibrary && defaultOpen,\n \"Accordion.Item: `defaultOpen` is ignored in library mode. Use `defaultValue` or `value` on \" +\n \"Accordion.Root instead.\",\n );\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Item: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition. Animate `::details-content` with CSS instead.\",\n );\n\n const registerItem = root?.registerItem;\n const libraryRef = useCallback(\n (element: HTMLDetailsElement) => {\n if (isDev && element.tagName !== \"DETAILS\") {\n console.error(\n `Accordion.Item: expected to render a <details> element but got <${element.tagName.toLowerCase()}>. ` +\n \"The platform semantics (toggle, find-in-page, exclusive groups) need a real <details>.\",\n );\n }\n const unregister = registerItem?.(value, element);\n return () => {\n unregister?.();\n };\n },\n [registerItem, value],\n );\n\n // Platform path (design 2.3): the Root reconciles pre-hydration clicks on mount and re-applies\n // committed state once nothing is in flight; this handler reports the browser's own flips.\n const onToggle = isLibrary\n ? (event: SyntheticEvent<HTMLDetailsElement>) => {\n const element = event.currentTarget;\n // Read `details.open`, never `ToggleEvent.newState`: `toggle` is coalesced.\n if (element.open === committedOpen) return; // our own commit echoing back\n if (disabled) {\n // A disabled item cannot be opened by the browser either; close it again now or, while\n // an action is in flight, when the Root re-applies committed state.\n if (!rootPending) element.open = committedOpen === true;\n return;\n }\n root.dispatchFromDom(value, element.open, event.nativeEvent);\n }\n : undefined;\n\n const state: AccordionItemState = { value, open: optimisticOpen, pending, disabled };\n const
|
|
1
|
+
{"version":3,"file":"accordion-item.mjs","names":[],"sources":["../../src/accordion/accordion-item.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useState } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n SyntheticEvent,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext, AccordionRootContext } from \"./accordion-context.js\";\nimport type { AccordionItemContextValue } from \"./accordion-context.js\";\n\nexport interface AccordionItemState {\n readonly value: string;\n /** Optimistic open state. `undefined` in JS-free mode (browser-owned, unknown to React). */\n readonly open: boolean | undefined;\n /** This item's open state is changing through an in-flight action. */\n readonly pending: boolean;\n /** Disabled by its own prop or by the Root. */\n readonly disabled: boolean;\n}\n\nexport interface AccordionItemProps extends Omit<\n ComponentPropsWithRef<\"details\">,\n \"open\" | \"name\" | \"onToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /** Identifies the item in the Root's `value`. Required in both modes. */\n value: string;\n /**\n * JS-free mode only: renders the initial `open` attribute; the browser owns it from then on.\n * Ignored (dev warning) in library mode, where the Root's `defaultValue` / `value` decide.\n */\n defaultOpen?: boolean;\n /**\n * Disables this item: the Trigger renders `aria-disabled` and never toggles. Enforced by a\n * click handler, so in JS-free mode it does not hold before hydration or with JS off.\n */\n disabled?: boolean;\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the `<details>`;\n * animates the commit that ends an action (the `open` attribute flip), not the optimistic\n * `data-open` flip. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionItemState>;\n style?: StateValue<CSSProperties | undefined, AccordionItemState>;\n children?: StateValue<ReactNode, AccordionItemState>;\n /** Must render a `<details>` element. */\n render?: RenderProp<ComponentPropsWithRef<\"details\">, AccordionItemState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * One accordion entry: the `<details>` element. In library mode its `open` attribute follows\n * committed state and `data-open` / `data-pending` follow optimistic state; in JS-free mode (or\n * outside any Root, as a standalone disclosure) the browser owns `open`.\n */\nexport function AccordionItem({\n value,\n defaultOpen = false,\n disabled: disabledProp = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionItemProps) {\n const root = use(AccordionRootContext);\n const isLibrary = root?.mode === \"library\";\n const disabled = disabledProp || (root?.disabled ?? false);\n const committedOpen = isLibrary ? root.committed.has(value) : undefined;\n const optimisticOpen = isLibrary ? root.optimistic.has(value) : undefined;\n const rootPending = root?.pending ?? false;\n const pending = isLibrary && rootPending && committedOpen !== optimisticOpen;\n // JS-free mode: the prop React writes never changes after the first render, so React never\n // rewrites the attribute the browser now owns.\n const [staticOpen] = useState(defaultOpen);\n\n useDevWarning(\n isLibrary && defaultOpen,\n \"Accordion.Item: `defaultOpen` is ignored in library mode. Use `defaultValue` or `value` on \" +\n \"Accordion.Root instead.\",\n );\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Item: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition. Animate `::details-content` with CSS instead.\",\n );\n\n const registerItem = root?.registerItem;\n const libraryRef = useCallback(\n (element: HTMLDetailsElement) => {\n if (isDev && element.tagName !== \"DETAILS\") {\n console.error(\n `Accordion.Item: expected to render a <details> element but got <${element.tagName.toLowerCase()}>. ` +\n \"The platform semantics (toggle, find-in-page, exclusive groups) need a real <details>.\",\n );\n }\n const unregister = registerItem?.(value, element);\n return () => {\n unregister?.();\n };\n },\n [registerItem, value],\n );\n\n // Platform path (design 2.3): the Root reconciles pre-hydration clicks on mount and re-applies\n // committed state once nothing is in flight; this handler reports the browser's own flips.\n const onToggle = isLibrary\n ? (event: SyntheticEvent<HTMLDetailsElement>) => {\n const element = event.currentTarget;\n // Read `details.open`, never `ToggleEvent.newState`: `toggle` is coalesced.\n if (element.open === committedOpen) return; // our own commit echoing back\n if (disabled) {\n // A disabled item cannot be opened by the browser either; close it again now or, while\n // an action is in flight, when the Root re-applies committed state.\n if (!rootPending) element.open = committedOpen === true;\n return;\n }\n root.dispatchFromDom(value, element.open, event.nativeEvent);\n }\n : undefined;\n\n const state: AccordionItemState = { value, open: optimisticOpen, pending, disabled };\n const dispatch = root?.dispatch;\n const registerTrigger = root?.registerTrigger;\n // Memoised (#48): changes only with this item's own values, not with every Root change.\n const itemContext = useMemo<AccordionItemContextValue>(\n () => ({\n value,\n disabled,\n committedOpen,\n optimisticOpen,\n pending,\n library: isLibrary,\n dispatch,\n registerTrigger,\n }),\n [value, disabled, committedOpen, optimisticOpen, pending, isLibrary, dispatch, registerTrigger],\n );\n\n const element = useRender<ComponentPropsWithRef<\"details\">, AccordionItemState>({\n defaultElement: \"details\",\n render,\n state,\n className,\n style,\n children,\n ref: libraryRef,\n props: rest,\n // Library-controlled layer: `open`, `name` and `onToggle` are owned by the library in at\n // least one mode, so a `render` element cannot override them either (conventions 1.2).\n libraryProps: {\n open: isLibrary ? committedOpen : staticOpen,\n // A click before hydration changes `open` under React's feet in both modes; the mismatch is\n // expected and reconciled (library mode) or the browser's business (JS-free mode).\n suppressHydrationWarning: true,\n // Owned in both modes: in library mode there is one writer of `open`, so a render element's\n // `name` (a second, platform-level writer) is removed.\n name: !isLibrary ? root?.name : undefined,\n // Only installed in library mode; owning the key in static mode would strip a render\n // element's own `onToggle`.\n ...(isLibrary && { onToggle }),\n \"data-open\": dataAttr(optimisticOpen === true),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n });\n\n return useTransitionBoundary(\n \"Accordion.Item\",\n isLibrary ? transition : undefined,\n <AccordionItemContext value={itemContext}>{element}</AccordionItemContext>,\n );\n}\n\nAccordionItem.displayName = \"AccordionItem\";\n"],"mappings":";;;;;;;;;AA6DA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;AAOvC,SAAgB,cAAc,EAC5B,OACA,cAAc,OACd,UAAU,eAAe,OACzB,YACA,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,OAAO,IAAI,oBAAoB;CACrC,MAAM,YAAY,MAAM,SAAS;CACjC,MAAM,WAAW,iBAAiB,MAAM,YAAY;CACpD,MAAM,gBAAgB,YAAY,KAAK,UAAU,IAAI,KAAK,IAAI,KAAA;CAC9D,MAAM,iBAAiB,YAAY,KAAK,WAAW,IAAI,KAAK,IAAI,KAAA;CAChE,MAAM,cAAc,MAAM,WAAW;CACrC,MAAM,UAAU,aAAa,eAAe,kBAAkB;CAG9D,MAAM,CAAC,cAAc,SAAS,WAAW;CAEzC,cACE,aAAa,aACb,oHAEF;CACA,cACE,CAAC,aAAa,eAAe,KAAA,GAC7B,sKAEF;CAEA,MAAM,eAAe,MAAM;CAC3B,MAAM,aAAa,aAChB,YAAgC;EAC/B,IAAI,SAAS,QAAQ,YAAY,WAC/B,QAAQ,MACN,mEAAmE,QAAQ,QAAQ,YAAY,EAAE,0FAEnG;EAEF,MAAM,aAAa,eAAe,OAAO,OAAO;EAChD,aAAa;GACX,aAAa;EACf;CACF,GACA,CAAC,cAAc,KAAK,CACtB;CAIA,MAAM,WAAW,aACZ,UAA8C;EAC7C,MAAM,UAAU,MAAM;EAEtB,IAAI,QAAQ,SAAS,eAAe;EACpC,IAAI,UAAU;GAGZ,IAAI,CAAC,aAAa,QAAQ,OAAO,kBAAkB;GACnD;EACF;EACA,KAAK,gBAAgB,OAAO,QAAQ,MAAM,MAAM,WAAW;CAC7D,IACA,KAAA;CAEJ,MAAM,QAA4B;EAAE;EAAO,MAAM;EAAgB;EAAS;CAAS;CACnF,MAAM,WAAW,MAAM;CACvB,MAAM,kBAAkB,MAAM;CAE9B,MAAM,cAAc,eACX;EACL;EACA;EACA;EACA;EACA;EACA,SAAS;EACT;EACA;CACF,IACA;EAAC;EAAO;EAAU;EAAe;EAAgB;EAAS;EAAW;EAAU;CAAe,CAChG;CAEA,MAAM,UAAU,UAAgE;EAC9E,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,KAAK;EACL,OAAO;EAGP,cAAc;GACZ,MAAM,YAAY,gBAAgB;GAGlC,0BAA0B;GAG1B,MAAM,CAAC,YAAY,MAAM,OAAO,KAAA;GAGhC,GAAI,aAAa,EAAE,SAAS;GAC5B,aAAa,SAAS,mBAAmB,IAAI;GAC7C,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;CACF,CAAC;CAED,OAAO,sBACL,kBACA,YAAY,aAAa,KAAA,GACzB,oBAAC,sBAAD;EAAsB,OAAO;EAAc,UAAA;CAA8B,CAAA,CAC3E;AACF;AAEA,cAAc,cAAc"}
|
|
@@ -3,7 +3,7 @@ import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { useRender } from "../internal/use-render.mjs";
|
|
5
5
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
6
|
-
import { AccordionItemContext
|
|
6
|
+
import { AccordionItemContext } from "./accordion-context.mjs";
|
|
7
7
|
import { use } from "react";
|
|
8
8
|
//#region src/accordion/accordion-panel.tsx
|
|
9
9
|
/**
|
|
@@ -11,10 +11,9 @@ import { use } from "react";
|
|
|
11
11
|
* is the parent's `open` attribute (`details:open > .panel`); it renders committed state.
|
|
12
12
|
*/
|
|
13
13
|
function AccordionPanel({ transition, className, style, children, render, ...rest }) {
|
|
14
|
-
const root = use(AccordionRootContext);
|
|
15
14
|
const item = use(AccordionItemContext);
|
|
16
15
|
if (item === null) throw new Error("Accordion.Panel must be rendered inside an Accordion.Item.");
|
|
17
|
-
const isLibrary =
|
|
16
|
+
const isLibrary = item.library;
|
|
18
17
|
useDevWarning(!isLibrary && transition !== void 0, "Accordion.Panel: `transition` has no effect in JS-free mode; the browser toggles the item outside any React transition.");
|
|
19
18
|
const state = {
|
|
20
19
|
value: item.value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-panel.mjs","names":[],"sources":["../../src/accordion/accordion-panel.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext
|
|
1
|
+
{"version":3,"file":"accordion-panel.mjs","names":[],"sources":["../../src/accordion/accordion-panel.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext } from \"./accordion-context.js\";\n\nexport interface AccordionPanelState {\n readonly value: string;\n /** Committed open state. `undefined` in JS-free mode. */\n readonly open: boolean | undefined;\n readonly disabled: boolean;\n}\n\nexport interface AccordionPanelProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the panel's\n * `div`; animates content that mounts or changes in the commit that ends an action.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionPanelState>;\n style?: StateValue<CSSProperties | undefined, AccordionPanelState>;\n /**\n * Always mounted (closed `<details>` content stays in the DOM, indexable and searchable). The\n * function form receives committed `open`, which is how content is mounted lazily and how\n * suspending content keeps the item closed until it is ready.\n */\n children?: StateValue<ReactNode, AccordionPanelState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionPanelState>;\n}\n\n/**\n * The item's content region: a `div` inside the `<details>`, after the Trigger. Its visibility\n * is the parent's `open` attribute (`details:open > .panel`); it renders committed state.\n */\nexport function AccordionPanel({\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionPanelProps) {\n // Only the Item's context (memoised per item, #48).\n const item = use(AccordionItemContext);\n if (item === null) {\n throw new Error(\"Accordion.Panel must be rendered inside an Accordion.Item.\");\n }\n const isLibrary = item.library;\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Panel: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition.\",\n );\n\n const state: AccordionPanelState = {\n value: item.value,\n open: item.committedOpen,\n disabled: item.disabled,\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionPanelState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n \"data-disabled\": dataAttr(item.disabled),\n },\n });\n\n return useTransitionBoundary(\"Accordion.Panel\", isLibrary ? transition : undefined, element);\n}\n\nAccordionPanel.displayName = \"AccordionPanel\";\n"],"mappings":";;;;;;;;;;;;AA8CA,SAAgB,eAAe,EAC7B,YACA,WACA,OACA,UACA,QACA,GAAG,QACmB;CAEtB,MAAM,OAAO,IAAI,oBAAoB;CACrC,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,4DAA4D;CAE9E,MAAM,YAAY,KAAK;CAEvB,cACE,CAAC,aAAa,eAAe,KAAA,GAC7B,yHAEF;CAEA,MAAM,QAA6B;EACjC,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,UAAU,KAAK;CACjB;CAEA,MAAM,UAAU,UAA6D;EAC3E,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc,EACZ,iBAAiB,SAAS,KAAK,QAAQ,EACzC;CACF,CAAC;CAED,OAAO,sBAAsB,mBAAmB,YAAY,aAAa,KAAA,GAAW,OAAO;AAC7F;AAEA,eAAe,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-root.d.mts","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"accordion-root.d.mts","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"mappings":";;;;iBA0BiB;;WAEN;;WAEA;;;iBAIM,oCAAoC;;;;;;WAM1C,QAAQ;;WAER;;WAEA;;UAGD,+BAA+B,KACvC;;EAKA;;;;;;EAMA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;iBAGnC,mCAAmC;;EAElD;;EAEA;;EAEA;;;;;;;;;;;;EAYA,oBAAoB,uBAAuB;;iBAG5B,oCAAoC;;;;;EAKnD;;EAEA;;EAEA;;EAEA,oBAAoB,4BAA4B;;;;;;;;;;YAWtC,qBAAqB,6BAA6B;;;;;wBAqF9C,cAAc,OAAO,qCAAkB,IAAA;yBAAvC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createActionDetails } from "../internal/action-details.mjs";
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { transitionTypes } from "../internal/transition-types.mjs";
|
|
5
|
+
import { useLatest } from "../internal/use-latest.mjs";
|
|
5
6
|
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
6
7
|
import { resolveStateValue, useRender } from "../internal/use-render.mjs";
|
|
7
8
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
@@ -84,7 +85,8 @@ function AccordionRoot(props) {
|
|
|
84
85
|
const [mode] = useState(isLibraryNow ? "library" : "static");
|
|
85
86
|
useDevWarning(mode === "library" !== isLibraryNow, "Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` were all absent on one render and present on another). Switching between JS-free and library mode is not supported.");
|
|
86
87
|
const action = valueChangeAction;
|
|
87
|
-
const
|
|
88
|
+
const controlledKey = value === void 0 ? void 0 : JSON.stringify(value);
|
|
89
|
+
const controlledSet = useMemo(() => controlledKey === void 0 ? void 0 : toSet(JSON.parse(controlledKey)), [controlledKey]);
|
|
88
90
|
const items = useRef(/* @__PURE__ */ new Map());
|
|
89
91
|
const triggers = useRef(/* @__PURE__ */ new Set());
|
|
90
92
|
const registeredValues = () => [...items.current.values()];
|
|
@@ -119,14 +121,16 @@ function AccordionRoot(props) {
|
|
|
119
121
|
open
|
|
120
122
|
}));
|
|
121
123
|
}, [dispatch]);
|
|
122
|
-
const
|
|
123
|
-
dispatchChange(reduce(optimistic, item, open, exclusive), item, open, "activate", event);
|
|
124
|
-
}, [
|
|
125
|
-
dispatchChange,
|
|
124
|
+
const latest = useLatest({
|
|
126
125
|
optimistic,
|
|
127
126
|
exclusive
|
|
128
|
-
|
|
127
|
+
});
|
|
128
|
+
const dispatchFromTrigger = useCallback((item, open, event) => {
|
|
129
|
+
const { optimistic, exclusive } = latest.current;
|
|
130
|
+
dispatchChange(reduce(optimistic, item, open, exclusive), item, open, "activate", event);
|
|
131
|
+
}, [dispatchChange, latest]);
|
|
129
132
|
const dispatchFromDom = useCallback((item, open, event) => {
|
|
133
|
+
const { optimistic, exclusive } = latest.current;
|
|
130
134
|
const base = latestInFlight()?.next ?? optimistic;
|
|
131
135
|
const next = reduce(base, item, open, exclusive);
|
|
132
136
|
if (setsEqual(next, base)) return;
|
|
@@ -134,8 +138,7 @@ function AccordionRoot(props) {
|
|
|
134
138
|
}, [
|
|
135
139
|
dispatchChange,
|
|
136
140
|
latestInFlight,
|
|
137
|
-
|
|
138
|
-
exclusive
|
|
141
|
+
latest
|
|
139
142
|
]);
|
|
140
143
|
const isLibrary = mode === "library";
|
|
141
144
|
const reconcileOnMount = useEffectEvent(() => {
|
|
@@ -181,23 +184,38 @@ function AccordionRoot(props) {
|
|
|
181
184
|
}
|
|
182
185
|
const id = useId();
|
|
183
186
|
const name = !isLibrary && exclusive ? id : void 0;
|
|
187
|
+
const rootPending = isLibrary && pending;
|
|
184
188
|
const state = {
|
|
185
|
-
pending:
|
|
189
|
+
pending: rootPending,
|
|
186
190
|
disabled
|
|
187
191
|
};
|
|
188
|
-
const
|
|
192
|
+
const contextCommitted = isLibrary ? committed : EMPTY;
|
|
193
|
+
const contextOptimistic = isLibrary ? optimistic : EMPTY;
|
|
194
|
+
const context = useMemo(() => ({
|
|
189
195
|
mode,
|
|
190
196
|
exclusive,
|
|
191
197
|
name,
|
|
192
198
|
disabled,
|
|
193
|
-
pending:
|
|
194
|
-
committed:
|
|
195
|
-
optimistic:
|
|
199
|
+
pending: rootPending,
|
|
200
|
+
committed: contextCommitted,
|
|
201
|
+
optimistic: contextOptimistic,
|
|
196
202
|
dispatch: dispatchFromTrigger,
|
|
197
203
|
dispatchFromDom,
|
|
198
204
|
registerItem,
|
|
199
205
|
registerTrigger
|
|
200
|
-
}
|
|
206
|
+
}), [
|
|
207
|
+
mode,
|
|
208
|
+
exclusive,
|
|
209
|
+
name,
|
|
210
|
+
disabled,
|
|
211
|
+
rootPending,
|
|
212
|
+
contextCommitted,
|
|
213
|
+
contextOptimistic,
|
|
214
|
+
dispatchFromTrigger,
|
|
215
|
+
dispatchFromDom,
|
|
216
|
+
registerItem,
|
|
217
|
+
registerTrigger
|
|
218
|
+
]);
|
|
201
219
|
const resolvedStyle = resolveStateValue(style, state);
|
|
202
220
|
const finalStyle = experimental_interpolateSize ? {
|
|
203
221
|
interpolateSize: "allow-keywords",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-root.mjs","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffectEvent,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { AccordionRootContext } from \"./accordion-context.js\";\nimport type {\n AccordionChangeReason,\n AccordionMode,\n AccordionRootContextValue,\n} from \"./accordion-context.js\";\n\nexport interface AccordionRootState {\n /** An action dispatched through this Accordion is in flight (library mode only). */\n readonly pending: boolean;\n /** The Root's `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface AccordionValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user activated a Trigger. `\"platform\"`: the browser changed `open` itself\n * (find-in-page, a text fragment, a click before hydration); `event` is then the `toggle`\n * event, or `undefined` when the mismatch was found on mount.\n */\n readonly reason: AccordionChangeReason;\n /** The item whose open state changed. */\n readonly item: string;\n /** That item's new open state. */\n readonly open: boolean;\n}\n\ninterface AccordionRootBaseProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n // `defaultValue` is also a native HTMLAttributes member; the Accordion's own definition wins.\n \"onKeyDown\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Disables every item: triggers render `aria-disabled` and never dispatch. Default `false`. */\n disabled?: boolean;\n /**\n * Renders `interpolate-size: allow-keywords` on the root so `::details-content` can transition\n * `height` to `auto`. Chromium-only polish today; use the `grid-template-rows` form for a\n * cross-engine animation. Merged with `style`; the consumer's declaration wins.\n */\n experimental_interpolateSize?: boolean;\n className?: StateValue<string | undefined, AccordionRootState>;\n style?: StateValue<CSSProperties | undefined, AccordionRootState>;\n children?: StateValue<ReactNode, AccordionRootState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionRootState>;\n}\n\nexport interface AccordionRootMultipleProps extends AccordionRootBaseProps {\n /** Any number of items may be open. */\n exclusive?: false;\n /** Controlled open items. `undefined` means uncontrolled; `[]` means none open. */\n value?: string[];\n /** Initial open items in uncontrolled library mode. */\n defaultValue?: string[];\n /**\n * Called with the next open items on every change. Runs inside the Root's transition; may be\n * sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n *\n * May also be called with `details.reason === \"platform\"` when the browser opened an item\n * itself (find-in-page, a click before hydration); committed state is re-applied to the DOM\n * once the action settles.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n valueChangeAction?: ChangeAction<string[], AccordionValueChangeDetails>;\n}\n\nexport interface AccordionRootExclusiveProps extends AccordionRootBaseProps {\n /**\n * At most one item open. JS-free mode: renders the same `name` on every `<details>` (the\n * platform's exclusive group). Library mode: the value is a single item or `null`.\n */\n exclusive: true;\n /** Controlled open item. `undefined` means uncontrolled; `null` means none open. */\n value?: string | null;\n /** Initial open item in uncontrolled library mode. */\n defaultValue?: string | null;\n /** See {@link AccordionRootMultipleProps.valueChangeAction}. */\n valueChangeAction?: ChangeAction<string | null, AccordionValueChangeDetails>;\n}\n\n/**\n * Props of `Accordion.Root`, discriminated on `exclusive`.\n *\n * Without `value`, `defaultValue` and `valueChangeAction` the Accordion runs in **JS-free mode**:\n * the browser owns each `<details open>`, nothing runs on toggle, and `exclusive` renders `name`.\n * With any of them it runs in **library mode**: the summary click is intercepted, `open` is driven\n * by committed state inside a transition, and triggers show optimistic `data-open`.\n */\nexport type AccordionRootProps = AccordionRootMultipleProps | AccordionRootExclusiveProps;\n\ntype AccordionValue = string[] | string | null;\ntype OpenSet = ReadonlySet<string>;\n\nconst EMPTY: OpenSet = new Set();\n\nfunction toSet(value: AccordionValue): OpenSet {\n if (value === null) return EMPTY;\n return typeof value === \"string\" ? new Set([value]) : new Set(value);\n}\n\n/**\n * Converts the open set back to the public value. Multiple mode lists the items in registration\n * order (the order the Items mounted, which is document order for a list rendered top to bottom),\n * not in the order they were opened; unregistered values keep their insertion order at the end.\n */\nfunction fromSet(set: OpenSet, exclusive: boolean, order: readonly string[]): AccordionValue {\n if (exclusive) {\n const [first] = set;\n return first ?? null;\n }\n const index = new Map(order.map((v, i) => [v, i] as const));\n return [...set].sort((a, b) => (index.get(a) ?? Infinity) - (index.get(b) ?? Infinity));\n}\n\n/** The optimistic reducer of design 4.1: add/remove one item, or replace when exclusive. */\nfunction reduce(set: OpenSet, item: string, open: boolean, exclusive: boolean): OpenSet {\n if (!open) {\n if (!set.has(item)) return set;\n const next = new Set(set);\n next.delete(item);\n return next;\n }\n if (exclusive) return new Set([item]);\n return set.has(item) ? set : new Set(set).add(item);\n}\n\nfunction setsEqual(a: OpenSet, b: OpenSet): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction byDocumentOrder<T extends Node>(a: T, b: T): number {\n if (a === b) return 0;\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n}\n\nconst NAV_KEYS = new Set([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"]);\n\n/** Writes `open` to the DOM; it fires `toggle`, whose handler sees DOM === committed and returns. */\nfunction applyOpen(element: HTMLDetailsElement, open: boolean): void {\n if (element.open !== open) element.open = open;\n}\n\nfunction isEnabledTrigger(el: HTMLElement): boolean {\n return el.getAttribute(\"aria-disabled\") !== \"true\";\n}\n\n/**\n * Picks the trigger to focus for an arrow/Home/End key: `sorted` is every registered trigger in\n * document order; disabled ones are skipped; arrows wrap. Works when `current` is itself disabled\n * (disabled triggers stay focusable).\n */\nfunction nextTrigger(\n sorted: readonly HTMLElement[],\n current: HTMLElement,\n key: string,\n): HTMLElement | undefined {\n const enabled = sorted.filter(isEnabledTrigger);\n if (enabled.length === 0) return undefined;\n if (key === \"Home\") return enabled[0];\n if (key === \"End\") return enabled[enabled.length - 1];\n const position = sorted.indexOf(current);\n if (key === \"ArrowDown\") {\n return enabled.find((el) => sorted.indexOf(el) > position) ?? enabled[0];\n }\n return enabled.findLast((el) => sorted.indexOf(el) < position) ?? enabled[enabled.length - 1];\n}\n\n/**\n * The accordion container: a `div` that owns the open state, the transition, the context the\n * other parts read, and ArrowUp/ArrowDown/Home/End navigation between triggers.\n */\nexport function AccordionRoot(props: AccordionRootProps) {\n const {\n exclusive = false,\n value,\n defaultValue,\n valueChangeAction,\n disabled = false,\n experimental_interpolateSize = false,\n className,\n style,\n children,\n render,\n ...rest\n } = props;\n\n // Mode is decided once, from the first render's props (design 12).\n const isLibraryNow =\n value !== undefined || defaultValue !== undefined || valueChangeAction !== undefined;\n const [mode] = useState<AccordionMode>(isLibraryNow ? \"library\" : \"static\");\n useDevWarning(\n (mode === \"library\") !== isLibraryNow,\n \"Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Both prop variants are narrowed by `exclusive` at the call site; internally one signature.\n const action = valueChangeAction as\n | ChangeAction<AccordionValue, AccordionValueChangeDetails>\n | undefined;\n\n // Keep the committed set's identity stable while the consumer's `value` is, so `useOptimistic`\n // only re-bases when the controlled value really changed.\n const controlledSet = useMemo(() => (value === undefined ? undefined : toSet(value)), [value]);\n\n const items = useRef(new Map<HTMLDetailsElement, string>());\n const triggers = useRef(new Set<HTMLElement>());\n const registeredValues = () => [...items.current.values()];\n\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n OpenSet,\n AccordionValueChangeDetails\n >({\n controlled: controlledSet,\n defaultValue: defaultValue === undefined ? undefined : toSet(defaultValue),\n initialValue: EMPTY,\n action:\n action && ((next, details) => action(fromSet(next, exclusive, registeredValues()), details)),\n transitionTypes: (_next, _prev, details) =>\n transitionTypes(\"accordion\", details.open ? \"expand\" : \"collapse\"),\n // One item per dispatch, re-based on the committed set, except the mount reconciliation\n // (reason \"platform\", no event), which folds every pre-hydration click into one `next`.\n reducer: (prev, next, details) =>\n details.reason === \"platform\" && details.event === undefined\n ? next\n : reduce(prev, details.item, details.open, exclusive),\n warn: { component: \"Accordion.Root\", prop: \"value\" },\n });\n\n const registerItem = useCallback((itemValue: string, element: HTMLDetailsElement) => {\n items.current.set(element, itemValue);\n return () => {\n items.current.delete(element);\n };\n }, []);\n\n const registerTrigger = useCallback((element: HTMLElement) => {\n triggers.current.add(element);\n return () => {\n triggers.current.delete(element);\n };\n }, []);\n\n const dispatchChange = useCallback(\n (\n next: OpenSet,\n item: string,\n open: boolean,\n reason: AccordionChangeReason,\n event: Event | undefined,\n ): void => {\n dispatch(next, createActionDetails(event, { reason, item, open }));\n },\n [dispatch],\n );\n\n const dispatchFromTrigger = useCallback<AccordionRootContextValue[\"dispatch\"]>(\n (item, open, event) => {\n // `next` comes from the optimistic set: what the user sees and intends (design 2.2).\n dispatchChange(reduce(optimistic, item, open, exclusive), item, open, \"activate\", event);\n },\n // `dispatchChange` closes over the hook's `dispatch`, which is new on every render.\n [dispatchChange, optimistic, exclusive],\n );\n\n const dispatchFromDom = useCallback<AccordionRootContextValue[\"dispatchFromDom\"]>(\n (item, open, event) => {\n // The browser flipped one item (design 2.3). `next` builds on what the user already\n // intends (the latest in-flight dispatch, else the optimistic set), never on the DOM: a\n // pending optimistic open would otherwise be lost to latest-wins. Exclusive mode therefore\n // lets the toggled item win when it opened (walkthrough 9.6).\n const base = latestInFlight()?.next ?? optimistic;\n const next = reduce(base, item, open, exclusive);\n if (setsEqual(next, base)) return;\n dispatchChange(next, item, open, \"platform\", event);\n },\n [dispatchChange, latestInFlight, optimistic, exclusive],\n );\n\n const isLibrary = mode === \"library\";\n\n // Mount reconciliation (design 2.3): clicks before hydration left some `<details open>` out of\n // step with committed state. Every mismatch is folded into ONE dispatch with reason \"platform\"\n // and no event; disabled items are simply put back. Runs once per mount; the StrictMode re-run\n // is deduplicated against the dispatch just made.\n const reconcileOnMount = useEffectEvent(() => {\n const base = latestInFlight()?.next ?? optimistic;\n const mismatches: { item: string; open: boolean }[] = [];\n for (const [element, item] of items.current) {\n const want = committed.has(item);\n if (element.open === want) continue;\n if (element.hasAttribute(\"data-disabled\")) applyOpen(element, want);\n else mismatches.push({ item, open: element.open });\n }\n if (mismatches.length === 0) return;\n // Exclusive mode: the first opened item in registration order wins (the others are re-closed\n // by the re-apply effect); otherwise fold every flip onto the base.\n const opened = exclusive ? mismatches.find((m) => m.open) : undefined;\n const chosen = opened ?? mismatches[0]!;\n const next = opened\n ? new Set([opened.item])\n : mismatches.reduce((set, m) => reduce(set, m.item, m.open, exclusive), base);\n if (setsEqual(next, base)) return;\n dispatchChange(next, chosen.item, chosen.open, \"platform\", undefined);\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // Re-apply committed state to the DOM once nothing is in flight: closes what find-in-page\n // revealed and the parent refused, or what a rejected action left open. Writing `open` fires\n // `toggle`, whose handler sees DOM === committed and does nothing.\n useLayoutEffect(() => {\n if (!isLibrary || pending || latestInFlight() !== null) return;\n for (const [element, item] of items.current) applyOpen(element, committed.has(item));\n }, [isLibrary, pending, committed, latestInFlight]);\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (!NAV_KEYS.has(event.key)) return;\n const target = event.target;\n if (!(target instanceof HTMLElement) || !triggers.current.has(target)) return;\n const sorted = [...triggers.current].sort(byDocumentOrder);\n const next = nextTrigger(sorted, target, event.key);\n if (next === undefined) return;\n event.preventDefault();\n // A nested Root handles its own triggers; the outer one must not move focus again.\n event.stopPropagation();\n next.focus();\n }\n\n const id = useId();\n const name = !isLibrary && exclusive ? id : undefined;\n const state: AccordionRootState = { pending: isLibrary && pending, disabled };\n const rootPending = state.pending;\n const contextCommitted = isLibrary ? committed : EMPTY;\n const contextOptimistic = isLibrary ? optimistic : EMPTY;\n\n // Not memoised, deliberately: `dispatchChange` closes over the hook's `dispatch`, which is a\n // new function on every render of the Root, so a `useMemo` here (design 12) could never reuse\n // its value and the React Compiler lint flags it as unpreservable. Parts re-render with the\n // Root, which is what happens today anyway whenever its state changes.\n const context: AccordionRootContextValue = {\n mode,\n exclusive,\n name,\n disabled,\n pending: rootPending,\n committed: contextCommitted,\n optimistic: contextOptimistic,\n dispatch: dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n };\n\n const resolvedStyle = resolveStateValue(style, state);\n const finalStyle: CSSProperties | undefined = experimental_interpolateSize\n ? { interpolateSize: \"allow-keywords\", ...resolvedStyle }\n : resolvedStyle;\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionRootState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style: finalStyle,\n children,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(disabled),\n onKeyDown,\n },\n });\n\n return <AccordionRootContext value={context}>{element}</AccordionRootContext>;\n}\n\nAccordionRoot.displayName = \"AccordionRoot\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accordion-root.mjs","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffectEvent,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { AccordionRootContext } from \"./accordion-context.js\";\nimport type {\n AccordionChangeReason,\n AccordionMode,\n AccordionRootContextValue,\n} from \"./accordion-context.js\";\n\nexport interface AccordionRootState {\n /** An action dispatched through this Accordion is in flight (library mode only). */\n readonly pending: boolean;\n /** The Root's `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface AccordionValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user activated a Trigger. `\"platform\"`: the browser changed `open` itself\n * (find-in-page, a text fragment, a click before hydration); `event` is then the `toggle`\n * event, or `undefined` when the mismatch was found on mount.\n */\n readonly reason: AccordionChangeReason;\n /** The item whose open state changed. */\n readonly item: string;\n /** That item's new open state. */\n readonly open: boolean;\n}\n\ninterface AccordionRootBaseProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n // `defaultValue` is also a native HTMLAttributes member; the Accordion's own definition wins.\n \"onKeyDown\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Disables every item: triggers render `aria-disabled` and never dispatch. Default `false`. */\n disabled?: boolean;\n /**\n * Renders `interpolate-size: allow-keywords` on the root so `::details-content` can transition\n * `height` to `auto`. Chromium-only polish today; use the `grid-template-rows` form for a\n * cross-engine animation. Merged with `style`; the consumer's declaration wins.\n */\n experimental_interpolateSize?: boolean;\n className?: StateValue<string | undefined, AccordionRootState>;\n style?: StateValue<CSSProperties | undefined, AccordionRootState>;\n children?: StateValue<ReactNode, AccordionRootState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionRootState>;\n}\n\nexport interface AccordionRootMultipleProps extends AccordionRootBaseProps {\n /** Any number of items may be open. */\n exclusive?: false;\n /** Controlled open items. `undefined` means uncontrolled; `[]` means none open. */\n value?: string[];\n /** Initial open items in uncontrolled library mode. */\n defaultValue?: string[];\n /**\n * Called with the next open items on every change. Runs inside the Root's transition; may be\n * sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n *\n * May also be called with `details.reason === \"platform\"` when the browser opened an item\n * itself (find-in-page, a click before hydration); committed state is re-applied to the DOM\n * once the action settles.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n valueChangeAction?: ChangeAction<string[], AccordionValueChangeDetails>;\n}\n\nexport interface AccordionRootExclusiveProps extends AccordionRootBaseProps {\n /**\n * At most one item open. JS-free mode: renders the same `name` on every `<details>` (the\n * platform's exclusive group). Library mode: the value is a single item or `null`.\n */\n exclusive: true;\n /** Controlled open item. `undefined` means uncontrolled; `null` means none open. */\n value?: string | null;\n /** Initial open item in uncontrolled library mode. */\n defaultValue?: string | null;\n /** See {@link AccordionRootMultipleProps.valueChangeAction}. */\n valueChangeAction?: ChangeAction<string | null, AccordionValueChangeDetails>;\n}\n\n/**\n * Props of `Accordion.Root`, discriminated on `exclusive`.\n *\n * Without `value`, `defaultValue` and `valueChangeAction` the Accordion runs in **JS-free mode**:\n * the browser owns each `<details open>`, nothing runs on toggle, and `exclusive` renders `name`.\n * With any of them it runs in **library mode**: the summary click is intercepted, `open` is driven\n * by committed state inside a transition, and triggers show optimistic `data-open`.\n */\nexport type AccordionRootProps = AccordionRootMultipleProps | AccordionRootExclusiveProps;\n\ntype AccordionValue = string[] | string | null;\ntype OpenSet = ReadonlySet<string>;\n\nconst EMPTY: OpenSet = new Set();\n\nfunction toSet(value: AccordionValue): OpenSet {\n if (value === null) return EMPTY;\n return typeof value === \"string\" ? new Set([value]) : new Set(value);\n}\n\n/**\n * Converts the open set back to the public value. Multiple mode lists the items in registration\n * order (the order the Items mounted, which is document order for a list rendered top to bottom),\n * not in the order they were opened; unregistered values keep their insertion order at the end.\n */\nfunction fromSet(set: OpenSet, exclusive: boolean, order: readonly string[]): AccordionValue {\n if (exclusive) {\n const [first] = set;\n return first ?? null;\n }\n const index = new Map(order.map((v, i) => [v, i] as const));\n return [...set].sort((a, b) => (index.get(a) ?? Infinity) - (index.get(b) ?? Infinity));\n}\n\n/** The optimistic reducer of design 4.1: add/remove one item, or replace when exclusive. */\nfunction reduce(set: OpenSet, item: string, open: boolean, exclusive: boolean): OpenSet {\n if (!open) {\n if (!set.has(item)) return set;\n const next = new Set(set);\n next.delete(item);\n return next;\n }\n if (exclusive) return new Set([item]);\n return set.has(item) ? set : new Set(set).add(item);\n}\n\nfunction setsEqual(a: OpenSet, b: OpenSet): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction byDocumentOrder<T extends Node>(a: T, b: T): number {\n if (a === b) return 0;\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n}\n\nconst NAV_KEYS = new Set([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"]);\n\n/** Writes `open` to the DOM; it fires `toggle`, whose handler sees DOM === committed and returns. */\nfunction applyOpen(element: HTMLDetailsElement, open: boolean): void {\n if (element.open !== open) element.open = open;\n}\n\nfunction isEnabledTrigger(el: HTMLElement): boolean {\n return el.getAttribute(\"aria-disabled\") !== \"true\";\n}\n\n/**\n * Picks the trigger to focus for an arrow/Home/End key: `sorted` is every registered trigger in\n * document order; disabled ones are skipped; arrows wrap. Works when `current` is itself disabled\n * (disabled triggers stay focusable).\n */\nfunction nextTrigger(\n sorted: readonly HTMLElement[],\n current: HTMLElement,\n key: string,\n): HTMLElement | undefined {\n const enabled = sorted.filter(isEnabledTrigger);\n if (enabled.length === 0) return undefined;\n if (key === \"Home\") return enabled[0];\n if (key === \"End\") return enabled[enabled.length - 1];\n const position = sorted.indexOf(current);\n if (key === \"ArrowDown\") {\n return enabled.find((el) => sorted.indexOf(el) > position) ?? enabled[0];\n }\n return enabled.findLast((el) => sorted.indexOf(el) < position) ?? enabled[enabled.length - 1];\n}\n\n/**\n * The accordion container: a `div` that owns the open state, the transition, the context the\n * other parts read, and ArrowUp/ArrowDown/Home/End navigation between triggers.\n */\nexport function AccordionRoot(props: AccordionRootProps) {\n const {\n exclusive = false,\n value,\n defaultValue,\n valueChangeAction,\n disabled = false,\n experimental_interpolateSize = false,\n className,\n style,\n children,\n render,\n ...rest\n } = props;\n\n // Mode is decided once, from the first render's props (design 12).\n const isLibraryNow =\n value !== undefined || defaultValue !== undefined || valueChangeAction !== undefined;\n const [mode] = useState<AccordionMode>(isLibraryNow ? \"library\" : \"static\");\n useDevWarning(\n (mode === \"library\") !== isLibraryNow,\n \"Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Both prop variants are narrowed by `exclusive` at the call site; internally one signature.\n const action = valueChangeAction as\n | ChangeAction<AccordionValue, AccordionValueChangeDetails>\n | undefined;\n\n // Keep the committed set's identity stable while the consumer's `value` has the same contents,\n // so `useOptimistic` only re-bases, and the context only changes, when the controlled value\n // really changed. Keyed on the contents, not the array: an inline `value={[\"a\"]}` is a new\n // array on every render. (Order-sensitive; a reordered array only costs one new set.)\n const controlledKey = value === undefined ? undefined : JSON.stringify(value);\n const controlledSet = useMemo(\n () =>\n controlledKey === undefined ? undefined : toSet(JSON.parse(controlledKey) as AccordionValue),\n [controlledKey],\n );\n\n const items = useRef(new Map<HTMLDetailsElement, string>());\n const triggers = useRef(new Set<HTMLElement>());\n const registeredValues = () => [...items.current.values()];\n\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n OpenSet,\n AccordionValueChangeDetails\n >({\n controlled: controlledSet,\n defaultValue: defaultValue === undefined ? undefined : toSet(defaultValue),\n initialValue: EMPTY,\n action:\n action && ((next, details) => action(fromSet(next, exclusive, registeredValues()), details)),\n transitionTypes: (_next, _prev, details) =>\n transitionTypes(\"accordion\", details.open ? \"expand\" : \"collapse\"),\n // One item per dispatch, re-based on the committed set, except the mount reconciliation\n // (reason \"platform\", no event), which folds every pre-hydration click into one `next`.\n reducer: (prev, next, details) =>\n details.reason === \"platform\" && details.event === undefined\n ? next\n : reduce(prev, details.item, details.open, exclusive),\n warn: { component: \"Accordion.Root\", prop: \"value\" },\n });\n\n const registerItem = useCallback((itemValue: string, element: HTMLDetailsElement) => {\n items.current.set(element, itemValue);\n return () => {\n items.current.delete(element);\n };\n }, []);\n\n const registerTrigger = useCallback((element: HTMLElement) => {\n triggers.current.add(element);\n return () => {\n triggers.current.delete(element);\n };\n }, []);\n\n const dispatchChange = useCallback(\n (\n next: OpenSet,\n item: string,\n open: boolean,\n reason: AccordionChangeReason,\n event: Event | undefined,\n ): void => {\n dispatch(next, createActionDetails(event, { reason, item, open }));\n },\n [dispatch],\n );\n\n // The two dispatchers below read the last committed optimistic set through `latest`, so they\n // keep one identity and the Items' contexts are reused across open-state changes (#48): a\n // handler only ever runs against the committed UI, so this is the set it closed over before.\n const latest = useLatest({ optimistic, exclusive });\n\n const dispatchFromTrigger = useCallback<AccordionRootContextValue[\"dispatch\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // `next` comes from the optimistic set: what the user sees and intends (design 2.2).\n dispatchChange(reduce(optimistic, item, open, exclusive), item, open, \"activate\", event);\n },\n [dispatchChange, latest],\n );\n\n const dispatchFromDom = useCallback<AccordionRootContextValue[\"dispatchFromDom\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // The browser flipped one item (design 2.3). `next` builds on what the user already\n // intends (the latest in-flight dispatch, else the optimistic set), never on the DOM: a\n // pending optimistic open would otherwise be lost to latest-wins. Exclusive mode therefore\n // lets the toggled item win when it opened (walkthrough 9.6).\n const base = latestInFlight()?.next ?? optimistic;\n const next = reduce(base, item, open, exclusive);\n if (setsEqual(next, base)) return;\n dispatchChange(next, item, open, \"platform\", event);\n },\n [dispatchChange, latestInFlight, latest],\n );\n\n const isLibrary = mode === \"library\";\n\n // Mount reconciliation (design 2.3): clicks before hydration left some `<details open>` out of\n // step with committed state. Every mismatch is folded into ONE dispatch with reason \"platform\"\n // and no event; disabled items are simply put back. Runs once per mount; the StrictMode re-run\n // is deduplicated against the dispatch just made.\n const reconcileOnMount = useEffectEvent(() => {\n const base = latestInFlight()?.next ?? optimistic;\n const mismatches: { item: string; open: boolean }[] = [];\n for (const [element, item] of items.current) {\n const want = committed.has(item);\n if (element.open === want) continue;\n if (element.hasAttribute(\"data-disabled\")) applyOpen(element, want);\n else mismatches.push({ item, open: element.open });\n }\n if (mismatches.length === 0) return;\n // Exclusive mode: the first opened item in registration order wins (the others are re-closed\n // by the re-apply effect); otherwise fold every flip onto the base.\n const opened = exclusive ? mismatches.find((m) => m.open) : undefined;\n const chosen = opened ?? mismatches[0]!;\n const next = opened\n ? new Set([opened.item])\n : mismatches.reduce((set, m) => reduce(set, m.item, m.open, exclusive), base);\n if (setsEqual(next, base)) return;\n dispatchChange(next, chosen.item, chosen.open, \"platform\", undefined);\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // Re-apply committed state to the DOM once nothing is in flight: closes what find-in-page\n // revealed and the parent refused, or what a rejected action left open. Writing `open` fires\n // `toggle`, whose handler sees DOM === committed and does nothing.\n useLayoutEffect(() => {\n if (!isLibrary || pending || latestInFlight() !== null) return;\n for (const [element, item] of items.current) applyOpen(element, committed.has(item));\n }, [isLibrary, pending, committed, latestInFlight]);\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (!NAV_KEYS.has(event.key)) return;\n const target = event.target;\n if (!(target instanceof HTMLElement) || !triggers.current.has(target)) return;\n const sorted = [...triggers.current].sort(byDocumentOrder);\n const next = nextTrigger(sorted, target, event.key);\n if (next === undefined) return;\n event.preventDefault();\n // A nested Root handles its own triggers; the outer one must not move focus again.\n event.stopPropagation();\n next.focus();\n }\n\n const id = useId();\n const name = !isLibrary && exclusive ? id : undefined;\n const rootPending = isLibrary && pending;\n const state: AccordionRootState = { pending: rootPending, disabled };\n const contextCommitted = isLibrary ? committed : EMPTY;\n const contextOptimistic = isLibrary ? optimistic : EMPTY;\n\n // Memoised (design 12, #48): the dispatchers are stable, so this changes only with the open\n // sets, pending and the props below.\n const context = useMemo<AccordionRootContextValue>(\n () => ({\n mode,\n exclusive,\n name,\n disabled,\n pending: rootPending,\n committed: contextCommitted,\n optimistic: contextOptimistic,\n dispatch: dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n }),\n [\n mode,\n exclusive,\n name,\n disabled,\n rootPending,\n contextCommitted,\n contextOptimistic,\n dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n ],\n );\n\n const resolvedStyle = resolveStateValue(style, state);\n const finalStyle: CSSProperties | undefined = experimental_interpolateSize\n ? { interpolateSize: \"allow-keywords\", ...resolvedStyle }\n : resolvedStyle;\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionRootState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style: finalStyle,\n children,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(disabled),\n onKeyDown,\n },\n });\n\n return <AccordionRootContext value={context}>{element}</AccordionRootContext>;\n}\n\nAccordionRoot.displayName = \"AccordionRoot\";\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { useRender } from "../internal/use-render.mjs";
|
|
5
5
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
6
|
-
import { AccordionItemContext
|
|
6
|
+
import { AccordionItemContext } from "./accordion-context.mjs";
|
|
7
7
|
import { use, useCallback } from "react";
|
|
8
8
|
//#region src/accordion/accordion-trigger.tsx
|
|
9
9
|
const isDev = process.env.NODE_ENV !== "production";
|
|
@@ -26,12 +26,11 @@ function nestedInteractive(target, summary) {
|
|
|
26
26
|
* on a summary, so assistive technology hears the committed state (from `<details open>`).
|
|
27
27
|
*/
|
|
28
28
|
function AccordionTrigger({ transition, className, style, children, render, ...rest }) {
|
|
29
|
-
const root = use(AccordionRootContext);
|
|
30
29
|
const item = use(AccordionItemContext);
|
|
31
30
|
if (item === null) throw new Error("Accordion.Trigger must be rendered inside an Accordion.Item.");
|
|
32
|
-
const isLibrary =
|
|
31
|
+
const isLibrary = item.library;
|
|
33
32
|
useDevWarning(!isLibrary && transition !== void 0, "Accordion.Trigger: `transition` has no effect in JS-free mode; the browser toggles the item outside any React transition.");
|
|
34
|
-
const registerTrigger =
|
|
33
|
+
const registerTrigger = item.registerTrigger;
|
|
35
34
|
const libraryRef = useCallback((element) => {
|
|
36
35
|
if (isDev && element.tagName !== "SUMMARY") console.error(`Accordion.Trigger: expected to render a <summary> element but got <${element.tagName.toLowerCase()}>. Only a <summary> toggles its parent <details>.`);
|
|
37
36
|
const unregister = registerTrigger?.(element);
|
|
@@ -53,7 +52,7 @@ function AccordionTrigger({ transition, className, style, children, render, ...r
|
|
|
53
52
|
}
|
|
54
53
|
event.preventDefault();
|
|
55
54
|
if (item.disabled || !isLibrary) return;
|
|
56
|
-
|
|
55
|
+
item.dispatch?.(item.value, !item.optimisticOpen, event.nativeEvent);
|
|
57
56
|
} : void 0;
|
|
58
57
|
const element = useRender({
|
|
59
58
|
defaultElement: "summary",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-trigger.mjs","names":[],"sources":["../../src/accordion/accordion-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext
|
|
1
|
+
{"version":3,"file":"accordion-trigger.mjs","names":[],"sources":["../../src/accordion/accordion-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext } from \"./accordion-context.js\";\nimport type { AccordionItemState } from \"./accordion-item.js\";\n\n/** The trigger is the item's control; it renders the item's state object. */\nexport type AccordionTriggerState = AccordionItemState;\n\nexport interface AccordionTriggerProps extends Omit<\n ComponentPropsWithRef<\"summary\">,\n \"onClick\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the `<summary>`.\n * Animates only the commit that ends an action (for example a spinner rendered from `pending`\n * disappearing); the optimistic `data-open` flip is a CSS job.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionTriggerState>;\n style?: StateValue<CSSProperties | undefined, AccordionTriggerState>;\n /** Rendered from optimistic state; must not suspend. A heading inside is recommended. */\n children?: StateValue<ReactNode, AccordionTriggerState>;\n /** Must render a `<summary>` element. */\n render?: RenderProp<ComponentPropsWithRef<\"summary\">, AccordionTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nconst NESTED_INTERACTIVE =\n \"a[href], button, label, input, select, textarea, [contenteditable]:not([contenteditable='false'])\";\n\n/**\n * Nested controls with an activation behaviour of their own. The browser runs theirs instead of\n * the summary's (no toggle), so their click must keep its default: navigation, check, submit.\n */\nconst SELF_ACTIVATING =\n \"a[href], button, label, input:is([type='button'], [type='submit'], [type='reset'], \" +\n \"[type='image'], [type='checkbox'], [type='radio'], [type='file'], [type='color'])\";\n\n/** The interactive descendant of the summary that was clicked (a link in the heading), if any. */\nfunction nestedInteractive(target: EventTarget | null, summary: HTMLElement): Element | null {\n if (!(target instanceof Element) || target === summary) return null;\n const interactive = target.closest(NESTED_INTERACTIVE);\n return interactive !== null && interactive !== summary && summary.contains(interactive)\n ? interactive\n : null;\n}\n\n/**\n * The item's disclosure control: the `<summary>`, which must be the first child of the Item. In\n * library mode the click is intercepted and dispatched through the Root's transition; the\n * element shows optimistic `data-open` and `data-pending`. The platform forbids `aria-expanded`\n * on a summary, so assistive technology hears the committed state (from `<details open>`).\n */\nexport function AccordionTrigger({\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionTriggerProps) {\n // Only the Item's context (memoised per item, #48), which carries what is needed of the Root's.\n const item = use(AccordionItemContext);\n if (item === null) {\n throw new Error(\"Accordion.Trigger must be rendered inside an Accordion.Item.\");\n }\n const isLibrary = item.library;\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Trigger: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition.\",\n );\n\n const registerTrigger = item.registerTrigger;\n const libraryRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && element.tagName !== \"SUMMARY\") {\n console.error(\n `Accordion.Trigger: expected to render a <summary> element but got <${element.tagName.toLowerCase()}>. ` +\n \"Only a <summary> toggles its parent <details>.\",\n );\n }\n const unregister = registerTrigger?.(element);\n // Always return a cleanup so the merged ref never calls back with `null`.\n return () => {\n unregister?.();\n };\n },\n [registerTrigger],\n );\n\n const state: AccordionTriggerState = {\n value: item.value,\n open: item.optimisticOpen,\n pending: item.pending,\n disabled: item.disabled,\n };\n\n // JS-free mode attaches no click handler unless the item is disabled (design 2.1 / section 6).\n const onClick =\n isLibrary || item.disabled\n ? (event: MouseEvent<HTMLElement>) => {\n const nested = nestedInteractive(event.target, event.currentTarget);\n if (nested !== null) {\n // A nested control never toggles. Controls without their own activation behaviour\n // (text input, select, textarea, contenteditable) would let the click fall through to\n // the summary's toggle, so cancel it; focus and pickers happen on mousedown and are\n // unaffected. Self-activating controls already pre-empt the summary's behaviour.\n if (!nested.matches(SELF_ACTIVATING)) event.preventDefault();\n return;\n }\n // Cancelling the click is what keeps the browser from touching `open`.\n event.preventDefault();\n if (item.disabled || !isLibrary) return;\n item.dispatch?.(item.value, !item.optimisticOpen, event.nativeEvent);\n }\n : undefined;\n\n const element = useRender<ComponentPropsWithRef<\"summary\">, AccordionTriggerState>({\n defaultElement: \"summary\",\n render,\n state,\n className,\n style,\n children,\n ref: libraryRef,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"aria-disabled\": item.disabled ? true : undefined,\n \"data-open\": dataAttr(item.optimisticOpen === true),\n \"data-pending\": dataAttr(item.pending),\n \"data-disabled\": dataAttr(item.disabled),\n // Only owned when installed, so a render element keeps its own `onClick` in JS-free mode.\n ...(onClick && { onClick }),\n },\n });\n\n return useTransitionBoundary(\"Accordion.Trigger\", isLibrary ? transition : undefined, element);\n}\n\nAccordionTrigger.displayName = \"AccordionTrigger\";\n"],"mappings":";;;;;;;;AA2CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,MAAM,qBACJ;;;;;AAMF,MAAM,kBACJ;;AAIF,SAAS,kBAAkB,QAA4B,SAAsC;CAC3F,IAAI,EAAE,kBAAkB,YAAY,WAAW,SAAS,OAAO;CAC/D,MAAM,cAAc,OAAO,QAAQ,kBAAkB;CACrD,OAAO,gBAAgB,QAAQ,gBAAgB,WAAW,QAAQ,SAAS,WAAW,IAClF,cACA;AACN;;;;;;;AAQA,SAAgB,iBAAiB,EAC/B,YACA,WACA,OACA,UACA,QACA,GAAG,QACqB;CAExB,MAAM,OAAO,IAAI,oBAAoB;CACrC,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,8DAA8D;CAEhF,MAAM,YAAY,KAAK;CAEvB,cACE,CAAC,aAAa,eAAe,KAAA,GAC7B,2HAEF;CAEA,MAAM,kBAAkB,KAAK;CAC7B,MAAM,aAAa,aAChB,YAAyB;EACxB,IAAI,SAAS,QAAQ,YAAY,WAC/B,QAAQ,MACN,sEAAsE,QAAQ,QAAQ,YAAY,EAAE,kDAEtG;EAEF,MAAM,aAAa,kBAAkB,OAAO;EAE5C,aAAa;GACX,aAAa;EACf;CACF,GACA,CAAC,eAAe,CAClB;CAEA,MAAM,QAA+B;EACnC,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,SAAS,KAAK;EACd,UAAU,KAAK;CACjB;CAGA,MAAM,UACJ,aAAa,KAAK,YACb,UAAmC;EAClC,MAAM,SAAS,kBAAkB,MAAM,QAAQ,MAAM,aAAa;EAClE,IAAI,WAAW,MAAM;GAKnB,IAAI,CAAC,OAAO,QAAQ,eAAe,GAAG,MAAM,eAAe;GAC3D;EACF;EAEA,MAAM,eAAe;EACrB,IAAI,KAAK,YAAY,CAAC,WAAW;EACjC,KAAK,WAAW,KAAK,OAAO,CAAC,KAAK,gBAAgB,MAAM,WAAW;CACrE,IACA,KAAA;CAEN,MAAM,UAAU,UAAmE;EACjF,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,KAAK;EACL,OAAO;EAEP,cAAc;GACZ,iBAAiB,KAAK,WAAW,OAAO,KAAA;GACxC,aAAa,SAAS,KAAK,mBAAmB,IAAI;GAClD,gBAAgB,SAAS,KAAK,OAAO;GACrC,iBAAiB,SAAS,KAAK,QAAQ;GAEvC,GAAI,WAAW,EAAE,QAAQ;EAC3B;CACF,CAAC;CAED,OAAO,sBAAsB,qBAAqB,YAAY,aAAa,KAAA,GAAW,OAAO;AAC/F;AAEA,iBAAiB,cAAc"}
|
|
@@ -11,7 +11,7 @@ export interface CheckboxState {
|
|
|
11
11
|
readonly indeterminate: boolean;
|
|
12
12
|
/** An action dispatched by this Checkbox is in flight (including the resulting render). */
|
|
13
13
|
readonly pending: boolean;
|
|
14
|
-
/** The `disabled` prop. */
|
|
14
|
+
/** The `disabled` prop (or the enclosing `Field.Root`'s). */
|
|
15
15
|
readonly disabled: boolean;
|
|
16
16
|
}
|
|
17
17
|
/** Details passed as the second argument of `checkedChangeAction`. */
|
|
@@ -79,12 +79,14 @@ export interface CheckboxProps extends Omit<ComponentPropsWithRef<"input">, "typ
|
|
|
79
79
|
*
|
|
80
80
|
* Owned keys (a `render` element's value is overridden): `type`, `checked`, `defaultChecked`,
|
|
81
81
|
* `indeterminate` (stripped), `aria-checked` (always removed), `disabled`, `data-checked`,
|
|
82
|
-
* `data-indeterminate`, `data-pending`, `data-disabled
|
|
82
|
+
* `data-indeterminate`, `data-pending`, `data-disabled`; inside a `Field.Root` also `id`,
|
|
83
|
+
* `name`, `required`, `aria-invalid`, `aria-describedby` (token-merged) and `data-invalid`, and
|
|
84
|
+
* `disabled` / `name` / `required` default from the Field. `role` passes through. Event handlers
|
|
83
85
|
* are merged rather than owned: a `render` element's own `onChange` (or `onClick`) runs first,
|
|
84
86
|
* then the library's; an `onClick` that calls `preventDefault()` cancels the change (nothing is
|
|
85
87
|
* dispatched).
|
|
86
88
|
*/
|
|
87
|
-
export declare function Checkbox({ checked, defaultChecked, indeterminate, checkedChangeAction, disabled, transition, className, style, render, ...rest }: CheckboxProps): import("react").ReactNode;
|
|
89
|
+
export declare function Checkbox({ checked, defaultChecked, indeterminate, checkedChangeAction, disabled: disabledProp, transition, className, style, render, ...rest }: CheckboxProps): import("react").ReactNode;
|
|
88
90
|
export declare namespace Checkbox {
|
|
89
91
|
var displayName: string;
|
|
90
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.d.mts","names":[],"sources":["../../src/checkbox/checkbox.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"checkbox.d.mts","names":[],"sources":["../../src/checkbox/checkbox.tsx"],"mappings":";;;iBAuBiB;;WAEN;;;;;WAKA;;WAEA;;WAEA;;;iBAIM,qCAAqC;;;;;;;WAO3C,QAAQ;;iBAGF,sBAAsB,KACrC;;EAWA;;;;;EAKA;;;;;;;;EAQA;;;;;;;;;;EAUA,sBAAsB,sBAAsB;;EAE5C;;EAEA;;;;;;;;;;EAUA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;;;;EAK9C,SAAS,WAAW,gCAAgC;;;;;;;;;;;;;;;;wBAiBtC,WACd,SACA,gBACA,eACA,qBACA,UAAU,cACV,YACA,WACA,OACA,WACG,QACF,gCAAa;yBAXA"}
|
|
@@ -5,6 +5,7 @@ import { dataAttr } from "../internal/data-attr.mjs";
|
|
|
5
5
|
import { transitionTypes } from "../internal/transition-types.mjs";
|
|
6
6
|
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
7
7
|
import { useRender } from "../internal/use-render.mjs";
|
|
8
|
+
import { fieldInputProps, useFieldControl, useFieldValueChange, useMergedRef } from "../internal/use-field-control.mjs";
|
|
8
9
|
import { guardCheckableChange, useNativeChecked } from "../internal/use-native-checked.mjs";
|
|
9
10
|
//#region src/checkbox/checkbox.tsx
|
|
10
11
|
/**
|
|
@@ -14,12 +15,23 @@ import { guardCheckableChange, useNativeChecked } from "../internal/use-native-c
|
|
|
14
15
|
*
|
|
15
16
|
* Owned keys (a `render` element's value is overridden): `type`, `checked`, `defaultChecked`,
|
|
16
17
|
* `indeterminate` (stripped), `aria-checked` (always removed), `disabled`, `data-checked`,
|
|
17
|
-
* `data-indeterminate`, `data-pending`, `data-disabled
|
|
18
|
+
* `data-indeterminate`, `data-pending`, `data-disabled`; inside a `Field.Root` also `id`,
|
|
19
|
+
* `name`, `required`, `aria-invalid`, `aria-describedby` (token-merged) and `data-invalid`, and
|
|
20
|
+
* `disabled` / `name` / `required` default from the Field. `role` passes through. Event handlers
|
|
18
21
|
* are merged rather than owned: a `render` element's own `onChange` (or `onClick`) runs first,
|
|
19
22
|
* then the library's; an `onClick` that calls `preventDefault()` cancels the change (nothing is
|
|
20
23
|
* dispatched).
|
|
21
24
|
*/
|
|
22
|
-
function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChangeAction, disabled
|
|
25
|
+
function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChangeAction, disabled: disabledProp, transition, className, style, render, ...rest }) {
|
|
26
|
+
const field = useFieldControl({
|
|
27
|
+
labelable: true,
|
|
28
|
+
name: rest.name,
|
|
29
|
+
disabled: disabledProp,
|
|
30
|
+
required: rest.required,
|
|
31
|
+
id: rest.id,
|
|
32
|
+
component: "Checkbox"
|
|
33
|
+
});
|
|
34
|
+
const disabled = field.disabled;
|
|
23
35
|
const { optimistic, pending, dispatch, latestInFlight } = useControllableAction({
|
|
24
36
|
controlled: checked,
|
|
25
37
|
defaultValue: defaultChecked,
|
|
@@ -31,6 +43,7 @@ function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChang
|
|
|
31
43
|
prop: "checked"
|
|
32
44
|
}
|
|
33
45
|
});
|
|
46
|
+
useFieldValueChange(optimistic);
|
|
34
47
|
const shownIndeterminate = indeterminate && !pending;
|
|
35
48
|
const inputRef = useNativeChecked({
|
|
36
49
|
type: "checkbox",
|
|
@@ -41,21 +54,24 @@ function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChang
|
|
|
41
54
|
latestInFlight,
|
|
42
55
|
onPlatformChange: (next, event) => dispatch(next, createActionDetails(event, { reason: "platform" }))
|
|
43
56
|
});
|
|
57
|
+
const state = {
|
|
58
|
+
checked: optimistic,
|
|
59
|
+
indeterminate: shownIndeterminate,
|
|
60
|
+
pending,
|
|
61
|
+
disabled
|
|
62
|
+
};
|
|
63
|
+
const ref = useMergedRef(inputRef, field.ref);
|
|
44
64
|
const element = useRender({
|
|
45
65
|
defaultElement: "input",
|
|
46
66
|
render,
|
|
47
|
-
state
|
|
48
|
-
checked: optimistic,
|
|
49
|
-
indeterminate: shownIndeterminate,
|
|
50
|
-
pending,
|
|
51
|
-
disabled
|
|
52
|
-
},
|
|
67
|
+
state,
|
|
53
68
|
className,
|
|
54
69
|
style,
|
|
55
70
|
children: void 0,
|
|
56
|
-
ref
|
|
71
|
+
ref,
|
|
57
72
|
props: rest,
|
|
58
73
|
libraryProps: {
|
|
74
|
+
...fieldInputProps(field),
|
|
59
75
|
type: "checkbox",
|
|
60
76
|
checked: optimistic,
|
|
61
77
|
defaultChecked: void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.mjs","names":[],"sources":["../../src/checkbox/checkbox.tsx"],"sourcesContent":["\"use client\";\nimport type { ChangeEvent, ComponentPropsWithRef, CSSProperties, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n RenderProp,\n StateValue,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { guardCheckableChange, useNativeChecked } from \"../internal/use-native-checked.js\";\nimport { useRender } from \"../internal/use-render.js\";\n\nexport interface CheckboxState {\n /** Optimistic checked state: what the user sees, including an in-flight change. */\n readonly checked: boolean;\n /**\n * The `indeterminate` prop, suspended while an action is in flight: `false` whenever `pending`\n * is true, so that the attribute, the DOM property and `:indeterminate` always agree.\n */\n readonly indeterminate: boolean;\n /** An action dispatched by this Checkbox is in flight (including the resulting render). */\n readonly pending: boolean;\n /** The `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `checkedChangeAction`. */\nexport interface CheckboxCheckedChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user toggled the box (click, Space, label click); `event` is the native\n * `click` event. `\"platform\"`: the browser changed the value itself: a form reset (`event` is\n * the `reset` event) or a click before hydration / Firefox form-state restoration found on\n * mount (`event` is `undefined`).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface CheckboxProps extends Omit<\n ComponentPropsWithRef<\"input\">,\n | \"type\"\n | \"checked\"\n | \"defaultChecked\"\n | \"onChange\"\n | \"aria-checked\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Controlled checked state. `undefined` means uncontrolled. */\n checked?: boolean;\n /**\n * Initial checked state in uncontrolled mode (default `false`). Also the value a form reset\n * returns to: the latest prop value is mirrored into the input's `defaultChecked`.\n */\n defaultChecked?: boolean;\n /**\n * Controlled-only mixed state (APG tri-state checkbox), derived by the parent from other state;\n * there is no `defaultIndeterminate` and no action. Rendered as `el.indeterminate`,\n * `data-indeterminate` and `:indeterminate`, except while an action is pending. Clicking a\n * mixed box dispatches `checked: true` (for the usual unchecked + mixed representation). Does\n * not affect what the form submits.\n */\n indeterminate?: boolean;\n /**\n * Called with the next checked state on every change. Runs inside the Checkbox's transition;\n * may be sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n * Also called with `details.reason === \"platform\"` for a form reset or a pre-hydration click.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation). `details.event.preventDefault()` is not a veto and is\n * unsupported here; use controlled mode to veto a change.\n */\n checkedChangeAction?: ChangeAction<boolean, CheckboxCheckedChangeDetails>;\n /** Renders a natively disabled input; never dispatches. Default `false`. */\n disabled?: boolean;\n /** Submitted as the input's value when checked. Default `\"on\"` (platform). */\n value?: string;\n /**\n * Forwarded as `default` to the Checkbox's `<ViewTransition>`. Default: no boundary. Animates the\n * commit that ends an action, not the optimistic flip; animate the flip with CSS on `:checked`.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, CheckboxState>;\n style?: StateValue<CSSProperties | undefined, CheckboxState>;\n /**\n * Replace the rendered element (element form) or take over rendering (function form). Must keep\n * an `<input type=\"checkbox\">`.\n */\n render?: RenderProp<ComponentPropsWithRef<\"input\">, CheckboxState>;\n}\n\n/**\n * A checkbox (WAI-ARIA \"Checkbox\" pattern, dual- and mixed-state). Renders\n * `<input type=\"checkbox\">` whose DOM `checked` is the optimistic state; the input carries\n * `data-pending` while `checkedChangeAction` is in flight and takes part in forms natively.\n *\n * Owned keys (a `render` element's value is overridden): `type`, `checked`, `defaultChecked`,\n * `indeterminate` (stripped), `aria-checked` (always removed), `disabled`, `data-checked`,\n * `data-indeterminate`, `data-pending`, `data-disabled`. `role` passes through. Event handlers\n * are merged rather than owned: a `render` element's own `onChange` (or `onClick`) runs first,\n * then the library's; an `onClick` that calls `preventDefault()` cancels the change (nothing is\n * dispatched).\n */\nexport function Checkbox({\n checked,\n defaultChecked,\n indeterminate = false,\n checkedChangeAction,\n disabled = false,\n transition,\n className,\n style,\n render,\n ...rest\n}: CheckboxProps) {\n const { optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n boolean,\n CheckboxCheckedChangeDetails\n >({\n controlled: checked,\n defaultValue: defaultChecked,\n initialValue: false,\n action: checkedChangeAction,\n transitionTypes: (next) => transitionTypes(\"checkbox\", next ? \"check\" : \"uncheck\"),\n warn: { component: \"Checkbox\", prop: \"checked\" },\n });\n\n // Suspended while pending (conventions 4.1): the browser clears the property on the click, and\n // the parent may not have recomputed yet. Restored on settle if the parent still says mixed.\n const shownIndeterminate = indeterminate && !pending;\n\n const inputRef = useNativeChecked({\n type: \"checkbox\",\n component: \"Checkbox\",\n checked: optimistic,\n // Uncontrolled: the latest prop is the reset target. Controlled: React's mount-time value.\n defaultChecked: checked === undefined ? (defaultChecked ?? false) : undefined,\n indeterminate: shownIndeterminate,\n latestInFlight,\n onPlatformChange: (next, event) =>\n dispatch(next, createActionDetails(event, { reason: \"platform\" })),\n });\n\n const state: CheckboxState = {\n checked: optimistic,\n indeterminate: shownIndeterminate,\n pending,\n disabled,\n };\n\n const element = useRender<ComponentPropsWithRef<\"input\">, CheckboxState>({\n defaultElement: \"input\",\n render,\n state,\n className,\n style,\n children: undefined,\n ref: inputRef,\n // Pass-through layer: a `render` element's own props win over these (`role` included).\n props: rest,\n // Library-controlled layer (owned keys, conventions 1.2).\n libraryProps: {\n type: \"checkbox\",\n // The DOM state is the optimistic state (conventions 3.2).\n checked: optimistic,\n defaultChecked: undefined,\n // Not a DOM attribute; the hook sets the property. Owned so a `render` element's own\n // `indeterminate` prop (which react-dom would warn about) is stripped.\n ...({ indeterminate: undefined } as object),\n \"aria-checked\": undefined,\n // Owned so that native disabledness, `data-disabled` and the click guard always agree: a\n // `render` element cannot re-enable (`disabled={false}`) or disable the input on its own.\n disabled,\n \"data-checked\": dataAttr(optimistic),\n \"data-indeterminate\": dataAttr(shownIndeterminate),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n // A natively disabled input fires no click; this covers `render` elements that do not\n // honour `disabled`.\n if (disabled) return;\n const nativeEvent = event.nativeEvent;\n // `indeterminate` plays no part: the platform's rule is \"flip checked, clear mixed\".\n guardCheckableChange(\n nativeEvent,\n () => dispatch(!optimistic, createActionDetails(nativeEvent, { reason: \"activate\" })),\n \"Checkbox\",\n );\n },\n },\n });\n\n return useTransitionBoundary(\"Checkbox\", transition, element);\n}\n\nCheckbox.displayName = \"Checkbox\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiHA,SAAgB,SAAS,EACvB,SACA,gBACA,gBAAgB,OAChB,qBACA,WAAW,OACX,YACA,WACA,OACA,QACA,GAAG,QACa;CAChB,MAAM,EAAE,YAAY,SAAS,UAAU,mBAAmB,sBAGxD;EACA,YAAY;EACZ,cAAc;EACd,cAAc;EACd,QAAQ;EACR,kBAAkB,SAAS,gBAAgB,YAAY,OAAO,UAAU,SAAS;EACjF,MAAM;GAAE,WAAW;GAAY,MAAM;EAAU;CACjD,CAAC;CAID,MAAM,qBAAqB,iBAAiB,CAAC;CAE7C,MAAM,WAAW,iBAAiB;EAChC,MAAM;EACN,WAAW;EACX,SAAS;EAET,gBAAgB,YAAY,KAAA,IAAa,kBAAkB,QAAS,KAAA;EACpE,eAAe;EACf;EACA,mBAAmB,MAAM,UACvB,SAAS,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW,CAAC,CAAC;CACrE,CAAC;CASD,MAAM,UAAU,UAAyD;EACvE,gBAAgB;EAChB;EACA,OAAA;GATA,SAAS;GACT,eAAe;GACf;GACA;EAMI;EACJ;EACA;EACA,UAAU,KAAA;EACV,KAAK;EAEL,OAAO;EAEP,cAAc;GACZ,MAAM;GAEN,SAAS;GACT,gBAAgB,KAAA;GAGV,eAAe,KAAA;GACrB,gBAAgB,KAAA;GAGhB;GACA,gBAAgB,SAAS,UAAU;GACnC,sBAAsB,SAAS,kBAAkB;GACjD,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;GAClC,WAAW,UAAyC;IAGlD,IAAI,UAAU;IACd,MAAM,cAAc,MAAM;IAE1B,qBACE,mBACM,SAAS,CAAC,YAAY,oBAAoB,aAAa,EAAE,QAAQ,WAAW,CAAC,CAAC,GACpF,UACF;GACF;EACF;CACF,CAAC;CAED,OAAO,sBAAsB,YAAY,YAAY,OAAO;AAC9D;AAEA,SAAS,cAAc"}
|
|
1
|
+
{"version":3,"file":"checkbox.mjs","names":[],"sources":["../../src/checkbox/checkbox.tsx"],"sourcesContent":["\"use client\";\nimport type { ChangeEvent, ComponentPropsWithRef, CSSProperties, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n RenderProp,\n StateValue,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport {\n fieldInputProps,\n useFieldControl,\n useFieldValueChange,\n useMergedRef,\n} from \"../internal/use-field-control.js\";\nimport { guardCheckableChange, useNativeChecked } from \"../internal/use-native-checked.js\";\nimport { useRender } from \"../internal/use-render.js\";\n\nexport interface CheckboxState {\n /** Optimistic checked state: what the user sees, including an in-flight change. */\n readonly checked: boolean;\n /**\n * The `indeterminate` prop, suspended while an action is in flight: `false` whenever `pending`\n * is true, so that the attribute, the DOM property and `:indeterminate` always agree.\n */\n readonly indeterminate: boolean;\n /** An action dispatched by this Checkbox is in flight (including the resulting render). */\n readonly pending: boolean;\n /** The `disabled` prop (or the enclosing `Field.Root`'s). */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `checkedChangeAction`. */\nexport interface CheckboxCheckedChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user toggled the box (click, Space, label click); `event` is the native\n * `click` event. `\"platform\"`: the browser changed the value itself: a form reset (`event` is\n * the `reset` event) or a click before hydration / Firefox form-state restoration found on\n * mount (`event` is `undefined`).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface CheckboxProps extends Omit<\n ComponentPropsWithRef<\"input\">,\n | \"type\"\n | \"checked\"\n | \"defaultChecked\"\n | \"onChange\"\n | \"aria-checked\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Controlled checked state. `undefined` means uncontrolled. */\n checked?: boolean;\n /**\n * Initial checked state in uncontrolled mode (default `false`). Also the value a form reset\n * returns to: the latest prop value is mirrored into the input's `defaultChecked`.\n */\n defaultChecked?: boolean;\n /**\n * Controlled-only mixed state (APG tri-state checkbox), derived by the parent from other state;\n * there is no `defaultIndeterminate` and no action. Rendered as `el.indeterminate`,\n * `data-indeterminate` and `:indeterminate`, except while an action is pending. Clicking a\n * mixed box dispatches `checked: true` (for the usual unchecked + mixed representation). Does\n * not affect what the form submits.\n */\n indeterminate?: boolean;\n /**\n * Called with the next checked state on every change. Runs inside the Checkbox's transition;\n * may be sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n * Also called with `details.reason === \"platform\"` for a form reset or a pre-hydration click.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation). `details.event.preventDefault()` is not a veto and is\n * unsupported here; use controlled mode to veto a change.\n */\n checkedChangeAction?: ChangeAction<boolean, CheckboxCheckedChangeDetails>;\n /** Renders a natively disabled input; never dispatches. Default `false`. */\n disabled?: boolean;\n /** Submitted as the input's value when checked. Default `\"on\"` (platform). */\n value?: string;\n /**\n * Forwarded as `default` to the Checkbox's `<ViewTransition>`. Default: no boundary. Animates the\n * commit that ends an action, not the optimistic flip; animate the flip with CSS on `:checked`.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, CheckboxState>;\n style?: StateValue<CSSProperties | undefined, CheckboxState>;\n /**\n * Replace the rendered element (element form) or take over rendering (function form). Must keep\n * an `<input type=\"checkbox\">`.\n */\n render?: RenderProp<ComponentPropsWithRef<\"input\">, CheckboxState>;\n}\n\n/**\n * A checkbox (WAI-ARIA \"Checkbox\" pattern, dual- and mixed-state). Renders\n * `<input type=\"checkbox\">` whose DOM `checked` is the optimistic state; the input carries\n * `data-pending` while `checkedChangeAction` is in flight and takes part in forms natively.\n *\n * Owned keys (a `render` element's value is overridden): `type`, `checked`, `defaultChecked`,\n * `indeterminate` (stripped), `aria-checked` (always removed), `disabled`, `data-checked`,\n * `data-indeterminate`, `data-pending`, `data-disabled`; inside a `Field.Root` also `id`,\n * `name`, `required`, `aria-invalid`, `aria-describedby` (token-merged) and `data-invalid`, and\n * `disabled` / `name` / `required` default from the Field. `role` passes through. Event handlers\n * are merged rather than owned: a `render` element's own `onChange` (or `onClick`) runs first,\n * then the library's; an `onClick` that calls `preventDefault()` cancels the change (nothing is\n * dispatched).\n */\nexport function Checkbox({\n checked,\n defaultChecked,\n indeterminate = false,\n checkedChangeAction,\n disabled: disabledProp,\n transition,\n className,\n style,\n render,\n ...rest\n}: CheckboxProps) {\n // Inside a Field.Root (design/components/field-form.md 7.2) the Field owns the id and the ARIA\n // wiring, and `name` / `required` / `disabled` default from it.\n const field = useFieldControl({\n labelable: true,\n name: rest.name,\n disabled: disabledProp,\n required: rest.required,\n id: rest.id,\n component: \"Checkbox\",\n });\n const disabled = field.disabled;\n\n const { optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n boolean,\n CheckboxCheckedChangeDetails\n >({\n controlled: checked,\n defaultValue: defaultChecked,\n initialValue: false,\n action: checkedChangeAction,\n transitionTypes: (next) => transitionTypes(\"checkbox\", next ? \"check\" : \"uncheck\"),\n warn: { component: \"Checkbox\", prop: \"checked\" },\n });\n // Inside a Field: a value React writes with no event (a rejected action rolling back, a\n // controlled change) re-reads validity and `validate` after the commit.\n useFieldValueChange(optimistic);\n\n // Suspended while pending (conventions 4.1): the browser clears the property on the click, and\n // the parent may not have recomputed yet. Restored on settle if the parent still says mixed.\n const shownIndeterminate = indeterminate && !pending;\n\n const inputRef = useNativeChecked({\n type: \"checkbox\",\n component: \"Checkbox\",\n checked: optimistic,\n // Uncontrolled: the latest prop is the reset target. Controlled: React's mount-time value.\n defaultChecked: checked === undefined ? (defaultChecked ?? false) : undefined,\n indeterminate: shownIndeterminate,\n latestInFlight,\n onPlatformChange: (next, event) =>\n dispatch(next, createActionDetails(event, { reason: \"platform\" })),\n });\n\n const state: CheckboxState = {\n checked: optimistic,\n indeterminate: shownIndeterminate,\n pending,\n disabled,\n };\n\n const ref = useMergedRef<HTMLInputElement>(inputRef, field.ref);\n\n const element = useRender<ComponentPropsWithRef<\"input\">, CheckboxState>({\n defaultElement: \"input\",\n render,\n state,\n className,\n style,\n children: undefined,\n ref,\n // Pass-through layer: a `render` element's own props win over these (`role` included).\n props: rest,\n // Library-controlled layer (owned keys, conventions 1.2).\n libraryProps: {\n ...fieldInputProps(field),\n type: \"checkbox\",\n // The DOM state is the optimistic state (conventions 3.2).\n checked: optimistic,\n defaultChecked: undefined,\n // Not a DOM attribute; the hook sets the property. Owned so a `render` element's own\n // `indeterminate` prop (which react-dom would warn about) is stripped.\n ...({ indeterminate: undefined } as object),\n \"aria-checked\": undefined,\n // Owned so that native disabledness, `data-disabled` and the click guard always agree: a\n // `render` element cannot re-enable (`disabled={false}`) or disable the input on its own.\n disabled,\n \"data-checked\": dataAttr(optimistic),\n \"data-indeterminate\": dataAttr(shownIndeterminate),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n // A natively disabled input fires no click; this covers `render` elements that do not\n // honour `disabled`.\n if (disabled) return;\n const nativeEvent = event.nativeEvent;\n // `indeterminate` plays no part: the platform's rule is \"flip checked, clear mixed\".\n guardCheckableChange(\n nativeEvent,\n () => dispatch(!optimistic, createActionDetails(nativeEvent, { reason: \"activate\" })),\n \"Checkbox\",\n );\n },\n },\n });\n\n return useTransitionBoundary(\"Checkbox\", transition, element);\n}\n\nCheckbox.displayName = \"Checkbox\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyHA,SAAgB,SAAS,EACvB,SACA,gBACA,gBAAgB,OAChB,qBACA,UAAU,cACV,YACA,WACA,OACA,QACA,GAAG,QACa;CAGhB,MAAM,QAAQ,gBAAgB;EAC5B,WAAW;EACX,MAAM,KAAK;EACX,UAAU;EACV,UAAU,KAAK;EACf,IAAI,KAAK;EACT,WAAW;CACb,CAAC;CACD,MAAM,WAAW,MAAM;CAEvB,MAAM,EAAE,YAAY,SAAS,UAAU,mBAAmB,sBAGxD;EACA,YAAY;EACZ,cAAc;EACd,cAAc;EACd,QAAQ;EACR,kBAAkB,SAAS,gBAAgB,YAAY,OAAO,UAAU,SAAS;EACjF,MAAM;GAAE,WAAW;GAAY,MAAM;EAAU;CACjD,CAAC;CAGD,oBAAoB,UAAU;CAI9B,MAAM,qBAAqB,iBAAiB,CAAC;CAE7C,MAAM,WAAW,iBAAiB;EAChC,MAAM;EACN,WAAW;EACX,SAAS;EAET,gBAAgB,YAAY,KAAA,IAAa,kBAAkB,QAAS,KAAA;EACpE,eAAe;EACf;EACA,mBAAmB,MAAM,UACvB,SAAS,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW,CAAC,CAAC;CACrE,CAAC;CAED,MAAM,QAAuB;EAC3B,SAAS;EACT,eAAe;EACf;EACA;CACF;CAEA,MAAM,MAAM,aAA+B,UAAU,MAAM,GAAG;CAE9D,MAAM,UAAU,UAAyD;EACvE,gBAAgB;EAChB;EACA;EACA;EACA;EACA,UAAU,KAAA;EACV;EAEA,OAAO;EAEP,cAAc;GACZ,GAAG,gBAAgB,KAAK;GACxB,MAAM;GAEN,SAAS;GACT,gBAAgB,KAAA;GAGV,eAAe,KAAA;GACrB,gBAAgB,KAAA;GAGhB;GACA,gBAAgB,SAAS,UAAU;GACnC,sBAAsB,SAAS,kBAAkB;GACjD,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;GAClC,WAAW,UAAyC;IAGlD,IAAI,UAAU;IACd,MAAM,cAAc,MAAM;IAE1B,qBACE,mBACM,SAAS,CAAC,YAAY,oBAAoB,aAAa,EAAE,QAAQ,WAAW,CAAC,CAAC,GACpF,UACF;GACF;EACF;CACF,CAAC;CAED,OAAO,sBAAsB,YAAY,YAAY,OAAO;AAC9D;AAEA,SAAS,cAAc"}
|