@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,2 @@
1
+ import{a as e,n as t,o as n,r}from"./parts-I0jdefyp.js";import{c as i,i as a,n as o,o as s}from"./diagnostics-BkQV0MaT.js";import{t as c}from"./ids-Cvu36mFL.js";import{t as l}from"./part-reference-B7zINPUK.js";import{createMixin as u,useBeforePaint as d,useId as f,useMemo as p}from"@bgub/fig";const m=Symbol(`fig-headless.field.description`),h=Symbol(`fig-headless.field.error`),g=u((e,n)=>{i(e,`field label`,`label`);let r=l(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}}),_=u((e,n)=>{let r=l(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}}),v=u((e,n,r)=>({bind:t(e,n.bind,(e,t)=>n.bind(e,t,{kind:`description`})),id:e.props.id??r.id})),y=u((e,n,r)=>({bind:t(e,n.bind,(e,t)=>n.bind(e,t,{kind:`error`})),id:e.props.id??r.id}));function b(t={}){let{disabled:i=!1,invalid:l=!1,required:u=!1}=t,b=f(),C=`${b}-control`,w=`${b}-label`,T=c(),E=e(),D=p(()=>n(E),[]);d(()=>{let e=D.items(),t=e.filter(e=>e.value.kind===`control`),n=e.filter(e=>e.value.kind===`label`);a(t,`field control`),a(n,`field label`);let i=t.at(-1);if(i===void 0)return;let c=i.node,u=n.at(-1);u?.value.kind===`label`&&u.value.control.sync(u.node,c.id),i.value.kind===`control`&&i.value.label.sync(c,u?.node.id),o(c);let d=e.filter(e=>e.value.kind===`description`).map(e=>e.node).sort(x),f=e.filter(e=>e.value.kind===`error`).map(e=>e.node).sort(x);s([...d,...f],`field messages`);let p=S([...(i.value.kind===`control`?i.value.describedBy?.split(/\s+/):void 0)??[],...d.map(e=>e.id),...l?f.map(e=>e.id):[]]).join(` `);r(c,`aria-describedby`,p)});let O={controlId:C,disabled:i,invalid:l,labelId:w,bind:D.bind,required:u};return{control:()=>_(O),description:(e=m)=>v(O,{id:T(e,`description`)}),error:(e=h)=>y(O,{id:T(e,`error`)}),label:()=>g(O)}}function x(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 S(e){return[...new Set(e.filter(e=>e!==``))]}function C(e){return e.children(b(e))}export{C as Field,b 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":"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"}
@@ -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-DoFLgUjR.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-B-oe_0c0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listbox-B-oe_0c0.js","names":[],"sources":["../src/internal/listbox.ts"],"sourcesContent":["import { createPartCollection } from \"./registration.ts\";\nimport { createComposite, sameValue } from \"./composite.ts\";\n\nexport interface ListboxOptionConfig {\n readonly disabled: boolean;\n readonly textValue: string | undefined;\n readonly value: unknown;\n}\n\nexport interface ListboxOption extends ListboxOptionConfig {\n readonly node: HTMLElement;\n}\n\n/** Ordered option registration and active-descendant movement. */\nexport function createListbox(name: string, registrationChanged: () => void) {\n const composite = createComposite({\n container: '[role=\"listbox\"]',\n item: '[role=\"option\"]',\n name,\n registrationChanged,\n });\n const registrations = createPartCollection<ListboxOption>();\n let search = \"\";\n let searchTimer: ReturnType<typeof setTimeout> | undefined;\n\n function bindContainer(node: HTMLElement, signal: AbortSignal): void {\n if (!composite.bindContainer(node, signal)) return;\n signal.addEventListener(\n \"abort\",\n () => {\n if (searchTimer !== undefined) clearTimeout(searchTimer);\n searchTimer = undefined;\n search = \"\";\n },\n { once: true },\n );\n }\n\n function bindOption(\n node: HTMLElement,\n signal: AbortSignal,\n config: ListboxOptionConfig,\n ): void {\n const option = { ...config, node };\n registrations.bind(node, signal, option);\n composite.bindItem(node, signal, config.value, config.disabled);\n }\n\n function options(): readonly ListboxOption[] {\n return composite\n .items()\n .flatMap((item) => registrations.get(item.node) ?? []);\n }\n\n function option(value: unknown): ListboxOption | undefined {\n return options().find((entry) => sameValue(entry.value, value));\n }\n\n function optionAt(target: EventTarget | null): ListboxOption | undefined {\n const item = composite.itemAt(target);\n return item === undefined ? undefined : registrations.get(item.node);\n }\n\n function move(current: unknown, key: string): ListboxOption | undefined {\n const enabled = options().filter((entry) => !entry.disabled);\n if (enabled.length === 0) return undefined;\n if (key === \"Home\") return enabled[0];\n if (key === \"End\") return enabled.at(-1);\n if (key !== \"ArrowDown\" && key !== \"ArrowUp\") return undefined;\n const currentIndex = enabled.findIndex((entry) =>\n sameValue(entry.value, current),\n );\n if (currentIndex === -1) {\n return key === \"ArrowDown\" ? enabled[0] : enabled.at(-1);\n }\n const delta = key === \"ArrowDown\" ? 1 : -1;\n return enabled[(currentIndex + delta + enabled.length) % enabled.length];\n }\n\n function typeahead(current: unknown, key: string): ListboxOption | undefined {\n if (key.length !== 1 || key === \" \") return undefined;\n if (searchTimer !== undefined) clearTimeout(searchTimer);\n searchTimer = setTimeout(() => {\n search = \"\";\n }, 500);\n const normalized = key.toLocaleLowerCase();\n const repeated = search.length === 1 && search === normalized;\n search = repeated ? search : search + normalized;\n const enabled = options().filter((entry) => !entry.disabled);\n const currentIndex = enabled.findIndex((entry) =>\n sameValue(entry.value, current),\n );\n const start = currentIndex === -1 ? 0 : currentIndex + (repeated ? 1 : 0);\n for (let offset = 0; offset < enabled.length; offset += 1) {\n const candidate = enabled[(start + offset) % enabled.length];\n const text =\n candidate?.textValue ?? candidate?.node.textContent?.trim() ?? \"\";\n if (\n candidate !== undefined &&\n text.toLocaleLowerCase().startsWith(search)\n ) {\n return candidate;\n }\n }\n return undefined;\n }\n\n return {\n ...composite,\n bindContainer,\n bindOption,\n move,\n option,\n optionAt,\n options,\n typeahead,\n };\n}\n"],"mappings":"2FAcA,SAAgB,EAAc,EAAc,EAAiC,CAC3E,IAAM,EAAY,EAAgB,CAChC,UAAW,mBACX,KAAM,kBACN,OACA,qBACF,CAAC,EACK,EAAgB,EAAoC,EACtD,EAAS,GACT,EAEJ,SAAS,EAAc,EAAmB,EAA2B,CAC9D,EAAU,cAAc,EAAM,CAAM,GACzC,EAAO,iBACL,YACM,CACA,IAAgB,IAAA,IAAW,aAAa,CAAW,EACvD,EAAc,IAAA,GACd,EAAS,EACX,EACA,CAAE,KAAM,EAAK,CACf,CACF,CAEA,SAAS,EACP,EACA,EACA,EACM,CACN,IAAM,EAAS,CAAE,GAAG,EAAQ,MAAK,EACjC,EAAc,KAAK,EAAM,EAAQ,CAAM,EACvC,EAAU,SAAS,EAAM,EAAQ,EAAO,MAAO,EAAO,QAAQ,CAChE,CAEA,SAAS,GAAoC,CAC3C,OAAO,EACJ,MAAM,CAAC,CACP,QAAS,GAAS,EAAc,IAAI,EAAK,IAAI,GAAK,CAAC,CAAC,CACzD,CAEA,SAAS,EAAO,EAA2C,CACzD,OAAO,EAAQ,CAAC,CAAC,KAAM,GAAU,EAAU,EAAM,MAAO,CAAK,CAAC,CAChE,CAEA,SAAS,EAAS,EAAuD,CACvE,IAAM,EAAO,EAAU,OAAO,CAAM,EACpC,OAAO,IAAS,IAAA,GAAY,IAAA,GAAY,EAAc,IAAI,EAAK,IAAI,CACrE,CAEA,SAAS,EAAK,EAAkB,EAAwC,CACtE,IAAM,EAAU,EAAQ,CAAC,CAAC,OAAQ,GAAU,CAAC,EAAM,QAAQ,EAC3D,GAAI,EAAQ,SAAW,EAAG,OAC1B,GAAI,IAAQ,OAAQ,OAAO,EAAQ,GACnC,GAAI,IAAQ,MAAO,OAAO,EAAQ,GAAG,EAAE,EACvC,GAAI,IAAQ,aAAe,IAAQ,UAAW,OAC9C,IAAM,EAAe,EAAQ,UAAW,GACtC,EAAU,EAAM,MAAO,CAAO,CAChC,EAKA,OAJI,IAAiB,GACZ,IAAQ,YAAc,EAAQ,GAAK,EAAQ,GAAG,EAAE,EAGlD,GAAS,GADF,IAAQ,YAAc,EAAI,IACD,EAAQ,QAAU,EAAQ,OACnE,CAEA,SAAS,EAAU,EAAkB,EAAwC,CAC3E,GAAI,EAAI,SAAW,GAAK,IAAQ,IAAK,OACjC,IAAgB,IAAA,IAAW,aAAa,CAAW,EACvD,EAAc,eAAiB,CAC7B,EAAS,EACX,EAAG,GAAG,EACN,IAAM,EAAa,EAAI,kBAAkB,EACnC,EAAW,EAAO,SAAW,GAAK,IAAW,EACnD,EAAS,EAAW,EAAS,EAAS,EACtC,IAAM,EAAU,EAAQ,CAAC,CAAC,OAAQ,GAAU,CAAC,EAAM,QAAQ,EACrD,EAAe,EAAQ,UAAW,GACtC,EAAU,EAAM,MAAO,CAAO,CAChC,EACM,EAAQ,IAAiB,GAAK,EAAI,GAAgB,KACxD,IAAK,IAAI,EAAS,EAAG,EAAS,EAAQ,OAAQ,GAAU,EAAG,CACzD,IAAM,EAAY,GAAS,EAAQ,GAAU,EAAQ,QAC/C,EACJ,GAAW,WAAa,GAAW,KAAK,aAAa,KAAK,GAAK,GACjE,GACE,IAAc,IAAA,IACd,EAAK,kBAAkB,CAAC,CAAC,WAAW,CAAM,EAE1C,OAAO,CAEX,CAEF,CAEA,MAAO,CACL,GAAG,EACH,gBACA,aACA,OACA,SACA,WACA,UACA,WACF,CACF"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./changes-DglRuvQa.js";import{a as t,n,r}from"./parts-I0jdefyp.js";import{a as i,t as a}from"./diagnostics-BkQV0MaT.js";import{i as o}from"./composite-DoFLgUjR.js";import{n as s}from"./controllable-value-CDbRzlId.js";import{t as c}from"./ids-Cvu36mFL.js";import{t as l}from"./listbox-B-oe_0c0.js";import{createMixin as u,useBeforePaint as d,useMemo as f,useStableEvent as p,useState as m}from"@bgub/fig";import{on as h}from"@bgub/fig-dom";const g=u((e,t)=>({"aria-activedescendant":t.highlighted===null?void 0:t.registry.option(t.highlighted)?.node.id??t.idFor(t.highlighted,`option`),"aria-disabled":t.disabled?`true`:void 0,"aria-multiselectable":t.multiple?`true`:void 0,"aria-readonly":t.readOnly?`true`:void 0,bind:n(e,t.registry,t.registry.bindContainer),"data-disabled":t.disabled?``:void 0,"data-readonly":t.readOnly?``:void 0,mix:[h(`click`,e=>{if(e.defaultPrevented)return;let n=t.registry.optionAt(e.target);n!==void 0&&(t.disabled||n.disabled?e.preventDefault():e.button===0&&(t.setHighlighted(n.value),t.readOnly?e.preventDefault():t.select(n,e)))}),h(`pointerdown`,e=>{t.registry.optionAt(e.target)!==void 0&&(e.preventDefault(),!t.disabled&&e.currentTarget instanceof HTMLElement&&e.currentTarget.focus())}),h(`pointermove`,e=>{if(e.pointerType===`touch`||t.disabled)return;let n=t.registry.optionAt(e.target);n!==void 0&&!n.disabled&&t.setHighlighted(n.value)}),h(`keydown`,e=>{if(e.defaultPrevented||e.target instanceof Element&&e.target.closest(`[role="listbox"]`)!==e.currentTarget||t.disabled||e.isComposing||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)return;let n=t.registry.move(t.highlighted,e.key);if(n!==void 0){e.preventDefault(),t.setHighlighted(n.value),t.multiple||t.select(n,e);return}if(e.key===`Enter`||e.key===` `){let n=t.registry.option(t.highlighted);n!==void 0&&(e.preventDefault(),t.select(n,e));return}let r=t.registry.typeahead(t.highlighted,e.key);r!==void 0&&(t.setHighlighted(r.value),t.multiple||t.select(r,e))})],role:`listbox`,tabindex:t.disabled?-1:e.props.tabindex??0})),_=u((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":o(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}});function v(n={}){let{disabled:u=!1,multiple:h=!1,readOnly:v=!1}=n,y=t(),x=s({value:n.value,defaultValue:n.defaultValue??[],onChange:n.onValueChange,equal:b,reconcile:y}),S=x.value;h||i(S,`single-select listbox`);let[C,w]=m(()=>({value:S[0]??null})),T=f(()=>l(`listbox`,y),[]),E=c(),D=p((t,n)=>{u||v||t.disabled||x.request(e=>{let n=e.some(e=>o(e,t.value));return h?n?e.filter(e=>!o(e,t.value)):[...e,t.value]:[t.value]},e(n,t.node))}),O=p(e=>{o(C.value,e)||w({value:e})});d(()=>{let e=T.options(),t=T.option(C.value)?.disabled===!1?C.value:S.find(e=>T.option(e)?.disabled===!1)??e.find(e=>!e.disabled)?.value??null;O(t);let n=T.containerNode();if(n!==null){a(n,`listbox`);let e=t===null?void 0:T.option(t)?.node.id??E(t,`option`);r(n,`aria-activedescendant`,e)}});let k={disabled:u,highlighted:C.value,idFor:E,multiple:h,readOnly:v,registry:T,select:D,setHighlighted:O};return{option:(e,t={})=>_(k,{disabled:t.disabled===!0,selected:S.some(t=>o(t,e)),textValue:t.textValue,value:e}),root:()=>g(k),values:S}}function y(e){return e.children(v(e))}function b(e,t){return e.length===t.length&&e.every((e,n)=>o(e,t[n]))}export{y as Listbox,v as useListbox};
2
+ //# sourceMappingURL=listbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listbox.js","names":["createListboxRegistry"],"sources":["../src/listbox/listbox.tsx"],"sourcesContent":["import {\n createMixin,\n type FigNode,\n type MixinContext,\n type MixinDescriptor,\n useBeforePaint,\n useMemo,\n useStableEvent,\n useState,\n} from \"@bgub/fig\";\nimport { on } from \"@bgub/fig-dom\";\nimport {\n type ChangeDetails,\n createChangeDetails,\n} from \"../internal/changes.ts\";\nimport { useControllableValue } from \"../internal/controllable-value.ts\";\nimport { sameValue } from \"../internal/composite.ts\";\nimport {\n assertAccessibleName,\n assertSingleSelection,\n} from \"../internal/diagnostics.ts\";\nimport { usePartIds } from \"../internal/ids.ts\";\nimport {\n createListbox as createListboxRegistry,\n type ListboxOption,\n} from \"../internal/listbox.ts\";\nimport { bindPart, setIdReference } from \"../internal/parts.ts\";\nimport { useRegistrationReconcile } from \"../internal/reconcile.ts\";\n\nexport type ListboxValueChangeDetails = ChangeDetails;\n\nexport type ListboxValueChangeHandler<Value = unknown> = (\n values: readonly Value[],\n details: ListboxValueChangeDetails,\n signal: AbortSignal,\n) => void;\n\nexport interface ListboxOptionOptions {\n disabled?: boolean;\n /** Text used for typeahead when it differs from the host's text content. */\n textValue?: string;\n}\n\nexport interface ListboxOptions<Value = unknown> {\n defaultValue?: readonly Value[];\n disabled?: boolean;\n multiple?: boolean;\n onValueChange?: ListboxValueChangeHandler<Value>;\n readOnly?: boolean;\n value?: readonly Value[];\n}\n\nexport interface ListboxParts<Value = unknown> {\n readonly values: readonly Value[];\n option(value: Value, options?: ListboxOptionOptions): MixinDescriptor;\n root(): MixinDescriptor;\n}\n\nexport interface ListboxProps<Value = unknown> extends ListboxOptions<Value> {\n children: (listbox: ListboxParts<Value>) => FigNode;\n}\n\ntype ListboxRegistry = ReturnType<typeof createListboxRegistry>;\n\ninterface ListboxState {\n readonly disabled: boolean;\n readonly highlighted: unknown;\n readonly idFor: (value: unknown, part: string) => string;\n readonly multiple: boolean;\n readonly readOnly: boolean;\n readonly registry: ListboxRegistry;\n readonly select: (option: ListboxOption, event: Event) => void;\n readonly setHighlighted: (value: unknown) => void;\n}\n\ninterface ListboxOptionState {\n readonly disabled: boolean;\n readonly selected: boolean;\n readonly textValue: string | undefined;\n readonly value: unknown;\n}\n\nconst listboxRootMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: ListboxState) => ({\n \"aria-activedescendant\":\n state.highlighted === null\n ? undefined\n : (state.registry.option(state.highlighted)?.node.id ??\n state.idFor(state.highlighted, \"option\")),\n \"aria-disabled\": state.disabled ? \"true\" : undefined,\n \"aria-multiselectable\": state.multiple ? \"true\" : undefined,\n \"aria-readonly\": state.readOnly ? \"true\" : undefined,\n bind: bindPart(context, state.registry, state.registry.bindContainer),\n \"data-disabled\": state.disabled ? \"\" : undefined,\n \"data-readonly\": state.readOnly ? \"\" : undefined,\n mix: [\n on(\"click\", (event) => {\n if (event.defaultPrevented) return;\n const option = state.registry.optionAt(event.target);\n if (option === undefined) return;\n if (state.disabled || option.disabled) {\n event.preventDefault();\n } else if (event.button === 0) {\n state.setHighlighted(option.value);\n if (state.readOnly) event.preventDefault();\n else state.select(option, event);\n }\n }),\n on(\"pointerdown\", (event) => {\n if (state.registry.optionAt(event.target) === undefined) return;\n event.preventDefault();\n if (!state.disabled && event.currentTarget instanceof HTMLElement) {\n event.currentTarget.focus();\n }\n }),\n on(\"pointermove\", (event) => {\n if (event.pointerType === \"touch\") return;\n if (state.disabled) return;\n const option = state.registry.optionAt(event.target);\n if (option !== undefined && !option.disabled) {\n state.setHighlighted(option.value);\n }\n }),\n on(\"keydown\", (event) => {\n if (event.defaultPrevented) return;\n if (\n event.target instanceof Element &&\n event.target.closest('[role=\"listbox\"]') !== event.currentTarget\n )\n return;\n if (\n state.disabled ||\n event.isComposing ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n ) {\n return;\n }\n const moved = state.registry.move(state.highlighted, event.key);\n if (moved !== undefined) {\n event.preventDefault();\n state.setHighlighted(moved.value);\n if (!state.multiple) state.select(moved, event);\n return;\n }\n if (event.key === \"Enter\" || event.key === \" \") {\n const option = state.registry.option(state.highlighted);\n if (option !== undefined) {\n event.preventDefault();\n state.select(option, event);\n }\n return;\n }\n const match = state.registry.typeahead(state.highlighted, event.key);\n if (match === undefined) return;\n state.setHighlighted(match.value);\n if (!state.multiple) state.select(match, event);\n }),\n ],\n role: \"listbox\",\n tabindex: state.disabled ? -1 : (context.props.tabindex ?? 0),\n }),\n);\n\nconst listboxOptionMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: ListboxState, own: ListboxOptionState) => {\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\n/** Coordinates selection and active-descendant navigation for one listbox. */\nexport function useListbox<Value = unknown>(\n options: ListboxOptions<Value> = {},\n): ListboxParts<Value> {\n const { disabled = false, multiple = false, readOnly = false } = options;\n const requestReconcile = useRegistrationReconcile();\n const selection = useControllableValue<readonly Value[]>({\n value: options.value,\n defaultValue: options.defaultValue ?? [],\n onChange: options.onValueChange,\n equal: sameValues,\n reconcile: requestReconcile,\n });\n const values = selection.value;\n if (!multiple) assertSingleSelection(values, \"single-select listbox\");\n\n const [highlighted, setHighlightedState] = useState<{\n readonly value: unknown;\n }>(() => ({ value: values[0] ?? null }));\n const registry = useMemo(\n () => createListboxRegistry(\"listbox\", requestReconcile),\n [],\n );\n const idFor = usePartIds();\n const select = useStableEvent((option: ListboxOption, event: Event) => {\n if (disabled || readOnly || option.disabled) return;\n selection.request(\n (current) => {\n const selected = current.some((value) =>\n sameValue(value, option.value),\n );\n return multiple\n ? selected\n ? current.filter((value) => !sameValue(value, option.value))\n : [...current, option.value as Value]\n : [option.value as Value];\n },\n createChangeDetails(event, option.node),\n );\n });\n const setHighlighted = useStableEvent((next: unknown) => {\n if (!sameValue(highlighted.value, next)) {\n setHighlightedState({ value: next });\n }\n });\n\n useBeforePaint(() => {\n const mounted = registry.options();\n const nextHighlight =\n registry.option(highlighted.value)?.disabled === false\n ? highlighted.value\n : (values.find((value) => registry.option(value)?.disabled === false) ??\n mounted.find((option) => !option.disabled)?.value ??\n null);\n setHighlighted(nextHighlight);\n\n const root = registry.containerNode();\n if (root !== null) {\n assertAccessibleName(root, \"listbox\");\n const optionId =\n nextHighlight === null\n ? undefined\n : (registry.option(nextHighlight)?.node.id ??\n idFor(nextHighlight, \"option\"));\n setIdReference(root, \"aria-activedescendant\", optionId);\n }\n });\n\n const state: ListboxState = {\n disabled,\n highlighted: highlighted.value,\n idFor,\n multiple,\n readOnly,\n registry,\n select,\n setHighlighted,\n };\n\n return {\n option: (value, optionOptions = {}) =>\n listboxOptionMixin(state, {\n disabled: optionOptions.disabled === true,\n selected: values.some((selected) => sameValue(selected, value)),\n textValue: optionOptions.textValue,\n value,\n }),\n root: () => listboxRootMixin(state),\n values,\n };\n}\n\n/** {@link useListbox} as a render-callback component. */\nexport function Listbox<Value = unknown>(props: ListboxProps<Value>): FigNode {\n return props.children(useListbox(props));\n}\n\nfunction sameValues(\n left: readonly unknown[],\n right: readonly unknown[],\n): boolean {\n return (\n left.length === right.length &&\n left.every((value, index) => sameValue(value, right[index]))\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../changes-DglRuvQa.js";import{n as t,s as n}from"../parts-I0jdefyp.js";import{a as r,i,n as a}from"../menu-DBxBFdYm.js";import{createMixin as o,useBeforePaint as s,useId as c,useMemo as l,useStableEvent as u,useState as d}from"@bgub/fig";import{on as f}from"@bgub/fig-dom";const p=o((e,n)=>{let r=n.disabled||e.props.disabled===!0;return{"aria-disabled":r?`true`:void 0,"aria-controls":n.popupId,"aria-expanded":n.open?`true`:`false`,"aria-haspopup":`menu`,bind:t(e,n.owner,n.bind),"data-disabled":r?``:void 0,"data-open":n.open?``:void 0,id:e.props.id??n.id,tabindex:e.props.tabindex??0,type:e.type===`button`?e.props.type??`button`:void 0,mix:[f(`contextmenu`,e=>{r||e.defaultPrevented||n.show(e)}),f(`keydown`,e=>{r||e.defaultPrevented||e.isComposing||e.keyCode===229||e.altKey||e.ctrlKey||e.metaKey||(e.key===`ContextMenu`||e.key===`F10`&&e.shiftKey)&&n.show(e)})]}});function m(t={}){let o=c(),f=t.id??`${o}-context-menu`,m=l(()=>({trigger:n(()=>{}),activation:null,positioner:g()}),[]),[h,_]=d(null),v=a({...t,id:f,onOpenChange:(n,r,i)=>{if(m.activation===null){t.onOpenChange?.(n,r,i);return}let a=e(m.activation,m.trigger.node()??void 0);t.onOpenChange?.(n,a,i),a.isCanceled&&r.cancel()}}),y=i(v),b=u((e,t)=>{m.trigger.bind(e,t),y.bindTrigger(e,t)}),x=u(e=>{let t=m.trigger.node();if(!(t===null||t.matches(`:disabled`))){e.preventDefault(),e instanceof KeyboardEvent?_(null):_({x:e.clientX,y:e.clientY}),m.activation=e;try{y.setOpen(!0,`first`)}finally{m.activation=null}}});s(e=>{e.addEventListener(`abort`,m.positioner.stop,{once:!0})},[]),s(()=>{m.positioner.sync(v.open?y.popup():null,m.trigger.node(),h)});let S={...v,trigger:()=>p({owner:m.trigger,bind:b,disabled:t.disabled===!0,id:`${o}-trigger`,open:v.open,popupId:f,show:x})};return r(S,y),S}function h(e){return e.children(m(e))}function g(){let e=null,t=null,n=null,r,i=()=>{};function a(){r?.(),r=void 0,e=null,t=null,i=()=>{}}function o(o,c,l){n=l,(o!==e||c!==t)&&(a(),o!==null&&c!==null&&(e=o,t=c,r=s(o,c))),i()}function s(e,t){let r=e.ownerDocument.defaultView;if(r===null)return()=>{};let a=[`position`,`right`,`bottom`,`margin`,`position-area`,`left`,`top`,`max-width`,`max-height`,`box-sizing`,`overflow`],o=new Map,s=new Map,c=()=>{for(let t of a){let n=o.get(t);if(n===void 0||e.style.getPropertyValue(t)!==s.get(t))continue;let[r,i]=n;r?e.style.setProperty(t,r,i):e.style.removeProperty(t)}s.clear()},l=(t,n)=>{e.style.setProperty(t,n),s.set(t,e.style.getPropertyValue(t))},u=``,d=``,f=()=>{let i=t.getBoundingClientRect(),a=r.getComputedStyle(t).direction,o=a?a===`rtl`:t.closest(`[dir]`)?.getAttribute(`dir`)===`rtl`,s=n??{x:o?i.right:i.left,y:i.bottom},c=r.visualViewport,f=c?.offsetLeft??0,p=c?.offsetTop??0,m=c?.width??r.innerWidth,h=c?.height??r.innerHeight,g=_(e),v=`${Math.max(0,m-16)/g}px`,y=`${Math.max(0,h-16)/g}px`;l(`max-width`,!u||u===`none`?v:`min(${u}, ${v})`),l(`max-height`,!d||d===`none`?y:`min(${d}, ${y})`);let b=e.getBoundingClientRect(),x=o?s.x-b.width:s.x,S=s.y+b.height>p+h-8?s.y-b.height:s.y;l(`left`,`${Math.max(f+8,Math.min(x,f+m-b.width-8))/g}px`),l(`top`,`${Math.max(p+8,Math.min(S,p+h-b.height-8))/g}px`)};i=()=>{c();for(let t of a)o.set(t,[e.style.getPropertyValue(t),e.style.getPropertyPriority(t)]);let t=r.getComputedStyle(e);u=t.maxWidth,d=t.maxHeight,l(`position`,`fixed`),l(`right`,`auto`),l(`bottom`,`auto`),l(`margin`,`0`),l(`position-area`,`none`),l(`box-sizing`,`border-box`),l(`overflow`,`auto`),f()};let p,m=()=>{p===void 0&&(p=r.requestAnimationFrame(()=>{p=void 0,f()}))};r.addEventListener(`resize`,m),r.addEventListener(`scroll`,m,!0),r.visualViewport?.addEventListener(`resize`,m),r.visualViewport?.addEventListener(`scroll`,m);let h=typeof ResizeObserver>`u`?null:new ResizeObserver(m);return h?.observe(e),h?.observe(t),()=>{p!==void 0&&r.cancelAnimationFrame(p),r.removeEventListener(`resize`,m),r.removeEventListener(`scroll`,m,!0),r.visualViewport?.removeEventListener(`resize`,m),r.visualViewport?.removeEventListener(`scroll`,m),h?.disconnect(),c()}}return{sync:o,stop:a}}function _(e){let t=e.currentCSSZoom;if(t!==void 0&&Number.isFinite(t)&&t>0)return t;let n=e.ownerDocument.defaultView;if(n===null)return 1;let r=1,i=e;for(;i!==null;){let e=n.getComputedStyle(i).getPropertyValue(`zoom`),t=parseFloat(e)/(e.endsWith(`%`)?100:1);if(Number.isFinite(t)&&t>0&&(r*=t),i.parentElement!==null)i=i.parentElement;else{let e=i.getRootNode();i=e instanceof ShadowRoot?e.host:null}}return Number.isFinite(r)&&r>0?r:1}export{h as ContextMenu,m as useContextMenu};
2
+ //# sourceMappingURL=context-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.js","names":[],"sources":["../../src/menu/context-menu.tsx"],"sourcesContent":["import {\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 { createChangeDetails } from \"../internal/changes.ts\";\nimport { bindPart } from \"../internal/parts.ts\";\nimport { createPartSlot } from \"../internal/registration.ts\";\nimport { menuController, registerMenuController } from \"./controller.ts\";\nimport { type MenuOptions, type MenuParts, useMenu } from \"./menu.tsx\";\n\nexport interface ContextMenuOptions<\n Value = unknown,\n> extends MenuOptions<Value> {\n disabled?: boolean;\n}\n\nexport interface ContextMenuProps<\n Value = unknown,\n> extends ContextMenuOptions<Value> {\n children: (menu: MenuParts<Value>) => FigNode;\n}\n\ninterface Point {\n readonly x: number;\n readonly y: number;\n}\n\ninterface TriggerState {\n readonly owner: object;\n readonly bind: (node: HTMLElement, signal: AbortSignal) => void;\n readonly disabled: boolean;\n readonly id: string;\n readonly open: boolean;\n readonly popupId: string;\n readonly show: (event: MouseEvent | KeyboardEvent) => void;\n}\n\nconst contextTriggerMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: TriggerState) => {\n const disabled = state.disabled || context.props.disabled === true;\n return {\n \"aria-disabled\": disabled ? \"true\" : undefined,\n \"aria-controls\": state.popupId,\n \"aria-expanded\": state.open ? \"true\" : \"false\",\n \"aria-haspopup\": \"menu\",\n bind: bindPart(context, state.owner, state.bind),\n \"data-disabled\": disabled ? \"\" : undefined,\n \"data-open\": state.open ? \"\" : undefined,\n id: context.props.id ?? state.id,\n // A context area may be an ordinary div. It must be reachable by the\n // keyboard, and capable of receiving focus back when the menu closes.\n tabindex: context.props.tabindex ?? 0,\n type:\n context.type === \"button\"\n ? (context.props.type ?? \"button\")\n : undefined,\n mix: [\n on(\"contextmenu\", (event) => {\n if (disabled || event.defaultPrevented) return;\n state.show(event);\n }),\n on(\"keydown\", (event) => {\n if (\n disabled ||\n event.defaultPrevented ||\n event.isComposing ||\n event.keyCode === 229 ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey\n )\n return;\n if (\n event.key === \"ContextMenu\" ||\n (event.key === \"F10\" && event.shiftKey)\n )\n state.show(event);\n }),\n ],\n };\n },\n);\n\n/** Optional point-positioned menu; ordinary clicks retain their native action. */\nexport function useContextMenu<Value = unknown>(\n options: ContextMenuOptions<Value> = {},\n): MenuParts<Value> {\n const id = useId();\n const popupId = options.id ?? `${id}-context-menu`;\n const tracker = useMemo(\n () => ({\n trigger: createPartSlot(() => {}),\n activation: null as MouseEvent | KeyboardEvent | null,\n positioner: createContextPositioner(),\n }),\n [],\n );\n const [point, setPoint] = useState<Point | null>(null);\n const menu = useMenu<Value>({\n ...options,\n id: popupId,\n onOpenChange: (open, details, signal) => {\n if (tracker.activation === null) {\n options.onOpenChange?.(open, details, signal);\n return;\n }\n const activation = createChangeDetails(\n tracker.activation,\n tracker.trigger.node() ?? undefined,\n );\n options.onOpenChange?.(open, activation, signal);\n if (activation.isCanceled) details.cancel();\n },\n });\n const controller = menuController(menu);\n const bind = useStableEvent((node: HTMLElement, signal: AbortSignal) => {\n tracker.trigger.bind(node, signal);\n controller.bindTrigger(node, signal);\n });\n const show = useStableEvent((event: MouseEvent | KeyboardEvent) => {\n const trigger = tracker.trigger.node();\n if (trigger === null || trigger.matches(\":disabled\")) return;\n event.preventDefault();\n if (event instanceof KeyboardEvent) {\n // Recompute from the element on resize/scroll for keyboard invocation.\n setPoint(null);\n } else setPoint({ x: event.clientX, y: event.clientY });\n tracker.activation = event;\n try {\n controller.setOpen(true, \"first\");\n } finally {\n tracker.activation = null;\n }\n });\n\n useBeforePaint((signal) => {\n signal.addEventListener(\"abort\", tracker.positioner.stop, { once: true });\n }, []);\n useBeforePaint(() => {\n tracker.positioner.sync(\n menu.open ? controller.popup() : null,\n tracker.trigger.node(),\n point,\n );\n });\n\n const parts: MenuParts<Value> = {\n ...menu,\n trigger: (): MixinDescriptor =>\n contextTriggerMixin({\n owner: tracker.trigger,\n bind,\n disabled: options.disabled === true,\n id: `${id}-trigger`,\n open: menu.open,\n popupId,\n show,\n }),\n };\n registerMenuController(parts, controller);\n return parts;\n}\n\n/** {@link useContextMenu} as a render-callback component. */\nexport function ContextMenu<Value = unknown>(\n props: ContextMenuProps<Value>,\n): FigNode {\n return props.children(useContextMenu(props));\n}\n\n/** Retains observation across renders; only changed hosts replace listeners. */\nfunction createContextPositioner() {\n let currentPopup: HTMLElement | null = null;\n let currentTrigger: HTMLElement | null = null;\n let currentPoint: Point | null = null;\n let cleanup: (() => void) | undefined;\n let reposition = () => {};\n\n function stop() {\n cleanup?.();\n cleanup = undefined;\n currentPopup = null;\n currentTrigger = null;\n reposition = () => {};\n }\n\n function sync(\n popup: HTMLElement | null,\n trigger: HTMLElement | null,\n point: Point | null,\n ) {\n currentPoint = point;\n if (popup !== currentPopup || trigger !== currentTrigger) {\n stop();\n if (popup !== null && trigger !== null) {\n currentPopup = popup;\n currentTrigger = trigger;\n cleanup = observe(popup, trigger);\n }\n }\n reposition();\n }\n\n function observe(popup: HTMLElement, trigger: HTMLElement): () => void {\n const view = popup.ownerDocument.defaultView;\n if (view === null) return () => {};\n // Point positioning deliberately replaces CSS anchor placement only for\n // this optional widget. Keep authored inline styles intact on teardown.\n const properties = [\n \"position\",\n \"right\",\n \"bottom\",\n \"margin\",\n \"position-area\",\n \"left\",\n \"top\",\n \"max-width\",\n \"max-height\",\n \"box-sizing\",\n \"overflow\",\n ] as const;\n const previous = new Map<string, readonly [string, string]>();\n const applied = new Map<string, string>();\n const restore = () => {\n for (const property of properties) {\n const original = previous.get(property);\n if (\n original === undefined ||\n popup.style.getPropertyValue(property) !== applied.get(property)\n )\n continue;\n const [value, priority] = original;\n if (value) popup.style.setProperty(property, value, priority);\n else popup.style.removeProperty(property);\n }\n applied.clear();\n };\n const write = (property: string, value: string) => {\n popup.style.setProperty(property, value);\n applied.set(property, popup.style.getPropertyValue(property));\n };\n let maxWidth = \"\";\n let maxHeight = \"\";\n const position = () => {\n const triggerRect = trigger.getBoundingClientRect();\n const direction = view.getComputedStyle(trigger).direction;\n const rtl = direction\n ? direction === \"rtl\"\n : trigger.closest(\"[dir]\")?.getAttribute(\"dir\") === \"rtl\";\n const origin = currentPoint ?? {\n x: rtl ? triggerRect.right : triggerRect.left,\n y: triggerRect.bottom,\n };\n const viewport = view.visualViewport;\n const left = viewport?.offsetLeft ?? 0;\n const top = viewport?.offsetTop ?? 0;\n const width = viewport?.width ?? view.innerWidth;\n const height = viewport?.height ?? view.innerHeight;\n const scale = effectiveZoom(popup);\n const availableWidth = `${Math.max(0, width - 16) / scale}px`;\n const availableHeight = `${Math.max(0, height - 16) / scale}px`;\n write(\n \"max-width\",\n !maxWidth || maxWidth === \"none\"\n ? availableWidth\n : `min(${maxWidth}, ${availableWidth})`,\n );\n write(\n \"max-height\",\n !maxHeight || maxHeight === \"none\"\n ? availableHeight\n : `min(${maxHeight}, ${availableHeight})`,\n );\n const rect = popup.getBoundingClientRect();\n const x = rtl ? origin.x - rect.width : origin.x;\n const y =\n origin.y + rect.height > top + height - 8\n ? origin.y - rect.height\n : origin.y;\n write(\n \"left\",\n `${Math.max(left + 8, Math.min(x, left + width - rect.width - 8)) / scale}px`,\n );\n write(\n \"top\",\n `${Math.max(top + 8, Math.min(y, top + height - rect.height - 8)) / scale}px`,\n );\n };\n reposition = () => {\n // New authored props/classes may change while open. Remove only values\n // still owned by us before reading the author's current constraints.\n restore();\n for (const property of properties)\n previous.set(property, [\n popup.style.getPropertyValue(property),\n popup.style.getPropertyPriority(property),\n ]);\n const authored = view.getComputedStyle(popup);\n maxWidth = authored.maxWidth;\n maxHeight = authored.maxHeight;\n write(\"position\", \"fixed\");\n write(\"right\", \"auto\");\n write(\"bottom\", \"auto\");\n write(\"margin\", \"0\");\n write(\"position-area\", \"none\");\n write(\"box-sizing\", \"border-box\");\n write(\"overflow\", \"auto\");\n position();\n };\n let frame: number | undefined;\n const schedule = () => {\n if (frame !== undefined) return;\n frame = view.requestAnimationFrame(() => {\n frame = undefined;\n position();\n });\n };\n view.addEventListener(\"resize\", schedule);\n view.addEventListener(\"scroll\", schedule, true);\n view.visualViewport?.addEventListener(\"resize\", schedule);\n view.visualViewport?.addEventListener(\"scroll\", schedule);\n const observer =\n typeof ResizeObserver === \"undefined\"\n ? null\n : new ResizeObserver(schedule);\n observer?.observe(popup);\n observer?.observe(trigger);\n return () => {\n if (frame !== undefined) view.cancelAnimationFrame(frame);\n view.removeEventListener(\"resize\", schedule);\n view.removeEventListener(\"scroll\", schedule, true);\n view.visualViewport?.removeEventListener(\"resize\", schedule);\n view.visualViewport?.removeEventListener(\"scroll\", schedule);\n observer?.disconnect();\n restore();\n };\n }\n return { sync, stop };\n}\n\n/** CSS zoom changes the coordinate space of fixed top-layer descendants. */\nfunction effectiveZoom(node: HTMLElement): number {\n const native = (node as HTMLElement & { currentCSSZoom?: number })\n .currentCSSZoom;\n if (native !== undefined && Number.isFinite(native) && native > 0)\n return native;\n const view = node.ownerDocument.defaultView;\n if (view === null) return 1;\n let zoom = 1;\n let current: Element | null = node;\n while (current !== null) {\n const value = view.getComputedStyle(current).getPropertyValue(\"zoom\");\n const factor = parseFloat(value) / (value.endsWith(\"%\") ? 100 : 1);\n if (Number.isFinite(factor) && factor > 0) zoom *= factor;\n if (current.parentElement !== null) current = current.parentElement;\n else {\n const root = current.getRootNode();\n current = root instanceof ShadowRoot ? root.host : null;\n }\n }\n return Number.isFinite(zoom) && zoom > 0 ? zoom : 1;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{n as e,s as t}from"../parts-I0jdefyp.js";import{a as n,i as r,n as i,r as a}from"../menu-DBxBFdYm.js";import{t as o}from"../hover-HF7JXkR2.js";import{createMixin as s,useBeforePaint as c,useMemo as l,useStableEvent as u}from"@bgub/fig";import{on as d}from"@bgub/fig-dom";function f(e,t,n,r,i){if(i.aborted)return;let a=o(),s=t.ownerDocument,c={x:e.clientX,y:e.clientY};function l(){a.cancel(),s.removeEventListener(`pointermove`,d,!0),i.removeEventListener(`abort`,l)}function u(){l(),n()}function d(e){if(e.pointerType!==`mouse`)return;let o=t.getBoundingClientRect(),s={x:e.clientX,y:e.clientY};if(t.isConnected&&s.x>=o.left&&s.x<=o.right&&s.y>=o.top&&s.y<=o.bottom){l();return}if(!t.isConnected||!p(c,s,o)){l(),a.schedule(!1,n,r,i);return}a.schedule(!1,u,Math.max(r,300),i)}s.addEventListener(`pointermove`,d,!0),i.addEventListener(`abort`,l,{once:!0}),a.schedule(!1,u,Math.max(r,300),i)}function p(e,t,n){let r,i;if(Math.max(n.left-e.x,0,e.x-n.right)>Math.max(n.top-e.y,0,e.y-n.bottom)){let t=e.x<n.left?n.left:n.right;r={x:t,y:n.top-4},i={x:t,y:n.bottom+4}}else{let t=e.y<n.top?n.top:n.bottom;r={x:n.left-4,y:t},i={x:n.right+4,y:t}}let a=m(e,r,t),o=m(r,i,t),s=m(i,e,t);return!(Math.min(a,o,s)<0&&Math.max(a,o,s)>0)}function m(e,t,n){return(t.x-e.x)*(n.y-e.y)-(t.y-e.y)*(n.x-e.x)}const h=s((t,n)=>({bind:e(t,n.bindTrigger,(e,t)=>{n.bindTrigger(e,t)&&t.addEventListener(`abort`,()=>n.hover(),{once:!0}),n.disabled&&n.hover()}),"aria-disabled":n.disabled?`true`:void 0,"data-disabled":n.disabled?``:void 0,mix:[d(`focusout`,e=>{e.relatedTarget instanceof Node&&n.popup()?.contains(e.relatedTarget)||(n.hover(),n.setOpen(!1,!1))}),d(`keydown`,e=>{n.hover(),!(e.defaultPrevented||e.isComposing||e.keyCode===229||n.disabled)&&e.key===x(e.currentTarget)&&(e.preventDefault(),n.setOpen(!0,`first`))}),d(`pointerenter`,e=>{n.disabled||e.pointerType!==`mouse`||n.hover(!n.open||void 0,e)}),d(`pointerleave`,e=>{e.pointerType===`mouse`&&n.hover(!1,e)})]})),g=s((e,t)=>({mix:[d(`keydown`,e=>{if(t.hover(),!(e.defaultPrevented||e.isComposing||e.keyCode===229)){if(e.key===`Tab`){t.closeTree();return}e.key===S(e.currentTarget)&&(e.preventDefault(),t.setOpen(!1))}}),d(`pointerenter`,e=>{e.pointerType===`mouse`&&t.hover()}),d(`pointerleave`,e=>{e.pointerType===`mouse`&&t.hover(!1,e)})]})),_=s((e,t,n,r,i)=>{let a=i.disabled||e.props.disabled===!0;return[t.submenuTrigger(r,a),d(`pointerdown`,e=>i.notePointer(e.pointerType)),d(`click`,e=>{e.defaultPrevented||e.button!==0||(e.preventDefault(),!a&&i.activate(e))}),d(`keydown`,e=>{e.defaultPrevented||e.isComposing||e.keyCode===229||e.key!==`Enter`||(e.preventDefault(),a||i.activate())}),n.trigger(!1,a),h({...i,disabled:a})]}),v=s((e,t,n)=>[t,g(n)]);function y(e,s,d={}){let{delay:p=100,disabled:m=!1,...h}=d,g=r(e),y=i({...h,onSelect:(e,t,n)=>{h.onSelect?.(e,t,n),!t.isCanceled&&a(t)&&g.closeTree(T())}}),b=r(y),x=l(o,[]),S=l(()=>t(()=>{}),[]),C=l(()=>({openedOnHover:!1,pointerType:``}),[]),w=u((e,t,n)=>{if(x.cancel(),e===void 0)return;let r=S.node(),i=()=>{S.node()!==r||!r?.isConnected||(C.openedOnHover=e,b.setOpen(e,!1))},a=b.popup();!e&&y.open&&t!==void 0&&a!==null&&r!==null?f(t,t.currentTarget===r?a:r,i,p,n):x.schedule(e,i,p,n)});c(()=>{(!e.open||m)&&w(void 0,void 0)}),c(()=>{y.open||(C.openedOnHover=!1,w(void 0,void 0))},[y.open]);function T(){let e=b.popup(),t=e?.ownerDocument.activeElement;return t!=null&&e?.contains(t)?t:void 0}let E=u(e=>{let t=e??T();y.setOpen(!1),g.closeTree(t)}),D={notePointer:e=>{C.pointerType=e},activate:e=>{w(void 0,void 0);let t=e!==void 0&&e.detail>0&&C.pointerType===`mouse`;C.pointerType=``;let n=!y.open||C.openedOnHover||t;C.openedOnHover=!1,b.setOpen(n,`first`)},closeTree:E,disabled:m,hover:(e,t)=>w(e,t),bindTrigger:S.bind,open:y.open,popup:()=>b.popup(),setOpen:(e,t)=>{C.openedOnHover=!1,b.setOpen(e,t)}},O={...y,menu:()=>v(y.menu(),D),trigger:()=>_(g,b,s,D)};return n(O,{...b,closeTree:E}),O}function b(e){return e.children(y(e.parent,e.value,e))}function x(e){return C(e)?`ArrowLeft`:`ArrowRight`}function S(e){return C(e)?`ArrowRight`:`ArrowLeft`}function C(e){if(!(e instanceof Element))return!1;let t=getComputedStyle(e).direction;return t?t===`rtl`:e.closest(`[dir]`)?.getAttribute(`dir`)===`rtl`}export{b as MenuSubmenu,y as useMenuSubmenu};
2
+ //# sourceMappingURL=submenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submenu.js","names":[],"sources":["../../src/internal/hover-transit.ts","../../src/menu/submenu.ts"],"sourcesContent":["import { createHoverIntent } from \"./hover.ts\";\n\n/** Keep a popup reachable across a gap, using its placed bounds, not its CSS side. */\nexport function trackHoverTransit(\n leave: PointerEvent,\n target: HTMLElement,\n close: () => void,\n delay: number,\n signal: AbortSignal,\n): void {\n if (signal.aborted) return;\n const intent = createHoverIntent();\n const document = target.ownerDocument;\n const start = { x: leave.clientX, y: leave.clientY };\n function stop(): void {\n intent.cancel();\n document.removeEventListener(\"pointermove\", move, true);\n signal.removeEventListener(\"abort\", stop);\n }\n function finish(): void {\n stop();\n close();\n }\n function move(event: PointerEvent): void {\n if (event.pointerType !== \"mouse\") return;\n const rect = target.getBoundingClientRect();\n const point = { x: event.clientX, y: event.clientY };\n if (\n target.isConnected &&\n point.x >= rect.left &&\n point.x <= rect.right &&\n point.y >= rect.top &&\n point.y <= rect.bottom\n ) {\n stop();\n return;\n }\n if (!target.isConnected || !inCorridor(start, point, rect)) {\n stop();\n intent.schedule(false, close, delay, signal);\n return;\n }\n // A stationary pointer must not leave the popup stuck open indefinitely.\n intent.schedule(false, finish, Math.max(delay, 300), signal);\n }\n document.addEventListener(\"pointermove\", move, true);\n signal.addEventListener(\"abort\", stop, { once: true });\n intent.schedule(false, finish, Math.max(delay, 300), signal);\n}\n\ninterface Point {\n readonly x: number;\n readonly y: number;\n}\n\nfunction inCorridor(start: Point, point: Point, rect: DOMRect): boolean {\n // Choose the facing edge in either axis. This also covers RTL and CSS flips.\n let a: Point;\n let b: Point;\n const dx = Math.max(rect.left - start.x, 0, start.x - rect.right);\n const dy = Math.max(rect.top - start.y, 0, start.y - rect.bottom);\n if (dx > dy) {\n const x = start.x < rect.left ? rect.left : rect.right;\n a = { x, y: rect.top - 4 };\n b = { x, y: rect.bottom + 4 };\n } else {\n const y = start.y < rect.top ? rect.top : rect.bottom;\n a = { x: rect.left - 4, y };\n b = { x: rect.right + 4, y };\n }\n const first = cross(start, a, point);\n const second = cross(a, b, point);\n const third = cross(b, start, point);\n return !(\n Math.min(first, second, third) < 0 && Math.max(first, second, third) > 0\n );\n}\n\nfunction cross(a: Point, b: Point, p: Point): number {\n return (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);\n}\n","import {\n createMixin,\n type FigNode,\n type MixinContext,\n type MixinDescriptor,\n useBeforePaint,\n useMemo,\n useStableEvent,\n} from \"@bgub/fig\";\nimport { on } from \"@bgub/fig-dom\";\nimport { createHoverIntent } from \"../internal/hover.ts\";\nimport { trackHoverTransit } from \"../internal/hover-transit.ts\";\nimport { createPartSlot } from \"../internal/registration.ts\";\nimport { bindPart } from \"../internal/parts.ts\";\nimport { type MenuOptions, type MenuParts, useMenu } from \"./menu.tsx\";\nimport {\n type MenuFocusTarget,\n type MenuController,\n menuClosesOnSelect,\n menuController,\n registerMenuController,\n} from \"./controller.ts\";\n\nexport interface MenuSubmenuOptions<\n Value = unknown,\n> extends MenuOptions<Value> {\n /** Delay for mouse opening and closing. Defaults to 100ms. */\n delay?: number;\n /** Disables the trigger in its parent menu. */\n disabled?: boolean;\n}\n\nexport interface MenuSubmenuProps<\n ParentValue,\n Value = unknown,\n> extends MenuSubmenuOptions<Value> {\n children: (submenu: MenuParts<Value>) => FigNode;\n parent: MenuParts<ParentValue>;\n value: ParentValue;\n}\n\ninterface SubmenuState {\n readonly activate: (event?: MouseEvent) => void;\n readonly notePointer: (type: string) => void;\n readonly closeTree: () => void;\n readonly disabled: boolean;\n readonly hover: (open?: boolean, event?: PointerEvent) => void;\n readonly bindTrigger: (node: HTMLElement, signal: AbortSignal) => boolean;\n readonly open: boolean;\n readonly popup: () => HTMLElement | null;\n readonly setOpen: (open: boolean, focus?: MenuFocusTarget) => void;\n}\n\nconst submenuTriggerBehavior = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: SubmenuState) => ({\n bind: bindPart(context, state.bindTrigger, (node, signal) => {\n if (state.bindTrigger(node, signal))\n signal.addEventListener(\"abort\", () => state.hover(), { once: true });\n if (state.disabled) state.hover();\n }),\n \"aria-disabled\": state.disabled ? \"true\" : undefined,\n \"data-disabled\": state.disabled ? \"\" : undefined,\n mix: [\n on(\"focusout\", (event) => {\n if (\n event.relatedTarget instanceof Node &&\n state.popup()?.contains(event.relatedTarget)\n )\n return;\n state.hover();\n state.setOpen(false, false);\n }),\n on(\"keydown\", (event) => {\n state.hover();\n if (\n event.defaultPrevented ||\n event.isComposing ||\n event.keyCode === 229 ||\n state.disabled\n )\n return;\n if (event.key !== submenuOpenKey(event.currentTarget)) return;\n event.preventDefault();\n state.setOpen(true, \"first\");\n }),\n on(\"pointerenter\", (event) => {\n if (state.disabled || event.pointerType !== \"mouse\") return;\n state.hover(state.open ? undefined : true, event);\n }),\n on(\"pointerleave\", (event) => {\n if (event.pointerType === \"mouse\") state.hover(false, event);\n }),\n ],\n }),\n);\n\nconst submenuMenuBehavior = /* @__PURE__ */ createMixin(\n (_context: MixinContext, state: SubmenuState) => ({\n mix: [\n on(\"keydown\", (event) => {\n state.hover();\n if (\n event.defaultPrevented ||\n event.isComposing ||\n event.keyCode === 229\n )\n return;\n if (event.key === \"Tab\") {\n state.closeTree();\n return;\n }\n if (event.key !== submenuCloseKey(event.currentTarget)) return;\n event.preventDefault();\n state.setOpen(false);\n }),\n on(\"pointerenter\", (event) => {\n if (event.pointerType === \"mouse\") state.hover();\n }),\n on(\"pointerleave\", (event) => {\n if (event.pointerType === \"mouse\") state.hover(false, event);\n }),\n ],\n }),\n);\n\nconst submenuTriggerMixin = /* @__PURE__ */ createMixin(\n (\n context: MixinContext,\n parent: MenuController,\n child: MenuController,\n value: unknown,\n state: SubmenuState,\n ) => {\n const disabled = state.disabled || context.props.disabled === true;\n return [\n parent.submenuTrigger(value, disabled),\n on(\"pointerdown\", (event) => state.notePointer(event.pointerType)),\n on(\"click\", (event) => {\n if (event.defaultPrevented || event.button !== 0) return;\n event.preventDefault();\n if (disabled) return;\n // Mouse presses enter a revealed submenu. Touch and virtual clicks\n // toggle it so assistive users can return to the parent menu.\n state.activate(event);\n }),\n on(\"keydown\", (event) => {\n if (\n event.defaultPrevented ||\n event.isComposing ||\n event.keyCode === 229 ||\n event.key !== \"Enter\"\n )\n return;\n event.preventDefault();\n if (!disabled) state.activate();\n }),\n child.trigger(false, disabled),\n submenuTriggerBehavior({ ...state, disabled }),\n ];\n },\n);\n\nconst submenuMenuMixin = /* @__PURE__ */ createMixin(\n (_context: MixinContext, menu: MixinDescriptor, state: SubmenuState) => [\n menu,\n submenuMenuBehavior(state),\n ],\n);\n\n/** A nested menu whose trigger remains an item in its parent menu. */\nexport function useMenuSubmenu<ParentValue, Value = unknown>(\n parent: MenuParts<ParentValue>,\n value: ParentValue,\n options: MenuSubmenuOptions<Value> = {},\n): MenuParts<Value> {\n const { delay = 100, disabled = false, ...menuOptions } = options;\n const parentController = menuController(parent);\n const menu = useMenu<Value>({\n ...menuOptions,\n onSelect: (selected, details, signal) => {\n menuOptions.onSelect?.(selected, details, signal);\n if (!details.isCanceled && menuClosesOnSelect(details)) {\n parentController.closeTree(focusedItem());\n }\n },\n });\n const controller = menuController(menu);\n const intent = useMemo(createHoverIntent, []);\n const trigger = useMemo(() => createPartSlot(() => {}), []);\n const activation = useMemo(\n () => ({ openedOnHover: false, pointerType: \"\" }),\n [],\n );\n const hover = useStableEvent(\n (\n next: boolean | undefined,\n event: PointerEvent | undefined,\n signal: AbortSignal,\n ) => {\n intent.cancel();\n if (next === undefined) return;\n const anchor = trigger.node();\n const commit = () => {\n if (trigger.node() !== anchor || !anchor?.isConnected) return;\n activation.openedOnHover = next;\n controller.setOpen(next, false);\n };\n const popup = controller.popup();\n if (\n !next &&\n menu.open &&\n event !== undefined &&\n popup !== null &&\n anchor !== null\n ) {\n trackHoverTransit(\n event,\n event.currentTarget === anchor ? popup : anchor,\n commit,\n delay,\n signal,\n );\n } else intent.schedule(next, commit, delay, signal);\n },\n );\n useBeforePaint(() => {\n if (!parent.open || disabled) hover(undefined, undefined);\n });\n useBeforePaint(() => {\n if (!menu.open) {\n activation.openedOnHover = false;\n hover(undefined, undefined);\n }\n }, [menu.open]);\n function focusedItem(): Element | undefined {\n const popup = controller.popup();\n const active = popup?.ownerDocument.activeElement;\n return active !== null && active !== undefined && popup?.contains(active)\n ? active\n : undefined;\n }\n const closeTree = useStableEvent((focusOrigin?: Element) => {\n const origin = focusOrigin ?? focusedItem();\n menu.setOpen(false);\n parentController.closeTree(origin);\n });\n const state = {\n notePointer: (type: string) => {\n activation.pointerType = type;\n },\n activate: (event?: MouseEvent) => {\n hover(undefined, undefined);\n // WebKit reports mouse clicks after touch pointer events. Use the\n // pointerdown source; zero-detail virtual clicks still toggle.\n const mouse =\n event !== undefined &&\n event.detail > 0 &&\n activation.pointerType === \"mouse\";\n activation.pointerType = \"\";\n const next = !menu.open || activation.openedOnHover || mouse;\n activation.openedOnHover = false;\n controller.setOpen(next, \"first\");\n },\n closeTree,\n disabled,\n hover: (next?: boolean, event?: PointerEvent) => hover(next, event),\n bindTrigger: trigger.bind,\n open: menu.open,\n popup: () => controller.popup(),\n setOpen: (open: boolean, focus?: MenuFocusTarget) => {\n activation.openedOnHover = false;\n controller.setOpen(open, focus);\n },\n };\n\n const parts: MenuParts<Value> = {\n ...menu,\n menu: () => submenuMenuMixin(menu.menu(), state),\n trigger: () =>\n submenuTriggerMixin(parentController, controller, value, state),\n };\n registerMenuController(parts, { ...controller, closeTree });\n return parts;\n}\n\n/** {@link useMenuSubmenu} as a render-callback component. */\nexport function MenuSubmenu<ParentValue, Value = unknown>(\n props: MenuSubmenuProps<ParentValue, Value>,\n): FigNode {\n return props.children(useMenuSubmenu(props.parent, props.value, props));\n}\n\nfunction submenuOpenKey(target: EventTarget | null): string {\n return rightToLeft(target) ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\nfunction submenuCloseKey(target: EventTarget | null): string {\n return rightToLeft(target) ? \"ArrowRight\" : \"ArrowLeft\";\n}\n\nfunction rightToLeft(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n const direction = getComputedStyle(target).direction;\n return direction\n ? direction === \"rtl\"\n : target.closest(\"[dir]\")?.getAttribute(\"dir\") === \"rtl\";\n}\n"],"mappings":"sRAGA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACM,CACN,GAAI,EAAO,QAAS,OACpB,IAAM,EAAS,EAAkB,EAC3B,EAAW,EAAO,cAClB,EAAQ,CAAE,EAAG,EAAM,QAAS,EAAG,EAAM,OAAQ,EACnD,SAAS,GAAa,CACpB,EAAO,OAAO,EACd,EAAS,oBAAoB,cAAe,EAAM,EAAI,EACtD,EAAO,oBAAoB,QAAS,CAAI,CAC1C,CACA,SAAS,GAAe,CACtB,EAAK,EACL,EAAM,CACR,CACA,SAAS,EAAK,EAA2B,CACvC,GAAI,EAAM,cAAgB,QAAS,OACnC,IAAM,EAAO,EAAO,sBAAsB,EACpC,EAAQ,CAAE,EAAG,EAAM,QAAS,EAAG,EAAM,OAAQ,EACnD,GACE,EAAO,aACP,EAAM,GAAK,EAAK,MAChB,EAAM,GAAK,EAAK,OAChB,EAAM,GAAK,EAAK,KAChB,EAAM,GAAK,EAAK,OAChB,CACA,EAAK,EACL,MACF,CACA,GAAI,CAAC,EAAO,aAAe,CAAC,EAAW,EAAO,EAAO,CAAI,EAAG,CAC1D,EAAK,EACL,EAAO,SAAS,GAAO,EAAO,EAAO,CAAM,EAC3C,MACF,CAEA,EAAO,SAAS,GAAO,EAAQ,KAAK,IAAI,EAAO,GAAG,EAAG,CAAM,CAC7D,CACA,EAAS,iBAAiB,cAAe,EAAM,EAAI,EACnD,EAAO,iBAAiB,QAAS,EAAM,CAAE,KAAM,EAAK,CAAC,EACrD,EAAO,SAAS,GAAO,EAAQ,KAAK,IAAI,EAAO,GAAG,EAAG,CAAM,CAC7D,CAOA,SAAS,EAAW,EAAc,EAAc,EAAwB,CAEtE,IAAI,EACA,EAGJ,GAFW,KAAK,IAAI,EAAK,KAAO,EAAM,EAAG,EAAG,EAAM,EAAI,EAAK,KAEtD,EADM,KAAK,IAAI,EAAK,IAAM,EAAM,EAAG,EAAG,EAAM,EAAI,EAAK,MAChD,EAAG,CACX,IAAM,EAAI,EAAM,EAAI,EAAK,KAAO,EAAK,KAAO,EAAK,MACjD,EAAI,CAAE,IAAG,EAAG,EAAK,IAAM,CAAE,EACzB,EAAI,CAAE,IAAG,EAAG,EAAK,OAAS,CAAE,CAC9B,KAAO,CACL,IAAM,EAAI,EAAM,EAAI,EAAK,IAAM,EAAK,IAAM,EAAK,OAC/C,EAAI,CAAE,EAAG,EAAK,KAAO,EAAG,GAAE,EAC1B,EAAI,CAAE,EAAG,EAAK,MAAQ,EAAG,GAAE,CAC7B,CACA,IAAM,EAAQ,EAAM,EAAO,EAAG,CAAK,EAC7B,EAAS,EAAM,EAAG,EAAG,CAAK,EAC1B,EAAQ,EAAM,EAAG,EAAO,CAAK,EACnC,MAAO,EACL,KAAK,IAAI,EAAO,EAAQ,CAAK,EAAI,GAAK,KAAK,IAAI,EAAO,EAAQ,CAAK,EAAI,EAE3E,CAEA,SAAS,EAAM,EAAU,EAAU,EAAkB,CACnD,OAAQ,EAAE,EAAI,EAAE,IAAM,EAAE,EAAI,EAAE,IAAM,EAAE,EAAI,EAAE,IAAM,EAAE,EAAI,EAAE,EAC5D,CC3BA,MAAM,EAAyC,GAC5C,EAAuB,KAAyB,CAC/C,KAAM,EAAS,EAAS,EAAM,aAAc,EAAM,IAAW,CACvD,EAAM,YAAY,EAAM,CAAM,GAChC,EAAO,iBAAiB,YAAe,EAAM,MAAM,EAAG,CAAE,KAAM,EAAK,CAAC,EAClE,EAAM,UAAU,EAAM,MAAM,CAClC,CAAC,EACD,gBAAiB,EAAM,SAAW,OAAS,IAAA,GAC3C,gBAAiB,EAAM,SAAW,GAAK,IAAA,GACvC,IAAK,CACH,EAAG,WAAa,GAAU,CAEtB,EAAM,yBAAyB,MAC/B,EAAM,MAAM,CAAC,EAAE,SAAS,EAAM,aAAa,IAG7C,EAAM,MAAM,EACZ,EAAM,QAAQ,GAAO,EAAK,EAC5B,CAAC,EACD,EAAG,UAAY,GAAU,CACvB,EAAM,MAAM,EAEV,IAAM,kBACN,EAAM,aACN,EAAM,UAAY,KAClB,EAAM,WAGJ,EAAM,MAAQ,EAAe,EAAM,aAAa,IACpD,EAAM,eAAe,EACrB,EAAM,QAAQ,GAAM,OAAO,EAC7B,CAAC,EACD,EAAG,eAAiB,GAAU,CACxB,EAAM,UAAY,EAAM,cAAgB,SAC5C,EAAM,MAAM,GAAM,MAAO,IAAA,GAAkB,CAAK,CAClD,CAAC,EACD,EAAG,eAAiB,GAAU,CACxB,EAAM,cAAgB,SAAS,EAAM,MAAM,GAAO,CAAK,CAC7D,CAAC,CACH,CACF,EACF,EAEM,EAAsC,GACzC,EAAwB,KAAyB,CAChD,IAAK,CACH,EAAG,UAAY,GAAU,CACvB,KAAM,MAAM,EAEV,IAAM,kBACN,EAAM,aACN,EAAM,UAAY,KAGpB,IAAI,EAAM,MAAQ,MAAO,CACvB,EAAM,UAAU,EAChB,MACF,CACI,EAAM,MAAQ,EAAgB,EAAM,aAAa,IACrD,EAAM,eAAe,EACrB,EAAM,QAAQ,EAAK,EAHnB,CAIF,CAAC,EACD,EAAG,eAAiB,GAAU,CACxB,EAAM,cAAgB,SAAS,EAAM,MAAM,CACjD,CAAC,EACD,EAAG,eAAiB,GAAU,CACxB,EAAM,cAAgB,SAAS,EAAM,MAAM,GAAO,CAAK,CAC7D,CAAC,CACH,CACF,EACF,EAEM,EAAsC,GAExC,EACA,EACA,EACA,EACA,IACG,CACH,IAAM,EAAW,EAAM,UAAY,EAAQ,MAAM,WAAa,GAC9D,MAAO,CACL,EAAO,eAAe,EAAO,CAAQ,EACrC,EAAG,cAAgB,GAAU,EAAM,YAAY,EAAM,WAAW,CAAC,EACjE,EAAG,QAAU,GAAU,CACjB,EAAM,kBAAoB,EAAM,SAAW,IAC/C,EAAM,eAAe,EACjB,IAGJ,EAAM,SAAS,CAAK,EACtB,CAAC,EACD,EAAG,UAAY,GAAU,CAErB,EAAM,kBACN,EAAM,aACN,EAAM,UAAY,KAClB,EAAM,MAAQ,UAGhB,EAAM,eAAe,EAChB,GAAU,EAAM,SAAS,EAChC,CAAC,EACD,EAAM,QAAQ,GAAO,CAAQ,EAC7B,EAAuB,CAAE,GAAG,EAAO,UAAS,CAAC,CAC/C,CACF,CACF,EAEM,EAAmC,GACtC,EAAwB,EAAuB,IAAwB,CACtE,EACA,EAAoB,CAAK,CAC3B,CACF,EAGA,SAAgB,EACd,EACA,EACA,EAAqC,CAAC,EACpB,CAClB,GAAM,CAAE,QAAQ,IAAK,WAAW,GAAO,GAAG,GAAgB,EACpD,EAAmB,EAAe,CAAM,EACxC,EAAO,EAAe,CAC1B,GAAG,EACH,UAAW,EAAU,EAAS,IAAW,CACvC,EAAY,WAAW,EAAU,EAAS,CAAM,EAC5C,CAAC,EAAQ,YAAc,EAAmB,CAAO,GACnD,EAAiB,UAAU,EAAY,CAAC,CAE5C,CACF,CAAC,EACK,EAAa,EAAe,CAAI,EAChC,EAAS,EAAQ,EAAmB,CAAC,CAAC,EACtC,EAAU,MAAc,MAAqB,CAAC,CAAC,EAAG,CAAC,CAAC,EACpD,EAAa,OACV,CAAE,cAAe,GAAO,YAAa,EAAG,GAC/C,CAAC,CACH,EACM,EAAQ,GAEV,EACA,EACA,IACG,CAEH,GADA,EAAO,OAAO,EACV,IAAS,IAAA,GAAW,OACxB,IAAM,EAAS,EAAQ,KAAK,EACtB,MAAe,CACf,EAAQ,KAAK,IAAM,GAAU,CAAC,GAAQ,cAC1C,EAAW,cAAgB,EAC3B,EAAW,QAAQ,EAAM,EAAK,EAChC,EACM,EAAQ,EAAW,MAAM,EAE7B,CAAC,GACD,EAAK,MACL,IAAU,IAAA,IACV,IAAU,MACV,IAAW,KAEX,EACE,EACA,EAAM,gBAAkB,EAAS,EAAQ,EACzC,EACA,EACA,CACF,EACK,EAAO,SAAS,EAAM,EAAQ,EAAO,CAAM,CACpD,CACF,EACA,MAAqB,EACf,CAAC,EAAO,MAAQ,IAAU,EAAM,IAAA,GAAW,IAAA,EAAS,CAC1D,CAAC,EACD,MAAqB,CACd,EAAK,OACR,EAAW,cAAgB,GAC3B,EAAM,IAAA,GAAW,IAAA,EAAS,EAE9B,EAAG,CAAC,EAAK,IAAI,CAAC,EACd,SAAS,GAAmC,CAC1C,IAAM,EAAQ,EAAW,MAAM,EACzB,EAAS,GAAO,cAAc,cACpC,OAAO,GAAW,MAAgC,GAAO,SAAS,CAAM,EACpE,EACA,IAAA,EACN,CACA,IAAM,EAAY,EAAgB,GAA0B,CAC1D,IAAM,EAAS,GAAe,EAAY,EAC1C,EAAK,QAAQ,EAAK,EAClB,EAAiB,UAAU,CAAM,CACnC,CAAC,EACK,EAAQ,CACZ,YAAc,GAAiB,CAC7B,EAAW,YAAc,CAC3B,EACA,SAAW,GAAuB,CAChC,EAAM,IAAA,GAAW,IAAA,EAAS,EAG1B,IAAM,EACJ,IAAU,IAAA,IACV,EAAM,OAAS,GACf,EAAW,cAAgB,QAC7B,EAAW,YAAc,GACzB,IAAM,EAAO,CAAC,EAAK,MAAQ,EAAW,eAAiB,EACvD,EAAW,cAAgB,GAC3B,EAAW,QAAQ,EAAM,OAAO,CAClC,EACA,YACA,WACA,OAAQ,EAAgB,IAAyB,EAAM,EAAM,CAAK,EAClE,YAAa,EAAQ,KACrB,KAAM,EAAK,KACX,UAAa,EAAW,MAAM,EAC9B,SAAU,EAAe,IAA4B,CACnD,EAAW,cAAgB,GAC3B,EAAW,QAAQ,EAAM,CAAK,CAChC,CACF,EAEM,EAA0B,CAC9B,GAAG,EACH,SAAY,EAAiB,EAAK,KAAK,EAAG,CAAK,EAC/C,YACE,EAAoB,EAAkB,EAAY,EAAO,CAAK,CAClE,EAEA,OADA,EAAuB,EAAO,CAAE,GAAG,EAAY,WAAU,CAAC,EACnD,CACT,CAGA,SAAgB,EACd,EACS,CACT,OAAO,EAAM,SAAS,EAAe,EAAM,OAAQ,EAAM,MAAO,CAAK,CAAC,CACxE,CAEA,SAAS,EAAe,EAAoC,CAC1D,OAAO,EAAY,CAAM,EAAI,YAAc,YAC7C,CAEA,SAAS,EAAgB,EAAoC,CAC3D,OAAO,EAAY,CAAM,EAAI,aAAe,WAC9C,CAEA,SAAS,EAAY,EAAqC,CACxD,GAAI,EAAE,aAAkB,SAAU,MAAO,GACzC,IAAM,EAAY,iBAAiB,CAAM,CAAC,CAAC,UAC3C,OAAO,EACH,IAAc,MACd,EAAO,QAAQ,OAAO,CAAC,EAAE,aAAa,KAAK,IAAM,KACvD"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./changes-DglRuvQa.js";import{i as t,n,o as r,s as i}from"./parts-I0jdefyp.js";import{t as a}from"./diagnostics-BkQV0MaT.js";import{n as o,t as s}from"./composite-DoFLgUjR.js";import{t as c}from"./part-reference-B7zINPUK.js";import{r as l}from"./popup-state-BUYfC2nG.js";import{n as u,r as d}from"./popover-DXxGM3tO.js";import{createMixin as f,useBeforePaint as p,useId as m,useMemo as h,useStableEvent as g}from"@bgub/fig";import{on as _}from"@bgub/fig-dom";function v(e,t){let n=!1,r,i=!1;function a(){let t=e();return t?.contains(t.ownerDocument.activeElement)===!0}return{closingFrom(e){r=e},beforeToggle:e=>{e.target===e.currentTarget&&(i=l(e)===!1&&a())},sync(e,o){if(e===n)return;if(n=e,e){r=void 0,i=!1,o();return}let s=t(),c=s?.ownerDocument,l=c?.activeElement;(a()||i&&l===c?.body||r!==void 0&&r===l)&&s?.focus(),r=void 0,i=!1}}}const y=f((e,t,r)=>({"aria-haspopup":`menu`,bind:n(e,t.registry,t.noteTrigger),id:e.props.id??t.triggerId,mix:_(`keydown`,e=>{if(!(e.defaultPrevented||e.isComposing||e.keyCode===229)){if(r.disabled){(e.key===`Enter`||e.key===` `)&&e.preventDefault();return}e.key===`Enter`||r.openWithArrows&&e.key===`ArrowDown`?(e.preventDefault(),t.open(`first`)):r.openWithArrows&&e.key===`ArrowUp`&&(e.preventDefault(),t.open(`last`))}})})),b=f((e,t)=>{let r=c(e,`aria-labelledby`,t.triggerId);return{...r.props,bind:n(e,t.registry,(e,n)=>t.registry.bindMenu(e,n,r)),mix:_(`keydown`,e=>{if(e.defaultPrevented||e.isComposing||e.keyCode===229)return;if(e.key===`Tab`){t.close();return}let n=t.registry.menuItemAt(e.target);if(n!==void 0&&(e.key===`Enter`||e.key===` `)){if(n.kind===`submenu`)return;e.preventDefault(),!n.disabled&&!(n.kind===`radio`&&n.checked)&&t.activate(n,e);return}if(n===void 0){if(e.target!==e.currentTarget||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)return;e.key===`ArrowDown`||e.key===`Home`?(e.preventDefault(),t.open(`first`)):e.key===`ArrowUp`||e.key===`End`?(e.preventDefault(),t.open(`last`)):t.registry.focusByTypeahead(e.key);return}t.registry.moveFocus(e,{loop:!0,orientation:`vertical`})===void 0&&!e.altKey&&!e.ctrlKey&&!e.metaKey&&t.registry.focusByTypeahead(e.key)}),role:`menu`,tabindex:e.props.tabindex??-1}}),x=f((e,t,n,r)=>[n,y(t,r)]),S=f((e,t,n)=>[_(`beforetoggle`,t.noteNativeToggle,{capture:!0}),n,b(t)]),C=f((e,r,i)=>{let a=i.disabled||e.props.disabled===!0;return{...t(e,{disabled:a}),"aria-checked":i.kind===`checkbox`||i.kind===`radio`?i.checked?`true`:`false`:void 0,bind:n(e,r.registry,(e,t)=>r.registry.bindMenuItem(e,t,{...i,disabled:a})),"data-checked":i.checked?``:void 0,mix:_(`click`,e=>{if(e.defaultPrevented)return;if(a){e.preventDefault();return}if(e.button!==0||i.kind===`submenu`)return;let t=r.registry.menuItemAt(e.currentTarget);t!==void 0&&!(t.kind===`radio`&&t.checked)&&r.activate(t,e)}),role:i.kind===`checkbox`?`menuitemcheckbox`:i.kind===`radio`?`menuitemradio`:`menuitem`,tabindex:-1}}),w=new WeakMap,T=new WeakSet;function E(e,t){w.set(e,t)}function D(e){let t=w.get(e);if(t===void 0)throw Error(`Fig Headless submenu parent must come from useMenu().`);return t}function O(e,t){t&&T.add(e)}function k(e){return T.has(e)}function A(){let e=s({container:`[role="menu"]`,item:`[role^="menuitem"]`,name:`menu`}),t=new WeakMap,n=r();function i(t,r,i){let a={...i,node:t};n.bind(t,r,a),e.bindItem(t,r,i.value,i.disabled)}function a(t){let r=e.itemAt(t);return r===void 0?void 0:n.get(r.node)}function o(n,r,i){t.set(n,i),e.bindContainer(n,r)}return{...e,bindMenu:o,bindMenuItem:i,menuItemAt:a,syncLabel:(e,n)=>t.get(e)?.sync(e,n)}}function j(t={}){let n=`${m()}-trigger`,r=u(t),s=h(()=>A(),[]),c=h(()=>i(()=>{}),[]),l=h(()=>({pending:null}),[]),f=h(()=>v(s.containerNode,c.node),[]),_=g((e,t)=>{d(r,e,t),c.bind(e,t)}),y=g((e,n,r)=>{t.onSelect?.(e,n,r)}),b=g(e=>{D(!0,e)}),w=g(e=>{f.closingFrom(e),r.setOpen(!1)}),T=g((t,n)=>{if(t.kind===`submenu`)return;let i={...e(n,t.node),kind:t.kind};O(i,t.closeOnSelect),y(t.value,i),!i.isCanceled&&t.closeOnSelect&&r.setOpen(!1)}),D=g((e,t=`first`)=>{e&&(r.open?(l.pending=null,k(t)):l.pending=t),r.setOpen(e)});p(()=>{let e=s.containerNode(),t=c.node();e!==null&&(s.syncLabel(e,t?.id),a(e,`menu`)),f.sync(r.open,()=>{let e=l.pending;l.pending=null,k(e)})});function k(e){if(e===!1)return;let t=s.items().filter(o);((e===`last`?t[t.length-1]:t[0])?.node??s.containerNode())?.focus()}let j={noteNativeToggle:f.beforeToggle,activate:T,close:w,noteTrigger:_,open:b,registry:s,triggerId:n},M={checkboxItem:(e,t)=>C(j,{checked:t.checked,closeOnSelect:t.closeOnSelect===!0,disabled:t.disabled===!0,kind:`checkbox`,value:e}),item:(e,t={})=>C(j,{checked:void 0,closeOnSelect:t.closeOnSelect!==!1,disabled:t.disabled===!0,kind:`item`,value:e}),menu:()=>S(j,r.popover()),open:r.open,radioItem:(e,t)=>C(j,{checked:t.checked,closeOnSelect:t.closeOnSelect===!0,disabled:t.disabled===!0,kind:`radio`,value:e}),setOpen:e=>D(e),trigger:()=>x(j,r.trigger(),{openWithArrows:!0})};return E(M,{closeTree:w,bindTrigger:_,popup:s.containerNode,setOpen:D,submenuTrigger:(e,t)=>C(j,{checked:void 0,closeOnSelect:!1,disabled:t,kind:`submenu`,value:e}),trigger:(e,t=!1)=>x(j,r.trigger(),{openWithArrows:e,disabled:t})}),M}function M(e){return e.children(j(e))}export{E as a,D as i,j as n,k as r,M as t};
2
+ //# sourceMappingURL=menu-DBxBFdYm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-DBxBFdYm.js","names":[],"sources":["../src/internal/popup-focus.ts","../src/menu/parts.ts","../src/menu/controller.ts","../src/menu/registry.ts","../src/menu/menu.tsx"],"sourcesContent":["import { toggledOpen } from \"./anchored-popup.ts\";\n\n/** Focus ownership for popups that move DOM focus into their content. */\nexport function createPopupFocus(\n popup: () => HTMLElement | null,\n trigger: () => HTMLElement | null,\n) {\n let wasOpen = false;\n let closingFocus: Element | undefined;\n let nativeLoss = false;\n function containsFocus(): boolean {\n const node = popup();\n return node?.contains(node.ownerDocument.activeElement) === true;\n }\n return {\n closingFrom(origin?: Element): void {\n closingFocus = origin;\n },\n beforeToggle: (event: Event): void => {\n if (event.target === event.currentTarget) {\n nativeLoss = toggledOpen(event) === false && containsFocus();\n }\n },\n sync(open: boolean, initialFocus: () => void): void {\n if (open === wasOpen) return;\n wasOpen = open;\n if (open) {\n closingFocus = undefined;\n nativeLoss = false;\n initialFocus();\n return;\n }\n const node = trigger();\n const document = node?.ownerDocument;\n const active = document?.activeElement;\n // Firefox can clear focus during native dismissal. Never override an\n // intentional move to another control, including an onSelect callback.\n if (\n containsFocus() ||\n (nativeLoss && active === document?.body) ||\n (closingFocus !== undefined && closingFocus === active)\n )\n node?.focus();\n closingFocus = undefined;\n nativeLoss = false;\n },\n };\n}\n","import { createPartReference } from \"../internal/part-reference.ts\";\nimport {\n createMixin,\n type MixinContext,\n type MixinDescriptor,\n} from \"@bgub/fig\";\nimport { on } from \"@bgub/fig-dom\";\nimport { bindPart, triggerProps } from \"../internal/parts.ts\";\nimport type {\n MenuItemConfig,\n MenuItemRegistration,\n MenuRegistry,\n} from \"./registry.ts\";\n\ninterface MenuTriggerOwnState {\n readonly disabled?: boolean;\n readonly openWithArrows: boolean;\n}\n\n/** Widget-level state every part reads. Built once per root render. */\nexport interface MenuPartState {\n readonly noteNativeToggle: (event: Event) => void;\n readonly activate: (item: MenuItemRegistration, event: Event) => void;\n readonly close: () => void;\n readonly noteTrigger: (node: HTMLElement, signal: AbortSignal) => void;\n readonly open: (at: \"first\" | \"last\") => void;\n readonly registry: MenuRegistry;\n readonly triggerId: string;\n}\n\nconst menuTriggerBehavior = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: MenuPartState, own: MenuTriggerOwnState) => ({\n \"aria-haspopup\": \"menu\",\n bind: bindPart(context, state.registry, state.noteTrigger),\n id: context.props.id ?? state.triggerId,\n // Click already opens through popovertarget; these are the keys the\n // pattern adds, and each one says where focus should land.\n mix: on(\"keydown\", (event) => {\n if (event.defaultPrevented || event.isComposing || event.keyCode === 229)\n return;\n if (own.disabled) {\n if (event.key === \"Enter\" || event.key === \" \") event.preventDefault();\n return;\n }\n if (\n event.key === \"Enter\" ||\n (own.openWithArrows && event.key === \"ArrowDown\")\n ) {\n event.preventDefault();\n state.open(\"first\");\n } else if (own.openWithArrows && event.key === \"ArrowUp\") {\n event.preventDefault();\n state.open(\"last\");\n }\n }),\n }),\n);\n\nconst menuBehavior = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: MenuPartState) => {\n const label = createPartReference(\n context,\n \"aria-labelledby\",\n state.triggerId,\n );\n return {\n ...label.props,\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindMenu(node, signal, label),\n ),\n mix: on(\"keydown\", (event) => {\n if (\n event.defaultPrevented ||\n event.isComposing ||\n event.keyCode === 229\n )\n return;\n // Escape and light dismiss belong to the platform; everything that\n // moves or commits inside the menu belongs here.\n if (event.key === \"Tab\") {\n state.close();\n return;\n }\n const item = state.registry.menuItemAt(event.target);\n if (\n item !== undefined &&\n (event.key === \"Enter\" || event.key === \" \")\n ) {\n if (item.kind === \"submenu\") return;\n event.preventDefault();\n if (!item.disabled && !(item.kind === \"radio\" && item.checked)) {\n state.activate(item, event);\n }\n return;\n }\n if (item === undefined) {\n // Empty/loading menus focus the container. Once items arrive, a\n // navigation key enters the list without requiring a reopen.\n if (\n event.target !== event.currentTarget ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n )\n return;\n if (event.key === \"ArrowDown\" || event.key === \"Home\") {\n event.preventDefault();\n state.open(\"first\");\n } else if (event.key === \"ArrowUp\" || event.key === \"End\") {\n event.preventDefault();\n state.open(\"last\");\n } else state.registry.focusByTypeahead(event.key);\n return;\n }\n const moved = state.registry.moveFocus(event, {\n loop: true,\n orientation: \"vertical\",\n });\n if (\n moved === undefined &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey\n ) {\n state.registry.focusByTypeahead(event.key);\n }\n }),\n role: \"menu\",\n tabindex: context.props.tabindex ?? -1,\n };\n },\n);\n\n/**\n * Layers menu semantics over the popover parts the menu is built on. Both are\n * descriptors, so they compose the way any two authored mixins would.\n */\nexport const menuTriggerMixin = /* @__PURE__ */ createMixin(\n (\n _context: MixinContext,\n state: MenuPartState,\n popoverTrigger: MixinDescriptor,\n own: MenuTriggerOwnState,\n ) => [popoverTrigger, menuTriggerBehavior(state, own)],\n);\n\nexport const menuMixin = /* @__PURE__ */ createMixin(\n (\n _context: MixinContext,\n state: MenuPartState,\n popoverMenu: MixinDescriptor,\n ) => [\n on(\"beforetoggle\", state.noteNativeToggle, { capture: true }),\n popoverMenu,\n menuBehavior(state),\n ],\n);\n\nexport const menuItemMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: MenuPartState, own: MenuItemConfig) => {\n const disabled = own.disabled || context.props.disabled === true;\n return {\n ...triggerProps(context, { disabled }),\n \"aria-checked\":\n own.kind === \"checkbox\" || own.kind === \"radio\"\n ? own.checked\n ? \"true\"\n : \"false\"\n : undefined,\n bind: bindPart(context, state.registry, (node, signal) =>\n state.registry.bindMenuItem(node, signal, { ...own, disabled }),\n ),\n \"data-checked\": own.checked ? \"\" : undefined,\n mix: on(\"click\", (event) => {\n if (event.defaultPrevented) return;\n if (disabled) {\n event.preventDefault();\n return;\n }\n if (event.button !== 0 || own.kind === \"submenu\") return;\n const item = state.registry.menuItemAt(event.currentTarget);\n if (item !== undefined && !(item.kind === \"radio\" && item.checked)) {\n state.activate(item, event);\n }\n }),\n role:\n own.kind === \"checkbox\"\n ? \"menuitemcheckbox\"\n : own.kind === \"radio\"\n ? \"menuitemradio\"\n : \"menuitem\",\n // Focus moves between items directly, so none of them is a tab stop:\n // Tab leaves the menu rather than walking it.\n tabindex: -1,\n };\n },\n);\n","import type { MixinDescriptor } from \"@bgub/fig\";\n\nexport type MenuFocusTarget = \"first\" | \"last\" | false;\n\nexport interface MenuController {\n closeTree(focusOrigin?: Element): void;\n bindTrigger(node: HTMLElement, signal: AbortSignal): void;\n popup(): HTMLElement | null;\n setOpen(open: boolean, focus?: MenuFocusTarget): void;\n submenuTrigger(value: unknown, disabled: boolean): MixinDescriptor;\n trigger(openWithArrows: boolean, disabled?: boolean): MixinDescriptor;\n}\n\nconst controllers = new WeakMap<object, MenuController>();\nconst closingSelections = new WeakSet<object>();\n\nexport function registerMenuController(\n parts: object,\n controller: MenuController,\n): void {\n controllers.set(parts, controller);\n}\n\nexport function menuController(parts: object): MenuController {\n const controller = controllers.get(parts);\n if (controller === undefined) {\n throw new Error(\"Fig Headless submenu parent must come from useMenu().\");\n }\n return controller;\n}\n\nexport function markMenuClose(details: object, close: boolean): void {\n if (close) closingSelections.add(details);\n}\n\nexport function menuClosesOnSelect(details: object): boolean {\n return closingSelections.has(details);\n}\n","import type { PartReference } from \"../internal/part-reference.ts\";\nimport { createPartCollection } from \"../internal/registration.ts\";\nimport { createComposite } from \"../internal/composite.ts\";\n\nexport type MenuItemKind = \"checkbox\" | \"item\" | \"radio\" | \"submenu\";\n\nexport interface MenuItemConfig {\n readonly checked: boolean | undefined;\n readonly closeOnSelect: boolean;\n readonly disabled: boolean;\n readonly kind: MenuItemKind;\n readonly value: unknown;\n}\n\nexport interface MenuItemRegistration extends MenuItemConfig {\n readonly node: HTMLElement;\n}\n\nexport type MenuRegistry = ReturnType<typeof createMenuRegistry>;\n\n/** Adds menu activation metadata to the shared focus composite. */\nexport function createMenuRegistry() {\n const composite = createComposite({\n container: '[role=\"menu\"]',\n item: '[role^=\"menuitem\"]',\n name: \"menu\",\n });\n const labels = new WeakMap<HTMLElement, PartReference>();\n const registrations = createPartCollection<MenuItemRegistration>();\n\n function bindMenuItem(\n node: HTMLElement,\n signal: AbortSignal,\n config: MenuItemConfig,\n ): void {\n const registration = { ...config, node };\n registrations.bind(node, signal, registration);\n composite.bindItem(node, signal, config.value, config.disabled);\n }\n\n function menuItemAt(target: EventTarget | null) {\n const item = composite.itemAt(target);\n return item === undefined ? undefined : registrations.get(item.node);\n }\n\n function bindMenu(\n node: HTMLElement,\n signal: AbortSignal,\n label: PartReference,\n ): void {\n labels.set(node, label);\n composite.bindContainer(node, signal);\n }\n\n return {\n ...composite,\n bindMenu,\n bindMenuItem,\n menuItemAt,\n syncLabel: (node: HTMLElement, id: string | undefined) =>\n labels.get(node)?.sync(node, id),\n };\n}\n","import {\n type FigNode,\n type MixinDescriptor,\n useBeforePaint,\n useId,\n useMemo,\n useStableEvent,\n} from \"@bgub/fig\";\nimport type { ChangeDetails } from \"../internal/changes.ts\";\nimport { createChangeDetails } from \"../internal/changes.ts\";\nimport { createPopupFocus } from \"../internal/popup-focus.ts\";\nimport { isNativeEnabled } from \"../internal/composite.ts\";\nimport { assertAccessibleName } from \"../internal/diagnostics.ts\";\nimport { createPartSlot } from \"../internal/registration.ts\";\nimport type {\n OpenChangeDetails,\n OpenChangeHandler,\n} from \"../internal/open-state.ts\";\nimport { type PopoverOptions, usePopover } from \"../popover/popover.tsx\";\nimport { bindPopoverSource } from \"../popover/registry.ts\";\nimport { menuItemMixin, menuMixin, menuTriggerMixin } from \"./parts.ts\";\nimport {\n markMenuClose,\n registerMenuController,\n type MenuFocusTarget,\n} from \"./controller.ts\";\nimport { createMenuRegistry, type MenuItemRegistration } from \"./registry.ts\";\n\nexport type MenuOpenChangeDetails = OpenChangeDetails;\n\nexport type MenuOpenChangeHandler = OpenChangeHandler;\n\nexport interface MenuSelectDetails extends ChangeDetails {\n readonly kind: \"checkbox\" | \"item\" | \"radio\";\n}\n\nexport type MenuSelectHandler<Value = unknown> = (\n value: Value,\n details: MenuSelectDetails,\n signal: AbortSignal,\n) => void;\n\nexport interface MenuItemOptions {\n /** Defaults to `true` for actions and `false` for checked items. */\n closeOnSelect?: boolean;\n disabled?: boolean;\n}\n\nexport interface MenuCheckedItemOptions extends MenuItemOptions {\n checked: boolean;\n}\n\nexport interface MenuOptions<Value = unknown> extends PopoverOptions {\n /** Runs when an item is chosen, before the menu closes. */\n onSelect?: MenuSelectHandler<Value>;\n}\n\nexport interface MenuParts<Value = unknown> {\n readonly open: boolean;\n checkboxItem(value: Value, options: MenuCheckedItemOptions): MixinDescriptor;\n /** Applies to the caller's own popover element. */\n menu(): MixinDescriptor;\n item(value: Value, options?: MenuItemOptions): MixinDescriptor;\n radioItem(value: Value, options: MenuCheckedItemOptions): MixinDescriptor;\n setOpen(open: boolean): void;\n trigger(): MixinDescriptor;\n}\n\nexport interface MenuProps<Value = unknown> extends MenuOptions<Value> {\n children: (menu: MenuParts<Value>) => FigNode;\n}\n\n/**\n * Coordinates one menu button.\n *\n * The popover underneath supplies the top layer, light dismiss, and Escape,\n * and the composite supplies ordering and movement. What the menu itself owns\n * is focus: `showPopover()` deliberately leaves focus where it was, so the\n * menu moves it to an item on open and returns it to the trigger on close.\n */\nexport function useMenu<Value = unknown>(\n options: MenuOptions<Value> = {},\n): MenuParts<Value> {\n const id = useId();\n const triggerId = `${id}-trigger`;\n const popover = usePopover(options);\n const registry = useMemo(() => createMenuRegistry(), []);\n const trigger = useMemo(() => createPartSlot(() => {}), []);\n const tracker = useMemo<{ pending: MenuFocusTarget | null }>(\n () => ({ pending: null }),\n [],\n );\n const focus = useMemo(\n () => createPopupFocus(registry.containerNode, trigger.node),\n [],\n );\n\n const noteTrigger = useStableEvent(\n (node: HTMLElement, signal: AbortSignal) => {\n bindPopoverSource(popover, node, signal);\n trigger.bind(node, signal);\n },\n );\n\n const emitSelect = useStableEvent(\n (value: unknown, details: MenuSelectDetails, signal: AbortSignal) => {\n options.onSelect?.(value as Value, details, signal);\n },\n );\n\n const open = useStableEvent((at: \"first\" | \"last\") => {\n setOpen(true, at);\n });\n\n const close = useStableEvent((focusOrigin?: Element) => {\n focus.closingFrom(focusOrigin);\n popover.setOpen(false);\n });\n\n const activate = useStableEvent(\n (item: MenuItemRegistration, event: Event) => {\n if (item.kind === \"submenu\") return;\n const details: MenuSelectDetails = {\n ...createChangeDetails(event, item.node),\n kind: item.kind,\n };\n markMenuClose(details, item.closeOnSelect);\n emitSelect(item.value, details);\n if (details.isCanceled) return;\n if (item.closeOnSelect) popover.setOpen(false);\n },\n );\n\n const setOpen = useStableEvent(\n (next: boolean, focus: MenuFocusTarget = \"first\") => {\n if (next) {\n if (popover.open) {\n tracker.pending = null;\n focusItem(focus);\n } else tracker.pending = focus;\n }\n popover.setOpen(next);\n },\n );\n\n // Focus is the menu's own work. It runs after the popover's own pass, which\n // is what puts the element in the top layer, so the items are reachable.\n useBeforePaint(() => {\n const menu = registry.containerNode();\n const triggerNode = trigger.node();\n if (menu !== null) {\n registry.syncLabel(menu, triggerNode?.id);\n assertAccessibleName(menu, \"menu\");\n }\n focus.sync(popover.open, () => {\n const pending = tracker.pending;\n tracker.pending = null;\n focusItem(pending);\n });\n });\n\n function focusItem(focus: MenuFocusTarget | null): void {\n if (focus === false) return;\n const items = registry.items().filter(isNativeEnabled);\n const target = focus === \"last\" ? items[items.length - 1] : items[0];\n (target?.node ?? registry.containerNode())?.focus();\n }\n\n const state = {\n noteNativeToggle: focus.beforeToggle,\n activate,\n close,\n noteTrigger,\n open,\n registry,\n triggerId,\n };\n\n const parts: MenuParts<Value> = {\n checkboxItem: (value, itemOptions) =>\n menuItemMixin(state, {\n checked: itemOptions.checked,\n closeOnSelect: itemOptions.closeOnSelect === true,\n disabled: itemOptions.disabled === true,\n kind: \"checkbox\",\n value,\n }),\n item: (value, itemOptions = {}) =>\n menuItemMixin(state, {\n checked: undefined,\n closeOnSelect: itemOptions.closeOnSelect !== false,\n disabled: itemOptions.disabled === true,\n kind: \"item\",\n value,\n }),\n menu: () => menuMixin(state, popover.popover()),\n open: popover.open,\n radioItem: (value, itemOptions) =>\n menuItemMixin(state, {\n checked: itemOptions.checked,\n closeOnSelect: itemOptions.closeOnSelect === true,\n disabled: itemOptions.disabled === true,\n kind: \"radio\",\n value,\n }),\n setOpen: (next) => setOpen(next),\n trigger: () =>\n menuTriggerMixin(state, popover.trigger(), {\n openWithArrows: true,\n }),\n };\n registerMenuController(parts, {\n closeTree: close,\n bindTrigger: noteTrigger,\n popup: registry.containerNode,\n setOpen,\n submenuTrigger: (value, itemDisabled) =>\n menuItemMixin(state, {\n checked: undefined,\n closeOnSelect: false,\n disabled: itemDisabled,\n kind: \"submenu\",\n value,\n }),\n trigger: (openWithArrows, disabled = false) =>\n menuTriggerMixin(state, popover.trigger(), { openWithArrows, disabled }),\n });\n return parts;\n}\n\n/**\n * {@link useMenu} as a component, for a menu that is not already a component\n * of its own. Opening re-renders this root rather than the caller.\n */\nexport function Menu<Value = unknown>(props: MenuProps<Value>): FigNode {\n return props.children(useMenu(props));\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{n as e,t}from"./menu-DBxBFdYm.js";export{t as Menu,e as useMenu};
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./changes-DglRuvQa.js";import{n as t}from"./controllable-value-CDbRzlId.js";import{useStableEvent as n}from"@bgub/fig";function r(r){let i=t({value:r.open,defaultValue:r.defaultOpen===!0,onChange:r.onOpenChange,reconcile:r.requestReconcile}),a=n((t,n,r)=>i.request(()=>t,e(n,r))),o=n(t=>i.request(()=>t,e(null)));return{open:i.value,requestOpen:a,setOpen:o}}export{r as t};
2
+ //# sourceMappingURL=open-state-G79p9WiE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-state-G79p9WiE.js","names":[],"sources":["../src/internal/open-state.ts"],"sourcesContent":["import { useStableEvent } from \"@bgub/fig\";\nimport { type ChangeDetails, createChangeDetails } from \"./changes.ts\";\nimport { useControllableValue } from \"./controllable-value.ts\";\n\nexport type OpenChangeDetails = ChangeDetails;\nexport type OpenChangeHandler = (\n open: boolean,\n details: OpenChangeDetails,\n signal: AbortSignal,\n) => void;\nexport interface OpenStateOptions {\n defaultOpen?: boolean;\n onOpenChange?: OpenChangeHandler;\n open?: boolean;\n requestReconcile: () => void;\n}\n\n/** Widget open intent; browser observation belongs to the popup adapter. */\nexport function useOpenState(options: OpenStateOptions) {\n const state = useControllableValue({\n value: options.open,\n defaultValue: options.defaultOpen === true,\n onChange: options.onOpenChange,\n reconcile: options.requestReconcile,\n });\n const requestOpen = useStableEvent(\n (next: boolean, event: Event, trigger: Element | undefined) =>\n state.request(() => next, createChangeDetails(event, trigger)),\n );\n const setOpen = useStableEvent((next: boolean) =>\n state.request(() => next, createChangeDetails(null)),\n );\n return { open: state.value, requestOpen, setOpen };\n}\n"],"mappings":"0IAkBA,SAAgB,EAAa,EAA2B,CACtD,IAAM,EAAQ,EAAqB,CACjC,MAAO,EAAQ,KACf,aAAc,EAAQ,cAAgB,GACtC,SAAU,EAAQ,aAClB,UAAW,EAAQ,gBACrB,CAAC,EACK,EAAc,GACjB,EAAe,EAAc,IAC5B,EAAM,YAAc,EAAM,EAAoB,EAAO,CAAO,CAAC,CACjE,EACM,EAAU,EAAgB,GAC9B,EAAM,YAAc,EAAM,EAAoB,IAAI,CAAC,CACrD,EACA,MAAO,CAAE,KAAM,EAAM,MAAO,cAAa,SAAQ,CACnD"}
@@ -0,0 +1,2 @@
1
+ import{r as e}from"./parts-I0jdefyp.js";function t(t,n,r){let i=()=>n===`aria-labelledby`&&t.props[`aria-label`]!==void 0,a=t.props[n]==null&&!i(),o=t.props[n]??(a?r:void 0);return{props:{[n]:o},sync(r,o){return!a||!t.owns(n)?!1:i()?(e(r,n,void 0),!1):(e(r,n,o),!0)}}}export{t};
2
+ //# sourceMappingURL=part-reference-B7zINPUK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"part-reference-B7zINPUK.js","names":[],"sources":["../src/internal/part-reference.ts"],"sourcesContent":["import type { MixinContext } from \"@bgub/fig\";\nimport { setIdReference } from \"./parts.ts\";\n\ntype ReferenceAttribute = \"aria-labelledby\" | \"aria-describedby\" | \"for\";\n\n/**\n * A relationship owns only its generated default. Resolve the final mixin\n * props at commit so a later author override wins; a later aria-label also\n * retires our generated aria-labelledby instead of leaving it to take priority.\n */\nexport function createPartReference(\n context: MixinContext,\n attribute: ReferenceAttribute,\n generated?: string,\n) {\n const blocked = () =>\n attribute === \"aria-labelledby\" &&\n context.props[\"aria-label\"] !== undefined;\n const automatic = context.props[attribute] == null && !blocked();\n const value = context.props[attribute] ?? (automatic ? generated : undefined);\n return {\n props: { [attribute]: value },\n sync(node: HTMLElement, target: string | undefined): boolean {\n if (!automatic || !context.owns(attribute)) return false;\n if (blocked()) {\n setIdReference(node, attribute, undefined);\n return false;\n }\n setIdReference(node, attribute, target);\n return true;\n },\n };\n}\n\nexport type PartReference = ReturnType<typeof createPartReference>;\n"],"mappings":"wCAUA,SAAgB,EACd,EACA,EACA,EACA,CACA,IAAM,MACJ,IAAc,mBACd,EAAQ,MAAM,gBAAkB,IAAA,GAC5B,EAAY,EAAQ,MAAM,IAAc,MAAQ,CAAC,EAAQ,EACzD,EAAQ,EAAQ,MAAM,KAAe,EAAY,EAAY,IAAA,IACnE,MAAO,CACL,MAAO,EAAG,GAAY,CAAM,EAC5B,KAAK,EAAmB,EAAqC,CAO3D,MANI,CAAC,GAAa,CAAC,EAAQ,KAAK,CAAS,EAAU,GAC/C,EAAQ,GACV,EAAe,EAAM,EAAW,IAAA,EAAS,EAClC,KAET,EAAe,EAAM,EAAW,CAAM,EAC/B,GACT,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ import{useBeforeLayout as e,useBeforePaint as t,useMemo as n,useStableEvent as r,useState as i}from"@bgub/fig";import{hostBinding as a}from"@bgub/fig-dom";function o(e=()=>{}){let t=new Map;function n(n,r,i){let a=t.get(n);if(a?.signal===r)return a.value=i,e(),!1;let o={node:n,signal:r,value:i};return t.set(n,o),e(),r.addEventListener(`abort`,()=>{t.get(n)===o&&(t.delete(n),e())},{once:!0}),!0}return{bind:n,get:e=>t.get(e)?.value,items:()=>[...t.values()]}}function s(e){let t=null;function n(n,r){if(t?.node===n&&t.signal===r)return e(),!1;let i={node:n,signal:r};return t=i,e(),r.addEventListener(`abort`,()=>{t===i&&(t=null,e())},{once:!0}),!0}return{bind:n,node:()=>t?.node??null}}function c(){let a=n(()=>({reconciled:!1}),[]);e(()=>{a.reconciled=!1});let[,o]=i(0);return t(()=>{a.reconciled=!0}),r(e=>{e.aborted||!a.reconciled||(a.reconciled=!1,o(e=>e+1))})}function l(e,t,n){return[e.props.bind,a(e,t,(e,t)=>{e instanceof HTMLElement&&n(e,t)})]}function u(e,t){return n=>{if(n.defaultPrevented)return;let r=e.itemAt(n.target);if(r!==void 0){if(r.disabled){n.preventDefault();return}n.button===0&&t(r,n)}}}function d(e,t){return{"aria-disabled":t.disabled?`true`:void 0,"data-disabled":t.disabled?``:void 0,disabled:e.props.disabled,id:e.props.id??t.id,type:e.type===`button`?e.props.type??`button`:void 0}}function f(e,t,n){n===void 0||n===``?e.removeAttribute(t):e.setAttribute(t,n)}export{c as a,d as i,l as n,o,f as r,s,u as t};
2
+ //# sourceMappingURL=parts-I0jdefyp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts-I0jdefyp.js","names":[],"sources":["../src/internal/registration.ts","../src/internal/reconcile.ts","../src/internal/parts.ts"],"sourcesContent":["export interface PartRegistration<Value> {\n readonly node: HTMLElement;\n readonly value: Value;\n}\n\n/** Registration data changes without ending the committed host lifetime. */\nexport function createPartCollection<Value>(changed: () => void = () => {}) {\n const entries = new Map<\n HTMLElement,\n { node: HTMLElement; value: Value; signal: AbortSignal }\n >();\n function bind(node: HTMLElement, signal: AbortSignal, value: Value): boolean {\n const previous = entries.get(node);\n if (previous?.signal === signal) {\n previous.value = value;\n // Host props such as id/form can change even when metadata is equal.\n changed();\n return false;\n }\n const entry = { node, signal, value };\n entries.set(node, entry);\n changed();\n signal.addEventListener(\n \"abort\",\n () => {\n if (entries.get(node) !== entry) return;\n entries.delete(node);\n changed();\n },\n { once: true },\n );\n return true;\n }\n return {\n bind,\n get: (node: HTMLElement): Value | undefined => entries.get(node)?.value,\n items: (): readonly PartRegistration<Value>[] => [...entries.values()],\n };\n}\n\n/** One role, with cleanup guarded by lifetime identity, not node identity. */\nexport function createPartSlot(changed: () => void) {\n let current: { node: HTMLElement; signal: AbortSignal } | null = null;\n function bind(node: HTMLElement, signal: AbortSignal): boolean {\n if (current?.node === node && current.signal === signal) {\n changed();\n return false;\n }\n const registration = { node, signal };\n current = registration;\n changed();\n signal.addEventListener(\n \"abort\",\n () => {\n if (current !== registration) return;\n current = null;\n changed();\n },\n { once: true },\n );\n return true;\n }\n return { bind, node: () => current?.node ?? null };\n}\n","import {\n useBeforePaint,\n useBeforeLayout,\n useMemo,\n useStableEvent,\n useState,\n} from \"@bgub/fig\";\n\n/**\n * Lets a widget root notice the DOM moving without it rendering.\n *\n * Two things cause that: a descendant component owning part of the markup, so\n * parts mount and unmount on their own, and a host element that changes itself\n * — a `<dialog>` closing on Escape. Either way the root has to render for its\n * before-paint pass to reconcile against the committed DOM. The guard matters:\n * part configurations update during a commit, and those registrations are\n * already covered by that commit’s before-paint pass.\n */\nexport function useRegistrationReconcile(): () => void {\n const tracker = useMemo(() => ({ reconciled: false }), []);\n // Binds run after this render, so they belong to it and need no extra work.\n useBeforeLayout(() => {\n tracker.reconciled = false;\n });\n const [, requestReconcile] = useState(0);\n\n useBeforePaint(() => {\n tracker.reconciled = true;\n });\n\n return useStableEvent((signal: AbortSignal) => {\n if (signal.aborted || !tracker.reconciled) return;\n tracker.reconciled = false;\n requestReconcile((epoch) => epoch + 1);\n });\n}\n","import type { MixinContext } from \"@bgub/fig\";\nimport { type Binding, hostBinding } from \"@bgub/fig-dom\";\nimport { type Composite, type CompositeItem } from \"./composite.ts\";\n\n/** Composes a widget's element binding after any the caller authored. */\nexport function bindPart(\n context: MixinContext,\n owner: object,\n bind: (node: HTMLElement, signal: AbortSignal) => void,\n): Binding {\n return [\n context.props.bind,\n hostBinding(context, owner, (node, signal) => {\n if (node instanceof HTMLElement) bind(node, signal);\n }),\n ];\n}\n\n/**\n * How every widget reacts to a click on one of its items: targets the\n * container does not own are ignored, a disabled item cancels the default\n * action instead of activating, and only the primary button activates.\n */\nexport function activateOnClick(\n registry: Composite,\n activate: (item: CompositeItem, event: MouseEvent) => void,\n): (event: MouseEvent) => void {\n return (event) => {\n if (event.defaultPrevented) return;\n const item = registry.itemAt(event.target);\n if (item === undefined) return;\n if (item.disabled) {\n event.preventDefault();\n return;\n }\n if (event.button === 0) activate(item, event);\n };\n}\n\n/**\n * The host props every activating item shares. Disabled items stay focusable\n * so their state stays discoverable: explicit widget disability uses ARIA\n * instead of native `disabled`. Otherwise preserve the caller's native prop.\n */\nexport function triggerProps(\n context: MixinContext,\n options: { readonly disabled: boolean; readonly id?: string },\n) {\n return {\n \"aria-disabled\": options.disabled ? \"true\" : undefined,\n \"data-disabled\": options.disabled ? \"\" : undefined,\n disabled: context.props.disabled,\n id: context.props.id ?? options.id,\n type:\n context.type === \"button\" ? (context.props.type ?? \"button\") : undefined,\n };\n}\n\n/** Points an ARIA relationship at an id, or removes it when there is none. */\nexport function setIdReference(\n node: HTMLElement,\n name: string,\n id: string | undefined,\n): void {\n if (id === undefined || id === \"\") node.removeAttribute(name);\n else node.setAttribute(name, id);\n}\n"],"mappings":"2JAMA,SAAgB,EAA4B,MAA4B,CAAC,EAAG,CAC1E,IAAM,EAAU,IAAI,IAIpB,SAAS,EAAK,EAAmB,EAAqB,EAAuB,CAC3E,IAAM,EAAW,EAAQ,IAAI,CAAI,EACjC,GAAI,GAAU,SAAW,EAIvB,MAHA,GAAS,MAAQ,EAEjB,EAAQ,EACD,GAET,IAAM,EAAQ,CAAE,OAAM,SAAQ,OAAM,EAYpC,OAXA,EAAQ,IAAI,EAAM,CAAK,EACvB,EAAQ,EACR,EAAO,iBACL,YACM,CACA,EAAQ,IAAI,CAAI,IAAM,IAC1B,EAAQ,OAAO,CAAI,EACnB,EAAQ,EACV,EACA,CAAE,KAAM,EAAK,CACf,EACO,EACT,CACA,MAAO,CACL,OACA,IAAM,GAAyC,EAAQ,IAAI,CAAI,CAAC,EAAE,MAClE,UAAiD,CAAC,GAAG,EAAQ,OAAO,CAAC,CACvE,CACF,CAGA,SAAgB,EAAe,EAAqB,CAClD,IAAI,EAA6D,KACjE,SAAS,EAAK,EAAmB,EAA8B,CAC7D,GAAI,GAAS,OAAS,GAAQ,EAAQ,SAAW,EAE/C,OADA,EAAQ,EACD,GAET,IAAM,EAAe,CAAE,OAAM,QAAO,EAYpC,MAXA,GAAU,EACV,EAAQ,EACR,EAAO,iBACL,YACM,CACA,IAAY,IAChB,EAAU,KACV,EAAQ,EACV,EACA,CAAE,KAAM,EAAK,CACf,EACO,EACT,CACA,MAAO,CAAE,OAAM,SAAY,GAAS,MAAQ,IAAK,CACnD,CC7CA,SAAgB,GAAuC,CACrD,IAAM,EAAU,OAAe,CAAE,WAAY,EAAM,GAAI,CAAC,CAAC,EAEzD,MAAsB,CACpB,EAAQ,WAAa,EACvB,CAAC,EACD,GAAM,EAAG,GAAoB,EAAS,CAAC,EAMvC,OAJA,MAAqB,CACnB,EAAQ,WAAa,EACvB,CAAC,EAEM,EAAgB,GAAwB,CACzC,EAAO,SAAW,CAAC,EAAQ,aAC/B,EAAQ,WAAa,GACrB,EAAkB,GAAU,EAAQ,CAAC,EACvC,CAAC,CACH,CC9BA,SAAgB,EACd,EACA,EACA,EACS,CACT,MAAO,CACL,EAAQ,MAAM,KACd,EAAY,EAAS,GAAQ,EAAM,IAAW,CACxC,aAAgB,aAAa,EAAK,EAAM,CAAM,CACpD,CAAC,CACH,CACF,CAOA,SAAgB,EACd,EACA,EAC6B,CAC7B,MAAQ,IAAU,CAChB,GAAI,EAAM,iBAAkB,OAC5B,IAAM,EAAO,EAAS,OAAO,EAAM,MAAM,EACrC,OAAS,IAAA,GACb,IAAI,EAAK,SAAU,CACjB,EAAM,eAAe,EACrB,MACF,CACI,EAAM,SAAW,GAAG,EAAS,EAAM,CAAK,CAD5C,CAEF,CACF,CAOA,SAAgB,EACd,EACA,EACA,CACA,MAAO,CACL,gBAAiB,EAAQ,SAAW,OAAS,IAAA,GAC7C,gBAAiB,EAAQ,SAAW,GAAK,IAAA,GACzC,SAAU,EAAQ,MAAM,SACxB,GAAI,EAAQ,MAAM,IAAM,EAAQ,GAChC,KACE,EAAQ,OAAS,SAAY,EAAQ,MAAM,MAAQ,SAAY,IAAA,EACnE,CACF,CAGA,SAAgB,EACd,EACA,EACA,EACM,CACF,IAAO,IAAA,IAAa,IAAO,GAAI,EAAK,gBAAgB,CAAI,EACvD,EAAK,aAAa,EAAM,CAAE,CACjC"}
@@ -0,0 +1,2 @@
1
+ import{a as e,i as t,n}from"./parts-I0jdefyp.js";import{c as r,l as i}from"./diagnostics-BkQV0MaT.js";import{n as a,t as o}from"./popup-state-BUYfC2nG.js";import{createMixin as s,useBeforePaint as c,useId as l,useMemo as u}from"@bgub/fig";import{on as d}from"@bgub/fig-dom";const f=s((e,i)=>(r(e,`popover trigger`,`button`),{...t(e,{disabled:!1}),"aria-controls":i.popoverId,"aria-expanded":i.open?`true`:`false`,bind:n(e,i.registry,i.registry.bindAnchor),"data-open":i.open?``:void 0,popovertarget:i.popoverId,mix:d(`click`,e=>{if(e.defaultPrevented||i.registry.supported())return;let t=e.currentTarget;i.requestOpen(!i.open,e,t instanceof Element?t:void 0)})})),p=s((e,t)=>(i(e,t.popoverId),{bind:n(e,t.registry,t.registry.bindPopup),"data-open":t.open?``:void 0,id:t.popoverId,mix:[d(`beforetoggle`,t.nativeToggle),d(`toggle`,t.nativeToggle)],popover:e.props.popover??`auto`})),m=new WeakMap;function h(e,t){m.set(e,t)}function g(e,t,n){let r=m.get(e);if(r===void 0)throw Error(`Fig Headless popover source must belong to usePopover().`);r(t,n)}function _(t={}){let n=e(),r=u(()=>a(n,`popover`),[]),{getOpen:i,open:s,requestOpen:d,setOpen:m,nativeToggle:g}=o({...t,requestReconcile:n}),_=l(),v=`--fig-popover-${_.replaceAll(/[^\w-]/g,`-`)}`,y=t.id??`${_}-popover`;c(()=>{r.sync(i(),v)});let b={open:s,popoverId:y,registry:r,requestOpen:d,nativeToggle:g},x={open:s,popover:()=>p(b),setOpen:m,trigger:()=>f(b)};return h(x,r.bindSource),x}function v(e){return e.children(_(e))}export{_ as n,g as r,v as t};
2
+ //# sourceMappingURL=popover-DXxGM3tO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popover-DXxGM3tO.js","names":[],"sources":["../src/popover/parts.ts","../src/popover/registry.ts","../src/popover/popover.tsx"],"sourcesContent":["import { createMixin, type MixinContext } from \"@bgub/fig\";\nimport { on } from \"@bgub/fig-dom\";\nimport { expectHost, expectPopupId } from \"../internal/diagnostics.ts\";\nimport { bindPart, triggerProps } from \"../internal/parts.ts\";\nimport type { PopoverRegistry } from \"./registry.ts\";\n\n/** Widget-level state every part reads. Built once per root render. */\nexport interface PopoverPartState {\n readonly nativeToggle: (event: Event) => void;\n readonly open: boolean;\n readonly popoverId: string;\n readonly registry: PopoverRegistry;\n readonly requestOpen: (\n open: boolean,\n event: Event,\n trigger: Element | undefined,\n ) => boolean;\n}\n\nexport const popoverTriggerMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: PopoverPartState) => {\n expectHost(context, \"popover trigger\", \"button\");\n return {\n ...triggerProps(context, { disabled: false }),\n \"aria-controls\": state.popoverId,\n \"aria-expanded\": state.open ? \"true\" : \"false\",\n bind: bindPart(context, state.registry, state.registry.bindAnchor),\n \"data-open\": state.open ? \"\" : undefined,\n popovertarget: state.popoverId,\n // With popover support the browser toggles through popovertarget, which\n // works before hydration; without it the widget does the toggling.\n mix: on(\"click\", (event) => {\n if (event.defaultPrevented || state.registry.supported()) return;\n const trigger = event.currentTarget;\n state.requestOpen(\n !state.open,\n event,\n trigger instanceof Element ? trigger : undefined,\n );\n }),\n };\n },\n);\n\nexport const popoverMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, state: PopoverPartState) => {\n expectPopupId(context, state.popoverId);\n return {\n bind: bindPart(context, state.registry, state.registry.bindPopup),\n \"data-open\": state.open ? \"\" : undefined,\n id: state.popoverId,\n mix: [\n on(\"beforetoggle\", state.nativeToggle),\n on(\"toggle\", state.nativeToggle),\n ],\n popover: context.props.popover ?? \"auto\",\n };\n },\n);\n","import type { createAnchoredPopup } from \"../internal/anchored-popup.ts\";\n\nexport type PopoverRegistry = ReturnType<typeof createAnchoredPopup>;\n\n// Widget composition can supply a native invoker without claiming CSS anchor\n// placement or changing the public popover parts contract.\nconst sourceBindings = new WeakMap<object, PopoverRegistry[\"bindSource\"]>();\n\nexport function registerPopoverSource(\n parts: object,\n bind: PopoverRegistry[\"bindSource\"],\n): void {\n sourceBindings.set(parts, bind);\n}\n\nexport function bindPopoverSource(\n parts: object,\n node: HTMLElement,\n signal: AbortSignal,\n): void {\n const bind = sourceBindings.get(parts);\n if (bind === undefined)\n throw new Error(\"Fig Headless popover source must belong to usePopover().\");\n bind(node, signal);\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 { usePopupState } from \"../internal/popup-state.ts\";\nimport { createAnchoredPopup } from \"../internal/anchored-popup.ts\";\nimport { useRegistrationReconcile } from \"../internal/reconcile.ts\";\nimport { popoverMixin, popoverTriggerMixin } from \"./parts.ts\";\nimport { registerPopoverSource } from \"./registry.ts\";\n\nexport type PopoverOpenChangeDetails = OpenChangeDetails;\n\nexport type PopoverOpenChangeHandler = OpenChangeHandler;\n\nexport interface PopoverOptions {\n defaultOpen?: boolean;\n /** The popover id emitted on the server and referenced by its trigger. */\n id?: string;\n onOpenChange?: PopoverOpenChangeHandler;\n open?: boolean;\n}\n\nexport interface PopoverParts {\n readonly open: boolean;\n /** Applies to the caller's own popover element. */\n popover(): MixinDescriptor;\n /** Opens or closes without an activation event. */\n setOpen(open: boolean): void;\n trigger(): MixinDescriptor;\n}\n\nexport interface PopoverProps extends PopoverOptions {\n children: (popover: PopoverParts) => FigNode;\n}\n\n/**\n * Coordinates one popover around the native popover API, which owns the top\n * layer, light dismiss, and Escape, and around CSS anchor positioning, which\n * owns placement. The widget owns open state and the wiring between the two\n * elements, and measures nothing.\n */\nexport function usePopover(options: PopoverOptions = {}): PopoverParts {\n const requestReconcile = useRegistrationReconcile();\n const registry = useMemo(\n () => createAnchoredPopup(requestReconcile, \"popover\"),\n [],\n );\n const { getOpen, open, requestOpen, setOpen, nativeToggle } = usePopupState({\n ...options,\n requestReconcile,\n });\n\n const id = useId();\n const anchorName = `--fig-popover-${id.replaceAll(/[^\\w-]/g, \"-\")}`;\n const popoverId = options.id ?? `${id}-popover`;\n\n useBeforePaint(() => {\n registry.sync(getOpen(), anchorName);\n });\n\n const state = { open, popoverId, registry, requestOpen, nativeToggle };\n\n const parts: PopoverParts = {\n open,\n popover: () => popoverMixin(state),\n setOpen,\n trigger: () => popoverTriggerMixin(state),\n };\n registerPopoverSource(parts, registry.bindSource);\n return parts;\n}\n\n/**\n * {@link usePopover} as a component, for a popover that is not already a\n * component of its own. Opening re-renders this root rather than the caller.\n */\nexport function Popover(props: PopoverProps): FigNode {\n return props.children(usePopover(props));\n}\n"],"mappings":"kRAmBA,MAAa,EAAsC,GAChD,EAAuB,KACtB,EAAW,EAAS,kBAAmB,QAAQ,EACxC,CACL,GAAG,EAAa,EAAS,CAAE,SAAU,EAAM,CAAC,EAC5C,gBAAiB,EAAM,UACvB,gBAAiB,EAAM,KAAO,OAAS,QACvC,KAAM,EAAS,EAAS,EAAM,SAAU,EAAM,SAAS,UAAU,EACjE,YAAa,EAAM,KAAO,GAAK,IAAA,GAC/B,cAAe,EAAM,UAGrB,IAAK,EAAG,QAAU,GAAU,CAC1B,GAAI,EAAM,kBAAoB,EAAM,SAAS,UAAU,EAAG,OAC1D,IAAM,EAAU,EAAM,cACtB,EAAM,YACJ,CAAC,EAAM,KACP,EACA,aAAmB,QAAU,EAAU,IAAA,EACzC,CACF,CAAC,CACH,EAEJ,EAEa,EAA+B,GACzC,EAAuB,KACtB,EAAc,EAAS,EAAM,SAAS,EAC/B,CACL,KAAM,EAAS,EAAS,EAAM,SAAU,EAAM,SAAS,SAAS,EAChE,YAAa,EAAM,KAAO,GAAK,IAAA,GAC/B,GAAI,EAAM,UACV,IAAK,CACH,EAAG,eAAgB,EAAM,YAAY,EACrC,EAAG,SAAU,EAAM,YAAY,CACjC,EACA,QAAS,EAAQ,MAAM,SAAW,MACpC,EAEJ,ECpDM,EAAiB,IAAI,QAE3B,SAAgB,EACd,EACA,EACM,CACN,EAAe,IAAI,EAAO,CAAI,CAChC,CAEA,SAAgB,EACd,EACA,EACA,EACM,CACN,IAAM,EAAO,EAAe,IAAI,CAAK,EACrC,GAAI,IAAS,IAAA,GACX,MAAU,MAAM,0DAA0D,EAC5E,EAAK,EAAM,CAAM,CACnB,CCwBA,SAAgB,EAAW,EAA0B,CAAC,EAAiB,CACrE,IAAM,EAAmB,EAAyB,EAC5C,EAAW,MACT,EAAoB,EAAkB,SAAS,EACrD,CAAC,CACH,EACM,CAAE,UAAS,OAAM,cAAa,UAAS,gBAAiB,EAAc,CAC1E,GAAG,EACH,kBACF,CAAC,EAEK,EAAK,EAAM,EACX,EAAa,iBAAiB,EAAG,WAAW,UAAW,GAAG,IAC1D,EAAY,EAAQ,IAAM,GAAG,EAAG,UAEtC,MAAqB,CACnB,EAAS,KAAK,EAAQ,EAAG,CAAU,CACrC,CAAC,EAED,IAAM,EAAQ,CAAE,OAAM,YAAW,WAAU,cAAa,cAAa,EAE/D,EAAsB,CAC1B,OACA,YAAe,EAAa,CAAK,EACjC,UACA,YAAe,EAAoB,CAAK,CAC1C,EAEA,OADA,EAAsB,EAAO,EAAS,UAAU,EACzC,CACT,CAMA,SAAgB,EAAQ,EAA8B,CACpD,OAAO,EAAM,SAAS,EAAW,CAAK,CAAC,CACzC"}
@@ -0,0 +1 @@
1
+ import{n as e,t}from"./popover-DXxGM3tO.js";export{t as Popover,e as usePopover};
@@ -0,0 +1,2 @@
1
+ import{a as e,n as t,s as n}from"../parts-I0jdefyp.js";import{createMixin as r,useBeforePaint as i,useMemo as a}from"@bgub/fig";function o(e,t,n,r,i){let a=e.ownerDocument.defaultView,o=e.getBoundingClientRect(),s,c=0,l=!1;function u(){if(r.aborted)return;let n=e.getBoundingClientRect();(n.x!==o.x||n.y!==o.y||n.width!==o.width||n.height!==o.height)&&(o=n,t(),f())}function d(){c=0,u(),!r.aborted&&(i||l)&&(c=a.requestAnimationFrame(d))}function f(t=1){if(s?.disconnect(),i||o.width===0||o.height===0)return;let n=e.ownerDocument.documentElement;s=new a.IntersectionObserver(e=>{if(r.aborted)return;let n=o;u();let i=e.at(-1)?.intersectionRatio??1;n===o&&t===1&&i>0&&i<1&&f(i)},{rootMargin:`${-o.top}px ${o.right-n.clientWidth}px ${o.bottom-n.clientHeight}px ${-o.left}px`,threshold:[t,1]}),s.observe(e)}let p=new a.IntersectionObserver(e=>{let t=e.at(-1);r.aborted||t===void 0||(n(t.intersectionRect.width<=0||t.intersectionRect.height<=0),l=t.intersectionRatio>0&&t.intersectionRatio<1,u(),l&&!c&&(c=a.requestAnimationFrame(d)))},{threshold:[0,1]});return p.observe(e),f(),a.addEventListener(`resize`,()=>{u(),f()},{signal:r}),i&&(c=a.requestAnimationFrame(d)),r.addEventListener(`abort`,()=>{p.disconnect(),s?.disconnect(),a.cancelAnimationFrame(c)},{once:!0}),u}const s=r((e,n)=>({bind:t(e,n,n.anchor.bind)})),c=r((e,n)=>({bind:t(e,n,n.bindPopup)}));function l(t){let n=e(),r=a(()=>d(n),[]);return i(e=>{let n=r.anchor.node(),i=r.popup.node(),a=r.styles;if(!t.open||n===null||i===null||a===void 0)return;let s=n.ownerDocument.defaultView;if(s===null)return;let c=0,l=!1,u=()=>{e.aborted||f(n,i,a,t,l)},d=()=>{e.aborted||c||(c=s.requestAnimationFrame(()=>{c=0,m(),u()}))};u();let p=new s.ResizeObserver(d);p.observe(i);for(let e=n;e!==null;e=e.parentElement)p.observe(e);let m=o(n,u,e=>{l=e,u()},e,t.trackAnchorAnimation??!1),h={signal:e,passive:!0};n.ownerDocument.addEventListener(`scroll`,e=>{e.target!==i&&d()},{...h,capture:!0}),s.addEventListener(`resize`,d,h),s.visualViewport?.addEventListener(`resize`,d,h),s.visualViewport?.addEventListener(`scroll`,d,h),e.addEventListener(`abort`,()=>{p.disconnect(),s.cancelAnimationFrame(c)},{once:!0})}),{anchor:()=>s(r),popup:()=>c(r)}}function u(e,t){let n=new Map;function r(t){let r=n.get(t);r!==void 0&&(e.style.getPropertyValue(t)===r.written&&e.style.getPropertyPriority(t)===``&&(r.value?e.style.setProperty(t,r.value,r.priority):e.style.removeProperty(t)),n.delete(t))}function i(t,i){if(n.get(t)?.written===i&&e.style.getPropertyValue(t)===i&&e.style.getPropertyPriority(t)===``)return;r(t);let a={value:e.style.getPropertyValue(t),priority:e.style.getPropertyPriority(t),written:i};e.style.setProperty(t,i),a.written=e.style.getPropertyValue(t),n.set(t,a)}return t.addEventListener(`abort`,()=>{for(let e of n.keys())r(e)},{once:!0}),{set:i,restore:r}}function d(e){let t=n(e),r=n(e),i={anchor:t,popup:r,bindPopup:a};function a(e,t){if(!r.bind(e,t))return;let n=u(e,t);i.styles=n;let a=[`data-side`,`data-align`,`data-anchor-hidden`].map(t=>[t,e.getAttribute(t)]);t.addEventListener(`abort`,()=>{for(let[t,n]of a)n===null?e.removeAttribute(t):e.setAttribute(t,n)},{once:!0});for(let[e,t]of Object.entries({position:`fixed`,"position-area":`none`,"position-try-fallbacks":`none`,top:`4px`,left:`4px`,right:`auto`,bottom:`auto`,"margin-top":`0px`,"margin-right":`0px`,"margin-bottom":`0px`,"margin-left":`0px`,"overflow-x":`auto`,"overflow-y":`auto`,"box-sizing":`border-box`}))n.set(e,t)}return i}function f(e,t,n,r,i){let a=e.ownerDocument.defaultView,o=a.visualViewport,s=o?.width??e.ownerDocument.documentElement.clientWidth,c=o?.height??e.ownerDocument.documentElement.clientHeight,l=Math.max(0,r.collisionPadding??4),u=Math.min(l,s/2),d=Math.min(l,c/2),f=(o?.offsetLeft??0)+u,m=(o?.offsetTop??0)+d,h=f+s-2*u,g=m+c-2*d,_=r.sideOffset??4,v=a.getComputedStyle(e).direction===`rtl`,y=e.getBoundingClientRect();t.toggleAttribute(`data-anchor-hidden`,i||y.width===0||y.height===0||y.right<=f-u||y.left>=h+u||y.bottom<=m-d||y.top>=g+d);let b=r.align??`start`,x=r.side??`bottom`;x===`inline-end`&&(x=v?`left`:`right`),x===`inline-start`&&(x=v?`right`:`left`);let S={top:p(y.top-m-_,0,g-m),bottom:p(g-y.bottom-_,0,g-m),left:p(y.left-f-_,0,h-f),right:p(h-y.right-_,0,h-f)},C={top:`bottom`,bottom:`top`,left:`right`,right:`left`};n.set(`--anchor-width`,`${y.width}px`),n.set(`--anchor-height`,`${y.height}px`),n.set(`--available-width`,`${h-f}px`),n.set(`--available-height`,`${g-m}px`);let w=t.scrollTop,T=t.scrollLeft;n.restore(`max-width`),n.restore(`max-height`);let E=a.getComputedStyle(t),D=E.maxWidth,O=E.maxHeight;function k(e,t){n.set(`--available-width`,`${Math.max(0,e)}px`),n.set(`--available-height`,`${Math.max(0,t)}px`),n.set(`max-width`,D===`none`||!D?`${Math.max(0,e)}px`:`min(${D}, ${Math.max(0,e)}px)`),n.set(`max-height`,O===`none`||!O?`${Math.max(0,t)}px`:`min(${O}, ${Math.max(0,t)}px)`)}k(h-f,g-m);let A=t.getBoundingClientRect(),j=x===`top`||x===`bottom`;(j?A.height:A.width)>S[x]&&S[C[x]]>S[x]&&(x=C[x]),!j&&A.width>S[x]&&(x=S.bottom>=A.height||S.bottom>=S.top?`bottom`:`top`,j=!0),k(j?h-f:S[x],j?S[x]:g-m);let M=(r.side===`inline-start`||r.side===`inline-end`)&&!j?x===`left`===v?`inline-end`:`inline-start`:x;t.setAttribute(`data-side`,M),t.setAttribute(`data-align`,b),A=t.getBoundingClientRect(),t.scrollTop=w,t.scrollLeft=T;let N=j?y.left:y.top,P=j?y.right:y.bottom,F=j?A.width:A.height,I=j&&v,L=b===`end`!==I,R=b===`center`?(N+P-F)/2:L?P-F:N,z=b===`end`?-1:1;R+=(r.alignOffset??0)*z*(I?-1:1);let B=j?R:x===`left`?y.left-_-A.width:y.right+_,V=j?x===`top`?y.top-_-A.height:y.bottom+_:R;B=p(B,f,h-A.width),V=p(V,m,g-A.height),n.set(`left`,`${B}px`),n.set(`top`,`${V}px`);let H=b!==`center`&&Math.abs((j?B:V)-R)<=1?L?F:0:p((N+P)/2-(j?B:V),0,F),U=j?H:(x===`left`?y.left:y.right)-B,W=j?(x===`top`?y.top:y.bottom)-V:H;n.set(`--transform-origin`,`${U}px ${W}px`)}function p(e,t,n){return Math.max(t,Math.min(e,n))}export{l as usePopupPosition};
2
+ //# sourceMappingURL=position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.js","names":[],"sources":["../../src/internal/popup-anchor.ts","../../src/popup/position.ts"],"sourcesContent":["/** Track an anchor without a permanent animation-frame loop. */\nexport function observePopupAnchor(\n anchor: HTMLElement,\n update: () => void,\n hidden: (value: boolean) => void,\n signal: AbortSignal,\n animation: boolean,\n): () => void {\n const window = anchor.ownerDocument.defaultView!;\n let rect = anchor.getBoundingClientRect();\n let movement: IntersectionObserver | undefined;\n let frame = 0;\n let partiallyClipped = false;\n\n function check(): void {\n if (signal.aborted) return;\n const next = anchor.getBoundingClientRect();\n if (\n next.x !== rect.x ||\n next.y !== rect.y ||\n next.width !== rect.width ||\n next.height !== rect.height\n ) {\n rect = next;\n update();\n watchMovement();\n }\n }\n\n function tick(): void {\n frame = 0;\n check();\n if (!signal.aborted && (animation || partiallyClipped)) {\n frame = window.requestAnimationFrame(tick);\n }\n }\n\n function watchMovement(threshold = 1): void {\n movement?.disconnect();\n if (animation || rect.width === 0 || rect.height === 0) return;\n const document = anchor.ownerDocument.documentElement;\n // Shrink the observer's root to the anchor's current bounds. Moving any\n // edge changes its intersection, even when no element changes size.\n movement = new window.IntersectionObserver(\n (entries) => {\n if (signal.aborted) return;\n const previous = rect;\n check();\n const ratio = entries.at(-1)?.intersectionRatio ?? 1;\n // Fractional root margins can round inward. Calibrate against the\n // observed ratio so a fully visible anchor doesn't become untrackable.\n if (previous === rect && threshold === 1 && ratio > 0 && ratio < 1) {\n watchMovement(ratio);\n }\n },\n {\n rootMargin: `${-rect.top}px ${rect.right - document.clientWidth}px ${rect.bottom - document.clientHeight}px ${-rect.left}px`,\n threshold: [threshold, 1],\n },\n );\n movement.observe(anchor);\n }\n\n const visibility = new window.IntersectionObserver(\n (entries) => {\n const entry = entries.at(-1);\n if (signal.aborted || entry === undefined) return;\n hidden(\n entry.intersectionRect.width <= 0 || entry.intersectionRect.height <= 0,\n );\n // A clipped anchor can move while its visible intersection stays the\n // same. Check its bounds only while clipping makes observation ambiguous.\n partiallyClipped =\n entry.intersectionRatio > 0 && entry.intersectionRatio < 1;\n check();\n if (partiallyClipped && !frame)\n frame = window.requestAnimationFrame(tick);\n },\n { threshold: [0, 1] },\n );\n visibility.observe(anchor);\n watchMovement();\n window.addEventListener(\n \"resize\",\n () => {\n check();\n watchMovement();\n },\n { signal },\n );\n if (animation) frame = window.requestAnimationFrame(tick);\n signal.addEventListener(\n \"abort\",\n () => {\n visibility.disconnect();\n movement?.disconnect();\n window.cancelAnimationFrame(frame);\n },\n { once: true },\n );\n return check;\n}\n","import {\n createMixin,\n type MixinContext,\n type MixinDescriptor,\n useBeforePaint,\n useMemo,\n} from \"@bgub/fig\";\nimport { bindPart } from \"../internal/parts.ts\";\nimport { observePopupAnchor } from \"../internal/popup-anchor.ts\";\nimport { createPartSlot } from \"../internal/registration.ts\";\nimport { useRegistrationReconcile } from \"../internal/reconcile.ts\";\n\nexport interface PopupPositionOptions {\n /** Match the popup widget's committed open state. */\n open: boolean;\n /** Preferred side in horizontal writing modes. Defaults to bottom. */\n side?: \"top\" | \"bottom\" | \"left\" | \"right\" | \"inline-start\" | \"inline-end\";\n /** Alignment along the side. Defaults to start. */\n align?: \"start\" | \"center\" | \"end\";\n /** Distance from the anchor in CSS pixels. Defaults to 4; may be negative. */\n sideOffset?: number;\n /** Cross-axis offset in CSS pixels; end alignment reverses its sign. Defaults to 0. */\n alignOffset?: number;\n /** Space to reserve at each viewport edge, in CSS pixels. Defaults to 4. */\n collisionPadding?: number;\n /** Check anchor bounds every frame for transform animations. Defaults to false. */\n trackAnchorAnimation?: boolean;\n}\n\nexport interface PopupPositionParts {\n anchor(): MixinDescriptor;\n popup(): MixinDescriptor;\n}\n\ntype PositionRegistry = ReturnType<typeof createPositionRegistry>;\ntype Side = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nconst anchorMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, registry: PositionRegistry) => ({\n bind: bindPart(context, registry, registry.anchor.bind),\n }),\n);\nconst popupMixin = /* @__PURE__ */ createMixin(\n (context: MixinContext, registry: PositionRegistry) => ({\n bind: bindPart(context, registry, registry.bindPopup),\n }),\n);\n\n/** Optional viewport positioning for native top-layer popups. */\nexport function usePopupPosition(\n options: PopupPositionOptions,\n): PopupPositionParts {\n const changed = useRegistrationReconcile();\n const registry = useMemo(() => createPositionRegistry(changed), []);\n useBeforePaint((signal) => {\n const anchor = registry.anchor.node();\n const popup = registry.popup.node();\n const styles = registry.styles;\n if (\n !options.open ||\n anchor === null ||\n popup === null ||\n styles === undefined\n )\n return;\n const window = anchor.ownerDocument.defaultView;\n if (window === null) return;\n let frame = 0;\n let clipped = false;\n const update = () => {\n if (!signal.aborted) position(anchor, popup, styles, options, clipped);\n };\n const schedule = () => {\n if (signal.aborted || frame) return;\n frame = window.requestAnimationFrame(() => {\n frame = 0;\n checkAnchor();\n update();\n });\n };\n update();\n const observer = new window.ResizeObserver(schedule);\n observer.observe(popup);\n for (\n let node: Element | null = anchor;\n node !== null;\n node = node.parentElement\n ) {\n observer.observe(node);\n }\n const checkAnchor = observePopupAnchor(\n anchor,\n update,\n (hidden) => {\n clipped = hidden;\n update();\n },\n signal,\n options.trackAnchorAnimation ?? false,\n );\n const events = { signal, passive: true };\n anchor.ownerDocument.addEventListener(\n \"scroll\",\n (event) => {\n // Scrolling this popup changes its contents, not its placement. Nested\n // popup positioners still observe it as an ancestor scroll target.\n if (event.target !== popup) schedule();\n },\n { ...events, capture: true },\n );\n window.addEventListener(\"resize\", schedule, events);\n window.visualViewport?.addEventListener(\"resize\", schedule, events);\n window.visualViewport?.addEventListener(\"scroll\", schedule, events);\n signal.addEventListener(\n \"abort\",\n () => {\n observer.disconnect();\n window.cancelAnimationFrame(frame);\n },\n { once: true },\n );\n });\n return {\n anchor: () => anchorMixin(registry),\n popup: () => popupMixin(registry),\n };\n}\n\n/** Restore authored values before measuring; retain new inline styles from rerenders. */\nfunction ownStyles(node: HTMLElement, signal: AbortSignal) {\n const saved = new Map<\n string,\n { value: string; priority: string; written: string }\n >();\n function restore(key: string): void {\n const previous = saved.get(key);\n if (previous === undefined) return;\n if (\n node.style.getPropertyValue(key) === previous.written &&\n node.style.getPropertyPriority(key) === \"\"\n ) {\n if (previous.value)\n node.style.setProperty(key, previous.value, previous.priority);\n else node.style.removeProperty(key);\n }\n saved.delete(key);\n }\n function set(key: string, value: string): void {\n if (\n saved.get(key)?.written === value &&\n node.style.getPropertyValue(key) === value &&\n node.style.getPropertyPriority(key) === \"\"\n )\n return;\n restore(key);\n const previous = {\n value: node.style.getPropertyValue(key),\n priority: node.style.getPropertyPriority(key),\n written: value,\n };\n node.style.setProperty(key, value);\n previous.written = node.style.getPropertyValue(key);\n saved.set(key, previous);\n }\n signal.addEventListener(\n \"abort\",\n () => {\n for (const key of saved.keys()) restore(key);\n },\n { once: true },\n );\n return { set, restore };\n}\n\nfunction createPositionRegistry(changed: () => void) {\n const anchor = createPartSlot(changed);\n const popup = createPartSlot(changed);\n const registry: {\n anchor: typeof anchor;\n popup: typeof popup;\n bindPopup: typeof bindPopup;\n styles?: ReturnType<typeof ownStyles>;\n } = { anchor, popup, bindPopup };\n function bindPopup(node: HTMLElement, signal: AbortSignal): void {\n if (!popup.bind(node, signal)) return;\n const styles = ownStyles(node, signal);\n registry.styles = styles;\n const attributes = [\"data-side\", \"data-align\", \"data-anchor-hidden\"].map(\n (key) => [key, node.getAttribute(key)] as const,\n );\n signal.addEventListener(\n \"abort\",\n () => {\n for (const [key, value] of attributes) {\n if (value === null) node.removeAttribute(key);\n else node.setAttribute(key, value);\n }\n },\n { once: true },\n );\n // Prepare while closed too: showPopover() must not expose broken native\n // anchor coordinates and scroll focus there before our layout pass.\n for (const [key, value] of Object.entries({\n position: \"fixed\",\n \"position-area\": \"none\",\n \"position-try-fallbacks\": \"none\",\n top: \"4px\",\n left: \"4px\",\n right: \"auto\",\n bottom: \"auto\",\n \"margin-top\": \"0px\",\n \"margin-right\": \"0px\",\n \"margin-bottom\": \"0px\",\n \"margin-left\": \"0px\",\n \"overflow-x\": \"auto\",\n \"overflow-y\": \"auto\",\n \"box-sizing\": \"border-box\",\n }))\n styles.set(key, value);\n }\n return registry;\n}\n\nfunction position(\n anchor: HTMLElement,\n popup: HTMLElement,\n styles: ReturnType<typeof ownStyles>,\n options: PopupPositionOptions,\n clipped: boolean,\n): void {\n const window = anchor.ownerDocument.defaultView!;\n const viewport = window.visualViewport;\n const width =\n viewport?.width ?? anchor.ownerDocument.documentElement.clientWidth;\n const height =\n viewport?.height ?? anchor.ownerDocument.documentElement.clientHeight;\n const padding = Math.max(0, options.collisionPadding ?? 4);\n const px = Math.min(padding, width / 2);\n const py = Math.min(padding, height / 2);\n const left = (viewport?.offsetLeft ?? 0) + px;\n const top = (viewport?.offsetTop ?? 0) + py;\n const right = left + width - 2 * px;\n const bottom = top + height - 2 * py;\n const offset = options.sideOffset ?? 4;\n const rtl = window.getComputedStyle(anchor).direction === \"rtl\";\n const a = anchor.getBoundingClientRect();\n popup.toggleAttribute(\n \"data-anchor-hidden\",\n clipped ||\n a.width === 0 ||\n a.height === 0 ||\n a.right <= left - px ||\n a.left >= right + px ||\n a.bottom <= top - py ||\n a.top >= bottom + py,\n );\n const align = options.align ?? \"start\";\n let side = options.side ?? \"bottom\";\n if (side === \"inline-end\") side = rtl ? \"left\" : \"right\";\n if (side === \"inline-start\") side = rtl ? \"right\" : \"left\";\n const space = {\n top: clamp(a.top - top - offset, 0, bottom - top),\n bottom: clamp(bottom - a.bottom - offset, 0, bottom - top),\n left: clamp(a.left - left - offset, 0, right - left),\n right: clamp(right - a.right - offset, 0, right - left),\n };\n const opposite: Record<Side, Side> = {\n top: \"bottom\",\n bottom: \"top\",\n left: \"right\",\n right: \"left\",\n };\n styles.set(\"--anchor-width\", `${a.width}px`);\n styles.set(\"--anchor-height\", `${a.height}px`);\n styles.set(\"--available-width\", `${right - left}px`);\n styles.set(\"--available-height\", `${bottom - top}px`);\n const scrollTop = popup.scrollTop;\n const scrollLeft = popup.scrollLeft;\n styles.restore(\"max-width\");\n styles.restore(\"max-height\");\n const computed = window.getComputedStyle(popup);\n const maxWidth = computed.maxWidth;\n const maxHeight = computed.maxHeight;\n function constrain(w: number, h: number): void {\n styles.set(\"--available-width\", `${Math.max(0, w)}px`);\n styles.set(\"--available-height\", `${Math.max(0, h)}px`);\n styles.set(\n \"max-width\",\n maxWidth === \"none\" || !maxWidth\n ? `${Math.max(0, w)}px`\n : `min(${maxWidth}, ${Math.max(0, w)}px)`,\n );\n styles.set(\n \"max-height\",\n maxHeight === \"none\" || !maxHeight\n ? `${Math.max(0, h)}px`\n : `min(${maxHeight}, ${Math.max(0, h)}px)`,\n );\n }\n constrain(right - left, bottom - top);\n let box = popup.getBoundingClientRect();\n let vertical = side === \"top\" || side === \"bottom\";\n const size = vertical ? box.height : box.width;\n if (size > space[side] && space[opposite[side]] > space[side])\n side = opposite[side];\n // Keep the useful submenu fallback: stack vertically if neither horizontal\n // side has room, rather than squeeze a readable menu into a narrow column.\n if (!vertical && box.width > space[side]) {\n side =\n space.bottom >= box.height || space.bottom >= space.top\n ? \"bottom\"\n : \"top\";\n vertical = true;\n }\n constrain(\n vertical ? right - left : space[side],\n vertical ? space[side] : bottom - top,\n );\n // Metadata can affect CSS dimensions, so publish it before the final measure.\n const logical =\n options.side === \"inline-start\" || options.side === \"inline-end\";\n const renderedSide =\n logical && !vertical\n ? (side === \"left\") !== rtl\n ? \"inline-start\"\n : \"inline-end\"\n : side;\n popup.setAttribute(\"data-side\", renderedSide);\n popup.setAttribute(\"data-align\", align);\n box = popup.getBoundingClientRect();\n // Relaxing constraints for measurement can clamp scroll offsets. Restore\n // them against the final dimensions so keyboard and pointer scrolling stick.\n popup.scrollTop = scrollTop;\n popup.scrollLeft = scrollLeft;\n const start = vertical ? a.left : a.top;\n const end = vertical ? a.right : a.bottom;\n const crossSize = vertical ? box.width : box.height;\n const reversed = vertical && rtl;\n const fromEnd = (align === \"end\") !== reversed;\n let cross =\n align === \"center\"\n ? (start + end - crossSize) / 2\n : fromEnd\n ? end - crossSize\n : start;\n // Base UI offsets start toward the interior and end toward the interior's\n // opposite direction; center follows the cross axis (RTL-aware vertically).\n const alignSign = align === \"end\" ? -1 : 1;\n cross += (options.alignOffset ?? 0) * alignSign * (reversed ? -1 : 1);\n let x = vertical\n ? cross\n : side === \"left\"\n ? a.left - offset - box.width\n : a.right + offset;\n let y = vertical\n ? side === \"top\"\n ? a.top - offset - box.height\n : a.bottom + offset\n : cross;\n x = clamp(x, left, right - box.width);\n y = clamp(y, top, bottom - box.height);\n styles.set(\"left\", `${x}px`);\n styles.set(\"top\", `${y}px`);\n // Arrowless aligned popups grow from the aligned edge. Once shifted, aim\n // toward the anchor's center instead, matching Base UI's origin convention.\n const crossOrigin =\n align !== \"center\" && Math.abs((vertical ? x : y) - cross) <= 1\n ? fromEnd\n ? crossSize\n : 0\n : clamp((start + end) / 2 - (vertical ? x : y), 0, crossSize);\n const originX = vertical\n ? crossOrigin\n : (side === \"left\" ? a.left : a.right) - x;\n const originY = vertical\n ? (side === \"top\" ? a.top : a.bottom) - y\n : crossOrigin;\n styles.set(\"--transform-origin\", `${originX}px ${originY}px`);\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max));\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{o as e}from"./parts-I0jdefyp.js";import{i as t}from"./diagnostics-BkQV0MaT.js";import{t as n}from"./open-state-G79p9WiE.js";import{useBeforeLayout as r,useMemo as i,useStableEvent as a}from"@bgub/fig";function o(n,r){let i=e(n),a=e(n);function o(e){let n=e===`source`?a.items():i.items().filter(t=>t.value===e);return t(n,`${r} ${e}`),n.at(-1)?.node}function s(e,t){let n=o(`anchor`);n?.style.setProperty(`anchor-name`,t);let r=o(`popup`);if(r!==void 0){if(r.style.setProperty(`position-anchor`,t),typeof r.showPopover!=`function`){r.hidden=!e;return}if(e!==r.matches(`:popover-open`)){if(e){let e=o(`source`)??n;r.showPopover(e===void 0?void 0:{source:e})}else r.hidePopover()}}}return{anchor:()=>o(`anchor`),bindAnchor:(e,t)=>i.bind(e,t,`anchor`),bindPopup:(e,t)=>i.bind(e,t,`popup`),bindSource:(e,t)=>a.bind(e,t,`source`),popup:()=>o(`popup`),supported:()=>typeof o(`popup`)?.showPopover==`function`,sync:s}}function s(e){let t=e.newState;if(t===`open`)return!0;if(t===`closed`)return!1}function c(e){let t=n(e),o=i(()=>({requested:t.open,native:void 0}),[]),c=i(()=>new WeakSet,[]);r(()=>{e.open!==void 0&&(o.requested=t.open),t.open===o.requested&&(o.native=void 0)});let l=a((e,n,r)=>{let i=t.requestOpen(e,n,r);return i&&(o.requested=e),i}),u=a(e=>{t.setOpen(e)&&(o.requested=e)}),d=a(e=>{if(e.defaultPrevented)return;if(e.target!==null){if(e.type===`beforetoggle`)c.add(e.target);else if(c.has(e.target))return}let t=s(e);if(t!==void 0){if(t!==o.requested&&!l(t,e,void 0)){e.preventDefault();return}o.native=t}});return{open:t.open,requestOpen:l,setOpen:u,nativeToggle:d,getOpen:()=>o.native??t.open}}export{o as n,s as r,c as t};
2
+ //# sourceMappingURL=popup-state-BUYfC2nG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popup-state-BUYfC2nG.js","names":[],"sources":["../src/internal/anchored-popup.ts","../src/internal/popup-state.ts"],"sourcesContent":["import { assertSinglePart } from \"./diagnostics.ts\";\nimport { createPartCollection } from \"./registration.ts\";\n\n/** Native top-layer synchronization shared by anchored popup widgets. */\nexport function createAnchoredPopup(\n registrationChanged: () => void,\n name: string,\n) {\n const parts = createPartCollection<\"anchor\" | \"popup\">(registrationChanged);\n // The same host may own both roles. Collections key by host, so source\n // registrations must not replace the CSS anchor registration.\n const sources = createPartCollection<\"source\">(registrationChanged);\n\n function part(kind: \"anchor\" | \"popup\" | \"source\"): HTMLElement | undefined {\n const matches =\n kind === \"source\"\n ? sources.items()\n : parts.items().filter((entry) => entry.value === kind);\n assertSinglePart(matches, `${name} ${kind}`);\n return matches.at(-1)?.node;\n }\n\n function sync(open: boolean, anchorName: string): void {\n const anchor = part(\"anchor\");\n anchor?.style.setProperty(\"anchor-name\", anchorName);\n const popup = part(\"popup\");\n if (popup === undefined) return;\n popup.style.setProperty(\"position-anchor\", anchorName);\n if (typeof popup.showPopover !== \"function\") {\n popup.hidden = !open;\n return;\n }\n if (open === popup.matches(\":popover-open\")) return;\n // The source establishes the native popover tree even when a child popup\n // is mounted outside its parent popup (for example through a portal).\n if (open) {\n const source = part(\"source\") ?? anchor;\n popup.showPopover(source === undefined ? undefined : { source });\n } else popup.hidePopover();\n }\n\n return {\n anchor: () => part(\"anchor\"),\n bindAnchor: (node: HTMLElement, signal: AbortSignal) =>\n parts.bind(node, signal, \"anchor\"),\n bindPopup: (node: HTMLElement, signal: AbortSignal) =>\n parts.bind(node, signal, \"popup\"),\n bindSource: (node: HTMLElement, signal: AbortSignal) =>\n sources.bind(node, signal, \"source\"),\n popup: () => part(\"popup\"),\n supported: () => typeof part(\"popup\")?.showPopover === \"function\",\n sync,\n };\n}\n\nexport function toggledOpen(event: Event): boolean | undefined {\n const state = (event as Event & { newState?: unknown }).newState;\n if (state === \"open\") return true;\n if (state === \"closed\") return false;\n return undefined;\n}\n","import { useBeforeLayout, useMemo, useStableEvent } from \"@bgub/fig\";\nimport { toggledOpen } from \"./anchored-popup.ts\";\nimport { type OpenStateOptions, useOpenState } from \"./open-state.ts\";\n\n/**\n * Native popovers can dismiss during another widget's commit. Keep that\n * observation separate from rendered visibility and imperative intent, so a\n * stale layout effect cannot reopen a dismissed peer or reveal suspended UI.\n */\nexport function usePopupState(options: OpenStateOptions) {\n const state = useOpenState(options);\n const tracker = useMemo(\n () => ({ requested: state.open, native: undefined as boolean | undefined }),\n [],\n );\n const beforeToggleSources = useMemo(() => new WeakSet<EventTarget>(), []);\n useBeforeLayout(() => {\n if (options.open !== undefined) tracker.requested = state.open;\n if (state.open === tracker.requested) tracker.native = undefined;\n });\n const requestOpen = useStableEvent(\n (next: boolean, event: Event, trigger: Element | undefined) => {\n const accepted = state.requestOpen(next, event, trigger);\n if (accepted) tracker.requested = next;\n return accepted;\n },\n );\n const setOpen = useStableEvent((next: boolean) => {\n if (state.setOpen(next)) tracker.requested = next;\n });\n const nativeToggle = useStableEvent((event: Event) => {\n if (event.defaultPrevented) return;\n // beforetoggle is synchronous; toggle is a queued notification that can\n // arrive after a newer request. Once a host reports beforetoggle, its\n // later toggle notifications must not become fresh requests.\n if (event.target !== null) {\n if (event.type === \"beforetoggle\") beforeToggleSources.add(event.target);\n else if (beforeToggleSources.has(event.target)) return;\n }\n const next = toggledOpen(event);\n if (next === undefined) return;\n // Before/after notifications describe one transition, not two requests.\n if (next !== tracker.requested && !requestOpen(next, event, undefined)) {\n event.preventDefault();\n return;\n }\n tracker.native = next;\n });\n return {\n open: state.open,\n requestOpen,\n setOpen,\n nativeToggle,\n getOpen: () => tracker.native ?? state.open,\n };\n}\n"],"mappings":"gNAIA,SAAgB,EACd,EACA,EACA,CACA,IAAM,EAAQ,EAAyC,CAAmB,EAGpE,EAAU,EAA+B,CAAmB,EAElE,SAAS,EAAK,EAA8D,CAC1E,IAAM,EACJ,IAAS,SACL,EAAQ,MAAM,EACd,EAAM,MAAM,CAAC,CAAC,OAAQ,GAAU,EAAM,QAAU,CAAI,EAE1D,OADA,EAAiB,EAAS,GAAG,EAAK,GAAG,GAAM,EACpC,EAAQ,GAAG,EAAE,CAAC,EAAE,IACzB,CAEA,SAAS,EAAK,EAAe,EAA0B,CACrD,IAAM,EAAS,EAAK,QAAQ,EAC5B,GAAQ,MAAM,YAAY,cAAe,CAAU,EACnD,IAAM,EAAQ,EAAK,OAAO,EACtB,OAAU,IAAA,GAEd,IADA,EAAM,MAAM,YAAY,kBAAmB,CAAU,EACjD,OAAO,EAAM,aAAgB,WAAY,CAC3C,EAAM,OAAS,CAAC,EAChB,MACF,CACI,OAAS,EAAM,QAAQ,eAAe,EAG1C,IAAI,EAAM,CACR,IAAM,EAAS,EAAK,QAAQ,GAAK,EACjC,EAAM,YAAY,IAAW,IAAA,GAAY,IAAA,GAAY,CAAE,QAAO,CAAC,CACjE,MAAO,EAAM,YAAY,CAAA,CAPzB,CAQF,CAEA,MAAO,CACL,WAAc,EAAK,QAAQ,EAC3B,YAAa,EAAmB,IAC9B,EAAM,KAAK,EAAM,EAAQ,QAAQ,EACnC,WAAY,EAAmB,IAC7B,EAAM,KAAK,EAAM,EAAQ,OAAO,EAClC,YAAa,EAAmB,IAC9B,EAAQ,KAAK,EAAM,EAAQ,QAAQ,EACrC,UAAa,EAAK,OAAO,EACzB,cAAiB,OAAO,EAAK,OAAO,CAAC,EAAE,aAAgB,WACvD,MACF,CACF,CAEA,SAAgB,EAAY,EAAmC,CAC7D,IAAM,EAAS,EAAyC,SACxD,GAAI,IAAU,OAAQ,MAAO,GAC7B,GAAI,IAAU,SAAU,MAAO,EAEjC,CCnDA,SAAgB,EAAc,EAA2B,CACvD,IAAM,EAAQ,EAAa,CAAO,EAC5B,EAAU,OACP,CAAE,UAAW,EAAM,KAAM,OAAQ,IAAA,EAAiC,GACzE,CAAC,CACH,EACM,EAAsB,MAAc,IAAI,QAAwB,CAAC,CAAC,EACxE,MAAsB,CAChB,EAAQ,OAAS,IAAA,KAAW,EAAQ,UAAY,EAAM,MACtD,EAAM,OAAS,EAAQ,YAAW,EAAQ,OAAS,IAAA,GACzD,CAAC,EACD,IAAM,EAAc,GACjB,EAAe,EAAc,IAAiC,CAC7D,IAAM,EAAW,EAAM,YAAY,EAAM,EAAO,CAAO,EAEvD,OADI,IAAU,EAAQ,UAAY,GAC3B,CACT,CACF,EACM,EAAU,EAAgB,GAAkB,CAC5C,EAAM,QAAQ,CAAI,IAAG,EAAQ,UAAY,EAC/C,CAAC,EACK,EAAe,EAAgB,GAAiB,CACpD,GAAI,EAAM,iBAAkB,OAI5B,GAAI,EAAM,SAAW,KAAM,CACzB,GAAI,EAAM,OAAS,eAAgB,EAAoB,IAAI,EAAM,MAAM,OAClE,GAAI,EAAoB,IAAI,EAAM,MAAM,EAAG,MAClD,CACA,IAAM,EAAO,EAAY,CAAK,EAC1B,OAAS,IAAA,GAEb,IAAI,IAAS,EAAQ,WAAa,CAAC,EAAY,EAAM,EAAO,IAAA,EAAS,EAAG,CACtE,EAAM,eAAe,EACrB,MACF,CACA,EAAQ,OAAS,CADjB,CAEF,CAAC,EACD,MAAO,CACL,KAAM,EAAM,KACZ,cACA,UACA,eACA,YAAe,EAAQ,QAAU,EAAM,IACzC,CACF"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./changes-DglRuvQa.js";import{a as t,n}from"./parts-I0jdefyp.js";import{c as r,t as i}from"./diagnostics-BkQV0MaT.js";import{i as a,t as o}from"./composite-DoFLgUjR.js";import{n as s}from"./controllable-value-CDbRzlId.js";import{t as c}from"./form-reset-B7G3-0OT.js";import{createMixin as l,useBeforePaint as u,useId as d,useMemo as f,useStableEvent as p}from"@bgub/fig";import{on as m}from"@bgub/fig-dom";const h=l((e,t)=>({"aria-orientation":t.orientation,"aria-readonly":t.readOnly?`true`:void 0,bind:n(e,t.registry,(e,n)=>t.registry.bindContainer(e,n)),"data-disabled":t.disabled?``:void 0,"data-orientation":t.orientation,"data-readonly":t.readOnly?``:void 0,mix:[m(`click`,e=>{t.readOnly&&t.registry.itemAt(e.target)!==void 0&&e.preventDefault()}),m(`change`,e=>{let n=t.registry.itemAt(e.target);n!==void 0&&t.select(n.value,e,n.node)})],role:`radiogroup`})),g=l((e,t,i)=>{r(e,`radio`,`input`);let a=i.disabled||e.props.disabled===!0;return{bind:n(e,t.registry,(e,n)=>{t.registry.bindItem(e,n,i.value,a),t.bindFormReset(e,n)}),checked:i.checked,"data-checked":i.checked?``:void 0,"data-disabled":a?``:void 0,"data-readonly":t.readOnly?``:void 0,disabled:a?!0:void 0,name:e.props.name??t.name,required:t.required?!0:e.props.required,type:`radio`,value:e.props.value??String(i.value)}});function _(n={}){let{disabled:r=!1,orientation:l=`vertical`,readOnly:m=!1,required:_=!1}=n,v=t(),y=s({value:n.value,defaultValue:n.defaultValue??null,onChange:n.onValueChange,equal:a,reconcile:v}),b=y.value,x=f(()=>o({container:`[role="radiogroup"]`,item:`input[type="radio"]`,name:`radio group`}),[]);u(()=>{let e=x.containerNode();e!==null&&i(e,`radio group`)});let S=f(()=>c(y.reset),[]),C=p((t,n,i)=>{if(n.defaultPrevented||r||i.matches(`:disabled`)||m){v();return}y.request(()=>t,e(n,i))}),w=d(),T=n.name??`${w}-radio-group`,E={bindFormReset:S.bind,disabled:r,name:T,orientation:l,readOnly:m,registry:x,required:_,select:C};return{name:T,radio:(e,t={})=>g(E,{checked:b!==null&&a(e,b),disabled:r||t.disabled===!0,value:e}),root:()=>h(E),value:b}}function v(e){return e.children(_(e))}export{v as RadioGroup,_ as useRadioGroup};
2
+ //# sourceMappingURL=radio-group.js.map