@funstack/ui 0.1.0-alpha.3 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accordion/accordion-root.mjs +2 -1
- package/dist/accordion/accordion-root.mjs.map +1 -1
- package/dist/combobox/combobox-clear.mjs +2 -1
- package/dist/combobox/combobox-clear.mjs.map +1 -1
- package/dist/combobox/combobox-list.mjs +2 -1
- package/dist/combobox/combobox-list.mjs.map +1 -1
- package/dist/combobox/combobox-popup.d.mts.map +1 -1
- package/dist/combobox/combobox-popup.mjs +7 -2
- package/dist/combobox/combobox-popup.mjs.map +1 -1
- package/dist/combobox/combobox-root.mjs +1 -1
- package/dist/combobox/combobox-trigger.mjs +2 -1
- package/dist/combobox/combobox-trigger.mjs.map +1 -1
- package/dist/dialog/dialog-close.mjs +2 -1
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-form.d.mts +14 -8
- package/dist/dialog/dialog-form.d.mts.map +1 -1
- package/dist/dialog/dialog-form.mjs +36 -5
- package/dist/dialog/dialog-form.mjs.map +1 -1
- package/dist/dialog/dialog-popup.d.mts.map +1 -1
- package/dist/dialog/dialog-popup.mjs +13 -5
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.mjs +1 -1
- package/dist/dialog/dialog-trigger.mjs +2 -1
- package/dist/dialog/dialog-trigger.mjs.map +1 -1
- package/dist/field/field-root.mjs +5 -3
- package/dist/field/field-root.mjs.map +1 -1
- package/dist/form/form-root.mjs +2 -1
- package/dist/form/form-root.mjs.map +1 -1
- package/dist/form/form-submit.mjs +2 -1
- package/dist/form/form-submit.mjs.map +1 -1
- package/dist/index.d.mts +27 -1
- package/dist/index.mjs +6 -1
- package/dist/internal/announce.mjs +53 -0
- package/dist/internal/announce.mjs.map +1 -0
- package/dist/internal/decimal.mjs +27 -0
- package/dist/internal/decimal.mjs.map +1 -0
- package/dist/internal/dev-warn.mjs +17 -0
- package/dist/internal/dev-warn.mjs.map +1 -0
- package/dist/internal/field-context.mjs.map +1 -1
- package/dist/internal/range-value.mjs +51 -0
- package/dist/internal/range-value.mjs.map +1 -0
- package/dist/internal/render-element.mjs +54 -0
- package/dist/internal/render-element.mjs.map +1 -0
- package/dist/internal/toolbar-context.mjs +17 -0
- package/dist/internal/toolbar-context.mjs.map +1 -0
- package/dist/internal/use-exit-animation.mjs +26 -6
- package/dist/internal/use-exit-animation.mjs.map +1 -1
- package/dist/internal/use-field-control.mjs +5 -3
- package/dist/internal/use-field-control.mjs.map +1 -1
- package/dist/internal/use-popover-popup.mjs +3 -2
- package/dist/internal/use-popover-popup.mjs.map +1 -1
- package/dist/internal/use-popover-trigger.mjs +1 -1
- package/dist/internal/use-render.mjs +11 -34
- package/dist/internal/use-render.mjs.map +1 -1
- package/dist/internal/use-roving-focus.mjs +301 -0
- package/dist/internal/use-roving-focus.mjs.map +1 -0
- package/dist/menu/menu-checkbox-item.mjs +2 -1
- package/dist/menu/menu-checkbox-item.mjs.map +1 -1
- package/dist/menu/menu-item.mjs +2 -1
- package/dist/menu/menu-item.mjs.map +1 -1
- package/dist/menu/menu-popup.d.mts.map +1 -1
- package/dist/menu/menu-popup.mjs +7 -2
- package/dist/menu/menu-popup.mjs.map +1 -1
- package/dist/menu/menu-radio-item.mjs +2 -1
- package/dist/menu/menu-radio-item.mjs.map +1 -1
- package/dist/menu/menu-root.mjs +1 -1
- package/dist/menu/menu-trigger.d.mts +2 -1
- package/dist/menu/menu-trigger.d.mts.map +1 -1
- package/dist/menu/menu-trigger.mjs +15 -4
- package/dist/menu/menu-trigger.mjs.map +1 -1
- package/dist/meter/index.d.mts +24 -0
- package/dist/meter/index.d.mts.map +1 -0
- package/dist/meter/index.mjs +23 -0
- package/dist/meter/index.mjs.map +1 -0
- package/dist/meter/meter-context.mjs +13 -0
- package/dist/meter/meter-context.mjs.map +1 -0
- package/dist/meter/meter-indicator.d.mts +19 -0
- package/dist/meter/meter-indicator.d.mts.map +1 -0
- package/dist/meter/meter-indicator.mjs +25 -0
- package/dist/meter/meter-indicator.mjs.map +1 -0
- package/dist/meter/meter-label.d.mts +19 -0
- package/dist/meter/meter-label.d.mts.map +1 -0
- package/dist/meter/meter-label.mjs +28 -0
- package/dist/meter/meter-label.mjs.map +1 -0
- package/dist/meter/meter-region.d.mts +4 -0
- package/dist/meter/meter-region.d.mts.map +1 -0
- package/dist/meter/meter-region.mjs +39 -0
- package/dist/meter/meter-region.mjs.map +1 -0
- package/dist/meter/meter-root.d.mts +71 -0
- package/dist/meter/meter-root.d.mts.map +1 -0
- package/dist/meter/meter-root.mjs +83 -0
- package/dist/meter/meter-root.mjs.map +1 -0
- package/dist/meter/meter-track.d.mts +19 -0
- package/dist/meter/meter-track.d.mts.map +1 -0
- package/dist/meter/meter-track.mjs +25 -0
- package/dist/meter/meter-track.mjs.map +1 -0
- package/dist/meter/meter-value.d.mts +20 -0
- package/dist/meter/meter-value.d.mts.map +1 -0
- package/dist/meter/meter-value.mjs +29 -0
- package/dist/meter/meter-value.mjs.map +1 -0
- package/dist/number-field/index.d.mts +26 -0
- package/dist/number-field/index.d.mts.map +1 -0
- package/dist/number-field/index.mjs +26 -0
- package/dist/number-field/index.mjs.map +1 -0
- package/dist/number-field/number-field-arithmetic.mjs +78 -0
- package/dist/number-field/number-field-arithmetic.mjs.map +1 -0
- package/dist/number-field/number-field-context.d.mts +2 -0
- package/dist/number-field/number-field-context.mjs +13 -0
- package/dist/number-field/number-field-context.mjs.map +1 -0
- package/dist/number-field/number-field-decrement.d.mts +16 -0
- package/dist/number-field/number-field-decrement.d.mts.map +1 -0
- package/dist/number-field/number-field-decrement.mjs +19 -0
- package/dist/number-field/number-field-decrement.mjs.map +1 -0
- package/dist/number-field/number-field-format.mjs +279 -0
- package/dist/number-field/number-field-format.mjs.map +1 -0
- package/dist/number-field/number-field-increment.d.mts +32 -0
- package/dist/number-field/number-field-increment.d.mts.map +1 -0
- package/dist/number-field/number-field-increment.mjs +68 -0
- package/dist/number-field/number-field-increment.mjs.map +1 -0
- package/dist/number-field/number-field-input.d.mts +29 -0
- package/dist/number-field/number-field-input.d.mts.map +1 -0
- package/dist/number-field/number-field-input.mjs +163 -0
- package/dist/number-field/number-field-input.mjs.map +1 -0
- package/dist/number-field/number-field-label.d.mts +23 -0
- package/dist/number-field/number-field-label.d.mts.map +1 -0
- package/dist/number-field/number-field-label.mjs +47 -0
- package/dist/number-field/number-field-label.mjs.map +1 -0
- package/dist/number-field/number-field-root.d.mts +108 -0
- package/dist/number-field/number-field-root.d.mts.map +1 -0
- package/dist/number-field/number-field-root.mjs +523 -0
- package/dist/number-field/number-field-root.mjs.map +1 -0
- package/dist/number-field/use-step-button.mjs +107 -0
- package/dist/number-field/use-step-button.mjs.map +1 -0
- package/dist/popover/popover-close.mjs +2 -1
- package/dist/popover/popover-close.mjs.map +1 -1
- package/dist/popover/popover-popup.d.mts.map +1 -1
- package/dist/popover/popover-popup.mjs +7 -2
- package/dist/popover/popover-popup.mjs.map +1 -1
- package/dist/popover/popover-root.mjs +1 -1
- package/dist/progress/index.d.mts +24 -0
- package/dist/progress/index.d.mts.map +1 -0
- package/dist/progress/index.mjs +24 -0
- package/dist/progress/index.mjs.map +1 -0
- package/dist/progress/progress-context.mjs +13 -0
- package/dist/progress/progress-context.mjs.map +1 -0
- package/dist/progress/progress-indicator.d.mts +19 -0
- package/dist/progress/progress-indicator.d.mts.map +1 -0
- package/dist/progress/progress-indicator.mjs +29 -0
- package/dist/progress/progress-indicator.mjs.map +1 -0
- package/dist/progress/progress-label.d.mts +19 -0
- package/dist/progress/progress-label.d.mts.map +1 -0
- package/dist/progress/progress-label.mjs +30 -0
- package/dist/progress/progress-label.mjs.map +1 -0
- package/dist/progress/progress-root.d.mts +68 -0
- package/dist/progress/progress-root.d.mts.map +1 -0
- package/dist/progress/progress-root.mjs +80 -0
- package/dist/progress/progress-root.mjs.map +1 -0
- package/dist/progress/progress-track.d.mts +19 -0
- package/dist/progress/progress-track.d.mts.map +1 -0
- package/dist/progress/progress-track.mjs +29 -0
- package/dist/progress/progress-track.mjs.map +1 -0
- package/dist/progress/progress-value.d.mts +20 -0
- package/dist/progress/progress-value.d.mts.map +1 -0
- package/dist/progress/progress-value.mjs +33 -0
- package/dist/progress/progress-value.mjs.map +1 -0
- package/dist/slider/slider-arithmetic.mjs +1 -17
- package/dist/slider/slider-arithmetic.mjs.map +1 -1
- package/dist/slider/slider-root.mjs +3 -2
- package/dist/slider/slider-root.mjs.map +1 -1
- package/dist/slider/slider-thumb.mjs +2 -1
- package/dist/slider/slider-thumb.mjs.map +1 -1
- package/dist/slider/slider-track.mjs +2 -1
- package/dist/slider/slider-track.mjs.map +1 -1
- package/dist/tabs/tabs-indicator.mjs +2 -1
- package/dist/tabs/tabs-indicator.mjs.map +1 -1
- package/dist/tabs/tabs-tab.mjs +2 -1
- package/dist/tabs/tabs-tab.mjs.map +1 -1
- package/dist/toast/toast-action.mjs +2 -1
- package/dist/toast/toast-action.mjs.map +1 -1
- package/dist/toast/toast-close.mjs +2 -1
- package/dist/toast/toast-close.mjs.map +1 -1
- package/dist/toast/toast-root.mjs +3 -2
- package/dist/toast/toast-root.mjs.map +1 -1
- package/dist/toast/toast-viewport.d.mts.map +1 -1
- package/dist/toast/toast-viewport.mjs +30 -7
- package/dist/toast/toast-viewport.mjs.map +1 -1
- package/dist/toast/toast.d.mts.map +1 -1
- package/dist/toast/toast.mjs +5 -1
- package/dist/toast/toast.mjs.map +1 -1
- package/dist/toggle/toggle.mjs +2 -1
- package/dist/toggle/toggle.mjs.map +1 -1
- package/dist/toggle-group/index.d.mts +16 -0
- package/dist/toggle-group/index.d.mts.map +1 -0
- package/dist/toggle-group/index.mjs +15 -0
- package/dist/toggle-group/index.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-context.d.mts +4 -0
- package/dist/toggle-group/toggle-group-context.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-context.mjs +13 -0
- package/dist/toggle-group/toggle-group-context.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-item.d.mts +47 -0
- package/dist/toggle-group/toggle-group-item.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-item.mjs +63 -0
- package/dist/toggle-group/toggle-group-item.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-root.d.mts +95 -0
- package/dist/toggle-group/toggle-group-root.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-root.mjs +211 -0
- package/dist/toggle-group/toggle-group-root.mjs.map +1 -0
- package/dist/toolbar/index.d.mts +18 -0
- package/dist/toolbar/index.d.mts.map +1 -0
- package/dist/toolbar/index.mjs +18 -0
- package/dist/toolbar/index.mjs.map +1 -0
- package/dist/toolbar/toolbar-button.d.mts +42 -0
- package/dist/toolbar/toolbar-button.d.mts.map +1 -0
- package/dist/toolbar/toolbar-button.mjs +50 -0
- package/dist/toolbar/toolbar-button.mjs.map +1 -0
- package/dist/toolbar/toolbar-root.d.mts +38 -0
- package/dist/toolbar/toolbar-root.d.mts.map +1 -0
- package/dist/toolbar/toolbar-root.mjs +66 -0
- package/dist/toolbar/toolbar-root.mjs.map +1 -0
- package/dist/toolbar/toolbar-separator.d.mts +25 -0
- package/dist/toolbar/toolbar-separator.d.mts.map +1 -0
- package/dist/toolbar/toolbar-separator.mjs +31 -0
- package/dist/toolbar/toolbar-separator.mjs.map +1 -0
- package/dist/tooltip/tooltip-popup.mjs +2 -1
- package/dist/tooltip/tooltip-popup.mjs.map +1 -1
- package/dist/tooltip/tooltip-root.mjs +18 -2
- package/dist/tooltip/tooltip-root.mjs.map +1 -1
- package/dist/tooltip/tooltip-trigger.mjs +2 -1
- package/dist/tooltip/tooltip-trigger.mjs.map +1 -1
- package/package.json +6 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-item.mjs","names":[],"sources":["../../src/menu/menu-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\n/** Details passed to `Menu.Item`'s `action`. */\nexport interface MenuItemDetails extends ActionDetails {}\n\nexport interface MenuItemState {\n /** This item's `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuItemProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"onPointerMove\" | \"disabled\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this item's own transition (`data-pending` on the item); the menu stays open\n * until it settles, then the whole menu closes (`closeOnActivate`). May be a Server Function\n * (`details.event` is virtual). A rejection leaves the menu open and reaches the nearest Error\n * Boundary above the item. Without `action`, activation only closes.\n */\n action?: Action<[details: MenuItemDetails]>;\n /** Close the whole menu once `action` settles. Default `true`. */\n closeOnActivate?: boolean;\n /** Renders `aria-disabled`: the item stays focusable and never activates. */\n disabled?: boolean;\n /** Text typeahead matches when the item's text content is not representative (an icon). */\n label?: string;\n className?: StateValue<string | undefined, MenuItemState>;\n style?: StateValue<CSSProperties | undefined, MenuItemState>;\n children?: StateValue<ReactNode, MenuItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuItemState>;\n}\n\n/**\n * A menu command: `<button type=\"button\" role=\"menuitem\" tabindex=\"-1\">`. Click, Enter or Space\n * runs `action` in the item's own transition; once it resolves the whole menu stack closes\n * through the top-level Root (`openChangeAction(false, { reason: \"activate\" })` awaited, view\n * transition if set). Hovering the item focuses it.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `role`, `tabIndex`, `disabled`\n * (always absent), `aria-disabled`, `onClick`, `onKeyDown`, `onPointerMove`, `data-pending`,\n * `data-disabled`, `data-typeahead`.\n */\nexport function MenuItem({\n action,\n closeOnActivate = true,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuItemProps) {\n const menu = useMenuContext(\"Menu.Item\");\n // The close belongs to the top-level menu, so its open generation guards it.\n const { pending, run } = useActionTrigger(menu.root.popover.openGeneration);\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.Item\",\n role: \"menuitem\",\n disabled,\n label,\n activate: (event) => {\n const details: MenuItemDetails = createActionDetails(event, {});\n // Repeated activations re-run the action; the first close wins (design 2.3).\n run(\n () => action?.(details),\n closeOnActivate ? () => menu.root.popover.requestClose(event, \"activate\") : undefined,\n );\n },\n });\n\n const state: MenuItemState = { pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuItem.displayName = \"MenuItem\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-item.mjs","names":[],"sources":["../../src/menu/menu-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\n/** Details passed to `Menu.Item`'s `action`. */\nexport interface MenuItemDetails extends ActionDetails {}\n\nexport interface MenuItemState {\n /** This item's `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuItemProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"onPointerMove\" | \"disabled\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this item's own transition (`data-pending` on the item); the menu stays open\n * until it settles, then the whole menu closes (`closeOnActivate`). May be a Server Function\n * (`details.event` is virtual). A rejection leaves the menu open and reaches the nearest Error\n * Boundary above the item. Without `action`, activation only closes.\n */\n action?: Action<[details: MenuItemDetails]>;\n /** Close the whole menu once `action` settles. Default `true`. */\n closeOnActivate?: boolean;\n /** Renders `aria-disabled`: the item stays focusable and never activates. */\n disabled?: boolean;\n /** Text typeahead matches when the item's text content is not representative (an icon). */\n label?: string;\n className?: StateValue<string | undefined, MenuItemState>;\n style?: StateValue<CSSProperties | undefined, MenuItemState>;\n children?: StateValue<ReactNode, MenuItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuItemState>;\n}\n\n/**\n * A menu command: `<button type=\"button\" role=\"menuitem\" tabindex=\"-1\">`. Click, Enter or Space\n * runs `action` in the item's own transition; once it resolves the whole menu stack closes\n * through the top-level Root (`openChangeAction(false, { reason: \"activate\" })` awaited, view\n * transition if set). Hovering the item focuses it.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `role`, `tabIndex`, `disabled`\n * (always absent), `aria-disabled`, `onClick`, `onKeyDown`, `onPointerMove`, `data-pending`,\n * `data-disabled`, `data-typeahead`.\n */\nexport function MenuItem({\n action,\n closeOnActivate = true,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuItemProps) {\n const menu = useMenuContext(\"Menu.Item\");\n // The close belongs to the top-level menu, so its open generation guards it.\n const { pending, run } = useActionTrigger(menu.root.popover.openGeneration);\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.Item\",\n role: \"menuitem\",\n disabled,\n label,\n activate: (event) => {\n const details: MenuItemDetails = createActionDetails(event, {});\n // Repeated activations re-run the action; the first close wins (design 2.3).\n run(\n () => action?.(details),\n closeOnActivate ? () => menu.root.popover.requestClose(event, \"activate\") : undefined,\n );\n },\n });\n\n const state: MenuItemState = { pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuItem.displayName = \"MenuItem\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,QACA,kBAAkB,MAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACa;CAChB,MAAM,OAAO,eAAe,WAAW;CAEvC,MAAM,EAAE,SAAS,QAAQ,iBAAiB,KAAK,KAAK,QAAQ,cAAc;CAE1E,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,UAA2B,oBAAoB,OAAO,CAAC,CAAC;GAE9D,UACQ,SAAS,OAAO,GACtB,wBAAwB,KAAK,KAAK,QAAQ,aAAa,OAAO,UAAU,IAAI,KAAA,CAC9E;EACF;CACF,CAAC;CAID,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA;GAL6B;GAAS;EAKlC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,SAAS,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-popup.d.mts","names":[],"sources":["../../src/menu/menu-popup.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"menu-popup.d.mts","names":[],"sources":["../../src/menu/menu-popup.tsx"],"mappings":";;;;YAkCY,WAAW;YACX,YAAY;iBAEP;;WAEN;WACA,MAAM;WACN,OAAO;;WAEP;;iBAGM,uBAAuB,KACtC;;;;;;;EAuBA,OAAO;;EAEP,QAAQ;;;;;;EAMR;;;;;;;EAOA;;;;;;;EAOA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,8BAA8B;;;;;;;;;;;;;;;wBA4BpC,YACd,MAAM,UACN,OACA,aACA,yBACA,YACA,WACA,OACA,UACA,WACG,QACF,iBAAc;yBAXD"}
|
package/dist/menu/menu-popup.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
3
3
|
import { mergeRefs } from "../internal/merge-props.mjs";
|
|
4
|
-
import { resolveStateValue
|
|
4
|
+
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
5
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
6
|
+
import { ToolbarContext } from "../internal/toolbar-context.mjs";
|
|
5
7
|
import { POSITION_TRY_FALLBACKS, positionArea } from "../internal/anchor-position.mjs";
|
|
6
8
|
import { usePopoverPopup } from "../internal/use-popover-popup.mjs";
|
|
7
9
|
import { MenuListContext, useMenuContext } from "./menu-context.mjs";
|
|
@@ -180,7 +182,10 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
180
182
|
else node = visible ? wrapContent(resolveStateValue(children, s)) : null;
|
|
181
183
|
return /* @__PURE__ */ jsx(MenuListContext, {
|
|
182
184
|
value: listContext,
|
|
183
|
-
children:
|
|
185
|
+
children: /* @__PURE__ */ jsx(ToolbarContext, {
|
|
186
|
+
value: null,
|
|
187
|
+
children: node
|
|
188
|
+
})
|
|
184
189
|
});
|
|
185
190
|
};
|
|
186
191
|
const element = useRender({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-popup.mjs","names":[],"sources":["../../src/menu/menu-popup.tsx"],"sourcesContent":["\"use client\";\nimport { Activity, useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { POSITION_TRY_FALLBACKS, positionArea } from \"../internal/anchor-position.js\";\nimport type { AnchorAlign, AnchorSide } from \"../internal/anchor-position.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { MenuListContext, useMenuContext } from \"./menu-context.js\";\nimport type { MenuListContextValue } from \"./menu-context.js\";\nimport {\n edgeItem,\n focusItem,\n hasModifier,\n isMenuItem,\n isRtl,\n menuItems,\n typeaheadText,\n} from \"./menu-dom.js\";\nimport { scheduleHoverClose } from \"./menu-hover.js\";\n\nexport type MenuSide = AnchorSide;\nexport type MenuAlign = AnchorAlign;\n\nexport interface MenuPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n readonly side: MenuSide | null;\n readonly align: MenuAlign;\n /** This popup belongs to a submenu. */\n readonly submenu: boolean;\n}\n\nexport interface MenuPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"role\"\n | \"aria-labelledby\"\n | \"onKeyDown\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onMouseDown\"\n | \"onPointerEnter\"\n | \"onPointerLeave\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * Placement against the Trigger through inline `position-area`, with `position-try-fallbacks`\n * flipping it when it does not fit. Default `\"bottom\"` for a top-level menu, `\"right\"` for a\n * submenu (whose fallback is `flip-inline` only). `null` emits no inline positioning (and no\n * `data-side` / `data-align`): the stylesheet owns placement.\n */\n side?: MenuSide | null;\n /** Alignment along the side. Default `\"start\"`. Ignored when `side` is `null`. */\n align?: MenuAlign;\n /**\n * `false` (default): the items are not rendered while the menu is closed; mounting them is part\n * of the opening transition, so items that suspend hold the open (and the Trigger pending)\n * until they are ready. `true`: they stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Experimental (Chromium 150+). Renders `focusgroup=\"menu wrap|nowrap\"` and, where the engine\n * implements `focusGroup`, lets the browser handle ArrowUp / ArrowDown / Home / End: items then\n * render no `tabindex` and the library's arrow-key handling steps aside. Typeahead, activation,\n * submenu keys and Tab stay in JS. No effect in other engines. Default `false`.\n */\n experimental_focusgroup?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popup element. Animates the opening\n * commit and every library-driven close (an item's activation, the Trigger, ArrowLeft, hover\n * leaving a submenu, controlled `open={false}`) in all engines; dismissals (Escape, a click\n * outside, Tab) animate with CSS only. Without it the part renders no `<ViewTransition>`.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, MenuPopupState>;\n style?: StateValue<CSSProperties | undefined, MenuPopupState>;\n children?: StateValue<ReactNode, MenuPopupState>;\n /** Should render a `<div>`; a `<dialog>` would run the dialog focusing steps. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuPopupState>;\n}\n\n/** Engine capabilities never change while the page lives. */\nconst subscribeNever = () => () => {};\n\n/** Typeahead buffer lifetime after the last keystroke (design 5.1). */\nconst TYPEAHEAD_RESET_MS = 500;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** What a press may focus inside a popup: items, the popup itself, the consumer's controls. */\nconst FOCUSABLE_SELECTOR =\n 'a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * The menu itself: `<div popover=\"auto\" role=\"menu\" aria-labelledby={trigger} tabindex=\"-1\">`,\n * placed against its Trigger with CSS Anchor Positioning. Owns the keyboard model (design 5):\n * ArrowUp / ArrowDown / Home / End move focus between items (or the browser does, with\n * `experimental_focusgroup` where supported), printable characters run typeahead, ArrowLeft\n * closes a submenu. The top-level popup closes the whole menu at once when focus leaves it (Tab).\n * Style it with `:popover-open`; it emits `data-side` / `data-align` but no `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `role`, `aria-labelledby` (token-merged),\n * `tabIndex`, `focusgroup` (with the flag), `onKeyDown`, `onBlur`, `onMouseDown` (a press on\n * non-item space keeps focus on the current item), `onPointerEnter` / `onPointerLeave`, and\n * `data-side` / `data-align` unless `side` is `null`.\n */\nexport function MenuPopup({\n side: sideProp,\n align = \"start\",\n keepMounted,\n experimental_focusgroup = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuPopupProps) {\n const menu = useMenuContext(\"Menu.Popup\");\n const { popover, submenu, loop } = menu;\n const side = sideProp === undefined ? (submenu ? \"right\" : \"bottom\") : sideProp;\n\n // As Tabs.List (#43): detected only with the flag, never during SSR or hydration.\n const nativeFocusgroup = useSyncExternalStore(\n subscribeNever,\n () => experimental_focusgroup && \"focusGroup\" in HTMLElement.prototype,\n () => false,\n );\n\n const typeahead = useRef({ text: \"\", at: 0 });\n const typingAhead = useCallback(() => {\n const { text, at } = typeahead.current;\n return text !== \"\" && Date.now() - at <= TYPEAHEAD_RESET_MS;\n }, []);\n\n const { setElement, visible, wrapContent } = usePopoverPopup(popover, {\n part: \"Menu.Popup\",\n initialFocus: undefined,\n // Design 6.1: the first (or last) enabled item by open cause, nothing for a hover open, the\n // popup itself when there are no items.\n // A pointer open focuses without the focus ring where the engine can be told so (H1).\n resolveInitialFocus: (popup) => {\n const { cause, pointer } = menu.takeOpenCause();\n if (cause === \"none\") return null;\n const element = edgeItem(popup, cause) ?? popup;\n return pointer ? { element, focusVisible: false } : element;\n },\n keepMounted,\n transition,\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName === \"DIALOG\") {\n console.error(\n \"Menu.Popup: rendered a <dialog>. Render a <div>: a <dialog popover> runs the dialog \" +\n \"focusing steps, which fight the menu's item focus.\",\n );\n }\n return () => {};\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const listContext = useMemo<MenuListContextValue>(\n () => ({ menu, nativeFocusgroup, typingAhead }),\n [menu, nativeFocusgroup, typingAhead],\n );\n\n const state: MenuPopupState = { open: popover.committed, side, align, submenu };\n\n const runTypeahead = (popup: HTMLElement, current: HTMLElement | null, key: string): boolean => {\n const now = Date.now();\n const buffer = typeahead.current;\n if (buffer.text === \"\" || now - buffer.at > TYPEAHEAD_RESET_MS) buffer.text = \"\";\n if (key === \" \" && buffer.text === \"\") return false;\n buffer.text += key.toLowerCase();\n buffer.at = now;\n const items = menuItems(popup);\n if (items.length === 0) return true;\n // A repeated single character cycles through the items starting with it; a longer buffer\n // keeps the current item while it still matches (design 5.1).\n const repeated = [...buffer.text].every((c) => c === buffer.text[0]);\n const search = repeated ? buffer.text[0]! : buffer.text;\n const index = current === null ? -1 : items.indexOf(current);\n const start = repeated || index < 0 ? index + 1 : index;\n for (let i = 0; i < items.length; i++) {\n const item = items[(start + i) % items.length]!;\n if (typeaheadText(item).startsWith(search)) {\n if (item !== current) focusItem(item);\n break;\n }\n }\n return true;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || hasModifier(event)) return;\n const popup = event.currentTarget;\n const target = event.target as HTMLElement;\n const onItem = isMenuItem(target, popup);\n if (!onItem && target !== popup) return;\n const key = event.key;\n\n // ArrowLeft (ArrowRight in RTL) closes a submenu; focus returns to its SubmenuTrigger\n // through the popover hook's nested focus rule (design 6.3).\n if (submenu && key === (isRtl(popup) ? \"ArrowRight\" : \"ArrowLeft\")) {\n event.preventDefault();\n popover.requestClose(event.nativeEvent, \"activate\");\n return;\n }\n\n if (key === \"ArrowDown\" || key === \"ArrowUp\" || key === \"Home\" || key === \"End\") {\n if (!onItem) {\n // The popup itself has focus (no items at open, or focus landed on it some other way):\n // into the list from its edge (review M1).\n event.preventDefault();\n const items = menuItems(popup);\n const item = key === \"ArrowDown\" || key === \"Home\" ? items[0] : items.at(-1);\n if (item !== undefined) focusItem(item);\n return;\n }\n // The browser moves focus itself where `focusgroup` is implemented.\n if (nativeFocusgroup) return;\n event.preventDefault();\n const items = menuItems(popup);\n const index = items.indexOf(target);\n const last = items.length - 1;\n let next: number;\n if (key === \"Home\") next = 0;\n else if (key === \"End\") next = last;\n else if (key === \"ArrowDown\") next = index >= last ? (loop ? 0 : last) : index + 1;\n else next = index <= 0 ? (loop ? last : 0) : index - 1;\n const item = items[next];\n if (item !== undefined && item !== target) focusItem(item);\n return;\n }\n\n if (key.length === 1 && runTypeahead(popup, onItem ? target : null, key)) {\n event.preventDefault();\n }\n };\n\n // Tab / Shift+Tab or any focus move out of the tree closes the whole stack at once (design\n // 2.2): submenus are DOM descendants of the top-level popup, the Trigger is outside on purpose.\n // A null `relatedTarget` (window blur, a removed element) closes nothing.\n const onBlur = (event: FocusEvent<HTMLElement>) => {\n if (submenu) return;\n const popup = event.currentTarget;\n const next = event.relatedTarget;\n if (!(next instanceof Node) || popup.contains(next)) return;\n if (popup.matches(\":popover-open\")) popup.hidePopover();\n };\n\n // A press on non-item space (padding, a Separator, a GroupLabel) would focus the popup itself\n // (`tabindex=\"-1\"`) and take focus off the current item; keep it where it is (review M1, the\n // pointer-focus model of design 6.2). Focusable content of the consumer's (a text field) still\n // takes focus.\n const onMouseDown = (event: MouseEvent<HTMLElement>) => {\n if (event.button !== 0) return;\n const target = event.target as Element;\n const focusable = target.closest(FOCUSABLE_SELECTOR);\n if (focusable === null || focusable.getAttribute(\"role\") === \"menu\") event.preventDefault();\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n role: \"menu\",\n \"aria-labelledby\": menu.triggerId,\n // The popup itself takes focus as the last resort when a menu has no items.\n tabIndex: -1,\n ...(experimental_focusgroup && { focusgroup: `menu ${loop ? \"wrap\" : \"nowrap\"}` }),\n onKeyDown,\n onBlur,\n onMouseDown,\n ...(submenu && {\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") menu.timers.cancelClose();\n },\n onPointerLeave: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") scheduleHoverClose(menu, event.nativeEvent);\n },\n }),\n ...(side !== null && { \"data-side\": side, \"data-align\": align }),\n };\n\n const content = (s: MenuPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n return <MenuListContext value={listContext}>{node}</MenuListContext>;\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The inline placement sits under the consumer's style: the consumer wins.\n style:\n side === null\n ? style\n : (s) => ({\n positionArea: positionArea(side, align),\n positionTryFallbacks: submenu ? \"flip-inline\" : POSITION_TRY_FALLBACKS,\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Menu.Popup\", transition, element);\n}\n\nMenuPopup.displayName = \"MenuPopup\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu-popup.mjs","names":[],"sources":["../../src/menu/menu-popup.tsx"],"sourcesContent":["\"use client\";\nimport { Activity, useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { POSITION_TRY_FALLBACKS, positionArea } from \"../internal/anchor-position.js\";\nimport type { AnchorAlign, AnchorSide } from \"../internal/anchor-position.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { ToolbarContext } from \"../internal/toolbar-context.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { MenuListContext, useMenuContext } from \"./menu-context.js\";\nimport type { MenuListContextValue } from \"./menu-context.js\";\nimport {\n edgeItem,\n focusItem,\n hasModifier,\n isMenuItem,\n isRtl,\n menuItems,\n typeaheadText,\n} from \"./menu-dom.js\";\nimport { scheduleHoverClose } from \"./menu-hover.js\";\n\nexport type MenuSide = AnchorSide;\nexport type MenuAlign = AnchorAlign;\n\nexport interface MenuPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n readonly side: MenuSide | null;\n readonly align: MenuAlign;\n /** This popup belongs to a submenu. */\n readonly submenu: boolean;\n}\n\nexport interface MenuPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"role\"\n | \"aria-labelledby\"\n | \"onKeyDown\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onMouseDown\"\n | \"onPointerEnter\"\n | \"onPointerLeave\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * Placement against the Trigger through inline `position-area`, with `position-try-fallbacks`\n * flipping it when it does not fit. Default `\"bottom\"` for a top-level menu, `\"right\"` for a\n * submenu (whose fallback is `flip-inline` only). `null` emits no inline positioning (and no\n * `data-side` / `data-align`): the stylesheet owns placement.\n */\n side?: MenuSide | null;\n /** Alignment along the side. Default `\"start\"`. Ignored when `side` is `null`. */\n align?: MenuAlign;\n /**\n * `false` (default): the items are not rendered while the menu is closed; mounting them is part\n * of the opening transition, so items that suspend hold the open (and the Trigger pending)\n * until they are ready. `true`: they stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Experimental (Chromium 150+). Renders `focusgroup=\"menu wrap|nowrap\"` and, where the engine\n * implements `focusGroup`, lets the browser handle ArrowUp / ArrowDown / Home / End: items then\n * render no `tabindex` and the library's arrow-key handling steps aside. Typeahead, activation,\n * submenu keys and Tab stay in JS. No effect in other engines. Default `false`.\n */\n experimental_focusgroup?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popup element. Animates the opening\n * commit and every library-driven close (an item's activation, the Trigger, ArrowLeft, hover\n * leaving a submenu, controlled `open={false}`) in all engines; dismissals (Escape, a click\n * outside, Tab) animate with CSS only. Without it the part renders no `<ViewTransition>`.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, MenuPopupState>;\n style?: StateValue<CSSProperties | undefined, MenuPopupState>;\n children?: StateValue<ReactNode, MenuPopupState>;\n /** Should render a `<div>`; a `<dialog>` would run the dialog focusing steps. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuPopupState>;\n}\n\n/** Engine capabilities never change while the page lives. */\nconst subscribeNever = () => () => {};\n\n/** Typeahead buffer lifetime after the last keystroke (design 5.1). */\nconst TYPEAHEAD_RESET_MS = 500;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** What a press may focus inside a popup: items, the popup itself, the consumer's controls. */\nconst FOCUSABLE_SELECTOR =\n 'a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * The menu itself: `<div popover=\"auto\" role=\"menu\" aria-labelledby={trigger} tabindex=\"-1\">`,\n * placed against its Trigger with CSS Anchor Positioning. Owns the keyboard model (design 5):\n * ArrowUp / ArrowDown / Home / End move focus between items (or the browser does, with\n * `experimental_focusgroup` where supported), printable characters run typeahead, ArrowLeft\n * closes a submenu. The top-level popup closes the whole menu at once when focus leaves it (Tab).\n * Style it with `:popover-open`; it emits `data-side` / `data-align` but no `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `role`, `aria-labelledby` (token-merged),\n * `tabIndex`, `focusgroup` (with the flag), `onKeyDown`, `onBlur`, `onMouseDown` (a press on\n * non-item space keeps focus on the current item), `onPointerEnter` / `onPointerLeave`, and\n * `data-side` / `data-align` unless `side` is `null`.\n */\nexport function MenuPopup({\n side: sideProp,\n align = \"start\",\n keepMounted,\n experimental_focusgroup = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuPopupProps) {\n const menu = useMenuContext(\"Menu.Popup\");\n const { popover, submenu, loop } = menu;\n const side = sideProp === undefined ? (submenu ? \"right\" : \"bottom\") : sideProp;\n\n // As Tabs.List (#43): detected only with the flag, never during SSR or hydration.\n const nativeFocusgroup = useSyncExternalStore(\n subscribeNever,\n () => experimental_focusgroup && \"focusGroup\" in HTMLElement.prototype,\n () => false,\n );\n\n const typeahead = useRef({ text: \"\", at: 0 });\n const typingAhead = useCallback(() => {\n const { text, at } = typeahead.current;\n return text !== \"\" && Date.now() - at <= TYPEAHEAD_RESET_MS;\n }, []);\n\n const { setElement, visible, wrapContent } = usePopoverPopup(popover, {\n part: \"Menu.Popup\",\n initialFocus: undefined,\n // Design 6.1: the first (or last) enabled item by open cause, nothing for a hover open, the\n // popup itself when there are no items.\n // A pointer open focuses without the focus ring where the engine can be told so (H1).\n resolveInitialFocus: (popup) => {\n const { cause, pointer } = menu.takeOpenCause();\n if (cause === \"none\") return null;\n const element = edgeItem(popup, cause) ?? popup;\n return pointer ? { element, focusVisible: false } : element;\n },\n keepMounted,\n transition,\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName === \"DIALOG\") {\n console.error(\n \"Menu.Popup: rendered a <dialog>. Render a <div>: a <dialog popover> runs the dialog \" +\n \"focusing steps, which fight the menu's item focus.\",\n );\n }\n return () => {};\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const listContext = useMemo<MenuListContextValue>(\n () => ({ menu, nativeFocusgroup, typingAhead }),\n [menu, nativeFocusgroup, typingAhead],\n );\n\n const state: MenuPopupState = { open: popover.committed, side, align, submenu };\n\n const runTypeahead = (popup: HTMLElement, current: HTMLElement | null, key: string): boolean => {\n const now = Date.now();\n const buffer = typeahead.current;\n if (buffer.text === \"\" || now - buffer.at > TYPEAHEAD_RESET_MS) buffer.text = \"\";\n if (key === \" \" && buffer.text === \"\") return false;\n buffer.text += key.toLowerCase();\n buffer.at = now;\n const items = menuItems(popup);\n if (items.length === 0) return true;\n // A repeated single character cycles through the items starting with it; a longer buffer\n // keeps the current item while it still matches (design 5.1).\n const repeated = [...buffer.text].every((c) => c === buffer.text[0]);\n const search = repeated ? buffer.text[0]! : buffer.text;\n const index = current === null ? -1 : items.indexOf(current);\n const start = repeated || index < 0 ? index + 1 : index;\n for (let i = 0; i < items.length; i++) {\n const item = items[(start + i) % items.length]!;\n if (typeaheadText(item).startsWith(search)) {\n if (item !== current) focusItem(item);\n break;\n }\n }\n return true;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || hasModifier(event)) return;\n const popup = event.currentTarget;\n const target = event.target as HTMLElement;\n const onItem = isMenuItem(target, popup);\n if (!onItem && target !== popup) return;\n const key = event.key;\n\n // ArrowLeft (ArrowRight in RTL) closes a submenu; focus returns to its SubmenuTrigger\n // through the popover hook's nested focus rule (design 6.3).\n if (submenu && key === (isRtl(popup) ? \"ArrowRight\" : \"ArrowLeft\")) {\n event.preventDefault();\n popover.requestClose(event.nativeEvent, \"activate\");\n return;\n }\n\n if (key === \"ArrowDown\" || key === \"ArrowUp\" || key === \"Home\" || key === \"End\") {\n if (!onItem) {\n // The popup itself has focus (no items at open, or focus landed on it some other way):\n // into the list from its edge (review M1).\n event.preventDefault();\n const items = menuItems(popup);\n const item = key === \"ArrowDown\" || key === \"Home\" ? items[0] : items.at(-1);\n if (item !== undefined) focusItem(item);\n return;\n }\n // The browser moves focus itself where `focusgroup` is implemented.\n if (nativeFocusgroup) return;\n event.preventDefault();\n const items = menuItems(popup);\n const index = items.indexOf(target);\n const last = items.length - 1;\n let next: number;\n if (key === \"Home\") next = 0;\n else if (key === \"End\") next = last;\n else if (key === \"ArrowDown\") next = index >= last ? (loop ? 0 : last) : index + 1;\n else next = index <= 0 ? (loop ? last : 0) : index - 1;\n const item = items[next];\n if (item !== undefined && item !== target) focusItem(item);\n return;\n }\n\n if (key.length === 1 && runTypeahead(popup, onItem ? target : null, key)) {\n event.preventDefault();\n }\n };\n\n // Tab / Shift+Tab or any focus move out of the tree closes the whole stack at once (design\n // 2.2): submenus are DOM descendants of the top-level popup, the Trigger is outside on purpose.\n // A null `relatedTarget` (window blur, a removed element) closes nothing.\n const onBlur = (event: FocusEvent<HTMLElement>) => {\n if (submenu) return;\n const popup = event.currentTarget;\n const next = event.relatedTarget;\n if (!(next instanceof Node) || popup.contains(next)) return;\n if (popup.matches(\":popover-open\")) popup.hidePopover();\n };\n\n // A press on non-item space (padding, a Separator, a GroupLabel) would focus the popup itself\n // (`tabindex=\"-1\"`) and take focus off the current item; keep it where it is (review M1, the\n // pointer-focus model of design 6.2). Focusable content of the consumer's (a text field) still\n // takes focus.\n const onMouseDown = (event: MouseEvent<HTMLElement>) => {\n if (event.button !== 0) return;\n const target = event.target as Element;\n const focusable = target.closest(FOCUSABLE_SELECTOR);\n if (focusable === null || focusable.getAttribute(\"role\") === \"menu\") event.preventDefault();\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n role: \"menu\",\n \"aria-labelledby\": menu.triggerId,\n // The popup itself takes focus as the last resort when a menu has no items.\n tabIndex: -1,\n ...(experimental_focusgroup && { focusgroup: `menu ${loop ? \"wrap\" : \"nowrap\"}` }),\n onKeyDown,\n onBlur,\n onMouseDown,\n ...(submenu && {\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") menu.timers.cancelClose();\n },\n onPointerLeave: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") scheduleHoverClose(menu, event.nativeEvent);\n },\n }),\n ...(side !== null && { \"data-side\": side, \"data-align\": align }),\n };\n\n const content = (s: MenuPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n // Not part of an enclosing Toolbar: a Menu.Trigger or ToggleGroup in here sees no Toolbar.\n return (\n <MenuListContext value={listContext}>\n <ToolbarContext value={null}>{node}</ToolbarContext>\n </MenuListContext>\n );\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The inline placement sits under the consumer's style: the consumer wins.\n style:\n side === null\n ? style\n : (s) => ({\n positionArea: positionArea(side, align),\n positionTryFallbacks: submenu ? \"flip-inline\" : POSITION_TRY_FALLBACKS,\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Menu.Popup\", transition, element);\n}\n\nMenuPopup.displayName = \"MenuPopup\";\n"],"mappings":";;;;;;;;;;;;;;;AAqGA,MAAM,6BAA6B,CAAC;;AAGpC,MAAM,qBAAqB;AAE3B,MAAM,QAAQ,QAAQ,IAAI,aAAa;;AAGvC,MAAM,qBACJ;;;;;;;;;;;;;;AAeF,SAAgB,UAAU,EACxB,MAAM,UACN,QAAQ,SACR,aACA,0BAA0B,OAC1B,YACA,WACA,OACA,UACA,QACA,GAAG,QACc;CACjB,MAAM,OAAO,eAAe,YAAY;CACxC,MAAM,EAAE,SAAS,SAAS,SAAS;CACnC,MAAM,OAAO,aAAa,KAAA,IAAa,UAAU,UAAU,WAAY;CAGvE,MAAM,mBAAmB,qBACvB,sBACM,2BAA2B,gBAAgB,YAAY,iBACvD,KACR;CAEA,MAAM,YAAY,OAAO;EAAE,MAAM;EAAI,IAAI;CAAE,CAAC;CAC5C,MAAM,cAAc,kBAAkB;EACpC,MAAM,EAAE,MAAM,OAAO,UAAU;EAC/B,OAAO,SAAS,MAAM,KAAK,IAAI,IAAI,MAAM;CAC3C,GAAG,CAAC,CAAC;CAEL,MAAM,EAAE,YAAY,SAAS,gBAAgB,gBAAgB,SAAS;EACpE,MAAM;EACN,cAAc,KAAA;EAId,sBAAsB,UAAU;GAC9B,MAAM,EAAE,OAAO,YAAY,KAAK,cAAc;GAC9C,IAAI,UAAU,QAAQ,OAAO;GAC7B,MAAM,UAAU,SAAS,OAAO,KAAK,KAAK;GAC1C,OAAO,UAAU;IAAE;IAAS,cAAc;GAAM,IAAI;EACtD;EACA;EACA;CACF,CAAC;CAED,MAAM,SAAS,aAAa,YAAyB;EACnD,IAAI,SAAS,QAAQ,YAAY,UAC/B,QAAQ,MACN,wIAEF;EAEF,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CACL,MAAM,MAAM,cAAc,UAAU,YAAY,MAAM,GAAG,CAAC,YAAY,MAAM,CAAC;CAE7E,MAAM,cAAc,eACX;EAAE;EAAM;EAAkB;CAAY,IAC7C;EAAC;EAAM;EAAkB;CAAW,CACtC;CAEA,MAAM,QAAwB;EAAE,MAAM,QAAQ;EAAW;EAAM;EAAO;CAAQ;CAE9E,MAAM,gBAAgB,OAAoB,SAA6B,QAAyB;EAC9F,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,SAAS,UAAU;EACzB,IAAI,OAAO,SAAS,MAAM,MAAM,OAAO,KAAK,oBAAoB,OAAO,OAAO;EAC9E,IAAI,QAAQ,OAAO,OAAO,SAAS,IAAI,OAAO;EAC9C,OAAO,QAAQ,IAAI,YAAY;EAC/B,OAAO,KAAK;EACZ,MAAM,QAAQ,UAAU,KAAK;EAC7B,IAAI,MAAM,WAAW,GAAG,OAAO;EAG/B,MAAM,WAAW,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,OAAO,MAAM,MAAM,OAAO,KAAK,EAAE;EACnE,MAAM,SAAS,WAAW,OAAO,KAAK,KAAM,OAAO;EACnD,MAAM,QAAQ,YAAY,OAAO,KAAK,MAAM,QAAQ,OAAO;EAC3D,MAAM,QAAQ,YAAY,QAAQ,IAAI,QAAQ,IAAI;EAClD,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,OAAO,OAAO,QAAQ,KAAK,MAAM;GACvC,IAAI,cAAc,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG;IAC1C,IAAI,SAAS,SAAS,UAAU,IAAI;IACpC;GACF;EACF;EACA,OAAO;CACT;CAEA,MAAM,aAAa,UAAsC;EACvD,IAAI,MAAM,oBAAoB,YAAY,KAAK,GAAG;EAClD,MAAM,QAAQ,MAAM;EACpB,MAAM,SAAS,MAAM;EACrB,MAAM,SAAS,WAAW,QAAQ,KAAK;EACvC,IAAI,CAAC,UAAU,WAAW,OAAO;EACjC,MAAM,MAAM,MAAM;EAIlB,IAAI,WAAW,SAAS,MAAM,KAAK,IAAI,eAAe,cAAc;GAClE,MAAM,eAAe;GACrB,QAAQ,aAAa,MAAM,aAAa,UAAU;GAClD;EACF;EAEA,IAAI,QAAQ,eAAe,QAAQ,aAAa,QAAQ,UAAU,QAAQ,OAAO;GAC/E,IAAI,CAAC,QAAQ;IAGX,MAAM,eAAe;IACrB,MAAM,QAAQ,UAAU,KAAK;IAC7B,MAAM,OAAO,QAAQ,eAAe,QAAQ,SAAS,MAAM,KAAK,MAAM,GAAG,EAAE;IAC3E,IAAI,SAAS,KAAA,GAAW,UAAU,IAAI;IACtC;GACF;GAEA,IAAI,kBAAkB;GACtB,MAAM,eAAe;GACrB,MAAM,QAAQ,UAAU,KAAK;GAC7B,MAAM,QAAQ,MAAM,QAAQ,MAAM;GAClC,MAAM,OAAO,MAAM,SAAS;GAC5B,IAAI;GACJ,IAAI,QAAQ,QAAQ,OAAO;QACtB,IAAI,QAAQ,OAAO,OAAO;QAC1B,IAAI,QAAQ,aAAa,OAAO,SAAS,OAAQ,OAAO,IAAI,OAAQ,QAAQ;QAC5E,OAAO,SAAS,IAAK,OAAO,OAAO,IAAK,QAAQ;GACrD,MAAM,OAAO,MAAM;GACnB,IAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,UAAU,IAAI;GACzD;EACF;EAEA,IAAI,IAAI,WAAW,KAAK,aAAa,OAAO,SAAS,SAAS,MAAM,GAAG,GACrE,MAAM,eAAe;CAEzB;CAKA,MAAM,UAAU,UAAmC;EACjD,IAAI,SAAS;EACb,MAAM,QAAQ,MAAM;EACpB,MAAM,OAAO,MAAM;EACnB,IAAI,EAAE,gBAAgB,SAAS,MAAM,SAAS,IAAI,GAAG;EACrD,IAAI,MAAM,QAAQ,eAAe,GAAG,MAAM,YAAY;CACxD;CAMA,MAAM,eAAe,UAAmC;EACtD,IAAI,MAAM,WAAW,GAAG;EAExB,MAAM,YADS,MAAM,OACI,QAAQ,kBAAkB;EACnD,IAAI,cAAc,QAAQ,UAAU,aAAa,MAAM,MAAM,QAAQ,MAAM,eAAe;CAC5F;CAEA,MAAM,eAA8D;EAClE,IAAI,QAAQ,IAAI;EAChB,SAAS;EACT,MAAM;EACN,mBAAmB,KAAK;EAExB,UAAU;EACV,GAAI,2BAA2B,EAAE,YAAY,QAAQ,OAAO,SAAS,WAAW;EAChF;EACA;EACA;EACA,GAAI,WAAW;GACb,iBAAiB,UAAqC;IACpD,IAAI,MAAM,gBAAgB,SAAS,KAAK,OAAO,YAAY;GAC7D;GACA,iBAAiB,UAAqC;IACpD,IAAI,MAAM,gBAAgB,SAAS,mBAAmB,MAAM,MAAM,WAAW;GAC/E;EACF;EACA,GAAI,SAAS,QAAQ;GAAE,aAAa;GAAM,cAAc;EAAM;CAChE;CAEA,MAAM,WAAW,MAAiC;EAChD,IAAI;EACJ,IAAI,gBAAgB,MAClB,OACE,oBAAC,UAAD;GAAU,MAAM,UAAU,YAAY;GACnC,UAAA,YAAY,kBAAkB,UAAU,CAAC,CAAC;EACnC,CAAA;OAGZ,OAAO,UAAU,YAAY,kBAAkB,UAAU,CAAC,CAAC,IAAI;EAGjE,OACE,oBAAC,iBAAD;GAAiB,OAAO;GACtB,UAAA,oBAAC,gBAAD;IAAgB,OAAO;IAAO,UAAA;GAAqB,CAAA;EACpC,CAAA;CAErB;CAEA,MAAM,UAAU,UAAwD;EACtE,gBAAgB;EAChB;EACA;EACA;EAEA,OACE,SAAS,OACL,SACC,OAAO;GACN,cAAc,aAAa,MAAM,KAAK;GACtC,sBAAsB,UAAU,gBAAgB;GAChD,GAAG,kBAAkB,OAAO,CAAC;EAC/B;EACN,UAAU;EACV,OAAO;EACP;EACA;CACF,CAAC;CAED,OAAO,sBAAsB,cAAc,YAAY,OAAO;AAChE;AAEA,UAAU,cAAc"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
|
-
import { rendersButton
|
|
3
|
+
import { rendersButton } from "../internal/render-element.mjs";
|
|
4
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
4
5
|
import { MenuRadioGroupContext, closeStackLater, useMenuContext } from "./menu-context.mjs";
|
|
5
6
|
import { useMenuItem } from "./use-menu-item.mjs";
|
|
6
7
|
import { use } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-radio-item.mjs","names":[],"sources":["../../src/menu/menu-radio-item.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { MenuCheckboxItemProps } from \"./menu-checkbox-item.js\";\nimport { closeStackLater, MenuRadioGroupContext, useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\nexport interface MenuRadioItemState {\n /** This item is the group's optimistic value (`aria-checked`). */\n readonly checked: boolean;\n /** The group's change to this item is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuRadioItemProps extends Omit<\n MenuCheckboxItemProps,\n | \"checked\"\n | \"defaultChecked\"\n | \"checkedChangeAction\"\n | \"className\"\n | \"style\"\n | \"children\"\n | \"render\"\n> {\n /** The value the group's `valueChangeAction` receives when this item is chosen. */\n value: string;\n className?: StateValue<string | undefined, MenuRadioItemState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioItemState>;\n children?: StateValue<ReactNode, MenuRadioItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuRadioItemState>;\n}\n\n/**\n * One option of a `Menu.RadioGroup`: `<button type=\"button\" role=\"menuitemradio\" aria-checked>`.\n * Activation selects it through the group's transition (nothing is dispatched when it is already\n * selected); the menu stays open unless `closeOnActivate`.\n *\n * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked`, `data-checked`, and\n * `value` (not rendered: a menu item is not a form control).\n */\nexport function MenuRadioItem({\n value,\n closeOnActivate = false,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioItemProps) {\n const menu = useMenuContext(\"Menu.RadioItem\");\n const group = use(MenuRadioGroupContext);\n if (group === null) throw new Error(\"Menu.RadioItem must be rendered inside a Menu.RadioGroup.\");\n\n const checked = group.value === value;\n const pending = group.pending && checked;\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.RadioItem\",\n role: \"menuitemradio\",\n disabled,\n label,\n activate: (event) => {\n const close = closeOnActivate ? closeStackLater(menu, event) : undefined;\n // Re-activating the selected item changes nothing (conventions 2.3 item 8); with\n // `closeOnActivate` it still closes the menu, as any activation does.\n if (checked) {\n close?.();\n return;\n }\n group.select(value, event, close);\n },\n });\n\n const state: MenuRadioItemState = { checked, pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuRadioItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n value: undefined,\n \"aria-checked\": checked,\n \"data-checked\": dataAttr(checked),\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuRadioItem.displayName = \"MenuRadioItem\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-radio-item.mjs","names":[],"sources":["../../src/menu/menu-radio-item.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { MenuCheckboxItemProps } from \"./menu-checkbox-item.js\";\nimport { closeStackLater, MenuRadioGroupContext, useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\nexport interface MenuRadioItemState {\n /** This item is the group's optimistic value (`aria-checked`). */\n readonly checked: boolean;\n /** The group's change to this item is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuRadioItemProps extends Omit<\n MenuCheckboxItemProps,\n | \"checked\"\n | \"defaultChecked\"\n | \"checkedChangeAction\"\n | \"className\"\n | \"style\"\n | \"children\"\n | \"render\"\n> {\n /** The value the group's `valueChangeAction` receives when this item is chosen. */\n value: string;\n className?: StateValue<string | undefined, MenuRadioItemState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioItemState>;\n children?: StateValue<ReactNode, MenuRadioItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuRadioItemState>;\n}\n\n/**\n * One option of a `Menu.RadioGroup`: `<button type=\"button\" role=\"menuitemradio\" aria-checked>`.\n * Activation selects it through the group's transition (nothing is dispatched when it is already\n * selected); the menu stays open unless `closeOnActivate`.\n *\n * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked`, `data-checked`, and\n * `value` (not rendered: a menu item is not a form control).\n */\nexport function MenuRadioItem({\n value,\n closeOnActivate = false,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioItemProps) {\n const menu = useMenuContext(\"Menu.RadioItem\");\n const group = use(MenuRadioGroupContext);\n if (group === null) throw new Error(\"Menu.RadioItem must be rendered inside a Menu.RadioGroup.\");\n\n const checked = group.value === value;\n const pending = group.pending && checked;\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.RadioItem\",\n role: \"menuitemradio\",\n disabled,\n label,\n activate: (event) => {\n const close = closeOnActivate ? closeStackLater(menu, event) : undefined;\n // Re-activating the selected item changes nothing (conventions 2.3 item 8); with\n // `closeOnActivate` it still closes the menu, as any activation does.\n if (checked) {\n close?.();\n return;\n }\n group.select(value, event, close);\n },\n });\n\n const state: MenuRadioItemState = { checked, pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuRadioItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n value: undefined,\n \"aria-checked\": checked,\n \"data-checked\": dataAttr(checked),\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuRadioItem.displayName = \"MenuRadioItem\";\n"],"mappings":";;;;;;;;;;;;;;;;AA4CA,SAAgB,cAAc,EAC5B,OACA,kBAAkB,OAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,OAAO,eAAe,gBAAgB;CAC5C,MAAM,QAAQ,IAAI,qBAAqB;CACvC,IAAI,UAAU,MAAM,MAAM,IAAI,MAAM,2DAA2D;CAE/F,MAAM,UAAU,MAAM,UAAU;CAChC,MAAM,UAAU,MAAM,WAAW;CAEjC,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,QAAQ,kBAAkB,gBAAgB,MAAM,KAAK,IAAI,KAAA;GAG/D,IAAI,SAAS;IACX,QAAQ;IACR;GACF;GACA,MAAM,OAAO,OAAO,OAAO,KAAK;EAClC;CACF,CAAC;CAID,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA,OAAA;GALkC;GAAS;GAAS;EAKhD;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,OAAO,KAAA;GACP,gBAAgB;GAChB,gBAAgB,SAAS,OAAO;GAChC,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,cAAc,cAAc"}
|
package/dist/menu/menu-root.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { resolveStateValue } from "../internal/
|
|
2
|
+
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
3
3
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
4
4
|
import { useDelayedPair } from "../internal/use-delayed-pair.mjs";
|
|
5
5
|
import { usePopoverRoot } from "../internal/use-popover-root.mjs";
|
|
@@ -22,7 +22,8 @@ export interface MenuTriggerProps extends Omit<ComponentPropsWithRef<"button">,
|
|
|
22
22
|
/**
|
|
23
23
|
* The menu button: `<button type="button" commandfor command="toggle-popover"
|
|
24
24
|
* aria-haspopup="menu" aria-expanded>`. A click (Enter, Space) toggles the menu through the
|
|
25
|
-
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused
|
|
25
|
+
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused (inside a
|
|
26
|
+
* vertical `Toolbar.Root`: the inline-end arrow, with the first item focused).
|
|
26
27
|
*
|
|
27
28
|
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `commandfor`,
|
|
28
29
|
* `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-trigger.d.mts","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"menu-trigger.d.mts","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"mappings":";;;;iBAWiB;;WAEN;;WAEA;WACA;;iBAGM,yBAAyB,KACxC;;EAIA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;wBAwCvC,cACd,UACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAPH"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { useToolbarContext } from "../internal/toolbar-context.mjs";
|
|
3
4
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
4
5
|
import { usePopoverTrigger } from "../internal/use-popover-trigger.mjs";
|
|
5
6
|
import { useMenuContext } from "./menu-context.mjs";
|
|
6
|
-
import { edgeItem, focusItem, hasModifier } from "./menu-dom.mjs";
|
|
7
|
+
import { edgeItem, focusItem, hasModifier, isRtl } from "./menu-dom.mjs";
|
|
7
8
|
//#region src/menu/menu-trigger.tsx
|
|
8
9
|
/**
|
|
9
10
|
* Opens with the first (`"first"`) or last (`"last"`) item focused, or focuses that item when the
|
|
@@ -29,7 +30,8 @@ function isPointerClick(event) {
|
|
|
29
30
|
/**
|
|
30
31
|
* The menu button: `<button type="button" commandfor command="toggle-popover"
|
|
31
32
|
* aria-haspopup="menu" aria-expanded>`. A click (Enter, Space) toggles the menu through the
|
|
32
|
-
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused
|
|
33
|
+
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused (inside a
|
|
34
|
+
* vertical `Toolbar.Root`: the inline-end arrow, with the first item focused).
|
|
33
35
|
*
|
|
34
36
|
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `commandfor`,
|
|
35
37
|
* `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,
|
|
@@ -38,11 +40,20 @@ function isPointerClick(event) {
|
|
|
38
40
|
function MenuTrigger({ disabled = false, className, style, children, render, ...rest }) {
|
|
39
41
|
const menu = useMenuContext("Menu.Trigger");
|
|
40
42
|
useDevWarning(menu.submenu, "Menu.Trigger: rendered in a submenu. Use Menu.SubmenuTrigger, which is also an item of the parent menu.");
|
|
43
|
+
const toolbar = useToolbarContext();
|
|
41
44
|
const onKeyDown = (event) => {
|
|
42
45
|
if (disabled || hasModifier(event)) return;
|
|
43
|
-
|
|
46
|
+
let which;
|
|
47
|
+
if (toolbar?.orientation === "vertical") {
|
|
48
|
+
const openKey = isRtl(event.currentTarget) ? "ArrowLeft" : "ArrowRight";
|
|
49
|
+
if (event.key !== openKey) return;
|
|
50
|
+
which = "first";
|
|
51
|
+
} else {
|
|
52
|
+
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
|
53
|
+
which = event.key === "ArrowDown" ? "first" : "last";
|
|
54
|
+
}
|
|
44
55
|
event.preventDefault();
|
|
45
|
-
openMenuWithFocus(menu, event.nativeEvent,
|
|
56
|
+
openMenuWithFocus(menu, event.nativeEvent, which);
|
|
46
57
|
};
|
|
47
58
|
const { state, libraryProps, ref } = usePopoverTrigger(menu.popover, {
|
|
48
59
|
part: "Menu.Trigger",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-trigger.mjs","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverTrigger } from \"../internal/use-popover-trigger.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport type { MenuContextValue } from \"./menu-context.js\";\nimport { edgeItem, focusItem, hasModifier } from \"./menu-dom.js\";\n\nexport interface MenuTriggerState {\n /** Optimistic open state. */\n readonly open: boolean;\n /** The Root's transition is pending (an open, a part-initiated close or a dismissal notification). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"aria-expanded\" | \"aria-haspopup\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`: the menu cannot be opened from this button. */\n disabled?: boolean;\n className?: StateValue<string | undefined, MenuTriggerState>;\n style?: StateValue<CSSProperties | undefined, MenuTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, MenuTriggerState>;\n /** Should render a `<button>` (the command path before hydration). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuTriggerState>;\n}\n\n/**\n * Opens with the first (`\"first\"`) or last (`\"last\"`) item focused, or focuses that item when the\n * menu is already open (a pre-hydration open, walkthrough 10). Shared with SubmenuTrigger.\n */\nexport function openMenuWithFocus(\n menu: MenuContextValue,\n event: Event,\n which: \"first\" | \"last\",\n): void {\n const { popover } = menu;\n if (popover.intended()) {\n const popup = popover.popupRef.current;\n if (popup !== null && popup.matches(\":popover-open\")) {\n const item = edgeItem(popup, which);\n if (item !== null) focusItem(item);\n }\n return;\n }\n menu.setOpenCause(which, isPointerClick(event));\n popover.requestOpen(event, \"activate\");\n}\n\n/** A click from a pointing device (`detail` counts clicks; keyboard and `click()` give 0). */\nexport function isPointerClick(event: Event): boolean {\n return event.type === \"click\" && (event as MouseEvent).detail > 0;\n}\n\n/**\n * The menu button: `<button type=\"button\" commandfor command=\"toggle-popover\"\n * aria-haspopup=\"menu\" aria-expanded>`. A click (Enter, Space) toggles the menu through the\n * Root's transition; ArrowDown / ArrowUp open it with the first / last item focused.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `commandfor`,\n * `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,\n * `data-pending`, `data-disabled`.\n */\nexport function MenuTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuTriggerProps) {\n const menu = useMenuContext(\"Menu.Trigger\");\n useDevWarning(\n menu.submenu,\n \"Menu.Trigger: rendered in a submenu. Use Menu.SubmenuTrigger, which is also an item of the \" +\n \"parent menu.\",\n );\n\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (disabled || hasModifier(event)) return;\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n // No page scroll
|
|
1
|
+
{"version":3,"file":"menu-trigger.mjs","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { useToolbarContext } from \"../internal/toolbar-context.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverTrigger } from \"../internal/use-popover-trigger.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport type { MenuContextValue } from \"./menu-context.js\";\nimport { edgeItem, focusItem, hasModifier, isRtl } from \"./menu-dom.js\";\n\nexport interface MenuTriggerState {\n /** Optimistic open state. */\n readonly open: boolean;\n /** The Root's transition is pending (an open, a part-initiated close or a dismissal notification). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"aria-expanded\" | \"aria-haspopup\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`: the menu cannot be opened from this button. */\n disabled?: boolean;\n className?: StateValue<string | undefined, MenuTriggerState>;\n style?: StateValue<CSSProperties | undefined, MenuTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, MenuTriggerState>;\n /** Should render a `<button>` (the command path before hydration). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuTriggerState>;\n}\n\n/**\n * Opens with the first (`\"first\"`) or last (`\"last\"`) item focused, or focuses that item when the\n * menu is already open (a pre-hydration open, walkthrough 10). Shared with SubmenuTrigger.\n */\nexport function openMenuWithFocus(\n menu: MenuContextValue,\n event: Event,\n which: \"first\" | \"last\",\n): void {\n const { popover } = menu;\n if (popover.intended()) {\n const popup = popover.popupRef.current;\n if (popup !== null && popup.matches(\":popover-open\")) {\n const item = edgeItem(popup, which);\n if (item !== null) focusItem(item);\n }\n return;\n }\n menu.setOpenCause(which, isPointerClick(event));\n popover.requestOpen(event, \"activate\");\n}\n\n/** A click from a pointing device (`detail` counts clicks; keyboard and `click()` give 0). */\nexport function isPointerClick(event: Event): boolean {\n return event.type === \"click\" && (event as MouseEvent).detail > 0;\n}\n\n/**\n * The menu button: `<button type=\"button\" commandfor command=\"toggle-popover\"\n * aria-haspopup=\"menu\" aria-expanded>`. A click (Enter, Space) toggles the menu through the\n * Root's transition; ArrowDown / ArrowUp open it with the first / last item focused (inside a\n * vertical `Toolbar.Root`: the inline-end arrow, with the first item focused).\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `commandfor`,\n * `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,\n * `data-pending`, `data-disabled`.\n */\nexport function MenuTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuTriggerProps) {\n const menu = useMenuContext(\"Menu.Trigger\");\n useDevWarning(\n menu.submenu,\n \"Menu.Trigger: rendered in a submenu. Use Menu.SubmenuTrigger, which is also an item of the \" +\n \"parent menu.\",\n );\n\n // Inside a vertical Toolbar, ArrowDown / ArrowUp are the Toolbar's: the trigger opens on the\n // inline-end arrow instead, as a submenu trigger does (design/components/toggle-group.md 7.4).\n const toolbar = useToolbarContext();\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (disabled || hasModifier(event)) return;\n let which: \"first\" | \"last\";\n if (toolbar?.orientation === \"vertical\") {\n const openKey = isRtl(event.currentTarget) ? \"ArrowLeft\" : \"ArrowRight\";\n if (event.key !== openKey) return;\n which = \"first\";\n } else {\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n which = event.key === \"ArrowDown\" ? \"first\" : \"last\";\n }\n // No page scroll, and an enclosing Toolbar sees `defaultPrevented`; Enter and Space stay the\n // native click.\n event.preventDefault();\n openMenuWithFocus(menu, event.nativeEvent, which);\n };\n\n const { state, libraryProps, ref } = usePopoverTrigger(menu.popover, {\n part: \"Menu.Trigger\",\n disabled,\n render,\n handlers: { onKeyDown },\n activate: (event) => {\n const native = event.nativeEvent;\n if (menu.popover.intended()) {\n menu.popover.requestClose(native, \"activate\");\n } else {\n menu.setOpenCause(\"first\", isPointerClick(native));\n menu.popover.requestOpen(native, \"activate\");\n }\n },\n });\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...libraryProps,\n id: menu.triggerId,\n \"aria-haspopup\": \"menu\",\n },\n ref,\n });\n}\n\nMenuTrigger.displayName = \"MenuTrigger\";\n"],"mappings":";;;;;;;;;;;;AAqCA,SAAgB,kBACd,MACA,OACA,OACM;CACN,MAAM,EAAE,YAAY;CACpB,IAAI,QAAQ,SAAS,GAAG;EACtB,MAAM,QAAQ,QAAQ,SAAS;EAC/B,IAAI,UAAU,QAAQ,MAAM,QAAQ,eAAe,GAAG;GACpD,MAAM,OAAO,SAAS,OAAO,KAAK;GAClC,IAAI,SAAS,MAAM,UAAU,IAAI;EACnC;EACA;CACF;CACA,KAAK,aAAa,OAAO,eAAe,KAAK,CAAC;CAC9C,QAAQ,YAAY,OAAO,UAAU;AACvC;;AAGA,SAAgB,eAAe,OAAuB;CACpD,OAAO,MAAM,SAAS,WAAY,MAAqB,SAAS;AAClE;;;;;;;;;;;AAYA,SAAgB,YAAY,EAC1B,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,OAAO,eAAe,cAAc;CAC1C,cACE,KAAK,SACL,yGAEF;CAIA,MAAM,UAAU,kBAAkB;CAClC,MAAM,aAAa,UAA4C;EAC7D,IAAI,YAAY,YAAY,KAAK,GAAG;EACpC,IAAI;EACJ,IAAI,SAAS,gBAAgB,YAAY;GACvC,MAAM,UAAU,MAAM,MAAM,aAAa,IAAI,cAAc;GAC3D,IAAI,MAAM,QAAQ,SAAS;GAC3B,QAAQ;EACV,OAAO;GACL,IAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,WAAW;GAC1D,QAAQ,MAAM,QAAQ,cAAc,UAAU;EAChD;EAGA,MAAM,eAAe;EACrB,kBAAkB,MAAM,MAAM,aAAa,KAAK;CAClD;CAEA,MAAM,EAAE,OAAO,cAAc,QAAQ,kBAAkB,KAAK,SAAS;EACnE,MAAM;EACN;EACA;EACA,UAAU,EAAE,UAAU;EACtB,WAAW,UAAU;GACnB,MAAM,SAAS,MAAM;GACrB,IAAI,KAAK,QAAQ,SAAS,GACxB,KAAK,QAAQ,aAAa,QAAQ,UAAU;QACvC;IACL,KAAK,aAAa,SAAS,eAAe,MAAM,CAAC;IACjD,KAAK,QAAQ,YAAY,QAAQ,UAAU;GAC7C;EACF;CACF,CAAC;CAED,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAG;GACH,IAAI,KAAK;GACT,iBAAiB;EACnB;EACA;CACF,CAAC;AACH;AAEA,YAAY,cAAc"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "../internal/platform-attributes.mjs";
|
|
2
|
+
import { MeterRegion } from "./meter-region.mjs";
|
|
3
|
+
import { MeterRoot, MeterRootProps, MeterState } from "./meter-root.mjs";
|
|
4
|
+
import { MeterIndicator, MeterIndicatorProps } from "./meter-indicator.mjs";
|
|
5
|
+
import { MeterLabel, MeterLabelProps } from "./meter-label.mjs";
|
|
6
|
+
import { MeterTrack, MeterTrackProps } from "./meter-track.mjs";
|
|
7
|
+
import { MeterValue, MeterValueProps } from "./meter-value.mjs";
|
|
8
|
+
//#region src/meter/index.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* Meter (WAI-ARIA `meter`): `Root` (`div[role=meter]`, directive-free, so a Server Component may
|
|
11
|
+
* render it and it runs on the server), `Track`, `Indicator` (sized by your CSS from
|
|
12
|
+
* `--meter-value`), `Label` and `Value`. `low` / `high` / `optimum` decide the region
|
|
13
|
+
* (`data-region`: `optimum`, `suboptimum`, `even-less-good`) with the platform's rules.
|
|
14
|
+
*/
|
|
15
|
+
export declare const Meter: {
|
|
16
|
+
readonly Root: typeof MeterRoot;
|
|
17
|
+
readonly Track: typeof MeterTrack;
|
|
18
|
+
readonly Indicator: typeof MeterIndicator;
|
|
19
|
+
readonly Label: typeof MeterLabel;
|
|
20
|
+
readonly Value: typeof MeterValue;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export type { MeterIndicatorProps, MeterLabelProps, MeterRegion, MeterRootProps, MeterState, MeterTrackProps, MeterValueProps };
|
|
24
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/meter/index.ts"],"mappings":";;;;;;;;;;;;;;qBAea;WACX,aAAI;WACJ,cAAK;WACL,kBAAS;WACT,cAAK;WACL,cAAK"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MeterIndicator } from "./meter-indicator.mjs";
|
|
2
|
+
import { MeterLabel } from "./meter-label.mjs";
|
|
3
|
+
import { MeterRoot } from "./meter-root.mjs";
|
|
4
|
+
import { MeterTrack } from "./meter-track.mjs";
|
|
5
|
+
import { MeterValue } from "./meter-value.mjs";
|
|
6
|
+
//#region src/meter/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Meter (WAI-ARIA `meter`): `Root` (`div[role=meter]`, directive-free, so a Server Component may
|
|
9
|
+
* render it and it runs on the server), `Track`, `Indicator` (sized by your CSS from
|
|
10
|
+
* `--meter-value`), `Label` and `Value`. `low` / `high` / `optimum` decide the region
|
|
11
|
+
* (`data-region`: `optimum`, `suboptimum`, `even-less-good`) with the platform's rules.
|
|
12
|
+
*/
|
|
13
|
+
const Meter = {
|
|
14
|
+
Root: MeterRoot,
|
|
15
|
+
Track: MeterTrack,
|
|
16
|
+
Indicator: MeterIndicator,
|
|
17
|
+
Label: MeterLabel,
|
|
18
|
+
Value: MeterValue
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Meter };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/meter/index.ts"],"sourcesContent":["// Ships the platform attribute typings with this subpath (conventions 7.4). Type-only: the\n// built JS has no import of it.\nimport \"../internal/platform-attributes.js\";\nimport { MeterIndicator } from \"./meter-indicator.js\";\nimport { MeterLabel } from \"./meter-label.js\";\nimport { MeterRoot } from \"./meter-root.js\";\nimport { MeterTrack } from \"./meter-track.js\";\nimport { MeterValue } from \"./meter-value.js\";\n\n/**\n * Meter (WAI-ARIA `meter`): `Root` (`div[role=meter]`, directive-free, so a Server Component may\n * render it and it runs on the server), `Track`, `Indicator` (sized by your CSS from\n * `--meter-value`), `Label` and `Value`. `low` / `high` / `optimum` decide the region\n * (`data-region`: `optimum`, `suboptimum`, `even-less-good`) with the platform's rules.\n */\nexport const Meter = {\n Root: MeterRoot,\n Track: MeterTrack,\n Indicator: MeterIndicator,\n Label: MeterLabel,\n Value: MeterValue,\n} as const;\n\nexport type { MeterRootProps, MeterState, MeterRegion } from \"./meter-root.js\";\nexport type { MeterTrackProps } from \"./meter-track.js\";\nexport type { MeterIndicatorProps } from \"./meter-indicator.js\";\nexport type { MeterLabelProps } from \"./meter-label.js\";\nexport type { MeterValueProps } from \"./meter-value.js\";\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,QAAQ;CACnB,MAAM;CACN,OAAO;CACP,WAAW;CACX,OAAO;CACP,OAAO;AACT"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, use } from "react";
|
|
3
|
+
//#region src/meter/meter-context.ts
|
|
4
|
+
const MeterContext = createContext(null);
|
|
5
|
+
function useMeterContext(part) {
|
|
6
|
+
const context = use(MeterContext);
|
|
7
|
+
if (context === null) throw new Error(`${part} must be rendered inside a Meter.Root.`);
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { MeterContext, useMeterContext };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=meter-context.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-context.mjs","names":[],"sources":["../../src/meter/meter-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { MeterState } from \"./meter-root.js\";\n\n/**\n * What the directive-free `Meter.Root` provides to its parts: plain serialisable data, so the\n * provider can be rendered by a Root running as a Server Component (design/components/progress.md\n * 8.1).\n */\nexport interface MeterContextValue {\n /** The Root's `useId`; `Meter.Label` renders `${id}-label`. */\n readonly id: string;\n readonly state: MeterState;\n}\n\nexport const MeterContext = createContext<MeterContextValue | null>(null);\n\nexport function useMeterContext(part: string): MeterContextValue {\n const context = use(MeterContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Meter.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AAeA,MAAa,eAAe,cAAwC,IAAI;AAExE,SAAgB,gBAAgB,MAAiC;CAC/D,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,uCAAuC;CAEjE,OAAO;AACT"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { MeterState } from "./meter-root.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/meter/meter-indicator.d.ts
|
|
5
|
+
export interface MeterIndicatorProps extends Omit<ComponentPropsWithRef<"span">, "className" | "style" | "children"> {
|
|
6
|
+
className?: StateValue<string | undefined, MeterState>;
|
|
7
|
+
style?: StateValue<CSSProperties | undefined, MeterState>;
|
|
8
|
+
children?: StateValue<ReactNode, MeterState>;
|
|
9
|
+
render?: RenderProp<ComponentPropsWithRef<"span">, MeterState>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `Meter.Indicator`: the fill. It renders no inline size: the consumer's CSS sizes it from the Root's inherited custom property. Owned keys: `data-region`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function MeterIndicator({ className, style, children, render, ...rest }: MeterIndicatorProps): ReactNode;
|
|
15
|
+
export declare namespace MeterIndicator {
|
|
16
|
+
var displayName: string;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=meter-indicator.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-indicator.d.mts","names":[],"sources":["../../src/meter/meter-indicator.tsx"],"mappings":";;;;iBAOiB,4BAA4B,KAC3C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;wBAMrC,iBACd,WACA,OACA,UACA,WACG,QACF,sBAAmB;yBANN"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { useMeterContext } from "./meter-context.mjs";
|
|
4
|
+
//#region src/meter/meter-indicator.tsx
|
|
5
|
+
/**
|
|
6
|
+
* `Meter.Indicator`: the fill. It renders no inline size: the consumer's CSS sizes it from the Root's inherited custom property. Owned keys: `data-region`.
|
|
7
|
+
*/
|
|
8
|
+
function MeterIndicator({ className, style, children, render, ...rest }) {
|
|
9
|
+
const { state } = useMeterContext("Meter.Indicator");
|
|
10
|
+
return useRender({
|
|
11
|
+
defaultElement: "span",
|
|
12
|
+
render,
|
|
13
|
+
state,
|
|
14
|
+
className,
|
|
15
|
+
style,
|
|
16
|
+
children,
|
|
17
|
+
props: rest,
|
|
18
|
+
libraryProps: { "data-region": state.region }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
MeterIndicator.displayName = "MeterIndicator";
|
|
22
|
+
//#endregion
|
|
23
|
+
export { MeterIndicator };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=meter-indicator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-indicator.mjs","names":[],"sources":["../../src/meter/meter-indicator.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMeterContext } from \"./meter-context.js\";\nimport type { MeterState } from \"./meter-root.js\";\n\nexport interface MeterIndicatorProps extends Omit<\n ComponentPropsWithRef<\"span\">,\n \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MeterState>;\n style?: StateValue<CSSProperties | undefined, MeterState>;\n children?: StateValue<ReactNode, MeterState>;\n render?: RenderProp<ComponentPropsWithRef<\"span\">, MeterState>;\n}\n\n/**\n * `Meter.Indicator`: the fill. It renders no inline size: the consumer's CSS sizes it from the Root's inherited custom property. Owned keys: `data-region`.\n */\nexport function MeterIndicator({\n className,\n style,\n children,\n render,\n ...rest\n}: MeterIndicatorProps) {\n const { state } = useMeterContext(\"Meter.Indicator\");\n return useRender<ComponentPropsWithRef<\"span\">, MeterState>({\n defaultElement: \"span\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n \"data-region\": state.region,\n },\n });\n}\n\nMeterIndicator.displayName = \"MeterIndicator\";\n"],"mappings":";;;;;;;AAoBA,SAAgB,eAAe,EAC7B,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,EAAE,UAAU,gBAAgB,iBAAiB;CACnD,OAAO,UAAqD;EAC1D,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc,EACZ,eAAe,MAAM,OACvB;CACF,CAAC;AACH;AAEA,eAAe,cAAc"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { MeterState } from "./meter-root.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/meter/meter-label.d.ts
|
|
5
|
+
export interface MeterLabelProps extends Omit<ComponentPropsWithRef<"span">, "id" | "className" | "style" | "children"> {
|
|
6
|
+
className?: StateValue<string | undefined, MeterState>;
|
|
7
|
+
style?: StateValue<CSSProperties | undefined, MeterState>;
|
|
8
|
+
children?: StateValue<ReactNode, MeterState>;
|
|
9
|
+
render?: RenderProp<ComponentPropsWithRef<"span">, MeterState>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `Meter.Label`: a `span` whose id the Root always references through `aria-labelledby` (the Root is not a labelable element, so a `<label for>` would associate nothing). Owned keys: `id`, `data-region`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function MeterLabel({ className, style, children, render, ...rest }: MeterLabelProps): ReactNode;
|
|
15
|
+
export declare namespace MeterLabel {
|
|
16
|
+
var displayName: string;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=meter-label.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-label.d.mts","names":[],"sources":["../../src/meter/meter-label.tsx"],"mappings":";;;;iBAOiB,wBAAwB,KACvC;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;wBAMrC,aAAa,WAAW,OAAO,UAAU,WAAW,QAAQ,kBAAe;yBAA3E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { useMeterContext } from "./meter-context.mjs";
|
|
4
|
+
//#region src/meter/meter-label.tsx
|
|
5
|
+
/**
|
|
6
|
+
* `Meter.Label`: a `span` whose id the Root always references through `aria-labelledby` (the Root is not a labelable element, so a `<label for>` would associate nothing). Owned keys: `id`, `data-region`.
|
|
7
|
+
*/
|
|
8
|
+
function MeterLabel({ className, style, children, render, ...rest }) {
|
|
9
|
+
const { id, state } = useMeterContext("Meter.Label");
|
|
10
|
+
return useRender({
|
|
11
|
+
defaultElement: "span",
|
|
12
|
+
render,
|
|
13
|
+
state,
|
|
14
|
+
className,
|
|
15
|
+
style,
|
|
16
|
+
children,
|
|
17
|
+
props: rest,
|
|
18
|
+
libraryProps: {
|
|
19
|
+
id: `${id}-label`,
|
|
20
|
+
"data-region": state.region
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
MeterLabel.displayName = "MeterLabel";
|
|
25
|
+
//#endregion
|
|
26
|
+
export { MeterLabel };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=meter-label.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-label.mjs","names":[],"sources":["../../src/meter/meter-label.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMeterContext } from \"./meter-context.js\";\nimport type { MeterState } from \"./meter-root.js\";\n\nexport interface MeterLabelProps extends Omit<\n ComponentPropsWithRef<\"span\">,\n \"id\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MeterState>;\n style?: StateValue<CSSProperties | undefined, MeterState>;\n children?: StateValue<ReactNode, MeterState>;\n render?: RenderProp<ComponentPropsWithRef<\"span\">, MeterState>;\n}\n\n/**\n * `Meter.Label`: a `span` whose id the Root always references through `aria-labelledby` (the Root is not a labelable element, so a `<label for>` would associate nothing). Owned keys: `id`, `data-region`.\n */\nexport function MeterLabel({ className, style, children, render, ...rest }: MeterLabelProps) {\n const { id, state } = useMeterContext(\"Meter.Label\");\n return useRender<ComponentPropsWithRef<\"span\">, MeterState>({\n defaultElement: \"span\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n id: `${id}-label`,\n \"data-region\": state.region,\n },\n });\n}\n\nMeterLabel.displayName = \"MeterLabel\";\n"],"mappings":";;;;;;;AAoBA,SAAgB,WAAW,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAAyB;CAC3F,MAAM,EAAE,IAAI,UAAU,gBAAgB,aAAa;CACnD,OAAO,UAAqD;EAC1D,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,IAAI,GAAG,GAAG;GACV,eAAe,MAAM;EACvB;CACF,CAAC;AACH;AAEA,WAAW,cAAc"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-region.d.mts","names":[],"sources":["../../src/meter/meter-region.ts"],"mappings":";YAGY"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { clamp } from "../internal/range-value.mjs";
|
|
2
|
+
//#region src/meter/meter-region.ts
|
|
3
|
+
function given(value) {
|
|
4
|
+
return value !== void 0 && Number.isFinite(value);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* HTML's defaults and clamping for `<meter>`, in order: `low` (default `min`) to `[min, max]`,
|
|
8
|
+
* `high` (default `max`) to `[low, max]`, `optimum` (default the midpoint) to `[min, max]`. A
|
|
9
|
+
* non-finite boundary is treated as absent, as the platform's attribute parser does.
|
|
10
|
+
*/
|
|
11
|
+
function resolveMeterBounds(min, max, low, high, optimum) {
|
|
12
|
+
const resolvedLow = clamp(given(low) ? low : min, min, max);
|
|
13
|
+
return {
|
|
14
|
+
low: resolvedLow,
|
|
15
|
+
high: clamp(given(high) ? high : max, resolvedLow, max),
|
|
16
|
+
optimum: clamp(given(optimum) ? optimum : (min + max) / 2, min, max)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The gauge region of a meter, with Chromium's comparison operators
|
|
21
|
+
* (`HTMLMeterElement::GetGaugeRegion`). Expects the clamped value and bounds.
|
|
22
|
+
*/
|
|
23
|
+
function meterRegion(value, low, high, optimum) {
|
|
24
|
+
if (optimum < low) {
|
|
25
|
+
if (value <= low) return "optimum";
|
|
26
|
+
if (value <= high) return "suboptimum";
|
|
27
|
+
return "even-less-good";
|
|
28
|
+
}
|
|
29
|
+
if (high < optimum) {
|
|
30
|
+
if (high <= value) return "optimum";
|
|
31
|
+
if (low <= value) return "suboptimum";
|
|
32
|
+
return "even-less-good";
|
|
33
|
+
}
|
|
34
|
+
return low <= value && value <= high ? "optimum" : "suboptimum";
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { meterRegion, resolveMeterBounds };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=meter-region.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-region.mjs","names":[],"sources":["../../src/meter/meter-region.ts"],"sourcesContent":["// No \"use client\": pure functions (design/components/progress.md 2.4).\nimport { clamp } from \"../internal/range-value.js\";\n\nexport type MeterRegion = \"optimum\" | \"suboptimum\" | \"even-less-good\";\n\nexport interface MeterBounds {\n readonly low: number;\n readonly high: number;\n readonly optimum: number;\n}\n\nfunction given(value: number | undefined): value is number {\n return value !== undefined && Number.isFinite(value);\n}\n\n/**\n * HTML's defaults and clamping for `<meter>`, in order: `low` (default `min`) to `[min, max]`,\n * `high` (default `max`) to `[low, max]`, `optimum` (default the midpoint) to `[min, max]`. A\n * non-finite boundary is treated as absent, as the platform's attribute parser does.\n */\nexport function resolveMeterBounds(\n min: number,\n max: number,\n low: number | undefined,\n high: number | undefined,\n optimum: number | undefined,\n): MeterBounds {\n const resolvedLow = clamp(given(low) ? low : min, min, max);\n const resolvedHigh = clamp(given(high) ? high : max, resolvedLow, max);\n const resolvedOptimum = clamp(given(optimum) ? optimum : (min + max) / 2, min, max);\n return { low: resolvedLow, high: resolvedHigh, optimum: resolvedOptimum };\n}\n\n/**\n * The gauge region of a meter, with Chromium's comparison operators\n * (`HTMLMeterElement::GetGaugeRegion`). Expects the clamped value and bounds.\n */\nexport function meterRegion(\n value: number,\n low: number,\n high: number,\n optimum: number,\n): MeterRegion {\n if (optimum < low) {\n // Lower is better.\n if (value <= low) return \"optimum\";\n if (value <= high) return \"suboptimum\";\n return \"even-less-good\";\n }\n if (high < optimum) {\n // Higher is better.\n if (high <= value) return \"optimum\";\n if (low <= value) return \"suboptimum\";\n return \"even-less-good\";\n }\n // The middle is best: never \"even-less-good\".\n return low <= value && value <= high ? \"optimum\" : \"suboptimum\";\n}\n"],"mappings":";;AAWA,SAAS,MAAM,OAA4C;CACzD,OAAO,UAAU,KAAA,KAAa,OAAO,SAAS,KAAK;AACrD;;;;;;AAOA,SAAgB,mBACd,KACA,KACA,KACA,MACA,SACa;CACb,MAAM,cAAc,MAAM,MAAM,GAAG,IAAI,MAAM,KAAK,KAAK,GAAG;CAG1D,OAAO;EAAE,KAAK;EAAa,MAFN,MAAM,MAAM,IAAI,IAAI,OAAO,KAAK,aAAa,GAEtB;EAAG,SADvB,MAAM,MAAM,OAAO,IAAI,WAAW,MAAM,OAAO,GAAG,KAAK,GACT;CAAE;AAC1E;;;;;AAMA,SAAgB,YACd,OACA,KACA,MACA,SACa;CACb,IAAI,UAAU,KAAK;EAEjB,IAAI,SAAS,KAAK,OAAO;EACzB,IAAI,SAAS,MAAM,OAAO;EAC1B,OAAO;CACT;CACA,IAAI,OAAO,SAAS;EAElB,IAAI,QAAQ,OAAO,OAAO;EAC1B,IAAI,OAAO,OAAO,OAAO;EACzB,OAAO;CACT;CAEA,OAAO,OAAO,SAAS,SAAS,OAAO,YAAY;AACrD"}
|