@bgub/fig-headless 0.0.0-stage → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +595 -2
  4. package/dist/accordion.d.ts +46 -0
  5. package/dist/accordion.js +2 -0
  6. package/dist/accordion.js.map +1 -0
  7. package/dist/changes--uvVfvy8.d.ts +11 -0
  8. package/dist/changes-DglRuvQa.js +2 -0
  9. package/dist/changes-DglRuvQa.js.map +1 -0
  10. package/dist/checkbox.d.ts +24 -0
  11. package/dist/checkbox.js +2 -0
  12. package/dist/checkbox.js.map +1 -0
  13. package/dist/combobox.d.ts +53 -0
  14. package/dist/combobox.js +2 -0
  15. package/dist/combobox.js.map +1 -0
  16. package/dist/command.d.ts +42 -0
  17. package/dist/command.js +2 -0
  18. package/dist/command.js.map +1 -0
  19. package/dist/composite-CG4JghHi.d.ts +4 -0
  20. package/dist/composite-CiKLX-Ti.js +2 -0
  21. package/dist/composite-CiKLX-Ti.js.map +1 -0
  22. package/dist/controllable-value-CDbRzlId.js +2 -0
  23. package/dist/controllable-value-CDbRzlId.js.map +1 -0
  24. package/dist/diagnostics-CoURcwqb.js +2 -0
  25. package/dist/diagnostics-CoURcwqb.js.map +1 -0
  26. package/dist/dialog.d.ts +44 -0
  27. package/dist/dialog.js +2 -0
  28. package/dist/dialog.js.map +1 -0
  29. package/dist/field.d.ts +35 -0
  30. package/dist/field.js +2 -0
  31. package/dist/field.js.map +1 -0
  32. package/dist/form-reset-B7G3-0OT.js +2 -0
  33. package/dist/form-reset-B7G3-0OT.js.map +1 -0
  34. package/dist/hover-HF7JXkR2.js +2 -0
  35. package/dist/hover-HF7JXkR2.js.map +1 -0
  36. package/dist/ids-Cvu36mFL.js +2 -0
  37. package/dist/ids-Cvu36mFL.js.map +1 -0
  38. package/dist/listbox-DcD6dHSg.js +2 -0
  39. package/dist/listbox-DcD6dHSg.js.map +1 -0
  40. package/dist/listbox.d.ts +32 -0
  41. package/dist/listbox.js +2 -0
  42. package/dist/listbox.js.map +1 -0
  43. package/dist/menu/context-menu.d.ts +15 -0
  44. package/dist/menu/context-menu.js +2 -0
  45. package/dist/menu/context-menu.js.map +1 -0
  46. package/dist/menu/submenu.d.ts +20 -0
  47. package/dist/menu/submenu.js +2 -0
  48. package/dist/menu/submenu.js.map +1 -0
  49. package/dist/menu-B4pKuF3z.js +2 -0
  50. package/dist/menu-B4pKuF3z.js.map +1 -0
  51. package/dist/menu.d.ts +52 -0
  52. package/dist/menu.js +1 -0
  53. package/dist/open-state-BmP__rp4.d.ts +6 -0
  54. package/dist/open-state-G79p9WiE.js +2 -0
  55. package/dist/open-state-G79p9WiE.js.map +1 -0
  56. package/dist/part-reference-B7zINPUK.js +2 -0
  57. package/dist/part-reference-B7zINPUK.js.map +1 -0
  58. package/dist/parts-I0jdefyp.js +2 -0
  59. package/dist/parts-I0jdefyp.js.map +1 -0
  60. package/dist/popover-CSPJNmHy.d.ts +37 -0
  61. package/dist/popover-UfJHN1h2.js +2 -0
  62. package/dist/popover-UfJHN1h2.js.map +1 -0
  63. package/dist/popover.d.ts +2 -0
  64. package/dist/popover.js +1 -0
  65. package/dist/popup/position.d.ts +26 -0
  66. package/dist/popup/position.js +2 -0
  67. package/dist/popup/position.js.map +1 -0
  68. package/dist/popup-state-CFSmDEwK.js +2 -0
  69. package/dist/popup-state-CFSmDEwK.js.map +1 -0
  70. package/dist/radio-group.d.ts +52 -0
  71. package/dist/radio-group.js +2 -0
  72. package/dist/radio-group.js.map +1 -0
  73. package/dist/relations-uMHtgW03.js +2 -0
  74. package/dist/relations-uMHtgW03.js.map +1 -0
  75. package/dist/select.d.ts +45 -0
  76. package/dist/select.js +2 -0
  77. package/dist/select.js.map +1 -0
  78. package/dist/slider.d.ts +35 -0
  79. package/dist/slider.js +2 -0
  80. package/dist/slider.js.map +1 -0
  81. package/dist/switch.d.ts +24 -0
  82. package/dist/switch.js +2 -0
  83. package/dist/switch.js.map +1 -0
  84. package/dist/tabs/indicator.d.ts +20 -0
  85. package/dist/tabs/indicator.js +2 -0
  86. package/dist/tabs/indicator.js.map +1 -0
  87. package/dist/tabs.d.ts +49 -0
  88. package/dist/tabs.js +2 -0
  89. package/dist/tabs.js.map +1 -0
  90. package/dist/toast.d.ts +34 -0
  91. package/dist/toast.js +2 -0
  92. package/dist/toast.js.map +1 -0
  93. package/dist/toggle-B0hOxshF.js +2 -0
  94. package/dist/toggle-B0hOxshF.js.map +1 -0
  95. package/dist/toggle-q6B-U4Nq.d.ts +29 -0
  96. package/dist/toolbar.d.ts +25 -0
  97. package/dist/toolbar.js +2 -0
  98. package/dist/toolbar.js.map +1 -0
  99. package/dist/tooltip.d.ts +31 -0
  100. package/dist/tooltip.js +2 -0
  101. package/dist/tooltip.js.map +1 -0
  102. package/dist-development/accordion.js +2 -0
  103. package/dist-development/accordion.js.map +1 -0
  104. package/dist-development/changes-DglRuvQa.js +2 -0
  105. package/dist-development/changes-DglRuvQa.js.map +1 -0
  106. package/dist-development/checkbox.js +2 -0
  107. package/dist-development/checkbox.js.map +1 -0
  108. package/dist-development/combobox.js +2 -0
  109. package/dist-development/combobox.js.map +1 -0
  110. package/dist-development/command.js +2 -0
  111. package/dist-development/command.js.map +1 -0
  112. package/dist-development/composite-DoFLgUjR.js +2 -0
  113. package/dist-development/composite-DoFLgUjR.js.map +1 -0
  114. package/dist-development/controllable-value-CDbRzlId.js +2 -0
  115. package/dist-development/controllable-value-CDbRzlId.js.map +1 -0
  116. package/dist-development/diagnostics-BkQV0MaT.js +2 -0
  117. package/dist-development/diagnostics-BkQV0MaT.js.map +1 -0
  118. package/dist-development/dialog.js +2 -0
  119. package/dist-development/dialog.js.map +1 -0
  120. package/dist-development/field.js +2 -0
  121. package/dist-development/field.js.map +1 -0
  122. package/dist-development/form-reset-B7G3-0OT.js +2 -0
  123. package/dist-development/form-reset-B7G3-0OT.js.map +1 -0
  124. package/dist-development/hover-HF7JXkR2.js +2 -0
  125. package/dist-development/hover-HF7JXkR2.js.map +1 -0
  126. package/dist-development/ids-Cvu36mFL.js +2 -0
  127. package/dist-development/ids-Cvu36mFL.js.map +1 -0
  128. package/dist-development/listbox-B-oe_0c0.js +2 -0
  129. package/dist-development/listbox-B-oe_0c0.js.map +1 -0
  130. package/dist-development/listbox.js +2 -0
  131. package/dist-development/listbox.js.map +1 -0
  132. package/dist-development/menu/context-menu.js +2 -0
  133. package/dist-development/menu/context-menu.js.map +1 -0
  134. package/dist-development/menu/submenu.js +2 -0
  135. package/dist-development/menu/submenu.js.map +1 -0
  136. package/dist-development/menu-DBxBFdYm.js +2 -0
  137. package/dist-development/menu-DBxBFdYm.js.map +1 -0
  138. package/dist-development/menu.js +1 -0
  139. package/dist-development/open-state-G79p9WiE.js +2 -0
  140. package/dist-development/open-state-G79p9WiE.js.map +1 -0
  141. package/dist-development/part-reference-B7zINPUK.js +2 -0
  142. package/dist-development/part-reference-B7zINPUK.js.map +1 -0
  143. package/dist-development/parts-I0jdefyp.js +2 -0
  144. package/dist-development/parts-I0jdefyp.js.map +1 -0
  145. package/dist-development/popover-DXxGM3tO.js +2 -0
  146. package/dist-development/popover-DXxGM3tO.js.map +1 -0
  147. package/dist-development/popover.js +1 -0
  148. package/dist-development/popup/position.js +2 -0
  149. package/dist-development/popup/position.js.map +1 -0
  150. package/dist-development/popup-state-BUYfC2nG.js +2 -0
  151. package/dist-development/popup-state-BUYfC2nG.js.map +1 -0
  152. package/dist-development/radio-group.js +2 -0
  153. package/dist-development/radio-group.js.map +1 -0
  154. package/dist-development/relations-CZpjLINW.js +2 -0
  155. package/dist-development/relations-CZpjLINW.js.map +1 -0
  156. package/dist-development/select.js +2 -0
  157. package/dist-development/select.js.map +1 -0
  158. package/dist-development/slider.js +2 -0
  159. package/dist-development/slider.js.map +1 -0
  160. package/dist-development/switch.js +2 -0
  161. package/dist-development/switch.js.map +1 -0
  162. package/dist-development/tabs/indicator.js +2 -0
  163. package/dist-development/tabs/indicator.js.map +1 -0
  164. package/dist-development/tabs.js +2 -0
  165. package/dist-development/tabs.js.map +1 -0
  166. package/dist-development/toast.js +2 -0
  167. package/dist-development/toast.js.map +1 -0
  168. package/dist-development/toggle-CIbzprg5.js +2 -0
  169. package/dist-development/toggle-CIbzprg5.js.map +1 -0
  170. package/dist-development/toolbar.js +2 -0
  171. package/dist-development/toolbar.js.map +1 -0
  172. package/dist-development/tooltip.js +2 -0
  173. package/dist-development/tooltip.js.map +1 -0
  174. package/package.json +177 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accordion.js","names":[],"sources":["../src/accordion/parts.ts","../src/accordion/accordion.tsx"],"sourcesContent":["import { createMixin, type MixinContext } from \"@bgub/fig\";\nimport { on } from \"@bgub/fig-dom\";\nimport { activateOnClick, bindPart, triggerProps } from \"../internal/parts.ts\";\nimport type { Composite, Orientation } from \"../internal/composite.ts\";\nimport { expectHost } from \"../internal/diagnostics.ts\";\nimport type { createRelations } from \"../internal/relations.ts\";\n\n/** Widget-level state every part reads. Built once per root render. */\nexport interface AccordionPartState {\n readonly disabled: boolean;\n readonly orientation: Orientation;\n readonly registry: Composite;\n readonly relations: ReturnType<typeof createRelations>;\n readonly toggle: (value: unknown, event: Event, trigger: Element) => void;\n}\n\ninterface AccordionTriggerOwnState {\n readonly disabled: boolean;\n readonly open: boolean;\n readonly panelId: string;\n readonly triggerId: string;\n readonly value: unknown;\n}\n\ninterface AccordionPanelOwnState {\n readonly open: boolean;\n readonly panelId: string;\n readonly triggerId: string;\n readonly value: unknown;\n}\n\n// The accordion owns trigger interaction the way the tab list does. Headers\n// keep their place in the tab order, so arrow movement is an extra rather than\n// a roving tab stop.\nexport const accordionMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: AccordionPartState) => ({\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindContainer(node, signal),\n ),\n \"data-disabled\": state.disabled ? \"\" : undefined,\n \"data-fig-accordion\": \"\",\n \"data-orientation\": state.orientation,\n mix: [\n on(\n \"click\",\n activateOnClick(state.registry, (trigger, event) =>\n state.toggle(trigger.value, event, trigger.node),\n ),\n ),\n on(\"keydown\", (event) => {\n if (state.registry.itemAt(event.target) === undefined) return;\n // Enter and Space already reach a native button as a click. Arrow,\n // Home, and End movement is the optional part of the pattern, and it\n // lands on disabled headers so a locked section stays discoverable.\n state.registry.moveFocus(event, {\n loop: false,\n orientation: state.orientation,\n });\n }),\n ],\n }),\n);\n\nexport const accordionTriggerMixin = /* @__PURE__ */ createMixin(\n (\n context: MixinContext,\n state: AccordionPartState,\n own: AccordionTriggerOwnState,\n ) => {\n expectHost(context, \"accordion trigger\", \"button\");\n const disabled = own.disabled || context.props.disabled === true;\n return {\n ...triggerProps(context, { disabled, id: own.triggerId }),\n \"aria-controls\": own.panelId,\n \"aria-expanded\": own.open ? \"true\" : \"false\",\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindItem(node, signal, own.value, disabled),\n ),\n \"data-fig-accordion-trigger\": \"\",\n \"data-open\": own.open ? \"\" : undefined,\n };\n },\n);\n\nexport const accordionPanelMixin = /* @__PURE__ */ createMixin(\n (\n context: MixinContext,\n state: AccordionPartState,\n own: AccordionPanelOwnState,\n ) => ({\n \"aria-labelledby\": own.triggerId,\n bind: bindPart(context, state.registry, (node, signal) =>\n state.relations.bindPanel(node, signal, own.value),\n ),\n \"data-open\": own.open ? \"\" : undefined,\n hidden: own.open ? undefined : true,\n id: context.props.id ?? own.panelId,\n role: \"region\",\n }),\n);\n","import {\n type FigNode,\n type MixinDescriptor,\n useBeforePaint,\n useMemo,\n useStableEvent,\n} from \"@bgub/fig\";\nimport {\n type ChangeDetails,\n createChangeDetails,\n} from \"../internal/changes.ts\";\nimport {\n createComposite,\n type Orientation,\n sameValue,\n} from \"../internal/composite.ts\";\nimport { useControllableValue } from \"../internal/controllable-value.ts\";\nimport { usePartIds } from \"../internal/ids.ts\";\nimport { useRegistrationReconcile } from \"../internal/reconcile.ts\";\nimport { createRelations } from \"../internal/relations.ts\";\nimport {\n accordionMixin,\n accordionPanelMixin,\n accordionTriggerMixin,\n} from \"./parts.ts\";\n\nexport type AccordionOrientation = Orientation;\n\nexport type AccordionValueChangeDetails = ChangeDetails;\n\nexport type AccordionValueChangeHandler<Value = unknown> = (\n values: readonly Value[],\n details: AccordionValueChangeDetails,\n signal: AbortSignal,\n) => void;\n\nexport interface AccordionTriggerOptions {\n disabled?: boolean;\n}\n\nexport interface AccordionOptions<Value = unknown> {\n /** Allows closing the last open panel in single mode. Defaults to `true`. */\n collapsible?: boolean;\n defaultValue?: readonly Value[];\n /** Disables every trigger. */\n disabled?: boolean;\n /** Keeps more than one panel open at a time. */\n multiple?: boolean;\n onValueChange?: AccordionValueChangeHandler<Value>;\n orientation?: AccordionOrientation;\n value?: readonly Value[];\n}\n\nexport interface AccordionParts<Value = unknown> {\n readonly values: readonly Value[];\n isOpen(value: Value): boolean;\n /** Marks a panel the caller keeps mounted; closed panels hide. */\n panel(value: Value): MixinDescriptor;\n root(): MixinDescriptor;\n trigger(value: Value, options?: AccordionTriggerOptions): MixinDescriptor;\n}\n\nexport interface AccordionProps<\n Value = unknown,\n> extends AccordionOptions<Value> {\n children: (accordion: AccordionParts<Value>) => FigNode;\n}\n\n/**\n * Coordinates one accordion: which panels are open, the relationships between\n * headers and regions, and optional arrow movement between headers. Call it in\n * the component that renders the accordion; every element stays the caller's.\n */\nexport function useAccordion<Value = unknown>(\n options: AccordionOptions<Value> = {},\n): AccordionParts<Value> {\n const {\n collapsible = true,\n disabled = false,\n multiple = false,\n orientation = \"vertical\",\n } = options;\n const registrationChanged = useRegistrationReconcile();\n const selection = useControllableValue<readonly Value[]>({\n value: options.value,\n defaultValue: options.defaultValue ?? [],\n onChange: options.onValueChange,\n reconcile: registrationChanged,\n });\n const values = selection.value;\n const registry = useMemo(\n () =>\n createComposite({\n container: \"[data-fig-accordion]\",\n item: \"[data-fig-accordion-trigger]\",\n name: \"accordion\",\n registrationChanged,\n }),\n [],\n );\n const relations = useMemo(\n () => createRelations(registry, registrationChanged),\n [],\n );\n useBeforePaint(() => {\n relations.sync();\n });\n\n const toggle = useStableEvent(\n (value: unknown, event: Event, trigger: Element) => {\n selection.request(\n (current) => {\n const open = current.some((entry) => sameValue(entry, value));\n if (open && !collapsible && !multiple) return current;\n return open\n ? current.filter((entry) => !sameValue(entry, value))\n : multiple\n ? [...current, value as Value]\n : [value as Value];\n },\n createChangeDetails(event, trigger),\n );\n },\n );\n\n const idFor = usePartIds();\n const state = { disabled, orientation, registry, relations, toggle };\n\n function isOpen(value: Value): boolean {\n return values.some((entry) => sameValue(entry, value));\n }\n\n function panel(panelValue: Value): MixinDescriptor {\n return accordionPanelMixin(state, {\n open: isOpen(panelValue),\n panelId: idFor(panelValue, \"panel\"),\n triggerId: idFor(panelValue, \"trigger\"),\n value: panelValue,\n });\n }\n\n return {\n isOpen,\n panel,\n root: () => accordionMixin(state),\n trigger: (triggerValue, triggerOptions = {}) =>\n accordionTriggerMixin(state, {\n disabled: disabled || triggerOptions.disabled === true,\n open: isOpen(triggerValue),\n panelId: idFor(triggerValue, \"panel\"),\n triggerId: idFor(triggerValue, \"trigger\"),\n value: triggerValue,\n }),\n values,\n };\n}\n\n/**\n * {@link useAccordion} as a component, for an accordion that is not already a\n * component of its own. Toggling re-renders this root rather than the caller.\n */\nexport function Accordion<Value = unknown>(\n props: AccordionProps<Value>,\n): FigNode {\n return props.children(useAccordion(props));\n}\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ //#region src/internal/changes.d.ts
2
+ /** The controls every widget hands a change handler. */
3
+ interface ChangeDetails {
4
+ /** The activation event, or `null` for an automatic change. */
5
+ readonly event: Event | null;
6
+ readonly isCanceled: boolean;
7
+ readonly trigger: Element | undefined;
8
+ cancel(): void;
9
+ }
10
+ //#endregion
11
+ export { ChangeDetails as t };
@@ -0,0 +1,2 @@
1
+ function e(e,t){return{cancel(){this.isCanceled=!0},event:e,isCanceled:!1,trigger:t}}export{e as t};
2
+ //# sourceMappingURL=changes-DglRuvQa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes-DglRuvQa.js","names":[],"sources":["../src/internal/changes.ts"],"sourcesContent":["/** The controls every widget hands a change handler. */\nexport interface ChangeDetails {\n /** The activation event, or `null` for an automatic change. */\n readonly event: Event | null;\n readonly isCanceled: boolean;\n readonly trigger: Element | undefined;\n cancel(): void;\n}\n\nexport function createChangeDetails(event: Event | null, trigger?: Element) {\n // Deliberately unannotated so the mutators can write the flags the public\n // interface exposes as readonly.\n const details = {\n cancel() {\n this.isCanceled = true;\n },\n event,\n isCanceled: false,\n trigger,\n };\n return details;\n}\n"],"mappings":"AASA,SAAgB,EAAoB,EAAqB,EAAmB,CAW1E,MAAO,CAPL,QAAS,CACP,KAAK,WAAa,EACpB,EACA,QACA,WAAY,GACZ,SAEW,CACf"}
@@ -0,0 +1,24 @@
1
+ import { i as ToggleControlParts, n as CheckedChangeHandler, r as ToggleControlOptions, t as CheckedChangeDetails } from "./toggle-q6B-U4Nq.js";
2
+ import { FigNode } from "@bgub/fig";
3
+ //#region src/checkbox/checkbox.d.ts
4
+ type CheckboxCheckedChangeDetails = CheckedChangeDetails;
5
+ type CheckboxCheckedChangeHandler = CheckedChangeHandler;
6
+ type CheckboxOptions = ToggleControlOptions;
7
+ type CheckboxParts = ToggleControlParts;
8
+ interface CheckboxProps extends CheckboxOptions {
9
+ children: (checkbox: CheckboxParts) => FigNode;
10
+ }
11
+ /**
12
+ * Coordinates one checkbox over a native input, which owns toggling, focus,
13
+ * Space, form submission, and validity. The widget owns the checked value it
14
+ * reports and writes `indeterminate`, which the platform exposes only as a
15
+ * property.
16
+ */
17
+ declare function useCheckbox(options?: CheckboxOptions): CheckboxParts;
18
+ /**
19
+ * {@link useCheckbox} as a component, for a checkbox that is not already a
20
+ * component of its own.
21
+ */
22
+ declare function Checkbox(props: CheckboxProps): FigNode;
23
+ //#endregion
24
+ export { Checkbox, CheckboxCheckedChangeDetails, CheckboxCheckedChangeHandler, CheckboxOptions, CheckboxParts, CheckboxProps, useCheckbox };
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./toggle-B0hOxshF.js";function t(t={}){return e(t,void 0)}function n(e){return e.children(t(e))}export{n as Checkbox,t as useCheckbox};
2
+ //# sourceMappingURL=checkbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkbox.js","names":[],"sources":["../src/checkbox/checkbox.tsx"],"sourcesContent":["import type { FigNode } from \"@bgub/fig\";\nimport type {\n CheckedChangeDetails,\n CheckedChangeHandler,\n ToggleControlOptions,\n ToggleControlParts,\n} from \"../internal/toggle.ts\";\nimport { useToggleControl } from \"../internal/toggle.ts\";\n\nexport type CheckboxCheckedChangeDetails = CheckedChangeDetails;\n\nexport type CheckboxCheckedChangeHandler = CheckedChangeHandler;\n\nexport type CheckboxOptions = ToggleControlOptions;\n\nexport type CheckboxParts = ToggleControlParts;\n\nexport interface CheckboxProps extends CheckboxOptions {\n children: (checkbox: CheckboxParts) => FigNode;\n}\n\n/**\n * Coordinates one checkbox over a native input, which owns toggling, focus,\n * Space, form submission, and validity. The widget owns the checked value it\n * reports and writes `indeterminate`, which the platform exposes only as a\n * property.\n */\nexport function useCheckbox(options: CheckboxOptions = {}): CheckboxParts {\n return useToggleControl(options, undefined);\n}\n\n/**\n * {@link useCheckbox} as a component, for a checkbox that is not already a\n * component of its own.\n */\nexport function Checkbox(props: CheckboxProps): FigNode {\n return props.children(useCheckbox(props));\n}\n"],"mappings":"yCA2BA,SAAgB,EAAY,EAA2B,CAAC,EAAkB,CACxE,OAAO,EAAiB,EAAS,IAAA,EAAS,CAC5C,CAMA,SAAgB,EAAS,EAA+B,CACtD,OAAO,EAAM,SAAS,EAAY,CAAK,CAAC,CAC1C"}
@@ -0,0 +1,53 @@
1
+ import { t as ChangeDetails } from "./changes--uvVfvy8.js";
2
+ import { n as OpenChangeHandler, t as OpenChangeDetails } from "./open-state-BmP__rp4.js";
3
+ import { FigNode, MixinDescriptor } from "@bgub/fig";
4
+ //#region src/combobox/combobox.d.ts
5
+ type ComboboxOpenChangeDetails = OpenChangeDetails;
6
+ type ComboboxOpenChangeHandler = OpenChangeHandler;
7
+ type ComboboxInputValueChangeDetails = ChangeDetails;
8
+ type ComboboxValueChangeDetails = ChangeDetails;
9
+ type ComboboxInputValueChangeHandler = (value: string, details: ComboboxInputValueChangeDetails, signal: AbortSignal) => void;
10
+ type ComboboxValueChangeHandler<Value = unknown> = (value: Value | null, details: ComboboxValueChangeDetails, signal: AbortSignal) => void;
11
+ interface ComboboxOptionOptions {
12
+ disabled?: boolean;
13
+ /** Text written to the input when this option is selected. */
14
+ textValue?: string;
15
+ }
16
+ interface ComboboxOptions<Value = unknown> {
17
+ /** Render the list in document flow instead of the native top layer. */
18
+ inline?: boolean;
19
+ defaultInputValue?: string;
20
+ defaultOpen?: boolean;
21
+ defaultValue?: Value | null;
22
+ disabled?: boolean;
23
+ /** Converts the selected identity to its submitted value. Defaults to String. */
24
+ getFormValue?: (value: Value) => string;
25
+ id?: string;
26
+ inputValue?: string;
27
+ name?: string;
28
+ onInputValueChange?: ComboboxInputValueChangeHandler;
29
+ onOpenChange?: ComboboxOpenChangeHandler;
30
+ onValueChange?: ComboboxValueChangeHandler<Value>;
31
+ open?: boolean;
32
+ readOnly?: boolean;
33
+ value?: Value | null;
34
+ }
35
+ interface ComboboxParts<Value = unknown> {
36
+ readonly inputValue: string;
37
+ readonly open: boolean;
38
+ readonly value: Value | null;
39
+ hiddenInput(): MixinDescriptor;
40
+ input(): MixinDescriptor;
41
+ option(value: Value, options?: ComboboxOptionOptions): MixinDescriptor;
42
+ popup(): MixinDescriptor;
43
+ setOpen(open: boolean): void;
44
+ }
45
+ interface ComboboxProps<Value = unknown> extends ComboboxOptions<Value> {
46
+ children: (combobox: ComboboxParts<Value>) => FigNode;
47
+ }
48
+ /** Coordinates an editable input with a caller-filtered listbox popup. */
49
+ declare function useCombobox<Value = unknown>(options?: ComboboxOptions<Value>): ComboboxParts<Value>;
50
+ /** {@link useCombobox} as a render-callback component. */
51
+ declare function Combobox<Value = unknown>(props: ComboboxProps<Value>): FigNode;
52
+ //#endregion
53
+ export { Combobox, ComboboxInputValueChangeDetails, ComboboxInputValueChangeHandler, ComboboxOpenChangeDetails, ComboboxOpenChangeHandler, ComboboxOptionOptions, ComboboxOptions, ComboboxParts, ComboboxProps, ComboboxValueChangeDetails, ComboboxValueChangeHandler, useCombobox };
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./changes-DglRuvQa.js";import{a as t,n,r}from"./parts-I0jdefyp.js";import{l as i}from"./diagnostics-CoURcwqb.js";import{i as a}from"./composite-CiKLX-Ti.js";import{n as o,t as s}from"./controllable-value-CDbRzlId.js";import{t as c}from"./ids-Cvu36mFL.js";import{t as l}from"./form-reset-B7G3-0OT.js";import{t as u}from"./part-reference-B7zINPUK.js";import{n as d,t as f}from"./popup-state-CFSmDEwK.js";import{t as p}from"./listbox-DcD6dHSg.js";import{createMixin as m,useBeforePaint as h,useId as g,useMemo as _,useStableEvent as v,useState as y}from"@bgub/fig";import{on as b}from"@bgub/fig-dom";const x=m((e,t)=>{let r=t.disabled||e.props.disabled===!0,i=t.readOnly||e.props.readonly===!0;return{"aria-activedescendant":t.open&&t.highlighted!==null?t.registry.option(t.highlighted)?.node.id??t.idFor(t.highlighted,`option`):void 0,"aria-autocomplete":`list`,"aria-controls":t.popupId,"aria-expanded":t.open?`true`:`false`,"aria-haspopup":`listbox`,"aria-readonly":i?`true`:void 0,bind:n(e,t.registry,t.bindInput),"data-open":t.open?``:void 0,"data-readonly":i?``:void 0,disabled:r?!0:void 0,mix:[b(`click`,e=>{e.defaultPrevented||r||t.requestOpen(!0,e,D(e))}),b(`focusout`,e=>{let n=e.relatedTarget;n instanceof Node&&t.registry.containerNode()?.contains(n)||t.requestOpen(!1,e,D(e))}),b(`input`,e=>{let n=e.currentTarget;n instanceof HTMLInputElement&&t.input(n.value,e,n)}),b(`keydown`,e=>{if(e.defaultPrevented||r||e.isComposing||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)return;if(e.key===`Escape`&&t.open&&!t.inline){e.preventDefault(),t.requestOpen(!1,e,D(e));return}let n=e.key===`ArrowDown`||e.key===`ArrowUp`?t.registry.move(t.highlighted,e.key):void 0;if(n!==void 0){e.preventDefault(),t.setHighlighted(n.value,!0),t.requestOpen(!0,e,D(e));return}if(e.key!==`Enter`||!t.open)return;let a=t.registry.option(t.highlighted);a!==void 0&&(e.preventDefault(),i||t.select(a,e))})],readonly:i?!0:void 0,role:`combobox`,value:t.inputValue}}),S=m((e,t)=>{i(e,t.popupId,`combobox popup`);let r=u(e,`aria-labelledby`);return{...r.props,bind:n(e,t.registry,(n,i)=>{t.labels.set(n,r),e.props[`aria-label`]===void 0&&n.removeAttribute(`aria-label`),t.bindPopup(n,i),t.registry.bindContainer(n,i)}),"data-open":t.open?``:void 0,id:t.popupId,hidden:t.inline?!t.open:void 0,mix:[b(`beforetoggle`,e=>{t.inline||t.nativeToggle(e)}),b(`toggle`,e=>{t.inline||t.nativeToggle(e)}),b(`pointerdown`,e=>{t.registry.optionAt(e.target)!==void 0&&e.preventDefault()}),b(`pointermove`,e=>{if(e.pointerType===`touch`)return;let n=t.registry.optionAt(e.target);n!==void 0&&!n.disabled&&t.setHighlighted(n.value,!1)}),b(`click`,e=>{if(e.defaultPrevented)return;let n=t.registry.optionAt(e.target);n!==void 0&&(n.disabled?e.preventDefault():e.button===0&&t.select(n,e))})],popover:t.inline?void 0:e.props.popover??`auto`,role:`listbox`}}),C=m((e,t,r)=>{let i=r.disabled||e.props.disabled===!0;return{"aria-disabled":i?`true`:void 0,"aria-selected":r.selected?`true`:`false`,bind:n(e,t.registry,(e,n)=>t.registry.bindOption(e,n,{disabled:i,textValue:r.textValue,value:r.value})),"data-disabled":i?``:void 0,"data-highlighted":a(t.highlighted,r.value)?``:void 0,"data-selected":r.selected?``:void 0,id:e.props.id??t.idFor(r.value,`option`),role:`option`,type:e.type===`button`?e.props.type??`button`:void 0,tabindex:-1}}),w=m((e,t)=>({bind:n(e,t.registry,t.bindHiddenInput),disabled:t.disabled?!0:void 0,name:e.props.name??t.name,type:`hidden`,value:e.props.value??t.formValue}));function T(n={}){let{disabled:i=!1,readOnly:u=!1}=n,m=t(),b=o({value:n.value,defaultValue:n.defaultValue??null,onChange:n.onValueChange,equal:a,reconcile:m}),T=o({value:n.inputValue,defaultValue:n.defaultInputValue??``,onChange:n.onInputValueChange,reconcile:m}),E=b.value,D=T.value,[k,A]=y(()=>({value:E})),j=_(()=>p(`combobox`,m),[]),M=_(()=>d(m,`combobox`),[]),{getOpen:N,open:P,requestOpen:F,setOpen:I,nativeToggle:L}=f({...n,requestReconcile:m}),R=g(),z=n.id??`${R}-popup`,B=`--fig-combobox-${R.replaceAll(/[^\w-]/g,`-`)}`,V=c(),H=_(()=>new WeakMap,[]),U=_(()=>({anchored:!1,scrollTo:void 0}),[]),W=v((e,t)=>{t!==void 0&&(U.scrollTo=t?{value:e}:void 0),a(k.value,e)?t&&m():A({value:e})}),G=v((t,n,r)=>{if(n.defaultPrevented||i||u||O(r)){r.value=T.current(),m();return}t!==T.current()&&s(e(n,r),T.propose(()=>t),b.propose(()=>null))&&F(!0,n,r)}),K=v((t,n)=>{if(i||u||t.disabled||O(M.anchor()))return;let r=t.value,a=t.textValue??t.node.textContent?.trim()??``;s(e(n,t.node),b.propose(()=>r),T.propose(()=>a))&&I(!1)}),q=v(()=>{b.reset(),T.reset()}),J=_(()=>l(q),[]);h(e=>{n.inline?U.anchored&&=(M.anchor()?.style.removeProperty(`anchor-name`),M.popup()?.style.removeProperty(`position-anchor`),!1):(M.sync(N(),B),U.anchored=!0);let t=U.scrollTo,i=P?j.options():[],o=i.find(e=>a(e.value,k.value));if(P){let e=i.find(e=>a(e.value,E)),t=o?.disabled===!1?k.value:e?.disabled===!1?E:i.find(e=>!e.disabled)?.value??null;W(t,void 0)}P&&t!==void 0&&a(t.value,k.value)&&o?.disabled===!1?queueMicrotask(()=>{e.aborted||U.scrollTo!==t||(U.scrollTo=void 0,o.node.scrollIntoView?.({block:`nearest`,inline:`nearest`}))}):U.scrollTo=void 0;let s=M.anchor();if(s!==void 0){let e=j.containerNode();if(e!==null){let t=s.getAttribute(`aria-labelledby`)??void 0,n=s.getAttribute(`aria-label`)??(s instanceof HTMLInputElement?[...s.labels??[]].map(e=>e.textContent?.trim()).filter(Boolean).join(` `):void 0);H.get(e)?.sync(e,t)&&r(e,`aria-label`,t?void 0:n)}let t=P&&k.value!==null?o?.node.id??V(k.value,`option`):void 0;r(s,`aria-activedescendant`,t)}});let Y=n.getFormValue??String,X={inline:n.inline===!0,labels:H,bindHiddenInput:J.bind,bindInput:(e,t)=>{M.bindAnchor(e,t),J.bind(e,t)},bindPopup:M.bindPopup,disabled:i,formValue:E===null?``:Y(E),highlighted:k.value,idFor:V,input:G,inputValue:D,name:n.name,open:P,popupId:z,readOnly:u,registry:j,requestOpen:F,nativeToggle:L,select:K,setHighlighted:W,setOpen:I};return{hiddenInput:()=>w(X),input:()=>x(X),inputValue:D,open:P,option:(e,t={})=>C(X,{disabled:t.disabled===!0,selected:E!==null&&a(E,e),textValue:t.textValue,value:e}),popup:()=>S(X),setOpen:I,value:E}}function E(e){return e.children(T(e))}function D(e){return e.currentTarget instanceof Element?e.currentTarget:void 0}function O(e){return e instanceof HTMLInputElement&&(e.readOnly||e.matches(`:disabled`))}export{E as Combobox,T as useCombobox};
2
+ //# sourceMappingURL=combobox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combobox.js","names":[],"sources":["../src/combobox/combobox.tsx"],"sourcesContent":["import {\n createPartReference,\n type PartReference,\n} from \"../internal/part-reference.ts\";\nimport {\n createMixin,\n type FigNode,\n type MixinContext,\n type MixinDescriptor,\n useBeforePaint,\n useId,\n useMemo,\n useStableEvent,\n useState,\n} from \"@bgub/fig\";\nimport { on } from \"@bgub/fig-dom\";\nimport { createAnchoredPopup } from \"../internal/anchored-popup.ts\";\nimport {\n type ChangeDetails,\n createChangeDetails,\n} from \"../internal/changes.ts\";\nimport {\n requestChanges,\n useControllableValue,\n} from \"../internal/controllable-value.ts\";\nimport { sameValue } from \"../internal/composite.ts\";\nimport {\n assertControlLabel,\n expectHost,\n expectPopupId,\n} from \"../internal/diagnostics.ts\";\nimport { createFormReset } from \"../internal/form-reset.ts\";\nimport { usePartIds } from \"../internal/ids.ts\";\nimport { createListbox, type ListboxOption } from \"../internal/listbox.ts\";\nimport type {\n OpenChangeDetails,\n OpenChangeHandler,\n} from \"../internal/open-state.ts\";\nimport { usePopupState } from \"../internal/popup-state.ts\";\nimport { bindPart, setIdReference } from \"../internal/parts.ts\";\nimport { useRegistrationReconcile } from \"../internal/reconcile.ts\";\n\nexport type ComboboxOpenChangeDetails = OpenChangeDetails;\nexport type ComboboxOpenChangeHandler = OpenChangeHandler;\nexport type ComboboxInputValueChangeDetails = ChangeDetails;\nexport type ComboboxValueChangeDetails = ChangeDetails;\n\nexport type ComboboxInputValueChangeHandler = (\n value: string,\n details: ComboboxInputValueChangeDetails,\n signal: AbortSignal,\n) => void;\n\nexport type ComboboxValueChangeHandler<Value = unknown> = (\n value: Value | null,\n details: ComboboxValueChangeDetails,\n signal: AbortSignal,\n) => void;\n\nexport interface ComboboxOptionOptions {\n disabled?: boolean;\n /** Text written to the input when this option is selected. */\n textValue?: string;\n}\n\nexport interface ComboboxOptions<Value = unknown> {\n /** Render the list in document flow instead of the native top layer. */\n inline?: boolean;\n defaultInputValue?: string;\n defaultOpen?: boolean;\n defaultValue?: Value | null;\n disabled?: boolean;\n /** Converts the selected identity to its submitted value. Defaults to String. */\n getFormValue?: (value: Value) => string;\n id?: string;\n inputValue?: string;\n name?: string;\n onInputValueChange?: ComboboxInputValueChangeHandler;\n onOpenChange?: ComboboxOpenChangeHandler;\n onValueChange?: ComboboxValueChangeHandler<Value>;\n open?: boolean;\n readOnly?: boolean;\n value?: Value | null;\n}\n\nexport interface ComboboxParts<Value = unknown> {\n readonly inputValue: string;\n readonly open: boolean;\n readonly value: Value | null;\n hiddenInput(): MixinDescriptor;\n input(): MixinDescriptor;\n option(value: Value, options?: ComboboxOptionOptions): MixinDescriptor;\n popup(): MixinDescriptor;\n setOpen(open: boolean): void;\n}\n\nexport interface ComboboxProps<Value = unknown> extends ComboboxOptions<Value> {\n children: (combobox: ComboboxParts<Value>) => FigNode;\n}\n\ntype ComboboxRegistry = ReturnType<typeof createListbox>;\n\ninterface ComboboxState {\n readonly inline: boolean;\n readonly labels: WeakMap<HTMLElement, PartReference>;\n readonly bindHiddenInput: (node: HTMLElement, signal: AbortSignal) => void;\n readonly bindInput: (node: HTMLElement, signal: AbortSignal) => void;\n readonly bindPopup: (node: HTMLElement, signal: AbortSignal) => void;\n readonly disabled: boolean;\n readonly formValue: string;\n readonly highlighted: unknown;\n readonly idFor: (value: unknown, part: string) => string;\n readonly input: (value: string, event: Event, node: HTMLInputElement) => void;\n readonly inputValue: string;\n readonly name: string | undefined;\n readonly open: boolean;\n readonly nativeToggle: (event: Event) => void;\n readonly popupId: string;\n readonly readOnly: boolean;\n readonly registry: ComboboxRegistry;\n readonly requestOpen: (\n open: boolean,\n event: Event,\n trigger: Element | undefined,\n ) => boolean;\n readonly select: (option: ListboxOption, event: Event) => void;\n readonly setHighlighted: (value: unknown, scroll: boolean) => void;\n readonly setOpen: (open: boolean) => void;\n}\n\ninterface ComboboxOptionState {\n readonly disabled: boolean;\n readonly selected: boolean;\n readonly textValue: string | undefined;\n readonly value: unknown;\n}\n\nconst comboboxInputMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: ComboboxState) => {\n expectHost(context, \"combobox input\", \"input\");\n const disabled = state.disabled || context.props.disabled === true;\n const readOnly = state.readOnly || context.props.readonly === true;\n return {\n \"aria-activedescendant\":\n state.open && state.highlighted !== null\n ? (state.registry.option(state.highlighted)?.node.id ??\n state.idFor(state.highlighted, \"option\"))\n : undefined,\n \"aria-autocomplete\": \"list\",\n \"aria-controls\": state.popupId,\n \"aria-expanded\": state.open ? \"true\" : \"false\",\n \"aria-haspopup\": \"listbox\",\n \"aria-readonly\": readOnly ? \"true\" : undefined,\n bind: bindPart(context, state.registry, state.bindInput),\n \"data-open\": state.open ? \"\" : undefined,\n \"data-readonly\": readOnly ? \"\" : undefined,\n disabled: disabled ? true : undefined,\n mix: [\n on(\"click\", (event) => {\n if (event.defaultPrevented) return;\n if (!disabled) {\n state.requestOpen(true, event, currentElement(event));\n }\n }),\n on(\"focusout\", (event) => {\n const next = event.relatedTarget;\n if (\n next instanceof Node &&\n state.registry.containerNode()?.contains(next)\n ) {\n return;\n }\n state.requestOpen(false, event, currentElement(event));\n }),\n on(\"input\", (event) => {\n const node = event.currentTarget;\n if (node instanceof HTMLInputElement) {\n state.input(node.value, event, node);\n }\n }),\n on(\"keydown\", (event) => {\n if (event.defaultPrevented) return;\n if (\n disabled ||\n event.isComposing ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n ) {\n return;\n }\n if (event.key === \"Escape\" && state.open && !state.inline) {\n event.preventDefault();\n state.requestOpen(false, event, currentElement(event));\n return;\n }\n const moved =\n event.key === \"ArrowDown\" || event.key === \"ArrowUp\"\n ? state.registry.move(state.highlighted, event.key)\n : undefined;\n if (moved !== undefined) {\n event.preventDefault();\n state.setHighlighted(moved.value, true);\n state.requestOpen(true, event, currentElement(event));\n return;\n }\n if (event.key !== \"Enter\" || !state.open) return;\n const option = state.registry.option(state.highlighted);\n if (option === undefined) return;\n // Enter means accepting the highlighted option, even when readonly\n // refuses that action. Do not turn it into an implicit form submit.\n event.preventDefault();\n if (!readOnly) state.select(option, event);\n }),\n ],\n readonly: readOnly ? true : undefined,\n role: \"combobox\",\n value: state.inputValue,\n };\n },\n);\n\nconst comboboxPopupMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: ComboboxState) => {\n expectPopupId(context, state.popupId, \"combobox popup\");\n const label = createPartReference(context, \"aria-labelledby\");\n return {\n ...label.props,\n bind: bindPart(context, state.registry, (node, signal) => {\n state.labels.set(node, label);\n // Clear a previously inherited literal name when the caller switches\n // to a reference. Authored literal names remain the caller's property.\n if (context.props[\"aria-label\"] === undefined)\n node.removeAttribute(\"aria-label\");\n state.bindPopup(node, signal);\n state.registry.bindContainer(node, signal);\n }),\n \"data-open\": state.open ? \"\" : undefined,\n id: state.popupId,\n hidden: state.inline ? !state.open : undefined,\n mix: [\n on(\"beforetoggle\", (event) => {\n if (!state.inline) state.nativeToggle(event);\n }),\n on(\"toggle\", (event) => {\n if (!state.inline) state.nativeToggle(event);\n }),\n on(\"pointerdown\", (event) => {\n if (state.registry.optionAt(event.target) !== undefined) {\n event.preventDefault();\n }\n }),\n on(\"pointermove\", (event) => {\n if (event.pointerType === \"touch\") return;\n const option = state.registry.optionAt(event.target);\n if (option !== undefined && !option.disabled) {\n state.setHighlighted(option.value, false);\n }\n }),\n on(\"click\", (event) => {\n if (event.defaultPrevented) return;\n const option = state.registry.optionAt(event.target);\n if (option === undefined) return;\n if (option.disabled) event.preventDefault();\n else if (event.button === 0) state.select(option, event);\n }),\n ],\n popover: state.inline ? undefined : (context.props.popover ?? \"auto\"),\n role: \"listbox\",\n };\n },\n);\n\nconst comboboxOptionMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: ComboboxState, own: ComboboxOptionState) => {\n const disabled = own.disabled || context.props.disabled === true;\n return {\n \"aria-disabled\": disabled ? \"true\" : undefined,\n \"aria-selected\": own.selected ? \"true\" : \"false\",\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindOption(node, signal, {\n disabled,\n textValue: own.textValue,\n value: own.value,\n }),\n ),\n \"data-disabled\": disabled ? \"\" : undefined,\n \"data-highlighted\": sameValue(state.highlighted, own.value)\n ? \"\"\n : undefined,\n \"data-selected\": own.selected ? \"\" : undefined,\n id: context.props.id ?? state.idFor(own.value, \"option\"),\n role: \"option\",\n type:\n context.type === \"button\"\n ? (context.props.type ?? \"button\")\n : undefined,\n tabindex: -1,\n };\n },\n);\n\nconst comboboxHiddenInputMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: ComboboxState) => {\n expectHost(context, \"combobox hidden input\", \"input\");\n return {\n bind: bindPart(context, state.registry, state.bindHiddenInput),\n disabled: state.disabled ? true : undefined,\n name: context.props.name ?? state.name,\n type: \"hidden\",\n value: context.props.value ?? state.formValue,\n };\n },\n);\n\n/** Coordinates an editable input with a caller-filtered listbox popup. */\nexport function useCombobox<Value = unknown>(\n options: ComboboxOptions<Value> = {},\n): ComboboxParts<Value> {\n const { disabled = false, readOnly = false } = options;\n const requestReconcile = useRegistrationReconcile();\n const selectedValue = useControllableValue<Value | null>({\n value: options.value,\n defaultValue: options.defaultValue ?? null,\n onChange: options.onValueChange,\n equal: sameValue,\n reconcile: requestReconcile,\n });\n const query = useControllableValue({\n value: options.inputValue,\n defaultValue: options.defaultInputValue ?? \"\",\n onChange: options.onInputValueChange,\n reconcile: requestReconcile,\n });\n const value = selectedValue.value;\n const inputValue = query.value;\n const [highlighted, setHighlightedState] = useState<{\n readonly value: unknown;\n }>(() => ({ value }));\n const registry = useMemo(\n () => createListbox(\"combobox\", requestReconcile),\n [],\n );\n const popup = useMemo(\n () => createAnchoredPopup(requestReconcile, \"combobox\"),\n [],\n );\n const { getOpen, open, requestOpen, setOpen, nativeToggle } = usePopupState({\n ...options,\n requestReconcile,\n });\n const id = useId();\n const popupId = options.id ?? `${id}-popup`;\n const anchorName = `--fig-combobox-${id.replaceAll(/[^\\w-]/g, \"-\")}`;\n const idFor = usePartIds();\n const labels = useMemo(() => new WeakMap<HTMLElement, PartReference>(), []);\n const trackers = useMemo(\n () => ({\n anchored: false,\n scrollTo: undefined as { value: unknown } | undefined,\n }),\n [],\n );\n const setHighlighted = useStableEvent(\n (next: unknown, scroll: boolean | undefined) => {\n if (scroll !== undefined)\n trackers.scrollTo = scroll ? { value: next } : undefined;\n if (!sameValue(highlighted.value, next)) {\n setHighlightedState({ value: next });\n } else if (scroll) requestReconcile();\n },\n );\n const changeInput = useStableEvent(\n (next: string, event: Event, node: HTMLInputElement) => {\n if (\n event.defaultPrevented ||\n disabled ||\n readOnly ||\n inputUnavailable(node)\n ) {\n node.value = query.current();\n requestReconcile();\n return;\n }\n if (next === query.current()) return;\n const accepted = requestChanges(\n createChangeDetails(event, node),\n query.propose(() => next),\n selectedValue.propose(() => null),\n );\n if (!accepted) return;\n requestOpen(true, event, node);\n },\n );\n const select = useStableEvent((option: ListboxOption, event: Event) => {\n if (\n disabled ||\n readOnly ||\n option.disabled ||\n inputUnavailable(popup.anchor())\n )\n return;\n const nextValue = option.value as Value;\n const nextInput = option.textValue ?? option.node.textContent?.trim() ?? \"\";\n const accepted = requestChanges(\n createChangeDetails(event, option.node),\n selectedValue.propose(() => nextValue),\n query.propose(() => nextInput),\n );\n if (!accepted) return;\n setOpen(false);\n });\n const reset = useStableEvent(() => {\n selectedValue.reset();\n query.reset();\n });\n const formReset = useMemo(() => createFormReset(reset), []);\n\n useBeforePaint((signal) => {\n if (options.inline) {\n if (trackers.anchored) {\n popup.anchor()?.style.removeProperty(\"anchor-name\");\n popup.popup()?.style.removeProperty(\"position-anchor\");\n trackers.anchored = false;\n }\n } else {\n popup.sync(getOpen(), anchorName);\n trackers.anchored = true;\n }\n // Read live DOM order once per reconciliation rather than scanning it for\n // each selected/highlighted lookup. Large inline lists reconcile on edits.\n const scrollTo = trackers.scrollTo;\n const mounted = open ? registry.options() : [];\n const highlightedOption = mounted.find((entry) =>\n sameValue(entry.value, highlighted.value),\n );\n if (open) {\n const selectedOption = mounted.find((entry) =>\n sameValue(entry.value, value),\n );\n const next =\n highlightedOption?.disabled === false\n ? highlighted.value\n : selectedOption?.disabled === false\n ? value\n : (mounted.find((entry) => !entry.disabled)?.value ?? null);\n setHighlighted(next, undefined);\n }\n // Keyboard navigation keeps virtual focus on the input, so it must scroll\n // the active option explicitly after the popup becomes visible. Pointer\n // highlights never scroll, avoiding hover/scroll feedback.\n if (\n open &&\n scrollTo !== undefined &&\n sameValue(scrollTo.value, highlighted.value) &&\n highlightedOption?.disabled === false\n ) {\n // Positioning hooks may constrain the popup later in this commit.\n queueMicrotask(() => {\n if (signal.aborted || trackers.scrollTo !== scrollTo) return;\n trackers.scrollTo = undefined;\n highlightedOption.node.scrollIntoView?.({\n block: \"nearest\",\n inline: \"nearest\",\n });\n });\n } else trackers.scrollTo = undefined;\n const input = popup.anchor();\n if (input !== undefined) {\n assertControlLabel(input);\n const popupNode = registry.containerNode();\n if (popupNode !== null) {\n const labelledBy = input.getAttribute(\"aria-labelledby\") ?? undefined;\n const label =\n input.getAttribute(\"aria-label\") ??\n (input instanceof HTMLInputElement\n ? [...(input.labels ?? [])]\n .map((label) => label.textContent?.trim())\n .filter(Boolean)\n .join(\" \")\n : undefined);\n // Referencing a textbox itself can name the listbox after its current\n // value. Reuse its label sources instead of referencing the control.\n if (labels.get(popupNode)?.sync(popupNode, labelledBy)) {\n setIdReference(\n popupNode,\n \"aria-label\",\n labelledBy ? undefined : label,\n );\n }\n }\n const optionId =\n open && highlighted.value !== null\n ? (highlightedOption?.node.id ?? idFor(highlighted.value, \"option\"))\n : undefined;\n setIdReference(input, \"aria-activedescendant\", optionId);\n }\n });\n\n const getFormValue = options.getFormValue ?? String;\n const state: ComboboxState = {\n inline: options.inline === true,\n labels,\n bindHiddenInput: formReset.bind,\n bindInput: (node, signal) => {\n popup.bindAnchor(node, signal);\n formReset.bind(node, signal);\n },\n bindPopup: popup.bindPopup,\n disabled,\n formValue: value === null ? \"\" : getFormValue(value),\n highlighted: highlighted.value,\n idFor,\n input: changeInput,\n inputValue,\n name: options.name,\n open,\n popupId,\n readOnly,\n registry,\n requestOpen,\n nativeToggle,\n select,\n setHighlighted,\n setOpen,\n };\n\n return {\n hiddenInput: () => comboboxHiddenInputMixin(state),\n input: () => comboboxInputMixin(state),\n inputValue,\n open,\n option: (optionValue, optionOptions = {}) =>\n comboboxOptionMixin(state, {\n disabled: optionOptions.disabled === true,\n selected: value !== null && sameValue(value, optionValue),\n textValue: optionOptions.textValue,\n value: optionValue,\n }),\n popup: () => comboboxPopupMixin(state),\n setOpen,\n value,\n };\n}\n\n/** {@link useCombobox} as a render-callback component. */\nexport function Combobox<Value = unknown>(\n props: ComboboxProps<Value>,\n): FigNode {\n return props.children(useCombobox(props));\n}\n\nfunction currentElement(event: Event): Element | undefined {\n return event.currentTarget instanceof Element\n ? event.currentTarget\n : undefined;\n}\n\n/** Native constraints can come from later mixins or a disabled fieldset. */\nfunction inputUnavailable(node: HTMLElement | undefined): boolean {\n return (\n node instanceof HTMLInputElement &&\n (node.readOnly || node.matches(\":disabled\"))\n );\n}\n"],"mappings":"wmBAyIA,MAAM,EAAqC,GACxC,EAAuB,IAAyB,CAE/C,IAAM,EAAW,EAAM,UAAY,EAAQ,MAAM,WAAa,GACxD,EAAW,EAAM,UAAY,EAAQ,MAAM,WAAa,GAC9D,MAAO,CACL,wBACE,EAAM,MAAQ,EAAM,cAAgB,KAC/B,EAAM,SAAS,OAAO,EAAM,WAAW,CAAC,EAAE,KAAK,IAChD,EAAM,MAAM,EAAM,YAAa,QAAQ,EACvC,IAAA,GACN,oBAAqB,OACrB,gBAAiB,EAAM,QACvB,gBAAiB,EAAM,KAAO,OAAS,QACvC,gBAAiB,UACjB,gBAAiB,EAAW,OAAS,IAAA,GACrC,KAAM,EAAS,EAAS,EAAM,SAAU,EAAM,SAAS,EACvD,YAAa,EAAM,KAAO,GAAK,IAAA,GAC/B,gBAAiB,EAAW,GAAK,IAAA,GACjC,SAAU,EAAW,GAAO,IAAA,GAC5B,IAAK,CACH,EAAG,QAAU,GAAU,CACjB,EAAM,kBACL,GACH,EAAM,YAAY,GAAM,EAAO,EAAe,CAAK,CAAC,CAExD,CAAC,EACD,EAAG,WAAa,GAAU,CACxB,IAAM,EAAO,EAAM,cAEjB,aAAgB,MAChB,EAAM,SAAS,cAAc,CAAC,EAAE,SAAS,CAAI,GAI/C,EAAM,YAAY,GAAO,EAAO,EAAe,CAAK,CAAC,CACvD,CAAC,EACD,EAAG,QAAU,GAAU,CACrB,IAAM,EAAO,EAAM,cACf,aAAgB,kBAClB,EAAM,MAAM,EAAK,MAAO,EAAO,CAAI,CAEvC,CAAC,EACD,EAAG,UAAY,GAAU,CAEvB,GADI,EAAM,kBAER,GACA,EAAM,aACN,EAAM,QACN,EAAM,SACN,EAAM,SACN,EAAM,SAEN,OAEF,GAAI,EAAM,MAAQ,UAAY,EAAM,MAAQ,CAAC,EAAM,OAAQ,CACzD,EAAM,eAAe,EACrB,EAAM,YAAY,GAAO,EAAO,EAAe,CAAK,CAAC,EACrD,MACF,CACA,IAAM,EACJ,EAAM,MAAQ,aAAe,EAAM,MAAQ,UACvC,EAAM,SAAS,KAAK,EAAM,YAAa,EAAM,GAAG,EAChD,IAAA,GACN,GAAI,IAAU,IAAA,GAAW,CACvB,EAAM,eAAe,EACrB,EAAM,eAAe,EAAM,MAAO,EAAI,EACtC,EAAM,YAAY,GAAM,EAAO,EAAe,CAAK,CAAC,EACpD,MACF,CACA,GAAI,EAAM,MAAQ,SAAW,CAAC,EAAM,KAAM,OAC1C,IAAM,EAAS,EAAM,SAAS,OAAO,EAAM,WAAW,EAClD,IAAW,IAAA,KAGf,EAAM,eAAe,EAChB,GAAU,EAAM,OAAO,EAAQ,CAAK,EAC3C,CAAC,CACH,EACA,SAAU,EAAW,GAAO,IAAA,GAC5B,KAAM,WACN,MAAO,EAAM,UACf,CACF,CACF,EAEM,EAAqC,GACxC,EAAuB,IAAyB,CAC/C,EAAc,EAAS,EAAM,QAAS,gBAAgB,EACtD,IAAM,EAAQ,EAAoB,EAAS,iBAAiB,EAC5D,MAAO,CACL,GAAG,EAAM,MACT,KAAM,EAAS,EAAS,EAAM,UAAW,EAAM,IAAW,CACxD,EAAM,OAAO,IAAI,EAAM,CAAK,EAGxB,EAAQ,MAAM,gBAAkB,IAAA,IAClC,EAAK,gBAAgB,YAAY,EACnC,EAAM,UAAU,EAAM,CAAM,EAC5B,EAAM,SAAS,cAAc,EAAM,CAAM,CAC3C,CAAC,EACD,YAAa,EAAM,KAAO,GAAK,IAAA,GAC/B,GAAI,EAAM,QACV,OAAQ,EAAM,OAAS,CAAC,EAAM,KAAO,IAAA,GACrC,IAAK,CACH,EAAG,eAAiB,GAAU,CACvB,EAAM,QAAQ,EAAM,aAAa,CAAK,CAC7C,CAAC,EACD,EAAG,SAAW,GAAU,CACjB,EAAM,QAAQ,EAAM,aAAa,CAAK,CAC7C,CAAC,EACD,EAAG,cAAgB,GAAU,CACvB,EAAM,SAAS,SAAS,EAAM,MAAM,IAAM,IAAA,IAC5C,EAAM,eAAe,CAEzB,CAAC,EACD,EAAG,cAAgB,GAAU,CAC3B,GAAI,EAAM,cAAgB,QAAS,OACnC,IAAM,EAAS,EAAM,SAAS,SAAS,EAAM,MAAM,EAC/C,IAAW,IAAA,IAAa,CAAC,EAAO,UAClC,EAAM,eAAe,EAAO,MAAO,EAAK,CAE5C,CAAC,EACD,EAAG,QAAU,GAAU,CACrB,GAAI,EAAM,iBAAkB,OAC5B,IAAM,EAAS,EAAM,SAAS,SAAS,EAAM,MAAM,EAC/C,IAAW,IAAA,KACX,EAAO,SAAU,EAAM,eAAe,EACjC,EAAM,SAAW,GAAG,EAAM,OAAO,EAAQ,CAAK,EACzD,CAAC,CACH,EACA,QAAS,EAAM,OAAS,IAAA,GAAa,EAAQ,MAAM,SAAW,OAC9D,KAAM,SACR,CACF,CACF,EAEM,EAAsC,GACzC,EAAuB,EAAsB,IAA6B,CACzE,IAAM,EAAW,EAAI,UAAY,EAAQ,MAAM,WAAa,GAC5D,MAAO,CACL,gBAAiB,EAAW,OAAS,IAAA,GACrC,gBAAiB,EAAI,SAAW,OAAS,QACzC,KAAM,EAAS,EAAS,EAAM,UAAW,EAAM,IAC7C,EAAM,SAAS,WAAW,EAAM,EAAQ,CACtC,WACA,UAAW,EAAI,UACf,MAAO,EAAI,KACb,CAAC,CACH,EACA,gBAAiB,EAAW,GAAK,IAAA,GACjC,mBAAoB,EAAU,EAAM,YAAa,EAAI,KAAK,EACtD,GACA,IAAA,GACJ,gBAAiB,EAAI,SAAW,GAAK,IAAA,GACrC,GAAI,EAAQ,MAAM,IAAM,EAAM,MAAM,EAAI,MAAO,QAAQ,EACvD,KAAM,SACN,KACE,EAAQ,OAAS,SACZ,EAAQ,MAAM,MAAQ,SACvB,IAAA,GACN,SAAU,EACZ,CACF,CACF,EAEM,EAA2C,GAC9C,EAAuB,KAEf,CACL,KAAM,EAAS,EAAS,EAAM,SAAU,EAAM,eAAe,EAC7D,SAAU,EAAM,SAAW,GAAO,IAAA,GAClC,KAAM,EAAQ,MAAM,MAAQ,EAAM,KAClC,KAAM,SACN,MAAO,EAAQ,MAAM,OAAS,EAAM,SACtC,EAEJ,EAGA,SAAgB,EACd,EAAkC,CAAC,EACb,CACtB,GAAM,CAAE,WAAW,GAAO,WAAW,IAAU,EACzC,EAAmB,EAAyB,EAC5C,EAAgB,EAAmC,CACvD,MAAO,EAAQ,MACf,aAAc,EAAQ,cAAgB,KACtC,SAAU,EAAQ,cAClB,MAAO,EACP,UAAW,CACb,CAAC,EACK,EAAQ,EAAqB,CACjC,MAAO,EAAQ,WACf,aAAc,EAAQ,mBAAqB,GAC3C,SAAU,EAAQ,mBAClB,UAAW,CACb,CAAC,EACK,EAAQ,EAAc,MACtB,EAAa,EAAM,MACnB,CAAC,EAAa,GAAuB,OAEjC,CAAE,OAAM,EAAE,EACd,EAAW,MACT,EAAc,WAAY,CAAgB,EAChD,CAAC,CACH,EACM,EAAQ,MACN,EAAoB,EAAkB,UAAU,EACtD,CAAC,CACH,EACM,CAAE,UAAS,OAAM,cAAa,UAAS,gBAAiB,EAAc,CAC1E,GAAG,EACH,kBACF,CAAC,EACK,EAAK,EAAM,EACX,EAAU,EAAQ,IAAM,GAAG,EAAG,QAC9B,EAAa,kBAAkB,EAAG,WAAW,UAAW,GAAG,IAC3D,EAAQ,EAAW,EACnB,EAAS,MAAc,IAAI,QAAuC,CAAC,CAAC,EACpE,EAAW,OACR,CACL,SAAU,GACV,SAAU,IAAA,EACZ,GACA,CAAC,CACH,EACM,EAAiB,GACpB,EAAe,IAAgC,CAC1C,IAAW,IAAA,KACb,EAAS,SAAW,EAAS,CAAE,MAAO,CAAK,EAAI,IAAA,IAC5C,EAAU,EAAY,MAAO,CAAI,EAE3B,GAAQ,EAAiB,EADlC,EAAoB,CAAE,MAAO,CAAK,CAAC,CAEvC,CACF,EACM,EAAc,GACjB,EAAc,EAAc,IAA2B,CACtD,GACE,EAAM,kBACN,GACA,GACA,EAAiB,CAAI,EACrB,CACA,EAAK,MAAQ,EAAM,QAAQ,EAC3B,EAAiB,EACjB,MACF,CACI,IAAS,EAAM,QAAQ,GACV,EACf,EAAoB,EAAO,CAAI,EAC/B,EAAM,YAAc,CAAI,EACxB,EAAc,YAAc,IAAI,CAEtB,GACZ,EAAY,GAAM,EAAO,CAAI,CAC/B,CACF,EACM,EAAS,GAAgB,EAAuB,IAAiB,CACrE,GACE,GACA,GACA,EAAO,UACP,EAAiB,EAAM,OAAO,CAAC,EAE/B,OACF,IAAM,EAAY,EAAO,MACnB,EAAY,EAAO,WAAa,EAAO,KAAK,aAAa,KAAK,GAAK,GACxD,EACf,EAAoB,EAAO,EAAO,IAAI,EACtC,EAAc,YAAc,CAAS,EACrC,EAAM,YAAc,CAAS,CAEnB,GACZ,EAAQ,EAAK,CACf,CAAC,EACK,EAAQ,MAAqB,CACjC,EAAc,MAAM,EACpB,EAAM,MAAM,CACd,CAAC,EACK,EAAY,MAAc,EAAgB,CAAK,EAAG,CAAC,CAAC,EAE1D,EAAgB,GAAW,CACrB,EAAQ,OACN,AAGF,EAAS,YAFT,EAAM,OAAO,CAAC,EAAE,MAAM,eAAe,aAAa,EAClD,EAAM,MAAM,CAAC,EAAE,MAAM,eAAe,iBAAiB,EACjC,KAGtB,EAAM,KAAK,EAAQ,EAAG,CAAU,EAChC,EAAS,SAAW,IAItB,IAAM,EAAW,EAAS,SACpB,EAAU,EAAO,EAAS,QAAQ,EAAI,CAAC,EACvC,EAAoB,EAAQ,KAAM,GACtC,EAAU,EAAM,MAAO,EAAY,KAAK,CAC1C,EACA,GAAI,EAAM,CACR,IAAM,EAAiB,EAAQ,KAAM,GACnC,EAAU,EAAM,MAAO,CAAK,CAC9B,EACM,EACJ,GAAmB,WAAa,GAC5B,EAAY,MACZ,GAAgB,WAAa,GAC3B,EACC,EAAQ,KAAM,GAAU,CAAC,EAAM,QAAQ,CAAC,EAAE,OAAS,KAC5D,EAAe,EAAM,IAAA,EAAS,CAChC,CAKE,GACA,IAAa,IAAA,IACb,EAAU,EAAS,MAAO,EAAY,KAAK,GAC3C,GAAmB,WAAa,GAGhC,mBAAqB,CACf,EAAO,SAAW,EAAS,WAAa,IAC5C,EAAS,SAAW,IAAA,GACpB,EAAkB,KAAK,iBAAiB,CACtC,MAAO,UACP,OAAQ,SACV,CAAC,EACH,CAAC,EACI,EAAS,SAAW,IAAA,GAC3B,IAAM,EAAQ,EAAM,OAAO,EAC3B,GAAI,IAAU,IAAA,GAAW,CAEvB,IAAM,EAAY,EAAS,cAAc,EACzC,GAAI,IAAc,KAAM,CACtB,IAAM,EAAa,EAAM,aAAa,iBAAiB,GAAK,IAAA,GACtD,EACJ,EAAM,aAAa,YAAY,IAC9B,aAAiB,iBACd,CAAC,GAAI,EAAM,QAAU,CAAC,CAAE,CAAC,CACtB,IAAK,GAAU,EAAM,aAAa,KAAK,CAAC,CAAC,CACzC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,EACX,IAAA,IAGF,EAAO,IAAI,CAAS,CAAC,EAAE,KAAK,EAAW,CAAU,GACnD,EACE,EACA,aACA,EAAa,IAAA,GAAY,CAC3B,CAEJ,CACA,IAAM,EACJ,GAAQ,EAAY,QAAU,KACzB,GAAmB,KAAK,IAAM,EAAM,EAAY,MAAO,QAAQ,EAChE,IAAA,GACN,EAAe,EAAO,wBAAyB,CAAQ,CACzD,CACF,CAAC,EAED,IAAM,EAAe,EAAQ,cAAgB,OACvC,EAAuB,CAC3B,OAAQ,EAAQ,SAAW,GAC3B,SACA,gBAAiB,EAAU,KAC3B,WAAY,EAAM,IAAW,CAC3B,EAAM,WAAW,EAAM,CAAM,EAC7B,EAAU,KAAK,EAAM,CAAM,CAC7B,EACA,UAAW,EAAM,UACjB,WACA,UAAW,IAAU,KAAO,GAAK,EAAa,CAAK,EACnD,YAAa,EAAY,MACzB,QACA,MAAO,EACP,aACA,KAAM,EAAQ,KACd,OACA,UACA,WACA,WACA,cACA,eACA,SACA,iBACA,SACF,EAEA,MAAO,CACL,gBAAmB,EAAyB,CAAK,EACjD,UAAa,EAAmB,CAAK,EACrC,aACA,OACA,QAAS,EAAa,EAAgB,CAAC,IACrC,EAAoB,EAAO,CACzB,SAAU,EAAc,WAAa,GACrC,SAAU,IAAU,MAAQ,EAAU,EAAO,CAAW,EACxD,UAAW,EAAc,UACzB,MAAO,CACT,CAAC,EACH,UAAa,EAAmB,CAAK,EACrC,UACA,OACF,CACF,CAGA,SAAgB,EACd,EACS,CACT,OAAO,EAAM,SAAS,EAAY,CAAK,CAAC,CAC1C,CAEA,SAAS,EAAe,EAAmC,CACzD,OAAO,EAAM,yBAAyB,QAClC,EAAM,cACN,IAAA,EACN,CAGA,SAAS,EAAiB,EAAwC,CAChE,OACE,aAAgB,mBACf,EAAK,UAAY,EAAK,QAAQ,WAAW,EAE9C"}
@@ -0,0 +1,42 @@
1
+ import { t as ChangeDetails } from "./changes--uvVfvy8.js";
2
+ import { ComboboxInputValueChangeHandler } from "./combobox.js";
3
+ import { FigNode, MixinDescriptor } from "@bgub/fig";
4
+ //#region src/command/command.d.ts
5
+ /** Values must be unique and non-null. Keep the items array stable between edits. */
6
+ interface CommandItem<Value = string> {
7
+ readonly value: Value;
8
+ readonly label: string;
9
+ readonly keywords?: readonly string[];
10
+ readonly group?: string;
11
+ readonly disabled?: boolean;
12
+ }
13
+ interface CommandOptions<Value = string> {
14
+ items: readonly CommandItem<Value>[];
15
+ defaultInputValue?: string;
16
+ disabled?: boolean;
17
+ inputValue?: string;
18
+ /** False preserves caller-supplied ordering/results, for example remote search. */
19
+ filter?: false | ((item: CommandItem<Value>, query: string) => boolean);
20
+ onInputValueChange?: ComboboxInputValueChangeHandler;
21
+ onAction?: (item: CommandItem<Value>, details: ChangeDetails, signal: AbortSignal) => void;
22
+ }
23
+ interface CommandParts<Value = string> {
24
+ readonly inputValue: string;
25
+ /** Matching items in caller order, including disabled matches. */
26
+ readonly items: readonly CommandItem<Value>[];
27
+ readonly empty: boolean;
28
+ input(): MixinDescriptor;
29
+ list(): MixinDescriptor;
30
+ item(item: CommandItem<Value>): MixinDescriptor;
31
+ group(label: string): MixinDescriptor;
32
+ emptyMessage(): MixinDescriptor;
33
+ }
34
+ interface CommandProps<Value = string> extends CommandOptions<Value> {
35
+ children: (command: CommandParts<Value>) => FigNode;
36
+ }
37
+ /** Searchable actions, composed from an always-visible inline Combobox. */
38
+ declare function useCommand<Value = string>(options: CommandOptions<Value>): CommandParts<Value>;
39
+ /** {@link useCommand} as a render-callback component. */
40
+ declare function Command<Value = string>(props: CommandProps<Value>): FigNode;
41
+ //#endregion
42
+ export { Command, CommandItem, CommandOptions, CommandParts, CommandProps, useCommand };
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./changes-DglRuvQa.js";import"./diagnostics-CoURcwqb.js";import{useCombobox as t}from"./combobox.js";import{createMixin as n,useMemo as r,useStableEvent as i}from"@bgub/fig";const a=n((e,t,n)=>({"aria-label":e.props[`aria-labelledby`]==null?e.props[`aria-label`]??t:void 0,hidden:n?!0:void 0,role:`group`})),o=n((e,t)=>({hidden:!t||void 0,role:`status`}));function s(n){let s=i((e,t,r)=>{n.onAction?.(e,t,r)}),c=t({disabled:n.disabled,inline:!0,open:!0,value:null,inputValue:n.inputValue,defaultInputValue:n.defaultInputValue,onInputValueChange:(e,t,r)=>{t.isCanceled||n.onInputValueChange?.(e,t,r)},onValueChange:(t,n)=>{if(t===null)return;n.cancel();let r=p.get(t);r===void 0||r.disabled||s(r,e(n.event,n.trigger))}}),l=c.inputValue,u=r(()=>(n.items,n.filter===void 0?n.items.map(e=>({item:e,text:[e.label,...e.keywords??[]].join(` `).toLocaleLowerCase()})):[]),[n.items,n.filter]),d=r(()=>{if(n.filter===!1)return n.items;if(n.filter!==void 0){let e=n.filter;return n.items.filter(t=>e(t,l))}let e=l.trim().toLocaleLowerCase();return e===``?n.items:u.filter(({text:t})=>t.includes(e)).map(({item:e})=>e)},[u,n.items,n.filter,l]),f=r(()=>new Set(d.map(e=>e.group)),[d]),p=r(()=>new Map(d.map(e=>[e.value,e])),[d]);return{inputValue:l,items:d,empty:d.length===0,input:()=>c.input(),list:()=>c.popup(),item:e=>c.option(e.value,{disabled:e.disabled,textValue:e.label}),group:e=>a(e,!f.has(e)),emptyMessage:()=>o(d.length===0)}}function c(e){return e.children(s(e))}export{c as Command,s as useCommand};
2
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","names":[],"sources":["../src/command/command.tsx"],"sourcesContent":["import {\n createMixin,\n type FigNode,\n type MixinDescriptor,\n useMemo,\n useStableEvent,\n} from \"@bgub/fig\";\nimport {\n type ComboboxInputValueChangeHandler,\n useCombobox,\n} from \"../combobox/combobox.tsx\";\nimport {\n type ChangeDetails,\n createChangeDetails,\n} from \"../internal/changes.ts\";\nimport { assertUniqueValues } from \"../internal/diagnostics.ts\";\n\n/** Values must be unique and non-null. Keep the items array stable between edits. */\nexport interface CommandItem<Value = string> {\n readonly value: Value;\n readonly label: string;\n readonly keywords?: readonly string[];\n readonly group?: string;\n readonly disabled?: boolean;\n}\n\nexport interface CommandOptions<Value = string> {\n items: readonly CommandItem<Value>[];\n defaultInputValue?: string;\n disabled?: boolean;\n inputValue?: string;\n /** False preserves caller-supplied ordering/results, for example remote search. */\n filter?: false | ((item: CommandItem<Value>, query: string) => boolean);\n onInputValueChange?: ComboboxInputValueChangeHandler;\n onAction?: (\n item: CommandItem<Value>,\n details: ChangeDetails,\n signal: AbortSignal,\n ) => void;\n}\n\nexport interface CommandParts<Value = string> {\n readonly inputValue: string;\n /** Matching items in caller order, including disabled matches. */\n readonly items: readonly CommandItem<Value>[];\n readonly empty: boolean;\n input(): MixinDescriptor;\n list(): MixinDescriptor;\n item(item: CommandItem<Value>): MixinDescriptor;\n group(label: string): MixinDescriptor;\n emptyMessage(): MixinDescriptor;\n}\n\nexport interface CommandProps<Value = string> extends CommandOptions<Value> {\n children: (command: CommandParts<Value>) => FigNode;\n}\n\nconst groupMixin = /* @__PURE__ */ createMixin(\n (context, label: string, hidden: boolean) => ({\n \"aria-label\":\n context.props[\"aria-labelledby\"] == null\n ? (context.props[\"aria-label\"] ?? label)\n : undefined,\n hidden: hidden ? true : undefined,\n role: \"group\",\n }),\n);\n\nconst emptyMixin = /* @__PURE__ */ createMixin((_context, empty: boolean) => ({\n hidden: empty ? undefined : true,\n role: \"status\",\n}));\n\n/** Searchable actions, composed from an always-visible inline Combobox. */\nexport function useCommand<Value = string>(\n options: CommandOptions<Value>,\n): CommandParts<Value> {\n const emitAction = useStableEvent(\n (item: CommandItem<Value>, details: ChangeDetails, signal: AbortSignal) => {\n options.onAction?.(item, details, signal);\n },\n );\n const combobox = useCombobox<Value>({\n disabled: options.disabled,\n inline: true,\n open: true,\n value: null,\n inputValue: options.inputValue,\n defaultInputValue: options.defaultInputValue,\n onInputValueChange: (next, details, signal) => {\n // Selecting an action never copies its label into the query.\n if (details.isCanceled) return;\n options.onInputValueChange?.(next, details, signal);\n },\n onValueChange: (value, details) => {\n if (value === null) return;\n // Refuse selection before invoking application code, including a callback\n // that throws. Repeated invocations remain actions, never stored values.\n details.cancel();\n const item = visible.get(value);\n if (item === undefined || item.disabled) return;\n emitAction(item, createChangeDetails(details.event, details.trigger));\n },\n });\n const inputValue = combobox.inputValue;\n const indexed = useMemo(() => {\n assertUniqueValues(options.items, \"command item\");\n return options.filter === undefined\n ? options.items.map((item) => ({\n item,\n text: [item.label, ...(item.keywords ?? [])]\n .join(\" \")\n .toLocaleLowerCase(),\n }))\n : [];\n }, [options.items, options.filter]);\n const items = useMemo(() => {\n if (options.filter === false) return options.items;\n if (options.filter !== undefined) {\n const filter = options.filter;\n return options.items.filter((item) => filter(item, inputValue));\n }\n const query = inputValue.trim().toLocaleLowerCase();\n return query === \"\"\n ? options.items\n : indexed\n .filter(({ text }) => text.includes(query))\n .map(({ item }) => item);\n }, [indexed, options.items, options.filter, inputValue]);\n const groups = useMemo(\n () => new Set(items.map((item) => item.group)),\n [items],\n );\n const visible = useMemo(\n () => new Map(items.map((item) => [item.value, item])),\n [items],\n );\n\n return {\n inputValue,\n items,\n empty: items.length === 0,\n input: () => combobox.input(),\n list: () => combobox.popup(),\n item: (item) =>\n combobox.option(item.value, {\n disabled: item.disabled,\n textValue: item.label,\n }),\n group: (label) => groupMixin(label, !groups.has(label)),\n emptyMessage: () => emptyMixin(items.length === 0),\n };\n}\n\n/** {@link useCommand} as a render-callback component. */\nexport function Command<Value = string>(props: CommandProps<Value>): FigNode {\n return props.children(useCommand(props));\n}\n"],"mappings":"iMAyDA,MAAM,EAA6B,GAChC,EAAS,EAAe,KAAqB,CAC5C,aACE,EAAQ,MAAM,oBAAsB,KAC/B,EAAQ,MAAM,eAAiB,EAChC,IAAA,GACN,OAAQ,EAAS,GAAO,IAAA,GACxB,KAAM,OACR,EACF,EAEM,EAA6B,GAAa,EAAU,KAAoB,CAC5E,OAAQ,IAAQ,IAAA,GAChB,KAAM,QACR,EAAE,EAGF,SAAgB,EACd,EACqB,CACrB,IAAM,EAAa,GAChB,EAA0B,EAAwB,IAAwB,CACzE,EAAQ,WAAW,EAAM,EAAS,CAAM,CAC1C,CACF,EACM,EAAW,EAAmB,CAClC,SAAU,EAAQ,SAClB,OAAQ,GACR,KAAM,GACN,MAAO,KACP,WAAY,EAAQ,WACpB,kBAAmB,EAAQ,kBAC3B,oBAAqB,EAAM,EAAS,IAAW,CAEzC,EAAQ,YACZ,EAAQ,qBAAqB,EAAM,EAAS,CAAM,CACpD,EACA,eAAgB,EAAO,IAAY,CACjC,GAAI,IAAU,KAAM,OAGpB,EAAQ,OAAO,EACf,IAAM,EAAO,EAAQ,IAAI,CAAK,EAC1B,IAAS,IAAA,IAAa,EAAK,UAC/B,EAAW,EAAM,EAAoB,EAAQ,MAAO,EAAQ,OAAO,CAAC,CACtE,CACF,CAAC,EACK,EAAa,EAAS,WACtB,EAAU,OACK,EAAQ,MACpB,EAAQ,SAAW,IAAA,GACtB,EAAQ,MAAM,IAAK,IAAU,CAC3B,OACA,KAAM,CAAC,EAAK,MAAO,GAAI,EAAK,UAAY,CAAC,CAAE,CAAC,CACzC,KAAK,GAAG,CAAC,CACT,kBAAkB,CACvB,EAAE,EACF,CAAC,GACJ,CAAC,EAAQ,MAAO,EAAQ,MAAM,CAAC,EAC5B,EAAQ,MAAc,CAC1B,GAAI,EAAQ,SAAW,GAAO,OAAO,EAAQ,MAC7C,GAAI,EAAQ,SAAW,IAAA,GAAW,CAChC,IAAM,EAAS,EAAQ,OACvB,OAAO,EAAQ,MAAM,OAAQ,GAAS,EAAO,EAAM,CAAU,CAAC,CAChE,CACA,IAAM,EAAQ,EAAW,KAAK,CAAC,CAAC,kBAAkB,EAClD,OAAO,IAAU,GACb,EAAQ,MACR,EACG,QAAQ,CAAE,UAAW,EAAK,SAAS,CAAK,CAAC,CAAC,CAC1C,KAAK,CAAE,UAAW,CAAI,CAC/B,EAAG,CAAC,EAAS,EAAQ,MAAO,EAAQ,OAAQ,CAAU,CAAC,EACjD,EAAS,MACP,IAAI,IAAI,EAAM,IAAK,GAAS,EAAK,KAAK,CAAC,EAC7C,CAAC,CAAK,CACR,EACM,EAAU,MACR,IAAI,IAAI,EAAM,IAAK,GAAS,CAAC,EAAK,MAAO,CAAI,CAAC,CAAC,EACrD,CAAC,CAAK,CACR,EAEA,MAAO,CACL,aACA,QACA,MAAO,EAAM,SAAW,EACxB,UAAa,EAAS,MAAM,EAC5B,SAAY,EAAS,MAAM,EAC3B,KAAO,GACL,EAAS,OAAO,EAAK,MAAO,CAC1B,SAAU,EAAK,SACf,UAAW,EAAK,KAClB,CAAC,EACH,MAAQ,GAAU,EAAW,EAAO,CAAC,EAAO,IAAI,CAAK,CAAC,EACtD,iBAAoB,EAAW,EAAM,SAAW,CAAC,CACnD,CACF,CAGA,SAAgB,EAAwB,EAAqC,CAC3E,OAAO,EAAM,SAAS,EAAW,CAAK,CAAC,CACzC"}
@@ -0,0 +1,4 @@
1
+ //#region src/internal/composite.d.ts
2
+ type Orientation = "horizontal" | "vertical";
3
+ //#endregion
4
+ export { Orientation as t };
@@ -0,0 +1,2 @@
1
+ import{o as e,s as t}from"./parts-I0jdefyp.js";import"./diagnostics-CoURcwqb.js";function n(n){let{container:s,item:c}=n,l=t(()=>n.registrationChanged?.()),u=!1,d=``,f,p=e(()=>n.registrationChanged?.());function m(e,t){return l.bind(e,t)}function h(e,t,n,r){let i={disabled:r,node:e,value:n};p.bind(e,t,i)}function g(){return l.node()}function _(){let e=l.node();if(e===null)return[];let t=[];for(let n of e.querySelectorAll(c)){let e=p.get(n);e!==void 0&&b(n)&&t.push(e)}return`${n.name}`,t}function v(e){return _().find(t=>i(t.value,e))}function y(e){if(!(e instanceof Element))return;let t=e.closest(c);if(!(t===null||!b(t)))return p.get(t)}function b(e){return l.node()!==null&&e.closest(s)===l.node()}function x(){let e=l.node();return e!==null&&e.contains(e.ownerDocument.activeElement)}function S(){return u}function C(e,t){u=!0;let n=()=>{u=!1};if(e instanceof Element){let r=e.ownerDocument;r.addEventListener(`pointerup`,n,{once:!0,signal:t}),r.addEventListener(`pointercancel`,n,{once:!0,signal:t})}o(t,n)}function w(e,t){if(e.defaultPrevented||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)return;let n=y(e.target);if(n===void 0)return;let i=_().filter(a);if(!i.includes(n))return;let o=t.skipDisabled===!0?i.filter(e=>!e.disabled||e===n):i,s=o.indexOf(n),c=o.length-1,l=t.edges!==!1,u;if(l&&e.key===`Home`)u=0;else if(l&&e.key===`End`)u=c;else{let n=r(e.key,t.orientation,E());if(n===null)return;u=t.loop?(s+n+o.length)%o.length:Math.min(c,Math.max(0,s+n))}e.preventDefault();let d=o[u];if(d!==void 0&&d!==n)return d.node.focus(),d}function T(e){if(e.length!==1||e===` `)return;f!==void 0&&clearTimeout(f),f=setTimeout(()=>{d=``},500);let t=d.length===1&&d===e.toLowerCase();d=t?d:d+e.toLowerCase();let n=_().filter(a),r=n.findIndex(e=>e.node===e.node.ownerDocument.activeElement),i=r===-1?0:r+ +!!t;for(let e=0;e<n.length;e+=1){let t=n[(i+e)%n.length],r=t?.node.textContent?.trim().toLowerCase()??``;if(t!==void 0&&r.startsWith(d))return t.node.focus(),t}}function E(){let e=l.node();if(e===null)return!1;let t=e.closest(`[dir]`)?.getAttribute(`dir`);return t===`rtl`||t===`ltr`?t===`rtl`:getComputedStyle(e).direction===`rtl`}return{bindContainer:m,bindItem:h,containerNode:g,containsFocus:x,focusByTypeahead:T,item:v,itemAt:y,items:_,moveFocus:w,notePointerDown:C,pointerActive:S}}function r(e,t,n){let r=t!==`horizontal`,i=t!==`vertical`;return r&&e===`ArrowUp`?-1:r&&e===`ArrowDown`?1:i&&e===`ArrowLeft`?n?1:-1:i&&e===`ArrowRight`?n?-1:1:null}function i(e,t){return e===t||Object.is(e,t)}function a(e){return!e.node.matches(`:disabled`)}function o(e,t){e.addEventListener(`abort`,t,{once:!0})}export{i,a as n,o as r,n as t};
2
+ //# sourceMappingURL=composite-CiKLX-Ti.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-CiKLX-Ti.js","names":[],"sources":["../src/internal/composite.ts"],"sourcesContent":["import { createPartCollection, createPartSlot } from \"./registration.ts\";\nimport { assertUniqueValues } from \"./diagnostics.ts\";\n\ndeclare const __FIG_DEV__: boolean | undefined;\n\nconst __DEV__ = typeof __FIG_DEV__ === \"boolean\" ? __FIG_DEV__ : false;\n\nexport type Orientation = \"horizontal\" | \"vertical\";\n\nexport interface CompositeItem {\n readonly disabled: boolean;\n readonly node: HTMLElement;\n readonly value: unknown;\n}\n\nexport interface CompositeOptions {\n /** Selector for the owning container, such as `[role=\"tablist\"]`. */\n readonly container: string;\n /** Selector for items inside it, such as `[role=\"tab\"]`. */\n readonly item: string;\n /** Pattern name used by development diagnostics. */\n readonly name: string;\n /**\n * Runs whenever the mounted set changes, including binds that belong to the\n * root's own render. Roots use it to reconcile against the committed DOM.\n * A widget that renders nothing from registration can leave it out.\n */\n readonly registrationChanged?: () => void;\n}\n\nexport interface FocusMoveOptions {\n /** Home and End jump to the first and last item. Defaults to `true`. */\n readonly edges?: boolean;\n /** Wraps movement at both ends. */\n readonly loop: boolean;\n /** `\"both\"` accepts either axis, the way a native radio group does. */\n readonly orientation: Orientation | \"both\";\n /**\n * Passes over disabled items. Tabs deliberately land on them so their\n * unavailable state is discoverable; radios cannot, because arrow movement\n * also selects.\n */\n readonly skipDisabled?: boolean;\n}\n\nexport type Composite = ReturnType<typeof createComposite>;\n\n/**\n * Shared registration and keyboard movement for widgets whose items live in\n * one container: tabs in a tablist, radios in a radiogroup, triggers in an\n * accordion.\n *\n * The live DOM is the source of truth for order and membership, so the\n * composite only remembers what the DOM cannot express: which value and\n * disabled state each host carries. A host signal identifies a registration lifetime; updates\n * replace its metadata, and an old signal cannot retire a newer registration.\n */\nexport function createComposite(options: CompositeOptions) {\n const { container: containerSelector, item: itemSelector } = options;\n const container = createPartSlot(() => options.registrationChanged?.());\n let pointerDown = false;\n let typeahead = \"\";\n let typeaheadTimer: ReturnType<typeof setTimeout> | undefined;\n const registrations = createPartCollection<CompositeItem>(() =>\n options.registrationChanged?.(),\n );\n\n function bindContainer(node: HTMLElement, signal: AbortSignal): boolean {\n const attached = container.bind(node, signal);\n if (__DEV__) items();\n return attached;\n }\n\n function bindItem(\n node: HTMLElement,\n signal: AbortSignal,\n value: unknown,\n disabled: boolean,\n ): void {\n const registration = { disabled, node, value };\n registrations.bind(node, signal, registration);\n if (__DEV__) items();\n }\n\n function containerNode(): HTMLElement | null {\n return container.node();\n }\n\n /** Every mounted item this container owns, in DOM order. */\n function items(): readonly CompositeItem[] {\n const owner = container.node();\n if (owner === null) return [];\n const ordered: CompositeItem[] = [];\n for (const node of owner.querySelectorAll<HTMLElement>(itemSelector)) {\n const registration = registrations.get(node);\n if (registration !== undefined && owns(node)) ordered.push(registration);\n }\n assertUniqueValues(ordered, `${options.name} item`);\n return ordered;\n }\n\n function item(value: unknown): CompositeItem | undefined {\n return items().find((registration) => sameValue(registration.value, value));\n }\n\n /** The registered item an event happened inside, when this container owns it. */\n function itemAt(target: EventTarget | null): CompositeItem | undefined {\n if (!(target instanceof Element)) return undefined;\n const node = target.closest<HTMLElement>(itemSelector);\n if (node === null || !owns(node)) return undefined;\n return registrations.get(node);\n }\n\n function owns(node: HTMLElement): boolean {\n return (\n container.node() !== null &&\n node.closest(containerSelector) === container.node()\n );\n }\n\n function containsFocus(): boolean {\n const node = container.node();\n return node !== null && node.contains(node.ownerDocument.activeElement);\n }\n\n /** True while a pointer press that may also move focus is still down. */\n function pointerActive(): boolean {\n return pointerDown;\n }\n\n function notePointerDown(\n target: EventTarget | null,\n signal: AbortSignal,\n ): void {\n pointerDown = true;\n const release = () => {\n pointerDown = false;\n };\n if (target instanceof Element) {\n const owner = target.ownerDocument;\n owner.addEventListener(\"pointerup\", release, { once: true, signal });\n owner.addEventListener(\"pointercancel\", release, { once: true, signal });\n }\n onAbort(signal, release);\n }\n\n /**\n * Moves focus for an arrow, Home, or End key, and reports the item that took\n * it so a caller can select along with the movement.\n */\n function moveFocus(\n event: KeyboardEvent,\n move: FocusMoveOptions,\n ): CompositeItem | undefined {\n if (\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n ) {\n return undefined;\n }\n const current = itemAt(event.target);\n if (current === undefined) return undefined;\n const ordered = items().filter(isNativeEnabled);\n if (!ordered.includes(current)) return undefined;\n\n const reachable =\n move.skipDisabled === true\n ? ordered.filter((entry) => !entry.disabled || entry === current)\n : ordered;\n const from = reachable.indexOf(current);\n const last = reachable.length - 1;\n\n const edges = move.edges !== false;\n let nextIndex: number;\n if (edges && event.key === \"Home\") nextIndex = 0;\n else if (edges && event.key === \"End\") nextIndex = last;\n else {\n const delta = arrowDelta(event.key, move.orientation, isRightToLeft());\n if (delta === null) return undefined;\n nextIndex = move.loop\n ? (from + delta + reachable.length) % reachable.length\n : Math.min(last, Math.max(0, from + delta));\n }\n\n event.preventDefault();\n const next = reachable[nextIndex];\n if (next === undefined || next === current) return undefined;\n next.node.focus();\n return next;\n }\n\n /**\n * Moves to the next item whose text starts with what has been typed.\n * Successive keystrokes extend the search until the pause resets it, and a\n * repeated single character steps through items starting with it.\n */\n function focusByTypeahead(key: string): CompositeItem | undefined {\n if (key.length !== 1 || key === \" \") return undefined;\n if (typeaheadTimer !== undefined) clearTimeout(typeaheadTimer);\n typeaheadTimer = setTimeout(() => {\n typeahead = \"\";\n }, 500);\n const repeat = typeahead.length === 1 && typeahead === key.toLowerCase();\n typeahead = repeat ? typeahead : typeahead + key.toLowerCase();\n\n const ordered = items().filter(isNativeEnabled);\n const active = ordered.findIndex(\n (entry) => entry.node === entry.node.ownerDocument.activeElement,\n );\n const from = active === -1 ? 0 : active + (repeat ? 1 : 0);\n for (let step = 0; step < ordered.length; step += 1) {\n const entry = ordered[(from + step) % ordered.length];\n const label = entry?.node.textContent?.trim().toLowerCase() ?? \"\";\n if (entry !== undefined && label.startsWith(typeahead)) {\n entry.node.focus();\n return entry;\n }\n }\n return undefined;\n }\n\n function isRightToLeft(): boolean {\n const node = container.node();\n if (node === null) return false;\n const declared = node.closest(\"[dir]\")?.getAttribute(\"dir\");\n if (declared === \"rtl\" || declared === \"ltr\") return declared === \"rtl\";\n return getComputedStyle(node).direction === \"rtl\";\n }\n\n return {\n bindContainer,\n bindItem,\n containerNode,\n containsFocus,\n focusByTypeahead,\n item,\n itemAt,\n items,\n moveFocus,\n notePointerDown,\n pointerActive,\n };\n}\n\nfunction arrowDelta(\n key: string,\n orientation: Orientation | \"both\",\n rightToLeft: boolean,\n): -1 | 1 | null {\n const vertical = orientation !== \"horizontal\";\n const horizontal = orientation !== \"vertical\";\n if (vertical && key === \"ArrowUp\") return -1;\n if (vertical && key === \"ArrowDown\") return 1;\n if (horizontal && key === \"ArrowLeft\") return rightToLeft ? 1 : -1;\n if (horizontal && key === \"ArrowRight\") return rightToLeft ? -1 : 1;\n return null;\n}\n\nexport function sameValue(left: unknown, right: unknown): boolean {\n return left === right || Object.is(left, right);\n}\n\n/** Native disability blocks DOM focus; ARIA disability remains discoverable. */\nexport function isNativeEnabled(item: CompositeItem): boolean {\n return !item.node.matches(\":disabled\");\n}\n\nexport function onAbort(signal: AbortSignal, callback: () => void): void {\n signal.addEventListener(\"abort\", callback, { once: true });\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{useBeforeLayout as e,useMemo as t,useStableEvent as n,useState as r}from"@bgub/fig";function i(e,...t){let n=()=>t.every(e=>e.isCurrent());for(let r of t){if(!n())break;r.notify(e)}let r=!e.isCanceled&&n();for(let n of t)n.settle(r,e);return r}function a(a){let[o,s]=r(()=>({value:a.defaultValue})),c=a.value===void 0?o.value:a.value,l=n((e,t,n)=>{a.onChange?.(e,t,n)}),u=t(()=>{let e=a.defaultValue,t=a,n=a.value!==void 0,r=c,o=c,u=0,d,f=()=>n?r:o;function p(e){n?t.reconcile():(o=e,s({value:e}))}function m(e){let n=++u,r=f(),i=e(r),a=!(t.equal??Object.is)(r,i);return d={revision:n,value:i},{isCurrent:()=>n===u,notify:e=>{a&&l(i,e)},settle:(e,r)=>{d?.revision===n&&(d=void 0),n===u&&(e?a&&p(i):r.event!==null&&t.reconcile())}}}function h(e,t){u++,d=void 0,p(e),t!==null&&l(e,t)}return{current:f,propose:m,request:(e,t)=>i(t,m(e)),reset:()=>h(n?f():e,null),restore:h,commit(e,i){let a=e.value!==void 0,s=d?.revision===u&&(e.equal??Object.is)(d.value,i);(n!==a||a&&!Object.is(t.value,e.value)&&!s)&&u++,(a||n!==a)&&(o=i),n=a,t=e,r=i}}},[]);return e(()=>{u.commit(a,c)}),{value:c,current:u.current,propose:u.propose,request:u.request,reset:u.reset,restore:u.restore}}export{a as n,i as t};
2
+ //# sourceMappingURL=controllable-value-CDbRzlId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controllable-value-CDbRzlId.js","names":[],"sources":["../src/internal/controllable-value.ts"],"sourcesContent":["import { useBeforeLayout, useMemo, useStableEvent, useState } from \"@bgub/fig\";\nimport type { ChangeDetails } from \"./changes.ts\";\n\ninterface ValueOptions<Value> {\n value: Value | undefined;\n defaultValue: Value;\n onChange?: (\n value: Value,\n details: ChangeDetails,\n signal: AbortSignal,\n ) => void;\n equal?: (left: Value, right: Value) => boolean;\n reconcile: () => void;\n}\n\ninterface Proposal {\n isCurrent(): boolean;\n notify(details: ChangeDetails): void;\n settle(accepted: boolean, details: ChangeDetails): void;\n}\n\n/** Notify all owners before accepting any of a compound change. */\nexport function requestChanges(\n details: ChangeDetails,\n ...proposals: Proposal[]\n): boolean {\n const isCurrent = () => proposals.every((proposal) => proposal.isCurrent());\n for (const proposal of proposals) {\n // A callback may synchronously replace the entire request. Do not notify\n // remaining fields with stale values or abort newer callback work.\n if (!isCurrent()) break;\n proposal.notify(details);\n }\n const accepted = !details.isCanceled && isCurrent();\n for (const proposal of proposals) proposal.settle(accepted, details);\n return accepted;\n}\n\n/**\n * One owner publishes committed configuration and retains accepted intent.\n * Rendering only computes a snapshot. Notifications run in the request path,\n * never in state updaters that rendering can replay.\n */\nexport function useControllableValue<Value>(options: ValueOptions<Value>) {\n const [local, setLocal] = useState(() => ({ value: options.defaultValue }));\n const value = options.value === undefined ? local.value : options.value;\n const notify = useStableEvent(\n (next: Value, details: ChangeDetails, signal: AbortSignal) => {\n options.onChange?.(next, details, signal);\n },\n );\n const owner = useMemo(() => {\n const initial = options.defaultValue;\n let committed = options;\n let controlled = options.value !== undefined;\n let rendered = value;\n let pending = value;\n let sequence = 0;\n let proposal: { revision: number; value: Value } | undefined;\n\n const current = () => (controlled ? rendered : pending);\n function accept(next: Value): void {\n if (controlled) committed.reconcile();\n else {\n pending = next;\n // A fresh record also restores native controls on a no-op form reset.\n setLocal({ value: next });\n }\n }\n function propose(update: (current: Value) => Value): Proposal {\n const revision = ++sequence;\n const previous = current();\n const next = update(previous);\n const changed = !(committed.equal ?? Object.is)(previous, next);\n proposal = { revision, value: next };\n return {\n isCurrent: () => revision === sequence,\n notify: (details) => {\n if (changed) notify(next, details);\n },\n settle: (accepted, details) => {\n if (proposal?.revision === revision) proposal = undefined;\n if (revision !== sequence) return;\n if (accepted) {\n if (changed) accept(next);\n }\n // Native events may already have changed the DOM. An imperative or\n // automatic rejection changed nothing; reconciling it can endlessly\n // retry a canceled automatic repair.\n else if (details.event !== null) committed.reconcile();\n },\n };\n }\n function restore(next: Value, details: ChangeDetails | null): void {\n sequence++;\n proposal = undefined;\n accept(next);\n // Automatic repairs notify after acceptance and cannot be canceled.\n if (details !== null) notify(next, details);\n }\n return {\n current,\n propose,\n request: (update: (current: Value) => Value, details: ChangeDetails) =>\n requestChanges(details, propose(update)),\n reset: () => restore(controlled ? current() : initial, null),\n restore,\n commit(next: ValueOptions<Value>, nextRendered: Value): void {\n const nextControlled = next.value !== undefined;\n // A synchronous owner commit can acknowledge this proposal while its\n // notification is still running. Only a different value supersedes it.\n const acknowledged =\n proposal?.revision === sequence &&\n (next.equal ?? Object.is)(proposal.value, nextRendered);\n if (\n controlled !== nextControlled ||\n (nextControlled &&\n !Object.is(committed.value, next.value) &&\n !acknowledged)\n )\n sequence++;\n if (nextControlled || controlled !== nextControlled)\n pending = nextRendered;\n controlled = nextControlled;\n committed = next;\n rendered = nextRendered;\n },\n };\n }, []);\n useBeforeLayout(() => {\n owner.commit(options, value);\n });\n return {\n value,\n current: owner.current,\n propose: owner.propose,\n request: owner.request,\n reset: owner.reset,\n restore: owner.restore,\n };\n}\n"],"mappings":"2FAsBA,SAAgB,EACd,EACA,GAAG,EACM,CACT,IAAM,MAAkB,EAAU,MAAO,GAAa,EAAS,UAAU,CAAC,EAC1E,IAAK,IAAM,KAAY,EAAW,CAGhC,GAAI,CAAC,EAAU,EAAG,MAClB,EAAS,OAAO,CAAO,CACzB,CACA,IAAM,EAAW,CAAC,EAAQ,YAAc,EAAU,EAClD,IAAK,IAAM,KAAY,EAAW,EAAS,OAAO,EAAU,CAAO,EACnE,OAAO,CACT,CAOA,SAAgB,EAA4B,EAA8B,CACxE,GAAM,CAAC,EAAO,GAAY,OAAgB,CAAE,MAAO,EAAQ,YAAa,EAAE,EACpE,EAAQ,EAAQ,QAAU,IAAA,GAAY,EAAM,MAAQ,EAAQ,MAC5D,EAAS,GACZ,EAAa,EAAwB,IAAwB,CAC5D,EAAQ,WAAW,EAAM,EAAS,CAAM,CAC1C,CACF,EACM,EAAQ,MAAc,CAC1B,IAAM,EAAU,EAAQ,aACpB,EAAY,EACZ,EAAa,EAAQ,QAAU,IAAA,GAC/B,EAAW,EACX,EAAU,EACV,EAAW,EACX,EAEE,MAAiB,EAAa,EAAW,EAC/C,SAAS,EAAO,EAAmB,CAC7B,EAAY,EAAU,UAAU,GAElC,EAAU,EAEV,EAAS,CAAE,MAAO,CAAK,CAAC,EAE5B,CACA,SAAS,EAAQ,EAA6C,CAC5D,IAAM,EAAW,EAAE,EACb,EAAW,EAAQ,EACnB,EAAO,EAAO,CAAQ,EACtB,EAAU,EAAE,EAAU,OAAS,OAAO,GAAA,CAAI,EAAU,CAAI,EAE9D,MADA,GAAW,CAAE,WAAU,MAAO,CAAK,EAC5B,CACL,cAAiB,IAAa,EAC9B,OAAS,GAAY,CACf,GAAS,EAAO,EAAM,CAAO,CACnC,EACA,QAAS,EAAU,IAAY,CACzB,GAAU,WAAa,IAAU,EAAW,IAAA,IAC5C,IAAa,IACb,EACE,GAAS,EAAO,CAAI,EAKjB,EAAQ,QAAU,MAAM,EAAU,UAAU,EACvD,CACF,CACF,CACA,SAAS,EAAQ,EAAa,EAAqC,CACjE,IACA,EAAW,IAAA,GACX,EAAO,CAAI,EAEP,IAAY,MAAM,EAAO,EAAM,CAAO,CAC5C,CACA,MAAO,CACL,UACA,UACA,SAAU,EAAmC,IAC3C,EAAe,EAAS,EAAQ,CAAM,CAAC,EACzC,UAAa,EAAQ,EAAa,EAAQ,EAAI,EAAS,IAAI,EAC3D,UACA,OAAO,EAA2B,EAA2B,CAC3D,IAAM,EAAiB,EAAK,QAAU,IAAA,GAGhC,EACJ,GAAU,WAAa,IACtB,EAAK,OAAS,OAAO,GAAA,CAAI,EAAS,MAAO,CAAY,GAEtD,IAAe,GACd,GACC,CAAC,OAAO,GAAG,EAAU,MAAO,EAAK,KAAK,GACtC,CAAC,IAEH,KACE,GAAkB,IAAe,KACnC,EAAU,GACZ,EAAa,EACb,EAAY,EACZ,EAAW,CACb,CACF,CACF,EAAG,CAAC,CAAC,EAIL,OAHA,MAAsB,CACpB,EAAM,OAAO,EAAS,CAAK,CAC7B,CAAC,EACM,CACL,QACA,QAAS,EAAM,QACf,QAAS,EAAM,QACf,QAAS,EAAM,QACf,MAAO,EAAM,MACb,QAAS,EAAM,OACjB,CACF"}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n){}function t(e,t,n=`popover`){}function n(e,t){}function r(e){}function i(e){}function a(e,t){}function o(e,t){}function s(e,t){}function c(e,t){}export{c as a,e as c,o as i,t as l,r as n,s as o,i as r,a as s,n as t};
2
+ //# sourceMappingURL=diagnostics-CoURcwqb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics-CoURcwqb.js","names":[],"sources":["../src/internal/diagnostics.ts"],"sourcesContent":["import type { MixinContext } from \"@bgub/fig\";\n\ndeclare const __FIG_DEV__: boolean | undefined;\n\nconst __DEV__ = typeof __FIG_DEV__ === \"boolean\" ? __FIG_DEV__ : false;\n\nexport function expectHost(\n context: MixinContext,\n part: string,\n expected: string,\n): void {\n if (!__DEV__) return;\n if (context.type === expected) return;\n throw new Error(\n `Fig Headless ${part} must be applied to <${expected}>, not <${context.type}>.`,\n );\n}\n\nexport function expectPopupId(\n context: MixinContext,\n id: string,\n part = \"popover\",\n): void {\n if (!__DEV__) return;\n if (id.trim() === \"\") {\n throw new Error(`Fig Headless ${part} id must not be empty.`);\n }\n const authored = context.props.id;\n if (authored === undefined || authored === id) return;\n throw new Error(\n `Fig Headless ${part} owns its id relationship. Pass id: \"${String(authored)}\" to the widget root instead of setting it on the host.`,\n );\n}\n\nexport function assertAccessibleName(node: HTMLElement, part: string): void {\n if (!__DEV__) return;\n const direct = node.getAttribute(\"aria-label\")?.trim();\n if (direct !== undefined && direct !== \"\") return;\n\n const references = node\n .getAttribute(\"aria-labelledby\")\n ?.split(/\\s+/)\n .filter(Boolean);\n if (\n references !== undefined &&\n references.some((id) => node.ownerDocument.getElementById(id) !== null)\n ) {\n return;\n }\n\n throw new Error(\n `Fig Headless ${part} requires an accessible name from aria-label or aria-labelledby.`,\n );\n}\n\nexport function assertControlLabel(control: HTMLElement): void {\n if (!__DEV__) return;\n const labels = (\n control as HTMLElement & { labels?: NodeListOf<HTMLLabelElement> }\n ).labels;\n if (labels !== undefined && labels.length > 0) return;\n assertAccessibleName(control, \"field control\");\n}\n\nexport function assertPanelOwner(owner: HTMLElement | undefined): void {\n if (!__DEV__ || owner !== undefined) return;\n throw new Error(\n \"Fig Headless panel has no matching control. Use the same value for both parts.\",\n );\n}\n\nexport function assertUniqueValues(\n items: readonly { readonly value: unknown }[],\n part: string,\n): void {\n if (!__DEV__) return;\n const values = new Set<unknown>();\n for (const item of items) {\n if (values.has(item.value)) {\n throw new Error(\n `Fig Headless ${part} values must be unique within one root.`,\n );\n }\n values.add(item.value);\n }\n}\n\nexport function assertSinglePart(\n items: readonly unknown[],\n part: string,\n): void {\n if (!__DEV__ || items.length < 2) return;\n throw new Error(\n `Fig Headless ${part} may be applied to only one mounted host.`,\n );\n}\n\nexport function assertUniqueIds(\n nodes: readonly HTMLElement[],\n part: string,\n): void {\n if (!__DEV__) return;\n const ids = new Set<string>();\n for (const node of nodes) {\n if (node.id === \"\") {\n throw new Error(`Fig Headless ${part} must use non-empty ids.`);\n }\n if (!ids.has(node.id)) {\n ids.add(node.id);\n continue;\n }\n throw new Error(`Fig Headless ${part} must use unique ids.`);\n }\n}\n\nexport function assertSingleSelection(\n values: readonly unknown[],\n part: string,\n): void {\n if (!__DEV__ || values.length < 2) return;\n throw new Error(`Fig Headless ${part} accepts at most one selected value.`);\n}\n"],"mappings":"AAMA,SAAgB,EACd,EACA,EACA,EACM,CAMR,CAEA,SAAgB,EACd,EACA,EACA,EAAO,UACD,CAUR,CAEA,SAAgB,EAAqB,EAAmB,EAAoB,CAmB5E,CAEA,SAAgB,EAAmB,EAA4B,CAO/D,CAEA,SAAgB,EAAiB,EAAsC,CAKvE,CAEA,SAAgB,EACd,EACA,EACM,CAWR,CAEA,SAAgB,EACd,EACA,EACM,CAKR,CAEA,SAAgB,EACd,EACA,EACM,CAaR,CAEA,SAAgB,EACd,EACA,EACM,CAGR"}
@@ -0,0 +1,44 @@
1
+ import { n as OpenChangeHandler, t as OpenChangeDetails } from "./open-state-BmP__rp4.js";
2
+ import { FigNode, MixinDescriptor } from "@bgub/fig";
3
+ //#region src/dialog/dialog.d.ts
4
+ type DialogOpenChangeDetails = OpenChangeDetails;
5
+ type DialogOpenChangeHandler = OpenChangeHandler;
6
+ interface DialogOptions {
7
+ /** Dismiss on a click outside the dialog box. Defaults to `true`. */
8
+ closeOnBackdrop?: boolean;
9
+ /** Dismiss on Escape. Defaults to `true`. */
10
+ closeOnEscape?: boolean;
11
+ defaultOpen?: boolean;
12
+ onOpenChange?: DialogOpenChangeHandler;
13
+ open?: boolean;
14
+ }
15
+ interface DialogParts {
16
+ readonly open: boolean;
17
+ /** Describes the dialog for assistive technology. */
18
+ description(): MixinDescriptor;
19
+ /** Applies to the caller's own `<dialog>` element. */
20
+ dialog(): MixinDescriptor;
21
+ /** A control inside the dialog that closes it. */
22
+ dismiss(): MixinDescriptor;
23
+ /** Opens or closes without an activation event. */
24
+ setOpen(open: boolean): void;
25
+ /** Names the dialog for assistive technology. */
26
+ title(): MixinDescriptor;
27
+ trigger(): MixinDescriptor;
28
+ }
29
+ interface DialogProps extends DialogOptions {
30
+ children: (dialog: DialogParts) => FigNode;
31
+ }
32
+ /**
33
+ * Coordinates one modal dialog around the native `<dialog>` element, which
34
+ * owns the top layer, focus containment and restoration, the inert background,
35
+ * and Escape. The widget owns open state, labelling, and dismissal policy.
36
+ */
37
+ declare function useDialog(options?: DialogOptions): DialogParts;
38
+ /**
39
+ * {@link useDialog} as a component, for a dialog that is not already a
40
+ * component of its own. Opening re-renders this root rather than the caller.
41
+ */
42
+ declare function Dialog(props: DialogProps): FigNode;
43
+ //#endregion
44
+ export { Dialog, DialogOpenChangeDetails, DialogOpenChangeHandler, DialogOptions, DialogParts, DialogProps, useDialog };
package/dist/dialog.js ADDED
@@ -0,0 +1,2 @@
1
+ import{a as e,i as t,n,o as r}from"./parts-I0jdefyp.js";import"./diagnostics-CoURcwqb.js";import{t as i}from"./part-reference-B7zINPUK.js";import{t as a}from"./open-state-G79p9WiE.js";import{createMixin as o,useBeforePaint as s,useId as c,useMemo as l}from"@bgub/fig";import{on as u}from"@bgub/fig-dom";const d=o((e,n)=>({...t(e,{disabled:!1}),"aria-haspopup":`dialog`,"data-open":n.open?``:void 0,mix:u(`click`,e=>{if(e.defaultPrevented)return;let t=e.currentTarget;n.requestOpen(!0,e,t instanceof Element?t:void 0)})})),f=o((e,t)=>{let r=i(e,`aria-labelledby`,t.titleId),a=i(e,`aria-describedby`,t.descriptionId);return{...r.props,...a.props,bind:n(e,t.registry,(e,n)=>t.registry.bindDialog(e,n,{title:r,description:a})),"data-open":t.open?``:void 0,mix:[u(`cancel`,e=>{if(!e.defaultPrevented){if(!t.closeOnEscape){e.preventDefault();return}t.requestOpen(!1,e,void 0)||e.preventDefault()}}),u(`close`,e=>{t.requestOpen(!1,e,void 0)}),u(`pointerdown`,e=>{let n=e.currentTarget;t.registry.noteBackdropPress(n instanceof HTMLElement&&e.target===n&&g(n,e))},{capture:!0}),u(`pointercancel`,()=>t.registry.noteBackdropPress(void 0)),u(`click`,e=>{let n=t.registry.takeBackdropPress();if(e.defaultPrevented||n===!1)return;let r=e.currentTarget;!t.closeOnBackdrop||!(r instanceof HTMLElement)||e.target!==r||!g(r,e)||t.requestOpen(!1,e,r)})]}}),p=o((e,t)=>({bind:n(e,t.registry,(e,n)=>t.registry.bindTitle(e,n)),id:e.props.id??t.titleId})),m=o((e,t)=>({bind:n(e,t.registry,(e,n)=>t.registry.bindDescription(e,n)),id:e.props.id??t.descriptionId})),h=o((e,n)=>({...t(e,{disabled:!1}),mix:u(`click`,e=>{if(e.defaultPrevented)return;let t=e.currentTarget;n.requestOpen(!1,e,t instanceof Element?t:void 0)})}));function g(e,t){let n=e.getBoundingClientRect();return t.clientX<n.left||t.clientX>n.right||t.clientY<n.top||t.clientY>n.bottom}function _(e){let t=r(e),n,i=new WeakMap;function a(e,n,r){e instanceof HTMLDialogElement&&(i.set(e,r),t.bind(e,n,`dialog`))}function o(){let e=c(`dialog`);return e instanceof HTMLDialogElement?e:null}function s(e){let t=o();if(t===null)return;e||(n=void 0);let r=i.get(t);r?.title.sync(t,c(`title`)?.id),r?.description.sync(t,c(`description`)?.id),e&&!t.open?t.showModal():!e&&t.open&&t.close()}return{bindDescription:(e,n)=>t.bind(e,n,`description`),bindDialog:a,noteBackdropPress:e=>{n=e},takeBackdropPress:()=>{let e=n;return n=void 0,e},bindTitle:(e,n)=>t.bind(e,n,`title`),node:o,sync:s};function c(e){let n=t.items().filter(t=>t.value===e);return`${e}`,n.at(-1)?.node}}function v(t={}){let{closeOnBackdrop:n=!0,closeOnEscape:r=!0}=t,i=c(),o=`${i}-title`,u=`${i}-description`,g=e(),v=l(()=>_(g),[]),{open:y,requestOpen:b,setOpen:x}=a({...t,requestReconcile:g});s(()=>{v.sync(y)});let S={closeOnBackdrop:n,closeOnEscape:r,descriptionId:u,open:y,registry:v,requestOpen:b,titleId:o};return{description:()=>m(S),dialog:()=>f(S),dismiss:()=>h(S),open:y,setOpen:x,title:()=>p(S),trigger:()=>d(S)}}function y(e){return e.children(v(e))}export{y as Dialog,v as useDialog};
2
+ //# sourceMappingURL=dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.js","names":[],"sources":["../src/dialog/parts.ts","../src/dialog/registry.ts","../src/dialog/dialog.tsx"],"sourcesContent":["import { createMixin, type MixinContext } from \"@bgub/fig\";\nimport { on } from \"@bgub/fig-dom\";\nimport { expectHost } from \"../internal/diagnostics.ts\";\nimport { bindPart, triggerProps } from \"../internal/parts.ts\";\nimport { createPartReference } from \"../internal/part-reference.ts\";\nimport type { DialogRegistry } from \"./registry.ts\";\n\n/** Widget-level state every part reads. Built once per root render. */\nexport interface DialogPartState {\n readonly closeOnBackdrop: boolean;\n readonly closeOnEscape: boolean;\n readonly descriptionId: string;\n readonly open: boolean;\n readonly registry: DialogRegistry;\n readonly requestOpen: (\n open: boolean,\n event: Event,\n trigger: Element | undefined,\n ) => boolean;\n readonly titleId: string;\n}\n\nexport const dialogTriggerMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: DialogPartState) => {\n expectHost(context, \"dialog trigger\", \"button\");\n return {\n ...triggerProps(context, { disabled: false }),\n \"aria-haspopup\": \"dialog\",\n \"data-open\": state.open ? \"\" : undefined,\n mix: on(\"click\", (event) => {\n if (event.defaultPrevented) return;\n const trigger = event.currentTarget;\n state.requestOpen(\n true,\n event,\n trigger instanceof Element ? trigger : undefined,\n );\n }),\n };\n },\n);\n\nexport const dialogMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: DialogPartState) => {\n expectHost(context, \"dialog\", \"dialog\");\n const title = createPartReference(\n context,\n \"aria-labelledby\",\n state.titleId,\n );\n const description = createPartReference(\n context,\n \"aria-describedby\",\n state.descriptionId,\n );\n return {\n ...title.props,\n ...description.props,\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindDialog(node, signal, { title, description }),\n ),\n \"data-open\": state.open ? \"\" : undefined,\n mix: [\n // Escape reaches the element as a cancelable `cancel`, so a handler that\n // cancels the change keeps the dialog open.\n on(\"cancel\", (event) => {\n if (event.defaultPrevented) return;\n if (!state.closeOnEscape) {\n event.preventDefault();\n return;\n }\n if (!state.requestOpen(false, event, undefined))\n event.preventDefault();\n }),\n // A native close can also come from a form submitted with method\n // \"dialog\", so state follows the element rather than the other way.\n on(\"close\", (event) => {\n state.requestOpen(false, event, undefined);\n }),\n on(\n \"pointerdown\",\n (event) => {\n const node = event.currentTarget;\n state.registry.noteBackdropPress(\n node instanceof HTMLElement &&\n event.target === node &&\n isOutsideBox(node, event),\n );\n },\n { capture: true },\n ),\n on(\"pointercancel\", () => state.registry.noteBackdropPress(undefined)),\n on(\"click\", (event) => {\n const beganOutside = state.registry.takeBackdropPress();\n if (event.defaultPrevented || beganOutside === false) return;\n const node = event.currentTarget;\n if (!state.closeOnBackdrop || !(node instanceof HTMLElement)) return;\n if (event.target !== node || !isOutsideBox(node, event)) return;\n state.requestOpen(false, event, node);\n }),\n ],\n };\n },\n);\n\nexport const dialogTitleMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: DialogPartState) => ({\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindTitle(node, signal),\n ),\n id: context.props.id ?? state.titleId,\n }),\n);\n\nexport const dialogDescriptionMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: DialogPartState) => ({\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindDescription(node, signal),\n ),\n id: context.props.id ?? state.descriptionId,\n }),\n);\n\nexport const dialogDismissMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: DialogPartState) => {\n expectHost(context, \"dialog dismiss control\", \"button\");\n return {\n ...triggerProps(context, { disabled: false }),\n mix: on(\"click\", (event) => {\n if (event.defaultPrevented) return;\n const trigger = event.currentTarget;\n state.requestOpen(\n false,\n event,\n trigger instanceof Element ? trigger : undefined,\n );\n }),\n };\n },\n);\n\n/**\n * A click on the backdrop targets the dialog itself, and so does a click on\n * the dialog's own padding. Only the first is a dismissal.\n */\nfunction isOutsideBox(node: HTMLElement, event: MouseEvent): boolean {\n const rect = node.getBoundingClientRect();\n return (\n event.clientX < rect.left ||\n event.clientX > rect.right ||\n event.clientY < rect.top ||\n event.clientY > rect.bottom\n );\n}\n","import {\n assertAccessibleName,\n assertSinglePart,\n} from \"../internal/diagnostics.ts\";\nimport type { PartReference } from \"../internal/part-reference.ts\";\nimport { createPartCollection } from \"../internal/registration.ts\";\n\nexport type DialogRegistry = ReturnType<typeof createDialogRegistry>;\n\n/**\n * Tracks the hosts of one dialog and drives the native element.\n *\n * `<dialog>` owns the hard parts — the top layer, focus containment, focus\n * restoration, inert background, and Escape — so the widget only decides when\n * it should be open and keeps its labelling in step with the committed DOM.\n */\nexport function createDialogRegistry(registrationChanged: () => void) {\n const parts = createPartCollection<\"description\" | \"dialog\" | \"title\">(\n registrationChanged,\n );\n\n let backdropPress: boolean | undefined;\n\n const references = new WeakMap<\n HTMLElement,\n { readonly title: PartReference; readonly description: PartReference }\n >();\n\n function bindDialog(\n node: HTMLElement,\n signal: AbortSignal,\n automatic: {\n readonly title: PartReference;\n readonly description: PartReference;\n },\n ): void {\n if (!(node instanceof HTMLDialogElement)) return;\n references.set(node, automatic);\n parts.bind(node, signal, \"dialog\");\n }\n\n function node(): HTMLDialogElement | null {\n const current = part(\"dialog\");\n return current instanceof HTMLDialogElement ? current : null;\n }\n\n /** Reconciles labelling and modality against the committed DOM. */\n function sync(open: boolean): void {\n const current = node();\n if (current === null) return;\n if (!open) backdropPress = undefined;\n const automatic = references.get(current);\n automatic?.title.sync(current, part(\"title\")?.id);\n automatic?.description.sync(current, part(\"description\")?.id);\n // Closed shells may defer their title and content until opened.\n if (open) assertAccessibleName(current, \"dialog\");\n if (open && !current.open) current.showModal();\n else if (!open && current.open) current.close();\n }\n\n return {\n bindDescription: (partNode: HTMLElement, signal: AbortSignal) =>\n parts.bind(partNode, signal, \"description\"),\n bindDialog,\n noteBackdropPress: (outside: boolean | undefined) => {\n backdropPress = outside;\n },\n takeBackdropPress: () => {\n const outside = backdropPress;\n backdropPress = undefined;\n return outside;\n },\n bindTitle: (partNode: HTMLElement, signal: AbortSignal) =>\n parts.bind(partNode, signal, \"title\"),\n node,\n sync,\n };\n\n function part(kind: \"description\" | \"dialog\" | \"title\") {\n const matches = parts.items().filter((entry) => entry.value === kind);\n assertSinglePart(matches, `dialog ${kind}`);\n return matches.at(-1)?.node;\n }\n}\n","import {\n type FigNode,\n type MixinDescriptor,\n useBeforePaint,\n useId,\n useMemo,\n} from \"@bgub/fig\";\nimport type {\n OpenChangeDetails,\n OpenChangeHandler,\n} from \"../internal/open-state.ts\";\nimport { useOpenState } from \"../internal/open-state.ts\";\nimport { useRegistrationReconcile } from \"../internal/reconcile.ts\";\nimport {\n dialogDescriptionMixin,\n dialogDismissMixin,\n dialogMixin,\n dialogTitleMixin,\n dialogTriggerMixin,\n} from \"./parts.ts\";\nimport { createDialogRegistry } from \"./registry.ts\";\n\nexport type DialogOpenChangeDetails = OpenChangeDetails;\n\nexport type DialogOpenChangeHandler = OpenChangeHandler;\n\nexport interface DialogOptions {\n /** Dismiss on a click outside the dialog box. Defaults to `true`. */\n closeOnBackdrop?: boolean;\n /** Dismiss on Escape. Defaults to `true`. */\n closeOnEscape?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: DialogOpenChangeHandler;\n open?: boolean;\n}\n\nexport interface DialogParts {\n readonly open: boolean;\n /** Describes the dialog for assistive technology. */\n description(): MixinDescriptor;\n /** Applies to the caller's own `<dialog>` element. */\n dialog(): MixinDescriptor;\n /** A control inside the dialog that closes it. */\n dismiss(): MixinDescriptor;\n /** Opens or closes without an activation event. */\n setOpen(open: boolean): void;\n /** Names the dialog for assistive technology. */\n title(): MixinDescriptor;\n trigger(): MixinDescriptor;\n}\n\nexport interface DialogProps extends DialogOptions {\n children: (dialog: DialogParts) => FigNode;\n}\n\n/**\n * Coordinates one modal dialog around the native `<dialog>` element, which\n * owns the top layer, focus containment and restoration, the inert background,\n * and Escape. The widget owns open state, labelling, and dismissal policy.\n */\nexport function useDialog(options: DialogOptions = {}): DialogParts {\n const { closeOnBackdrop = true, closeOnEscape = true } = options;\n const id = useId();\n const titleId = `${id}-title`;\n const descriptionId = `${id}-description`;\n const requestReconcile = useRegistrationReconcile();\n const registry = useMemo(() => createDialogRegistry(requestReconcile), []);\n const { open, requestOpen, setOpen } = useOpenState({\n ...options,\n requestReconcile,\n });\n\n useBeforePaint(() => {\n registry.sync(open);\n });\n\n const state = {\n closeOnBackdrop,\n closeOnEscape,\n descriptionId,\n open,\n registry,\n requestOpen,\n titleId,\n };\n\n return {\n description: () => dialogDescriptionMixin(state),\n dialog: () => dialogMixin(state),\n dismiss: () => dialogDismissMixin(state),\n open,\n setOpen,\n title: () => dialogTitleMixin(state),\n trigger: () => dialogTriggerMixin(state),\n };\n}\n\n/**\n * {@link useDialog} as a component, for a dialog that is not already a\n * component of its own. Opening re-renders this root rather than the caller.\n */\nexport function Dialog(props: DialogProps): FigNode {\n return props.children(useDialog(props));\n}\n"],"mappings":"+SAsBA,MAAa,EAAqC,GAC/C,EAAuB,KAEf,CACL,GAAG,EAAa,EAAS,CAAE,SAAU,EAAM,CAAC,EAC5C,gBAAiB,SACjB,YAAa,EAAM,KAAO,GAAK,IAAA,GAC/B,IAAK,EAAG,QAAU,GAAU,CAC1B,GAAI,EAAM,iBAAkB,OAC5B,IAAM,EAAU,EAAM,cACtB,EAAM,YACJ,GACA,EACA,aAAmB,QAAU,EAAU,IAAA,EACzC,CACF,CAAC,CACH,EAEJ,EAEa,EAA8B,GACxC,EAAuB,IAA2B,CAEjD,IAAM,EAAQ,EACZ,EACA,kBACA,EAAM,OACR,EACM,EAAc,EAClB,EACA,mBACA,EAAM,aACR,EACA,MAAO,CACL,GAAG,EAAM,MACT,GAAG,EAAY,MACf,KAAM,EAAS,EAAS,EAAM,UAAW,EAAM,IAC7C,EAAM,SAAS,WAAW,EAAM,EAAQ,CAAE,QAAO,aAAY,CAAC,CAChE,EACA,YAAa,EAAM,KAAO,GAAK,IAAA,GAC/B,IAAK,CAGH,EAAG,SAAW,GAAU,CAClB,MAAM,iBACV,IAAI,CAAC,EAAM,cAAe,CACxB,EAAM,eAAe,EACrB,MACF,CACK,EAAM,YAAY,GAAO,EAAO,IAAA,EAAS,GAC5C,EAAM,eAAe,CAFvB,CAGF,CAAC,EAGD,EAAG,QAAU,GAAU,CACrB,EAAM,YAAY,GAAO,EAAO,IAAA,EAAS,CAC3C,CAAC,EACD,EACE,cACC,GAAU,CACT,IAAM,EAAO,EAAM,cACnB,EAAM,SAAS,kBACb,aAAgB,aACd,EAAM,SAAW,GACjB,EAAa,EAAM,CAAK,CAC5B,CACF,EACA,CAAE,QAAS,EAAK,CAClB,EACA,EAAG,oBAAuB,EAAM,SAAS,kBAAkB,IAAA,EAAS,CAAC,EACrE,EAAG,QAAU,GAAU,CACrB,IAAM,EAAe,EAAM,SAAS,kBAAkB,EACtD,GAAI,EAAM,kBAAoB,IAAiB,GAAO,OACtD,IAAM,EAAO,EAAM,cACf,CAAC,EAAM,iBAAmB,EAAE,aAAgB,cAC5C,EAAM,SAAW,GAAQ,CAAC,EAAa,EAAM,CAAK,GACtD,EAAM,YAAY,GAAO,EAAO,CAAI,CACtC,CAAC,CACH,CACF,CACF,CACF,EAEa,EAAmC,GAC7C,EAAuB,KAA4B,CAClD,KAAM,EAAS,EAAS,EAAM,UAAW,EAAM,IAC7C,EAAM,SAAS,UAAU,EAAM,CAAM,CACvC,EACA,GAAI,EAAQ,MAAM,IAAM,EAAM,OAChC,EACF,EAEa,EAAyC,GACnD,EAAuB,KAA4B,CAClD,KAAM,EAAS,EAAS,EAAM,UAAW,EAAM,IAC7C,EAAM,SAAS,gBAAgB,EAAM,CAAM,CAC7C,EACA,GAAI,EAAQ,MAAM,IAAM,EAAM,aAChC,EACF,EAEa,EAAqC,GAC/C,EAAuB,KAEf,CACL,GAAG,EAAa,EAAS,CAAE,SAAU,EAAM,CAAC,EAC5C,IAAK,EAAG,QAAU,GAAU,CAC1B,GAAI,EAAM,iBAAkB,OAC5B,IAAM,EAAU,EAAM,cACtB,EAAM,YACJ,GACA,EACA,aAAmB,QAAU,EAAU,IAAA,EACzC,CACF,CAAC,CACH,EAEJ,EAMA,SAAS,EAAa,EAAmB,EAA4B,CACnE,IAAM,EAAO,EAAK,sBAAsB,EACxC,OACE,EAAM,QAAU,EAAK,MACrB,EAAM,QAAU,EAAK,OACrB,EAAM,QAAU,EAAK,KACrB,EAAM,QAAU,EAAK,MAEzB,CCzIA,SAAgB,EAAqB,EAAiC,CACpE,IAAM,EAAQ,EACZ,CACF,EAEI,EAEE,EAAa,IAAI,QAKvB,SAAS,EACP,EACA,EACA,EAIM,CACA,aAAgB,oBACtB,EAAW,IAAI,EAAM,CAAS,EAC9B,EAAM,KAAK,EAAM,EAAQ,QAAQ,EACnC,CAEA,SAAS,GAAiC,CACxC,IAAM,EAAU,EAAK,QAAQ,EAC7B,OAAO,aAAmB,kBAAoB,EAAU,IAC1D,CAGA,SAAS,EAAK,EAAqB,CACjC,IAAM,EAAU,EAAK,EACrB,GAAI,IAAY,KAAM,OACjB,IAAM,EAAgB,IAAA,IAC3B,IAAM,EAAY,EAAW,IAAI,CAAO,EACxC,GAAW,MAAM,KAAK,EAAS,EAAK,OAAO,CAAC,EAAE,EAAE,EAChD,GAAW,YAAY,KAAK,EAAS,EAAK,aAAa,CAAC,EAAE,EAAE,EAGxD,GAAQ,CAAC,EAAQ,KAAM,EAAQ,UAAU,EACpC,CAAC,GAAQ,EAAQ,MAAM,EAAQ,MAAM,CAChD,CAEA,MAAO,CACL,iBAAkB,EAAuB,IACvC,EAAM,KAAK,EAAU,EAAQ,aAAa,EAC5C,aACA,kBAAoB,GAAiC,CACnD,EAAgB,CAClB,EACA,sBAAyB,CACvB,IAAM,EAAU,EAEhB,MADA,GAAgB,IAAA,GACT,CACT,EACA,WAAY,EAAuB,IACjC,EAAM,KAAK,EAAU,EAAQ,OAAO,EACtC,OACA,MACF,EAEA,SAAS,EAAK,EAA0C,CACtD,IAAM,EAAU,EAAM,MAAM,CAAC,CAAC,OAAQ,GAAU,EAAM,QAAU,CAAI,EAEpE,MAD0B,GAAU,EAAV,EACnB,EAAQ,GAAG,EAAE,CAAC,EAAE,IACzB,CACF,CCvBA,SAAgB,EAAU,EAAyB,CAAC,EAAgB,CAClE,GAAM,CAAE,kBAAkB,GAAM,gBAAgB,IAAS,EACnD,EAAK,EAAM,EACX,EAAU,GAAG,EAAG,QAChB,EAAgB,GAAG,EAAG,cACtB,EAAmB,EAAyB,EAC5C,EAAW,MAAc,EAAqB,CAAgB,EAAG,CAAC,CAAC,EACnE,CAAE,OAAM,cAAa,WAAY,EAAa,CAClD,GAAG,EACH,kBACF,CAAC,EAED,MAAqB,CACnB,EAAS,KAAK,CAAI,CACpB,CAAC,EAED,IAAM,EAAQ,CACZ,kBACA,gBACA,gBACA,OACA,WACA,cACA,SACF,EAEA,MAAO,CACL,gBAAmB,EAAuB,CAAK,EAC/C,WAAc,EAAY,CAAK,EAC/B,YAAe,EAAmB,CAAK,EACvC,OACA,UACA,UAAa,EAAiB,CAAK,EACnC,YAAe,EAAmB,CAAK,CACzC,CACF,CAMA,SAAgB,EAAO,EAA6B,CAClD,OAAO,EAAM,SAAS,EAAU,CAAK,CAAC,CACxC"}
@@ -0,0 +1,35 @@
1
+ import { FigNode, MixinDescriptor } from "@bgub/fig";
2
+ //#region src/field/field.d.ts
3
+ interface FieldOptions {
4
+ disabled?: boolean;
5
+ /** Marks the control invalid and points it at the error message. */
6
+ invalid?: boolean;
7
+ required?: boolean;
8
+ }
9
+ interface FieldParts {
10
+ /** Applies to the caller's own control: an input, select, or widget part. */
11
+ control(): MixinDescriptor;
12
+ /** A stable key is required when rendering more than one description. */
13
+ description(key?: unknown): MixinDescriptor;
14
+ /** A stable key is required when rendering more than one error. */
15
+ error(key?: unknown): MixinDescriptor;
16
+ label(): MixinDescriptor;
17
+ }
18
+ interface FieldProps extends FieldOptions {
19
+ children: (field: FieldParts) => FigNode;
20
+ }
21
+ /**
22
+ * Ties one control to its label, description, and error message.
23
+ *
24
+ * The relationships are the whole job, and only the committed DOM knows which
25
+ * parts a caller actually rendered: a description that is not there must not
26
+ * be referenced, and an error that appears has to join the description list.
27
+ */
28
+ declare function useField(options?: FieldOptions): FieldParts;
29
+ /**
30
+ * {@link useField} as a component, for a field that is not already a
31
+ * component of its own.
32
+ */
33
+ declare function Field(props: FieldProps): FigNode;
34
+ //#endregion
35
+ export { Field, FieldOptions, FieldParts, FieldProps, useField };
package/dist/field.js ADDED
@@ -0,0 +1,2 @@
1
+ import{a as e,n as t,o as n,r}from"./parts-I0jdefyp.js";import"./diagnostics-CoURcwqb.js";import{t as i}from"./ids-Cvu36mFL.js";import{t as a}from"./part-reference-B7zINPUK.js";import{createMixin as o,useBeforePaint as s,useId as c,useMemo as l}from"@bgub/fig";const u=Symbol(`fig-headless.field.description`),d=Symbol(`fig-headless.field.error`),f=o((e,n)=>{let r=a(e,`for`,n.controlId);return{bind:t(e,n.bind,(e,t)=>n.bind(e,t,{kind:`label`,control:r})),...r.props,id:e.props.id??n.labelId}}),p=o((e,n)=>{let r=a(e,`aria-labelledby`,n.labelId);return{...r.props,"aria-invalid":n.invalid?`true`:void 0,bind:t(e,n.bind,(t,i)=>n.bind(t,i,{describedBy:e.props[`aria-describedby`],label:r,kind:`control`})),disabled:n.disabled?!0:e.props.disabled,id:e.props.id??n.controlId,required:n.required?!0:e.props.required}}),m=o((e,n,r)=>({bind:t(e,n.bind,(e,t)=>n.bind(e,t,{kind:`description`})),id:e.props.id??r.id})),h=o((e,n,r)=>({bind:t(e,n.bind,(e,t)=>n.bind(e,t,{kind:`error`})),id:e.props.id??r.id}));function g(t={}){let{disabled:a=!1,invalid:o=!1,required:g=!1}=t,y=c(),b=`${y}-control`,x=`${y}-label`,S=i(),C=e(),w=l(()=>n(C),[]);s(()=>{let e=w.items(),t=e.filter(e=>e.value.kind===`control`),n=e.filter(e=>e.value.kind===`label`),i=t.at(-1);if(i===void 0)return;let a=i.node,s=n.at(-1);s?.value.kind===`label`&&s.value.control.sync(s.node,a.id),i.value.kind===`control`&&i.value.label.sync(a,s?.node.id);let c=e.filter(e=>e.value.kind===`description`).map(e=>e.node).sort(_),l=e.filter(e=>e.value.kind===`error`).map(e=>e.node).sort(_);[...c,...l];let u=v([...(i.value.kind===`control`?i.value.describedBy?.split(/\s+/):void 0)??[],...c.map(e=>e.id),...o?l.map(e=>e.id):[]]).join(` `);r(a,`aria-describedby`,u)});let T={controlId:b,disabled:a,invalid:o,labelId:x,bind:w.bind,required:g};return{control:()=>p(T),description:(e=u)=>m(T,{id:S(e,`description`)}),error:(e=d)=>h(T,{id:S(e,`error`)}),label:()=>f(T)}}function _(e,t){let n=e.compareDocumentPosition(t);return n&Node.DOCUMENT_POSITION_DISCONNECTED?0:n&Node.DOCUMENT_POSITION_FOLLOWING?-1:n&Node.DOCUMENT_POSITION_PRECEDING?1:0}function v(e){return[...new Set(e.filter(e=>e!==``))]}function y(e){return e.children(g(e))}export{y as Field,g as useField};
2
+ //# sourceMappingURL=field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.js","names":[],"sources":["../src/field/field.tsx"],"sourcesContent":["import {\n createMixin,\n type FigNode,\n type MixinContext,\n type MixinDescriptor,\n useBeforePaint,\n useId,\n useMemo,\n} from \"@bgub/fig\";\nimport {\n assertControlLabel,\n assertSinglePart,\n assertUniqueIds,\n expectHost,\n} from \"../internal/diagnostics.ts\";\nimport {\n createPartReference,\n type PartReference,\n} from \"../internal/part-reference.ts\";\nimport { usePartIds } from \"../internal/ids.ts\";\nimport { bindPart, setIdReference } from \"../internal/parts.ts\";\nimport { createPartCollection } from \"../internal/registration.ts\";\nimport { useRegistrationReconcile } from \"../internal/reconcile.ts\";\n\nexport interface FieldOptions {\n disabled?: boolean;\n /** Marks the control invalid and points it at the error message. */\n invalid?: boolean;\n required?: boolean;\n}\n\nexport interface FieldParts {\n /** Applies to the caller's own control: an input, select, or widget part. */\n control(): MixinDescriptor;\n /** A stable key is required when rendering more than one description. */\n description(key?: unknown): MixinDescriptor;\n /** A stable key is required when rendering more than one error. */\n error(key?: unknown): MixinDescriptor;\n label(): MixinDescriptor;\n}\n\nexport interface FieldProps extends FieldOptions {\n children: (field: FieldParts) => FigNode;\n}\n\ninterface FieldState {\n readonly controlId: string;\n readonly disabled: boolean;\n readonly invalid: boolean;\n readonly labelId: string;\n readonly bind: (\n node: HTMLElement,\n signal: AbortSignal,\n part: FieldPart,\n ) => void;\n readonly required: boolean;\n}\n\ntype FieldPart =\n | {\n readonly kind: \"control\";\n readonly describedBy: string | undefined;\n readonly label: PartReference;\n }\n | { readonly kind: \"description\" }\n | { readonly kind: \"error\" }\n | { readonly kind: \"label\"; readonly control: PartReference };\n\ninterface FieldMessageOwnState {\n readonly id: string;\n}\n\nconst defaultDescription = Symbol(\"fig-headless.field.description\");\nconst defaultError = Symbol(\"fig-headless.field.error\");\n\nconst fieldLabelMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: FieldState) => {\n expectHost(context, \"field label\", \"label\");\n const control = createPartReference(context, \"for\", state.controlId);\n return {\n bind: bindPart(context, state.bind, (node, signal) =>\n state.bind(node, signal, {\n kind: \"label\",\n control,\n }),\n ),\n // A wrapping label needs no `for`, but pointing at the control also works\n // when the two are siblings, which is the arrangement that needs help.\n ...control.props,\n id: context.props.id ?? state.labelId,\n };\n },\n);\n\nconst fieldControlMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: FieldState) => {\n const label = createPartReference(\n context,\n \"aria-labelledby\",\n state.labelId,\n );\n return {\n ...label.props,\n \"aria-invalid\": state.invalid ? \"true\" : undefined,\n bind: bindPart(context, state.bind, (node, signal) =>\n state.bind(node, signal, {\n describedBy: context.props[\"aria-describedby\"],\n label,\n kind: \"control\",\n }),\n ),\n disabled: state.disabled ? true : context.props.disabled,\n id: context.props.id ?? state.controlId,\n required: state.required ? true : context.props.required,\n };\n },\n);\n\nconst fieldDescriptionMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: FieldState, own: FieldMessageOwnState) => ({\n bind: bindPart(context, state.bind, (node, signal) =>\n state.bind(node, signal, { kind: \"description\" }),\n ),\n id: context.props.id ?? own.id,\n }),\n);\n\nconst fieldErrorMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: FieldState, own: FieldMessageOwnState) => ({\n bind: bindPart(context, state.bind, (node, signal) =>\n state.bind(node, signal, { kind: \"error\" }),\n ),\n id: context.props.id ?? own.id,\n }),\n);\n\n/**\n * Ties one control to its label, description, and error message.\n *\n * The relationships are the whole job, and only the committed DOM knows which\n * parts a caller actually rendered: a description that is not there must not\n * be referenced, and an error that appears has to join the description list.\n */\nexport function useField(options: FieldOptions = {}): FieldParts {\n const { disabled = false, invalid = false, required = false } = options;\n const id = useId();\n const controlId = `${id}-control`;\n const labelId = `${id}-label`;\n const idFor = usePartIds();\n const requestReconcile = useRegistrationReconcile();\n const parts = useMemo(\n () => createPartCollection<FieldPart>(requestReconcile),\n [],\n );\n\n useBeforePaint(() => {\n const registrations = parts.items();\n const controls = registrations.filter(\n (part) => part.value.kind === \"control\",\n );\n const labels = registrations.filter((part) => part.value.kind === \"label\");\n assertSinglePart(controls, \"field control\");\n assertSinglePart(labels, \"field label\");\n const control = controls.at(-1);\n if (control === undefined) return;\n const node = control.node;\n const label = labels.at(-1);\n if (label?.value.kind === \"label\")\n label.value.control.sync(label.node, node.id);\n if (control.value.kind === \"control\")\n control.value.label.sync(node, label?.node.id);\n assertControlLabel(node);\n const descriptions = registrations\n .filter((part) => part.value.kind === \"description\")\n .map((part) => part.node)\n .sort(compareMessageOrder);\n const errors = registrations\n .filter((part) => part.value.kind === \"error\")\n .map((part) => part.node)\n .sort(compareMessageOrder);\n assertUniqueIds([...descriptions, ...errors], \"field messages\");\n const authored =\n control.value.kind === \"control\"\n ? control.value.describedBy?.split(/\\s+/)\n : undefined;\n const described = uniqueReferences([\n ...(authored ?? []),\n ...descriptions.map((description) => description.id),\n ...(invalid ? errors.map((error) => error.id) : []),\n ]).join(\" \");\n setIdReference(node, \"aria-describedby\", described);\n });\n\n const state: FieldState = {\n controlId,\n disabled,\n invalid,\n labelId,\n bind: parts.bind,\n required,\n };\n\n return {\n control: () => fieldControlMixin(state),\n description: (key = defaultDescription) =>\n fieldDescriptionMixin(state, { id: idFor(key, \"description\") }),\n error: (key = defaultError) =>\n fieldErrorMixin(state, { id: idFor(key, \"error\") }),\n label: () => fieldLabelMixin(state),\n };\n}\n\n// Stable registrations outlive keyed moves. Read order after DOM placement;\n// unrelated trees have no DOM order, so retain their registration order.\nfunction compareMessageOrder(first: HTMLElement, second: HTMLElement): number {\n const position = first.compareDocumentPosition(second);\n if (position & Node.DOCUMENT_POSITION_DISCONNECTED) return 0;\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\n return 0;\n}\n\nfunction uniqueReferences(references: readonly string[]): string[] {\n return [...new Set(references.filter((reference) => reference !== \"\"))];\n}\n\n/**\n * {@link useField} as a component, for a field that is not already a\n * component of its own.\n */\nexport function Field(props: FieldProps): FigNode {\n return props.children(useField(props));\n}\n"],"mappings":"qQAwEA,MAAM,EAAqB,OAAO,gCAAgC,EAC5D,EAAe,OAAO,0BAA0B,EAEhD,EAAkC,GACrC,EAAuB,IAAsB,CAE5C,IAAM,EAAU,EAAoB,EAAS,MAAO,EAAM,SAAS,EACnE,MAAO,CACL,KAAM,EAAS,EAAS,EAAM,MAAO,EAAM,IACzC,EAAM,KAAK,EAAM,EAAQ,CACvB,KAAM,QACN,SACF,CAAC,CACH,EAGA,GAAG,EAAQ,MACX,GAAI,EAAQ,MAAM,IAAM,EAAM,OAChC,CACF,CACF,EAEM,EAAoC,GACvC,EAAuB,IAAsB,CAC5C,IAAM,EAAQ,EACZ,EACA,kBACA,EAAM,OACR,EACA,MAAO,CACL,GAAG,EAAM,MACT,eAAgB,EAAM,QAAU,OAAS,IAAA,GACzC,KAAM,EAAS,EAAS,EAAM,MAAO,EAAM,IACzC,EAAM,KAAK,EAAM,EAAQ,CACvB,YAAa,EAAQ,MAAM,oBAC3B,QACA,KAAM,SACR,CAAC,CACH,EACA,SAAU,EAAM,SAAW,GAAO,EAAQ,MAAM,SAChD,GAAI,EAAQ,MAAM,IAAM,EAAM,UAC9B,SAAU,EAAM,SAAW,GAAO,EAAQ,MAAM,QAClD,CACF,CACF,EAEM,EAAwC,GAC3C,EAAuB,EAAmB,KAA+B,CACxE,KAAM,EAAS,EAAS,EAAM,MAAO,EAAM,IACzC,EAAM,KAAK,EAAM,EAAQ,CAAE,KAAM,aAAc,CAAC,CAClD,EACA,GAAI,EAAQ,MAAM,IAAM,EAAI,EAC9B,EACF,EAEM,EAAkC,GACrC,EAAuB,EAAmB,KAA+B,CACxE,KAAM,EAAS,EAAS,EAAM,MAAO,EAAM,IACzC,EAAM,KAAK,EAAM,EAAQ,CAAE,KAAM,OAAQ,CAAC,CAC5C,EACA,GAAI,EAAQ,MAAM,IAAM,EAAI,EAC9B,EACF,EASA,SAAgB,EAAS,EAAwB,CAAC,EAAe,CAC/D,GAAM,CAAE,WAAW,GAAO,UAAU,GAAO,WAAW,IAAU,EAC1D,EAAK,EAAM,EACX,EAAY,GAAG,EAAG,UAClB,EAAU,GAAG,EAAG,QAChB,EAAQ,EAAW,EACnB,EAAmB,EAAyB,EAC5C,EAAQ,MACN,EAAgC,CAAgB,EACtD,CAAC,CACH,EAEA,MAAqB,CACnB,IAAM,EAAgB,EAAM,MAAM,EAC5B,EAAW,EAAc,OAC5B,GAAS,EAAK,MAAM,OAAS,SAChC,EACM,EAAS,EAAc,OAAQ,GAAS,EAAK,MAAM,OAAS,OAAO,EAGnE,EAAU,EAAS,GAAG,EAAE,EAC9B,GAAI,IAAY,IAAA,GAAW,OAC3B,IAAM,EAAO,EAAQ,KACf,EAAQ,EAAO,GAAG,EAAE,EACtB,GAAO,MAAM,OAAS,SACxB,EAAM,MAAM,QAAQ,KAAK,EAAM,KAAM,EAAK,EAAE,EAC1C,EAAQ,MAAM,OAAS,WACzB,EAAQ,MAAM,MAAM,KAAK,EAAM,GAAO,KAAK,EAAE,EAE/C,IAAM,EAAe,EAClB,OAAQ,GAAS,EAAK,MAAM,OAAS,aAAa,CAAC,CACnD,IAAK,GAAS,EAAK,IAAI,CAAC,CACxB,KAAK,CAAmB,EACrB,EAAS,EACZ,OAAQ,GAAS,EAAK,MAAM,OAAS,OAAO,CAAC,CAC7C,IAAK,GAAS,EAAK,IAAI,CAAC,CACxB,KAAK,CAAmB,EACX,CAAC,GAAG,EAAc,GAAG,CAAM,EAK3C,IAAM,EAAY,EAAiB,CACjC,IAJA,EAAQ,MAAM,OAAS,UACnB,EAAQ,MAAM,aAAa,MAAM,KAAK,EACtC,IAAA,KAEY,CAAC,EACjB,GAAG,EAAa,IAAK,GAAgB,EAAY,EAAE,EACnD,GAAI,EAAU,EAAO,IAAK,GAAU,EAAM,EAAE,EAAI,CAAC,CACnD,CAAC,CAAC,CAAC,KAAK,GAAG,EACX,EAAe,EAAM,mBAAoB,CAAS,CACpD,CAAC,EAED,IAAM,EAAoB,CACxB,YACA,WACA,UACA,UACA,KAAM,EAAM,KACZ,UACF,EAEA,MAAO,CACL,YAAe,EAAkB,CAAK,EACtC,aAAc,EAAM,IAClB,EAAsB,EAAO,CAAE,GAAI,EAAM,EAAK,aAAa,CAAE,CAAC,EAChE,OAAQ,EAAM,IACZ,EAAgB,EAAO,CAAE,GAAI,EAAM,EAAK,OAAO,CAAE,CAAC,EACpD,UAAa,EAAgB,CAAK,CACpC,CACF,CAIA,SAAS,EAAoB,EAAoB,EAA6B,CAC5E,IAAM,EAAW,EAAM,wBAAwB,CAAM,EAIrD,OAHI,EAAW,KAAK,+BAAuC,EACvD,EAAW,KAAK,4BAAoC,GACpD,EAAW,KAAK,4BAAoC,EACjD,CACT,CAEA,SAAS,EAAiB,EAAyC,CACjE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAW,OAAQ,GAAc,IAAc,EAAE,CAAC,CAAC,CACxE,CAMA,SAAgB,EAAM,EAA4B,CAChD,OAAO,EAAM,SAAS,EAAS,CAAK,CAAC,CACvC"}
@@ -0,0 +1,2 @@
1
+ function e(e){let t=new Map,n=new Map;function r(r,i){let a=n.get(r),o=r instanceof HTMLInputElement?r.form:null;if(a?.signal===i&&a.form===o||(a?.release(),o===null))return;let s=o,c=t.get(s);if(c===void 0){let n=new AbortController;c={controller:n,count:0},t.set(s,c),s.addEventListener(`reset`,n=>{queueMicrotask(()=>{t.has(s)&&!n.defaultPrevented&&e()})},{signal:n.signal})}c.count++;let l={form:s,signal:i,release:u};n.set(r,l);function u(){if(n.get(r)!==l)return;n.delete(r),i.removeEventListener(`abort`,u);let e=t.get(s);e===void 0||--e.count>0||(e.controller.abort(),t.delete(s))}i.addEventListener(`abort`,u,{once:!0})}return{bind:r}}export{e as t};
2
+ //# sourceMappingURL=form-reset-B7G3-0OT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-reset-B7G3-0OT.js","names":[],"sources":["../src/internal/form-reset.ts"],"sourcesContent":["/** Listens once per form, following committed host/form ownership. */\nexport function createFormReset(onReset: () => void) {\n const forms = new Map<\n HTMLFormElement,\n { controller: AbortController; count: number }\n >();\n const inputs = new Map<\n HTMLElement,\n { form: HTMLFormElement; signal: AbortSignal; release: () => void }\n >();\n\n function bind(node: HTMLElement, signal: AbortSignal): void {\n const previous = inputs.get(node);\n const nextForm = node instanceof HTMLInputElement ? node.form : null;\n if (previous?.signal === signal && previous.form === nextForm) return;\n previous?.release();\n if (nextForm === null) return;\n const form = nextForm;\n let registration = forms.get(form);\n if (registration === undefined) {\n const controller = new AbortController();\n registration = { controller, count: 0 };\n forms.set(form, registration);\n form.addEventListener(\n \"reset\",\n (event) => {\n // The platform resets native values after dispatch. Cancellation is\n // final only then; this delay is about the reset event, not rebinding.\n queueMicrotask(() => {\n if (forms.has(form) && !event.defaultPrevented) onReset();\n });\n },\n { signal: controller.signal },\n );\n }\n registration.count++;\n const entry = { form, signal, release };\n inputs.set(node, entry);\n function release(): void {\n if (inputs.get(node) !== entry) return;\n inputs.delete(node);\n signal.removeEventListener(\"abort\", release);\n const current = forms.get(form);\n if (current === undefined || --current.count > 0) return;\n current.controller.abort();\n forms.delete(form);\n }\n signal.addEventListener(\"abort\", release, { once: true });\n }\n return { bind };\n}\n"],"mappings":"AACA,SAAgB,EAAgB,EAAqB,CACnD,IAAM,EAAQ,IAAI,IAIZ,EAAS,IAAI,IAKnB,SAAS,EAAK,EAAmB,EAA2B,CAC1D,IAAM,EAAW,EAAO,IAAI,CAAI,EAC1B,EAAW,aAAgB,iBAAmB,EAAK,KAAO,KAGhE,GAFI,GAAU,SAAW,GAAU,EAAS,OAAS,IACrD,GAAU,QAAQ,EACd,IAAa,MAAM,OACvB,IAAM,EAAO,EACT,EAAe,EAAM,IAAI,CAAI,EACjC,GAAI,IAAiB,IAAA,GAAW,CAC9B,IAAM,EAAa,IAAI,gBACvB,EAAe,CAAE,aAAY,MAAO,CAAE,EACtC,EAAM,IAAI,EAAM,CAAY,EAC5B,EAAK,iBACH,QACC,GAAU,CAGT,mBAAqB,CACf,EAAM,IAAI,CAAI,GAAK,CAAC,EAAM,kBAAkB,EAAQ,CAC1D,CAAC,CACH,EACA,CAAE,OAAQ,EAAW,MAAO,CAC9B,CACF,CACA,EAAa,QACb,IAAM,EAAQ,CAAE,OAAM,SAAQ,SAAQ,EACtC,EAAO,IAAI,EAAM,CAAK,EACtB,SAAS,GAAgB,CACvB,GAAI,EAAO,IAAI,CAAI,IAAM,EAAO,OAChC,EAAO,OAAO,CAAI,EAClB,EAAO,oBAAoB,QAAS,CAAO,EAC3C,IAAM,EAAU,EAAM,IAAI,CAAI,EAC1B,IAAY,IAAA,IAAa,EAAE,EAAQ,MAAQ,IAC/C,EAAQ,WAAW,MAAM,EACzB,EAAM,OAAO,CAAI,EACnB,CACA,EAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,CAC1D,CACA,MAAO,CAAE,MAAK,CAChB"}
@@ -0,0 +1,2 @@
1
+ function e(){let e,t=!1;return{cancel:()=>e?.(),cancelOpening:()=>{t&&e?.()},schedule(n,r,i,a){if(e?.(),a.aborted)return;t=n;let o=setTimeout(()=>{s(),r()},i);function s(){clearTimeout(o),a.removeEventListener(`abort`,s),t=!1,e=void 0}e=s,a.addEventListener(`abort`,s,{once:!0})}}}export{e as t};
2
+ //# sourceMappingURL=hover-HF7JXkR2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover-HF7JXkR2.js","names":[],"sources":["../src/internal/hover.ts"],"sourcesContent":["/** One pending hover intent. Replacing it releases its timer and abort listener. */\nexport function createHoverIntent() {\n let cancel: (() => void) | undefined;\n let opening = false;\n return {\n cancel: () => cancel?.(),\n cancelOpening: () => {\n if (opening) cancel?.();\n },\n schedule(\n open: boolean,\n change: () => void,\n delay: number,\n signal: AbortSignal,\n ): void {\n cancel?.();\n if (signal.aborted) return;\n opening = open;\n const timer = setTimeout(() => {\n clear();\n change();\n }, delay);\n function clear(): void {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", clear);\n opening = false;\n cancel = undefined;\n }\n cancel = clear;\n signal.addEventListener(\"abort\", clear, { once: true });\n },\n };\n}\n"],"mappings":"AACA,SAAgB,GAAoB,CAClC,IAAI,EACA,EAAU,GACd,MAAO,CACL,WAAc,IAAS,EACvB,kBAAqB,CACf,GAAS,IAAS,CACxB,EACA,SACE,EACA,EACA,EACA,EACM,CAEN,GADA,IAAS,EACL,EAAO,QAAS,OACpB,EAAU,EACV,IAAM,EAAQ,eAAiB,CAC7B,EAAM,EACN,EAAO,CACT,EAAG,CAAK,EACR,SAAS,GAAc,CACrB,aAAa,CAAK,EAClB,EAAO,oBAAoB,QAAS,CAAK,EACzC,EAAU,GACV,EAAS,IAAA,EACX,CACA,EAAS,EACT,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,CACxD,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ import{useId as e,useMemo as t}from"@bgub/fig";function n(){let n=e(),r=t(()=>new Map,[]);return(e,t)=>{let i=r.get(e);return i===void 0&&(i=r.size,r.set(e,i)),`${n}-${t}-${i}`}}export{n as t};
2
+ //# sourceMappingURL=ids-Cvu36mFL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids-Cvu36mFL.js","names":[],"sources":["../src/internal/ids.ts"],"sourcesContent":["import { useId, useMemo } from \"@bgub/fig\";\n\n/**\n * Generates stable ids for the parts of one widget instance. Values are\n * arbitrary, so each one takes the next index the first time it appears.\n */\nexport function usePartIds(): (value: unknown, part: string) => string {\n const id = useId();\n const indexes = useMemo(() => new Map<unknown, number>(), []);\n\n return (value, part) => {\n let index = indexes.get(value);\n if (index === undefined) {\n index = indexes.size;\n indexes.set(value, index);\n }\n return `${id}-${part}-${index}`;\n };\n}\n"],"mappings":"+CAMA,SAAgB,GAAuD,CACrE,IAAM,EAAK,EAAM,EACX,EAAU,MAAc,IAAI,IAAwB,CAAC,CAAC,EAE5D,OAAQ,EAAO,IAAS,CACtB,IAAI,EAAQ,EAAQ,IAAI,CAAK,EAK7B,OAJI,IAAU,IAAA,KACZ,EAAQ,EAAQ,KAChB,EAAQ,IAAI,EAAO,CAAK,GAEnB,GAAG,EAAG,GAAG,EAAK,GAAG,GAC1B,CACF"}
@@ -0,0 +1,2 @@
1
+ import{o as e}from"./parts-I0jdefyp.js";import{i as t,t as n}from"./composite-CiKLX-Ti.js";function r(r,i){let a=n({container:`[role="listbox"]`,item:`[role="option"]`,name:r,registrationChanged:i}),o=e(),s=``,c;function l(e,t){a.bindContainer(e,t)&&t.addEventListener(`abort`,()=>{c!==void 0&&clearTimeout(c),c=void 0,s=``},{once:!0})}function u(e,t,n){let r={...n,node:e};o.bind(e,t,r),a.bindItem(e,t,n.value,n.disabled)}function d(){return a.items().flatMap(e=>o.get(e.node)??[])}function f(e){return d().find(n=>t(n.value,e))}function p(e){let t=a.itemAt(e);return t===void 0?void 0:o.get(t.node)}function m(e,n){let r=d().filter(e=>!e.disabled);if(r.length===0)return;if(n===`Home`)return r[0];if(n===`End`)return r.at(-1);if(n!==`ArrowDown`&&n!==`ArrowUp`)return;let i=r.findIndex(n=>t(n.value,e));return i===-1?n===`ArrowDown`?r[0]:r.at(-1):r[(i+(n===`ArrowDown`?1:-1)+r.length)%r.length]}function h(e,n){if(n.length!==1||n===` `)return;c!==void 0&&clearTimeout(c),c=setTimeout(()=>{s=``},500);let r=n.toLocaleLowerCase(),i=s.length===1&&s===r;s=i?s:s+r;let a=d().filter(e=>!e.disabled),o=a.findIndex(n=>t(n.value,e)),l=o===-1?0:o+ +!!i;for(let e=0;e<a.length;e+=1){let t=a[(l+e)%a.length],n=t?.textValue??t?.node.textContent?.trim()??``;if(t!==void 0&&n.toLocaleLowerCase().startsWith(s))return t}}return{...a,bindContainer:l,bindOption:u,move:m,option:f,optionAt:p,options:d,typeahead:h}}export{r as t};
2
+ //# sourceMappingURL=listbox-DcD6dHSg.js.map