@rotki/ui-library 2.22.0 → 2.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/dist/components/buttons/button/button-styles.js +1 -1
  2. package/dist/components/buttons/button/button-styles.js.map +1 -1
  3. package/dist/components/buttons/button-group/RuiButtonGroup.js.map +1 -1
  4. package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  5. package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/forms/category-picker/RuiCategoryPicker.js.map +1 -1
  7. package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js +11 -5
  8. package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  9. package/dist/components/forms/category-picker/category-picker-styles.js +3 -3
  10. package/dist/components/forms/category-picker/category-picker-styles.js.map +1 -1
  11. package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  12. package/dist/components/overlays/menu/RuiMenu.js.map +1 -1
  13. package/dist/components/overlays/menu/RuiMenu.vue.d.ts +2 -0
  14. package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  15. package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  16. package/dist/composables/floating.d.ts +11 -0
  17. package/dist/composables/floating.js +21 -2
  18. package/dist/composables/floating.js.map +1 -1
  19. package/dist/style.css +25 -12
  20. package/dist/web-types.json +1 -1
  21. package/package.json +1 -1
@@ -99,7 +99,7 @@ var buttonStyles = tv({
99
99
  {
100
100
  color: "grey",
101
101
  variant: "outlined",
102
- class: { root: "outline-rui-text" }
102
+ class: { root: "outline-black/[0.23] dark:outline-white/[0.23]" }
103
103
  },
104
104
  {
105
105
  color: "grey",
@@ -1 +1 @@
1
- {"version":3,"file":"button-styles.js","names":[],"sources":["../../../../src/components/buttons/button/button-styles.ts"],"sourcesContent":["import { tv } from '@/utils/tv';\n\nexport const buttonStyles = tv({\n slots: {\n root: [\n 'text-sm leading-5 font-medium outline outline-1 outline-transparent outline-offset-[-1px]',\n // `position: relative` is only needed as a positioning context for the\n // absolute-positioned loading spinner; applied via the `loading` variant\n // below. Putting it here would collide with consumers that need to pin\n // the button with `fixed`/`absolute` (cascade order picks the later\n // utility — usually `relative` — and the consumer's `right-*`/`bottom-*`\n // end up as relative offsets, which misplaces FABs).\n 'flex items-center justify-center gap-x-2',\n 'px-4 py-1.5 rounded transition-all',\n // Default (md) icon sizing is driven by a CSS custom property so that\n // RuiIcon's `size` prop (stamped as inline `style=\"--rui-icon-size\"` on\n // the svg) can override it cleanly. Size variants below redefine the\n // property with `!` so they beat this baseline on the same element;\n // inline style on the svg then beats the inherited value when a\n // consumer supplies their own size. See rotki/ui-library#512.\n '[--rui-icon-size:1.125rem]',\n // `disabled` on the element covers two states: actually disabled and\n // loading (RuiButton sets `disabled = disabled || loading`). The\n // color/bg/text overrides for the \"grey disabled\" look live in the\n // `loading: false` compounds below so they skip firing on loading\n // buttons — where we want the variant color to stay visible behind\n // the spinner. Cursor stays here since both states are non-clickable.\n 'disabled:cursor-not-allowed',\n 'focus-visible:!ring-2',\n ].join(' '),\n label: 'inline-block text-nowrap',\n spinner: 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',\n },\n variants: {\n variant: {\n default: {},\n outlined: {},\n text: { root: 'px-2' },\n fab: { root: 'rounded-full py-2' },\n // `leading-[1.125rem]` on the label collapses the 20px line-box (inherited\n // from the root's `leading-5`) down to 18px so it matches the md icon box\n // (`--rui-icon-size: 1.125rem`). Without it, `flex items-center` centers\n // the 20px line-box against the 18px icon box and the baseline-aligned\n // glyphs visually drift above the icon's optical center — most readable\n // in tight list rows. See rotki/ui-library#515.\n list: { root: 'p-3 px-3 rounded-none w-full justify-start text-left', label: 'w-full leading-[1.125rem]' },\n },\n size: {\n // Each size variant redefines `--rui-icon-size` on the button so the\n // value inherits into RuiIcons in button slots (prepend/append/default)\n // and drives their `width: var(--rui-icon-size, 1.5rem)` class. `!` on\n // the arbitrary property makes the variant beat the md baseline in the\n // base root regardless of CSS source order. A consumer passing `size`\n // on RuiIcon still wins: that path stamps an inline style on the svg\n // element itself, which beats the inherited value from the button.\n 'xs': { root: 'px-2 py-[0.125rem] text-[.75rem] leading-4 ![--rui-icon-size:0.75rem]' },\n 'sm': { root: 'px-2.5 py-1 text-[.8125rem] leading-5 ![--rui-icon-size:1rem]' },\n 'lg': { root: 'px-6 py-2 text-[1rem] leading-5 ![--rui-icon-size:1.25rem]' },\n // `xl` targets the 40px input height — the common toolbar case where a\n // RuiButton needs to line up with RuiTextField / RuiMenuSelect. For a\n // 44px jumbo-CTA button (auth screens, empty-state primaries), reach for\n // `2xl` instead.\n 'xl': { root: 'px-6 py-2 text-[1rem] leading-6 ![--rui-icon-size:1.375rem]' },\n '2xl': { root: 'px-6 py-2.5 text-[1rem] leading-6 ![--rui-icon-size:1.375rem]' },\n },\n color: {\n grey: { root: 'bg-rui-grey-200 hover:bg-rui-grey-100 active:bg-rui-grey-50 text-rui-text ring-rui-grey-400 dark:bg-rui-grey-300 dark:text-rui-light-text dark:ring-rui-grey-600' },\n primary: { root: 'bg-rui-primary hover:bg-rui-primary-darker active:bg-rui-primary-darker/90 text-rui-dark-text ring-rui-primary/40 dark:text-rui-text dark:ring-rui-primary/60' },\n secondary: { root: 'bg-rui-secondary hover:bg-rui-secondary-darker active:bg-rui-secondary-darker/90 text-rui-dark-text ring-rui-secondary/40 dark:text-rui-text dark:ring-rui-secondary/60' },\n error: { root: 'bg-rui-error hover:bg-rui-error-darker active:bg-rui-error-darker/90 text-rui-dark-text ring-rui-error/40 dark:text-rui-text dark:ring-rui-error/60' },\n warning: { root: 'bg-rui-warning hover:bg-rui-warning-darker active:bg-rui-warning-darker/90 text-rui-dark-text ring-rui-warning/40 dark:text-rui-text dark:ring-rui-warning/60' },\n info: { root: 'bg-rui-info hover:bg-rui-info-darker active:bg-rui-info-darker/90 text-rui-dark-text ring-rui-info/40 dark:text-rui-text dark:ring-rui-info/60' },\n success: { root: 'bg-rui-success hover:bg-rui-success-darker active:bg-rui-success-darker/90 text-rui-dark-text ring-rui-success/40 dark:text-rui-text dark:ring-rui-success/60' },\n },\n rounded: {\n true: { root: 'rounded-full' },\n },\n icon: {\n // Padding and icon sizing live in the `icon + size` compound variants\n // below so every size resolves to a height matching its text-button\n // counterpart; this base only contributes the round shape.\n true: { root: 'rounded-full' },\n },\n active: {\n true: {},\n false: {},\n },\n loading: {\n true: { root: 'relative !cursor-progress space-x-0 [&>*:not([data-spinner])]:opacity-0 [&>*:not([data-spinner])]:invisible' },\n false: {},\n },\n hideFocusIndicator: {\n true: { root: 'focus-visible:!ring-0' },\n },\n },\n compoundVariants: [\n // === Disabled (not loading) appearance ===\n // Applied only when `loading: false` so that loading buttons keep their\n // variant color/outline visible behind the spinner; pure disabled buttons\n // fade to the Material disabled palette.\n { loading: false, class: { root: 'disabled:!bg-black/[.12] dark:disabled:!bg-white/[.12] disabled:!text-rui-text-disabled disabled:active:!text-rui-text-disabled' } },\n { loading: false, variant: 'outlined', class: { root: 'disabled:!bg-transparent dark:disabled:!bg-transparent disabled:active:!bg-transparent disabled:outline-rui-text-disabled' } },\n { loading: false, variant: 'text', class: { root: 'disabled:!bg-transparent dark:disabled:!bg-transparent disabled:active:!bg-transparent' } },\n { loading: false, variant: 'list', class: { root: 'disabled:!bg-transparent dark:disabled:!bg-transparent disabled:active:!bg-transparent' } },\n\n // === Grey color variants ===\n { color: 'grey', active: true, class: { root: 'bg-rui-grey-50' } },\n { color: 'grey', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-black/[.04] active:bg-black/10 dark:bg-transparent dark:active:bg-white/10 dark:hover:bg-white/[.04] dark:text-rui-text' } },\n { color: 'grey', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-black/10 dark:bg-white/30' } },\n { color: 'grey', variant: 'outlined', class: { root: 'outline-rui-text' } },\n { color: 'grey', variant: 'text', class: { root: 'text-rui-text-secondary' } },\n\n // === Context colors — outlined/text variants ===\n // `dark:text-rui-<color>` is required to beat `dark:text-rui-text` set by\n // the base color variant (meant for filled buttons where text sits on a\n // colored bg). Without the override, outlined/text/list buttons in dark\n // mode render white text against a themed outline/underline.\n { color: 'primary', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-primary-lighter/[.04] active:bg-rui-primary-lighter/10 text-rui-primary dark:text-rui-primary' } },\n { color: 'secondary', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-secondary-lighter/[.04] active:bg-rui-secondary-lighter/10 text-rui-secondary dark:text-rui-secondary' } },\n { color: 'error', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-error-lighter/[.04] active:bg-rui-error-lighter/10 text-rui-error dark:text-rui-error' } },\n { color: 'warning', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-warning-lighter/[.04] active:bg-rui-warning-lighter/10 text-rui-warning dark:text-rui-warning' } },\n { color: 'info', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-info-lighter/[.04] active:bg-rui-info-lighter/10 text-rui-info dark:text-rui-info' } },\n { color: 'success', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-success-lighter/[.04] active:bg-rui-success-lighter/10 text-rui-success dark:text-rui-success' } },\n\n // === Context colors — active default ===\n { color: 'primary', active: true, class: { root: 'bg-rui-primary-darker' } },\n { color: 'secondary', active: true, class: { root: 'bg-rui-secondary-darker' } },\n { color: 'error', active: true, class: { root: 'bg-rui-error-darker' } },\n { color: 'warning', active: true, class: { root: 'bg-rui-warning-darker' } },\n { color: 'info', active: true, class: { root: 'bg-rui-info-darker' } },\n { color: 'success', active: true, class: { root: 'bg-rui-success-darker' } },\n\n // === Context colors — active outlined/text ===\n { color: 'primary', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-primary-lighter/30' } },\n { color: 'secondary', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-secondary-lighter/30' } },\n { color: 'error', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-error-lighter/30' } },\n { color: 'warning', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-warning-lighter/30' } },\n { color: 'info', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-info-lighter/30' } },\n { color: 'success', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-success-lighter/30' } },\n\n // === Context colors — outlined border ===\n { color: 'primary', variant: 'outlined', class: { root: 'outline-rui-primary/[0.5]' } },\n { color: 'secondary', variant: 'outlined', class: { root: 'outline-rui-secondary/[0.5]' } },\n { color: 'error', variant: 'outlined', class: { root: 'outline-rui-error/[0.5]' } },\n { color: 'warning', variant: 'outlined', class: { root: 'outline-rui-warning/[0.5]' } },\n { color: 'info', variant: 'outlined', class: { root: 'outline-rui-info/[0.5]' } },\n { color: 'success', variant: 'outlined', class: { root: 'outline-rui-success/[0.5]' } },\n\n // === Size overrides per variant ===\n { variant: 'text', size: 'xs', class: { root: 'px-1' } },\n { variant: 'text', size: 'sm', class: { root: 'px-1.5' } },\n { variant: 'text', size: 'lg', class: { root: 'px-2.5' } },\n { variant: 'text', size: 'xl', class: { root: 'px-2.5' } },\n { variant: 'text', size: '2xl', class: { root: 'px-2.5' } },\n { variant: 'fab', size: 'xs', class: { root: 'py-1 px-1' } },\n { variant: 'fab', size: 'sm', class: { root: 'py-1.5 px-2' } },\n { variant: 'fab', size: 'lg', class: { root: 'py-3' } },\n { variant: 'list', size: 'xs', class: { root: 'px-3 py-0.5' } },\n { variant: 'list', size: 'sm', class: { root: 'px-3 py-1' } },\n // Icon-only button sizing — every size lands at the same height as the\n // text button of the matching size, with a ~60–70% icon ratio (Material-3\n // style). The `size` rule sizes icons to 12/16/20/22px for text-button\n // prepend/append slots; here icon-only buttons get a larger glyph so they\n // don't look lost in the square. Values are rem-based throughout: p-1 =\n // 0.25rem, p-1.5 = 0.375rem, p-2 = 0.5rem; glyph sizes use rem literals.\n // xs uses an arbitrary 0.1875rem (3px) padding — no Tailwind token hits\n // it cleanly — to keep the 70% icon ratio with a 14px glyph.\n //\n // xs: p-[0.1875rem] + 0.875rem icon = 0.375 + 0.875 = 1.25rem (20px) — 70%\n // sm: p-1 + 1.25rem icon = 0.5 + 1.25 = 1.75rem (28px) — 71%\n // md (default): p-1.5 + 1.25rem icon = 0.75 + 1.25 = 2rem (32px) — 63%\n // lg: p-1.5 + 1.5rem icon = 0.75 + 1.5 = 2.25rem (36px) — 67%\n // xl: p-2 + 1.5rem icon = 1 + 1.5 = 2.5rem (40px) — 60%\n // 2xl: p-2 + 1.75rem icon = 1 + 1.75 = 2.75rem (44px) — 64%\n { icon: true, class: { root: 'p-1.5 ![--rui-icon-size:1.25rem]' } },\n { icon: true, size: 'xs', class: { root: 'p-[0.1875rem] ![--rui-icon-size:0.875rem]' } },\n { icon: true, size: 'sm', class: { root: 'p-1 ![--rui-icon-size:1.25rem]' } },\n { icon: true, size: 'lg', class: { root: 'p-1.5 ![--rui-icon-size:1.5rem]' } },\n { icon: true, size: 'xl', class: { root: 'p-2 ![--rui-icon-size:1.5rem]' } },\n { icon: true, size: '2xl', class: { root: 'p-2 ![--rui-icon-size:1.75rem]' } },\n { variant: 'fab', icon: true, size: 'sm', class: { root: 'px-2 py-2' } },\n ],\n compoundSlots: [\n // Dark mode default variant text override for warning/success/info\n { slots: ['root'], color: ['warning', 'success', 'info'], variant: 'default', class: 'dark:text-rui-light-text' },\n // Dark mode active outlined/text override for primary/secondary\n { slots: ['root'], color: 'primary', variant: ['outlined', 'text', 'list'], active: true, class: 'dark:bg-rui-primary-darker/60 dark:text-rui-primary-lighter' },\n { slots: ['root'], color: 'secondary', variant: ['outlined', 'text', 'list'], active: true, class: 'dark:bg-rui-secondary-darker/60 dark:text-rui-secondary-lighter' },\n ],\n defaultVariants: {\n variant: 'default',\n color: 'grey',\n active: false,\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"button-styles.js","names":[],"sources":["../../../../src/components/buttons/button/button-styles.ts"],"sourcesContent":["import { tv } from '@/utils/tv';\n\nexport const buttonStyles = tv({\n slots: {\n root: [\n 'text-sm leading-5 font-medium outline outline-1 outline-transparent outline-offset-[-1px]',\n // `position: relative` is only needed as a positioning context for the\n // absolute-positioned loading spinner; applied via the `loading` variant\n // below. Putting it here would collide with consumers that need to pin\n // the button with `fixed`/`absolute` (cascade order picks the later\n // utility — usually `relative` — and the consumer's `right-*`/`bottom-*`\n // end up as relative offsets, which misplaces FABs).\n 'flex items-center justify-center gap-x-2',\n 'px-4 py-1.5 rounded transition-all',\n // Default (md) icon sizing is driven by a CSS custom property so that\n // RuiIcon's `size` prop (stamped as inline `style=\"--rui-icon-size\"` on\n // the svg) can override it cleanly. Size variants below redefine the\n // property with `!` so they beat this baseline on the same element;\n // inline style on the svg then beats the inherited value when a\n // consumer supplies their own size. See rotki/ui-library#512.\n '[--rui-icon-size:1.125rem]',\n // `disabled` on the element covers two states: actually disabled and\n // loading (RuiButton sets `disabled = disabled || loading`). The\n // color/bg/text overrides for the \"grey disabled\" look live in the\n // `loading: false` compounds below so they skip firing on loading\n // buttons — where we want the variant color to stay visible behind\n // the spinner. Cursor stays here since both states are non-clickable.\n 'disabled:cursor-not-allowed',\n 'focus-visible:!ring-2',\n ].join(' '),\n label: 'inline-block text-nowrap',\n spinner: 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',\n },\n variants: {\n variant: {\n default: {},\n outlined: {},\n text: { root: 'px-2' },\n fab: { root: 'rounded-full py-2' },\n // `leading-[1.125rem]` on the label collapses the 20px line-box (inherited\n // from the root's `leading-5`) down to 18px so it matches the md icon box\n // (`--rui-icon-size: 1.125rem`). Without it, `flex items-center` centers\n // the 20px line-box against the 18px icon box and the baseline-aligned\n // glyphs visually drift above the icon's optical center — most readable\n // in tight list rows. See rotki/ui-library#515.\n list: { root: 'p-3 px-3 rounded-none w-full justify-start text-left', label: 'w-full leading-[1.125rem]' },\n },\n size: {\n // Each size variant redefines `--rui-icon-size` on the button so the\n // value inherits into RuiIcons in button slots (prepend/append/default)\n // and drives their `width: var(--rui-icon-size, 1.5rem)` class. `!` on\n // the arbitrary property makes the variant beat the md baseline in the\n // base root regardless of CSS source order. A consumer passing `size`\n // on RuiIcon still wins: that path stamps an inline style on the svg\n // element itself, which beats the inherited value from the button.\n 'xs': { root: 'px-2 py-[0.125rem] text-[.75rem] leading-4 ![--rui-icon-size:0.75rem]' },\n 'sm': { root: 'px-2.5 py-1 text-[.8125rem] leading-5 ![--rui-icon-size:1rem]' },\n 'lg': { root: 'px-6 py-2 text-[1rem] leading-5 ![--rui-icon-size:1.25rem]' },\n // `xl` targets the 40px input height — the common toolbar case where a\n // RuiButton needs to line up with RuiTextField / RuiMenuSelect. For a\n // 44px jumbo-CTA button (auth screens, empty-state primaries), reach for\n // `2xl` instead.\n 'xl': { root: 'px-6 py-2 text-[1rem] leading-6 ![--rui-icon-size:1.375rem]' },\n '2xl': { root: 'px-6 py-2.5 text-[1rem] leading-6 ![--rui-icon-size:1.375rem]' },\n },\n color: {\n grey: { root: 'bg-rui-grey-200 hover:bg-rui-grey-100 active:bg-rui-grey-50 text-rui-text ring-rui-grey-400 dark:bg-rui-grey-300 dark:text-rui-light-text dark:ring-rui-grey-600' },\n primary: { root: 'bg-rui-primary hover:bg-rui-primary-darker active:bg-rui-primary-darker/90 text-rui-dark-text ring-rui-primary/40 dark:text-rui-text dark:ring-rui-primary/60' },\n secondary: { root: 'bg-rui-secondary hover:bg-rui-secondary-darker active:bg-rui-secondary-darker/90 text-rui-dark-text ring-rui-secondary/40 dark:text-rui-text dark:ring-rui-secondary/60' },\n error: { root: 'bg-rui-error hover:bg-rui-error-darker active:bg-rui-error-darker/90 text-rui-dark-text ring-rui-error/40 dark:text-rui-text dark:ring-rui-error/60' },\n warning: { root: 'bg-rui-warning hover:bg-rui-warning-darker active:bg-rui-warning-darker/90 text-rui-dark-text ring-rui-warning/40 dark:text-rui-text dark:ring-rui-warning/60' },\n info: { root: 'bg-rui-info hover:bg-rui-info-darker active:bg-rui-info-darker/90 text-rui-dark-text ring-rui-info/40 dark:text-rui-text dark:ring-rui-info/60' },\n success: { root: 'bg-rui-success hover:bg-rui-success-darker active:bg-rui-success-darker/90 text-rui-dark-text ring-rui-success/40 dark:text-rui-text dark:ring-rui-success/60' },\n },\n rounded: {\n true: { root: 'rounded-full' },\n },\n icon: {\n // Padding and icon sizing live in the `icon + size` compound variants\n // below so every size resolves to a height matching its text-button\n // counterpart; this base only contributes the round shape.\n true: { root: 'rounded-full' },\n },\n active: {\n true: {},\n false: {},\n },\n loading: {\n true: { root: 'relative !cursor-progress space-x-0 [&>*:not([data-spinner])]:opacity-0 [&>*:not([data-spinner])]:invisible' },\n false: {},\n },\n hideFocusIndicator: {\n true: { root: 'focus-visible:!ring-0' },\n },\n },\n compoundVariants: [\n // === Disabled (not loading) appearance ===\n // Applied only when `loading: false` so that loading buttons keep their\n // variant color/outline visible behind the spinner; pure disabled buttons\n // fade to the Material disabled palette.\n { loading: false, class: { root: 'disabled:!bg-black/[.12] dark:disabled:!bg-white/[.12] disabled:!text-rui-text-disabled disabled:active:!text-rui-text-disabled' } },\n { loading: false, variant: 'outlined', class: { root: 'disabled:!bg-transparent dark:disabled:!bg-transparent disabled:active:!bg-transparent disabled:outline-rui-text-disabled' } },\n { loading: false, variant: 'text', class: { root: 'disabled:!bg-transparent dark:disabled:!bg-transparent disabled:active:!bg-transparent' } },\n { loading: false, variant: 'list', class: { root: 'disabled:!bg-transparent dark:disabled:!bg-transparent disabled:active:!bg-transparent' } },\n\n // === Grey color variants ===\n { color: 'grey', active: true, class: { root: 'bg-rui-grey-50' } },\n { color: 'grey', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-black/[.04] active:bg-black/10 dark:bg-transparent dark:active:bg-white/10 dark:hover:bg-white/[.04] dark:text-rui-text' } },\n { color: 'grey', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-black/10 dark:bg-white/30' } },\n // Material draws the colourless outlined button at 23% of the text colour, not at\n // full strength: an outline as dark as the label reads as an error state and shouts\n // next to the context colours, which are already at 50% (see below). The rest of the\n // library uses the same restraint for neutral edges (cards and tables sit at 12%).\n { color: 'grey', variant: 'outlined', class: { root: 'outline-black/[0.23] dark:outline-white/[0.23]' } },\n { color: 'grey', variant: 'text', class: { root: 'text-rui-text-secondary' } },\n\n // === Context colors — outlined/text variants ===\n // `dark:text-rui-<color>` is required to beat `dark:text-rui-text` set by\n // the base color variant (meant for filled buttons where text sits on a\n // colored bg). Without the override, outlined/text/list buttons in dark\n // mode render white text against a themed outline/underline.\n { color: 'primary', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-primary-lighter/[.04] active:bg-rui-primary-lighter/10 text-rui-primary dark:text-rui-primary' } },\n { color: 'secondary', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-secondary-lighter/[.04] active:bg-rui-secondary-lighter/10 text-rui-secondary dark:text-rui-secondary' } },\n { color: 'error', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-error-lighter/[.04] active:bg-rui-error-lighter/10 text-rui-error dark:text-rui-error' } },\n { color: 'warning', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-warning-lighter/[.04] active:bg-rui-warning-lighter/10 text-rui-warning dark:text-rui-warning' } },\n { color: 'info', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-info-lighter/[.04] active:bg-rui-info-lighter/10 text-rui-info dark:text-rui-info' } },\n { color: 'success', variant: ['outlined', 'text', 'list'], class: { root: 'bg-transparent hover:bg-rui-success-lighter/[.04] active:bg-rui-success-lighter/10 text-rui-success dark:text-rui-success' } },\n\n // === Context colors — active default ===\n { color: 'primary', active: true, class: { root: 'bg-rui-primary-darker' } },\n { color: 'secondary', active: true, class: { root: 'bg-rui-secondary-darker' } },\n { color: 'error', active: true, class: { root: 'bg-rui-error-darker' } },\n { color: 'warning', active: true, class: { root: 'bg-rui-warning-darker' } },\n { color: 'info', active: true, class: { root: 'bg-rui-info-darker' } },\n { color: 'success', active: true, class: { root: 'bg-rui-success-darker' } },\n\n // === Context colors — active outlined/text ===\n { color: 'primary', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-primary-lighter/30' } },\n { color: 'secondary', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-secondary-lighter/30' } },\n { color: 'error', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-error-lighter/30' } },\n { color: 'warning', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-warning-lighter/30' } },\n { color: 'info', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-info-lighter/30' } },\n { color: 'success', variant: ['outlined', 'text', 'list'], active: true, class: { root: 'bg-rui-success-lighter/30' } },\n\n // === Context colors — outlined border ===\n { color: 'primary', variant: 'outlined', class: { root: 'outline-rui-primary/[0.5]' } },\n { color: 'secondary', variant: 'outlined', class: { root: 'outline-rui-secondary/[0.5]' } },\n { color: 'error', variant: 'outlined', class: { root: 'outline-rui-error/[0.5]' } },\n { color: 'warning', variant: 'outlined', class: { root: 'outline-rui-warning/[0.5]' } },\n { color: 'info', variant: 'outlined', class: { root: 'outline-rui-info/[0.5]' } },\n { color: 'success', variant: 'outlined', class: { root: 'outline-rui-success/[0.5]' } },\n\n // === Size overrides per variant ===\n { variant: 'text', size: 'xs', class: { root: 'px-1' } },\n { variant: 'text', size: 'sm', class: { root: 'px-1.5' } },\n { variant: 'text', size: 'lg', class: { root: 'px-2.5' } },\n { variant: 'text', size: 'xl', class: { root: 'px-2.5' } },\n { variant: 'text', size: '2xl', class: { root: 'px-2.5' } },\n { variant: 'fab', size: 'xs', class: { root: 'py-1 px-1' } },\n { variant: 'fab', size: 'sm', class: { root: 'py-1.5 px-2' } },\n { variant: 'fab', size: 'lg', class: { root: 'py-3' } },\n { variant: 'list', size: 'xs', class: { root: 'px-3 py-0.5' } },\n { variant: 'list', size: 'sm', class: { root: 'px-3 py-1' } },\n // Icon-only button sizing — every size lands at the same height as the\n // text button of the matching size, with a ~60–70% icon ratio (Material-3\n // style). The `size` rule sizes icons to 12/16/20/22px for text-button\n // prepend/append slots; here icon-only buttons get a larger glyph so they\n // don't look lost in the square. Values are rem-based throughout: p-1 =\n // 0.25rem, p-1.5 = 0.375rem, p-2 = 0.5rem; glyph sizes use rem literals.\n // xs uses an arbitrary 0.1875rem (3px) padding — no Tailwind token hits\n // it cleanly — to keep the 70% icon ratio with a 14px glyph.\n //\n // xs: p-[0.1875rem] + 0.875rem icon = 0.375 + 0.875 = 1.25rem (20px) — 70%\n // sm: p-1 + 1.25rem icon = 0.5 + 1.25 = 1.75rem (28px) — 71%\n // md (default): p-1.5 + 1.25rem icon = 0.75 + 1.25 = 2rem (32px) — 63%\n // lg: p-1.5 + 1.5rem icon = 0.75 + 1.5 = 2.25rem (36px) — 67%\n // xl: p-2 + 1.5rem icon = 1 + 1.5 = 2.5rem (40px) — 60%\n // 2xl: p-2 + 1.75rem icon = 1 + 1.75 = 2.75rem (44px) — 64%\n { icon: true, class: { root: 'p-1.5 ![--rui-icon-size:1.25rem]' } },\n { icon: true, size: 'xs', class: { root: 'p-[0.1875rem] ![--rui-icon-size:0.875rem]' } },\n { icon: true, size: 'sm', class: { root: 'p-1 ![--rui-icon-size:1.25rem]' } },\n { icon: true, size: 'lg', class: { root: 'p-1.5 ![--rui-icon-size:1.5rem]' } },\n { icon: true, size: 'xl', class: { root: 'p-2 ![--rui-icon-size:1.5rem]' } },\n { icon: true, size: '2xl', class: { root: 'p-2 ![--rui-icon-size:1.75rem]' } },\n { variant: 'fab', icon: true, size: 'sm', class: { root: 'px-2 py-2' } },\n ],\n compoundSlots: [\n // Dark mode default variant text override for warning/success/info\n { slots: ['root'], color: ['warning', 'success', 'info'], variant: 'default', class: 'dark:text-rui-light-text' },\n // Dark mode active outlined/text override for primary/secondary\n { slots: ['root'], color: 'primary', variant: ['outlined', 'text', 'list'], active: true, class: 'dark:bg-rui-primary-darker/60 dark:text-rui-primary-lighter' },\n { slots: ['root'], color: 'secondary', variant: ['outlined', 'text', 'list'], active: true, class: 'dark:bg-rui-secondary-darker/60 dark:text-rui-secondary-lighter' },\n ],\n defaultVariants: {\n variant: 'default',\n color: 'grey',\n active: false,\n },\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RuiButtonGroup.js","names":[],"sources":["../../../../src/components/buttons/button-group/RuiButtonGroup.vue"],"sourcesContent":["<script lang=\"ts\" generic=\"T = undefined\" setup>\nimport type { ButtonSize } from '@/components/buttons/button/button-props';\nimport type { ContextColorsType } from '@/consts/colors';\nimport { Fragment, isVNode } from 'vue';\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n vertical?: boolean;\n color?: ContextColorsType;\n activeColor?: ContextColorsType;\n variant?: 'default' | 'outlined' | 'text';\n size?: ButtonSize;\n gap?: 'sm' | 'md' | 'lg';\n required?: boolean;\n disabled?: boolean;\n}\n\ndefineOptions({\n name: 'RuiButtonGroup',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<T | T[]>();\n\nconst {\n vertical = false,\n color,\n activeColor,\n variant = 'default',\n size,\n gap,\n required = false,\n disabled = false,\n} = defineProps<Props>();\n\nconst slots = useSlots();\n\nconst buttonGroupStyles = tv({\n slots: {\n root: 'inline-flex rounded divide-x divide-rui-grey-400 outline outline-1 outline-transparent outline-offset-[-1px]',\n button: 'border-0 outline-0 focus:z-[1]',\n },\n variants: {\n vertical: {\n true: {\n root: 'flex-col items-start divide-x-0 divide-y',\n button: 'w-full',\n },\n false: {},\n },\n gap: {\n none: {\n button: '!rounded-none',\n },\n sm: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-2',\n button: 'outline-1',\n },\n md: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-4',\n button: 'outline-1',\n },\n lg: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-6',\n button: 'outline-1',\n },\n },\n variant: {\n default: {},\n outlined: {\n root: 'outline-rui-text divide-rui-text',\n },\n text: {},\n },\n color: {\n primary: {},\n secondary: {},\n error: {},\n warning: {},\n info: {},\n success: {},\n },\n },\n compoundVariants: [\n // First/last child rounding when not separated (! needed to override RuiButton's CSS module border-radius)\n { gap: 'none', vertical: false, class: { button: 'first:!rounded-l last:!rounded-r' } },\n { gap: 'none', vertical: true, class: { button: 'first:!rounded-t last:!rounded-b' } },\n\n // Color dividers (default variant)\n { color: 'primary', class: { root: 'divide-rui-primary-darker' } },\n { color: 'secondary', class: { root: 'divide-rui-secondary-darker' } },\n { color: 'error', class: { root: 'divide-rui-error-darker' } },\n { color: 'warning', class: { root: 'divide-rui-warning-darker' } },\n { color: 'info', class: { root: 'divide-rui-info-darker' } },\n { color: 'success', class: { root: 'divide-rui-success-darker' } },\n\n // Color dividers for outlined/text (overrides darker dividers above)\n { color: 'primary', variant: ['outlined', 'text'], class: { root: 'divide-rui-primary/[0.5]' } },\n { color: 'secondary', variant: ['outlined', 'text'], class: { root: 'divide-rui-secondary/[0.5]' } },\n { color: 'error', variant: ['outlined', 'text'], class: { root: 'divide-rui-error/[0.5]' } },\n { color: 'warning', variant: ['outlined', 'text'], class: { root: 'divide-rui-warning/[0.5]' } },\n { color: 'info', variant: ['outlined', 'text'], class: { root: 'divide-rui-info/[0.5]' } },\n { color: 'success', variant: ['outlined', 'text'], class: { root: 'divide-rui-success/[0.5]' } },\n\n // Color outline for outlined variant\n { color: 'primary', variant: 'outlined', class: { root: 'outline-rui-primary/[0.5]' } },\n { color: 'secondary', variant: 'outlined', class: { root: 'outline-rui-secondary/[0.5]' } },\n { color: 'error', variant: 'outlined', class: { root: 'outline-rui-error/[0.5]' } },\n { color: 'warning', variant: 'outlined', class: { root: 'outline-rui-warning/[0.5]' } },\n { color: 'info', variant: 'outlined', class: { root: 'outline-rui-info/[0.5]' } },\n { color: 'success', variant: 'outlined', class: { root: 'outline-rui-success/[0.5]' } },\n ],\n defaultVariants: {\n vertical: false,\n gap: 'none',\n variant: 'default',\n },\n});\n\nconst ui = computed<ReturnType<typeof buttonGroupStyles>>(() => buttonGroupStyles({\n vertical,\n gap: gap ?? 'none',\n variant,\n color,\n}));\n\nconst children = computed<VNode[]>(() => {\n const selectedValue: T | T[] | undefined = get(modelValue);\n const slotContent = slots.default?.() ?? [];\n const resolved = flattenSlotContent(slotContent);\n\n return resolved.map((child, i) => {\n // child props are in kebab-case\n const value = child.props?.['model-value'];\n const active = isActive(value ?? i, selectedValue);\n const resolvedColor = active && activeColor ? activeColor : color;\n\n // Only fall back to the group's size when the child did not set one itself.\n // This lets consumers override size per-button while still benefiting from\n // the group-level default.\n const childSize = child.props?.size;\n\n child.props = {\n ...child.props,\n active,\n ...(disabled && { disabled: true }),\n ...(resolvedColor && { color: resolvedColor }),\n ...(!childSize && size && { size }),\n };\n\n return child;\n });\n});\n\n/**\n * Flattens slot content by unwrapping Fragments (created by v-for).\n * Returns only actual VNode children.\n */\nfunction flattenSlotContent(nodes: VNode[]): VNode[] {\n return nodes.flatMap((node) => {\n if (node.type === Fragment && Array.isArray(node.children) && node.children.length > 0)\n return flattenSlotContent(node.children.filter(isVNode));\n\n return [node];\n });\n}\n\nfunction isActive(id: T, selected?: T | T[]): boolean {\n if (Array.isArray(selected))\n return selected.includes(id);\n\n return selected === id;\n}\n\nfunction onClick(id: T): void {\n const selected = get(modelValue);\n\n if (Array.isArray(selected)) {\n const index = selected.indexOf(id);\n if (index >= 0) {\n if (required && selected.length === 1)\n return;\n\n set(modelValue, selected.filter((_, i) => i !== index));\n }\n else {\n set(modelValue, [...selected, id]);\n }\n }\n else if (required) {\n set(modelValue, id);\n }\n else {\n set(modelValue, isActive(id, selected) ? undefined : id);\n }\n}\n</script>\n\n<template>\n <div\n :class=\"ui.root()\"\n v-bind=\"$attrs\"\n >\n <Component\n :is=\"child\"\n v-for=\"(child, i) in children\"\n :key=\"i\"\n :class=\"ui.button()\"\n :variant=\"variant\"\n @update:model-value=\"onClick($event ?? i)\"\n />\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiButtonGroup.js","names":[],"sources":["../../../../src/components/buttons/button-group/RuiButtonGroup.vue"],"sourcesContent":["<script lang=\"ts\" generic=\"T = undefined\" setup>\nimport type { ButtonSize } from '@/components/buttons/button/button-props';\nimport type { ContextColorsType } from '@/consts/colors';\nimport { Fragment, isVNode } from 'vue';\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n vertical?: boolean;\n color?: ContextColorsType;\n activeColor?: ContextColorsType;\n variant?: 'default' | 'outlined' | 'text';\n size?: ButtonSize;\n gap?: 'sm' | 'md' | 'lg';\n required?: boolean;\n disabled?: boolean;\n}\n\ndefineOptions({\n name: 'RuiButtonGroup',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<T | T[]>();\n\nconst {\n vertical = false,\n color,\n activeColor,\n variant = 'default',\n size,\n gap,\n required = false,\n disabled = false,\n} = defineProps<Props>();\n\nconst slots = useSlots();\n\nconst buttonGroupStyles = tv({\n slots: {\n root: 'inline-flex rounded divide-x divide-rui-grey-400 outline outline-1 outline-transparent outline-offset-[-1px]',\n button: 'border-0 outline-0 focus:z-[1]',\n },\n variants: {\n vertical: {\n true: {\n root: 'flex-col items-start divide-x-0 divide-y',\n button: 'w-full',\n },\n false: {},\n },\n gap: {\n none: {\n button: '!rounded-none',\n },\n sm: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-2',\n button: 'outline-1',\n },\n md: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-4',\n button: 'outline-1',\n },\n lg: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-6',\n button: 'outline-1',\n },\n },\n variant: {\n default: {},\n outlined: {\n // Matches RuiButton's colourless outlined treatment: a neutral edge at Material's\n // 23%, rather than an outline and dividers as dark as the labels themselves.\n root: 'outline-black/[0.23] divide-black/[0.23] dark:outline-white/[0.23] dark:divide-white/[0.23]',\n },\n text: {},\n },\n color: {\n primary: {},\n secondary: {},\n error: {},\n warning: {},\n info: {},\n success: {},\n },\n },\n compoundVariants: [\n // First/last child rounding when not separated (! needed to override RuiButton's CSS module border-radius)\n { gap: 'none', vertical: false, class: { button: 'first:!rounded-l last:!rounded-r' } },\n { gap: 'none', vertical: true, class: { button: 'first:!rounded-t last:!rounded-b' } },\n\n // Color dividers (default variant)\n { color: 'primary', class: { root: 'divide-rui-primary-darker' } },\n { color: 'secondary', class: { root: 'divide-rui-secondary-darker' } },\n { color: 'error', class: { root: 'divide-rui-error-darker' } },\n { color: 'warning', class: { root: 'divide-rui-warning-darker' } },\n { color: 'info', class: { root: 'divide-rui-info-darker' } },\n { color: 'success', class: { root: 'divide-rui-success-darker' } },\n\n // Color dividers for outlined/text (overrides darker dividers above)\n { color: 'primary', variant: ['outlined', 'text'], class: { root: 'divide-rui-primary/[0.5]' } },\n { color: 'secondary', variant: ['outlined', 'text'], class: { root: 'divide-rui-secondary/[0.5]' } },\n { color: 'error', variant: ['outlined', 'text'], class: { root: 'divide-rui-error/[0.5]' } },\n { color: 'warning', variant: ['outlined', 'text'], class: { root: 'divide-rui-warning/[0.5]' } },\n { color: 'info', variant: ['outlined', 'text'], class: { root: 'divide-rui-info/[0.5]' } },\n { color: 'success', variant: ['outlined', 'text'], class: { root: 'divide-rui-success/[0.5]' } },\n\n // Color outline for outlined variant\n { color: 'primary', variant: 'outlined', class: { root: 'outline-rui-primary/[0.5]' } },\n { color: 'secondary', variant: 'outlined', class: { root: 'outline-rui-secondary/[0.5]' } },\n { color: 'error', variant: 'outlined', class: { root: 'outline-rui-error/[0.5]' } },\n { color: 'warning', variant: 'outlined', class: { root: 'outline-rui-warning/[0.5]' } },\n { color: 'info', variant: 'outlined', class: { root: 'outline-rui-info/[0.5]' } },\n { color: 'success', variant: 'outlined', class: { root: 'outline-rui-success/[0.5]' } },\n ],\n defaultVariants: {\n vertical: false,\n gap: 'none',\n variant: 'default',\n },\n});\n\nconst ui = computed<ReturnType<typeof buttonGroupStyles>>(() => buttonGroupStyles({\n vertical,\n gap: gap ?? 'none',\n variant,\n color,\n}));\n\nconst children = computed<VNode[]>(() => {\n const selectedValue: T | T[] | undefined = get(modelValue);\n const slotContent = slots.default?.() ?? [];\n const resolved = flattenSlotContent(slotContent);\n\n return resolved.map((child, i) => {\n // child props are in kebab-case\n const value = child.props?.['model-value'];\n const active = isActive(value ?? i, selectedValue);\n const resolvedColor = active && activeColor ? activeColor : color;\n\n // Only fall back to the group's size when the child did not set one itself.\n // This lets consumers override size per-button while still benefiting from\n // the group-level default.\n const childSize = child.props?.size;\n\n child.props = {\n ...child.props,\n active,\n ...(disabled && { disabled: true }),\n ...(resolvedColor && { color: resolvedColor }),\n ...(!childSize && size && { size }),\n };\n\n return child;\n });\n});\n\n/**\n * Flattens slot content by unwrapping Fragments (created by v-for).\n * Returns only actual VNode children.\n */\nfunction flattenSlotContent(nodes: VNode[]): VNode[] {\n return nodes.flatMap((node) => {\n if (node.type === Fragment && Array.isArray(node.children) && node.children.length > 0)\n return flattenSlotContent(node.children.filter(isVNode));\n\n return [node];\n });\n}\n\nfunction isActive(id: T, selected?: T | T[]): boolean {\n if (Array.isArray(selected))\n return selected.includes(id);\n\n return selected === id;\n}\n\nfunction onClick(id: T): void {\n const selected = get(modelValue);\n\n if (Array.isArray(selected)) {\n const index = selected.indexOf(id);\n if (index >= 0) {\n if (required && selected.length === 1)\n return;\n\n set(modelValue, selected.filter((_, i) => i !== index));\n }\n else {\n set(modelValue, [...selected, id]);\n }\n }\n else if (required) {\n set(modelValue, id);\n }\n else {\n set(modelValue, isActive(id, selected) ? undefined : id);\n }\n}\n</script>\n\n<template>\n <div\n :class=\"ui.root()\"\n v-bind=\"$attrs\"\n >\n <Component\n :is=\"child\"\n v-for=\"(child, i) in children\"\n :key=\"i\"\n :class=\"ui.button()\"\n :variant=\"variant\"\n @update:model-value=\"onClick($event ?? i)\"\n />\n </div>\n</template>\n"],"mappings":""}
@@ -62,7 +62,7 @@ var RuiButtonGroup_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
62
62
  },
63
63
  variant: {
64
64
  default: {},
65
- outlined: { root: "outline-rui-text divide-rui-text" },
65
+ outlined: { root: "outline-black/[0.23] divide-black/[0.23] dark:outline-white/[0.23] dark:divide-white/[0.23]" },
66
66
  text: {}
67
67
  },
68
68
  color: {
@@ -1 +1 @@
1
- {"version":3,"file":"RuiButtonGroup.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"sources":["../../../../src/components/buttons/button-group/RuiButtonGroup.vue"],"sourcesContent":["<script lang=\"ts\" generic=\"T = undefined\" setup>\nimport type { ButtonSize } from '@/components/buttons/button/button-props';\nimport type { ContextColorsType } from '@/consts/colors';\nimport { Fragment, isVNode } from 'vue';\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n vertical?: boolean;\n color?: ContextColorsType;\n activeColor?: ContextColorsType;\n variant?: 'default' | 'outlined' | 'text';\n size?: ButtonSize;\n gap?: 'sm' | 'md' | 'lg';\n required?: boolean;\n disabled?: boolean;\n}\n\ndefineOptions({\n name: 'RuiButtonGroup',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<T | T[]>();\n\nconst {\n vertical = false,\n color,\n activeColor,\n variant = 'default',\n size,\n gap,\n required = false,\n disabled = false,\n} = defineProps<Props>();\n\nconst slots = useSlots();\n\nconst buttonGroupStyles = tv({\n slots: {\n root: 'inline-flex rounded divide-x divide-rui-grey-400 outline outline-1 outline-transparent outline-offset-[-1px]',\n button: 'border-0 outline-0 focus:z-[1]',\n },\n variants: {\n vertical: {\n true: {\n root: 'flex-col items-start divide-x-0 divide-y',\n button: 'w-full',\n },\n false: {},\n },\n gap: {\n none: {\n button: '!rounded-none',\n },\n sm: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-2',\n button: 'outline-1',\n },\n md: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-4',\n button: 'outline-1',\n },\n lg: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-6',\n button: 'outline-1',\n },\n },\n variant: {\n default: {},\n outlined: {\n root: 'outline-rui-text divide-rui-text',\n },\n text: {},\n },\n color: {\n primary: {},\n secondary: {},\n error: {},\n warning: {},\n info: {},\n success: {},\n },\n },\n compoundVariants: [\n // First/last child rounding when not separated (! needed to override RuiButton's CSS module border-radius)\n { gap: 'none', vertical: false, class: { button: 'first:!rounded-l last:!rounded-r' } },\n { gap: 'none', vertical: true, class: { button: 'first:!rounded-t last:!rounded-b' } },\n\n // Color dividers (default variant)\n { color: 'primary', class: { root: 'divide-rui-primary-darker' } },\n { color: 'secondary', class: { root: 'divide-rui-secondary-darker' } },\n { color: 'error', class: { root: 'divide-rui-error-darker' } },\n { color: 'warning', class: { root: 'divide-rui-warning-darker' } },\n { color: 'info', class: { root: 'divide-rui-info-darker' } },\n { color: 'success', class: { root: 'divide-rui-success-darker' } },\n\n // Color dividers for outlined/text (overrides darker dividers above)\n { color: 'primary', variant: ['outlined', 'text'], class: { root: 'divide-rui-primary/[0.5]' } },\n { color: 'secondary', variant: ['outlined', 'text'], class: { root: 'divide-rui-secondary/[0.5]' } },\n { color: 'error', variant: ['outlined', 'text'], class: { root: 'divide-rui-error/[0.5]' } },\n { color: 'warning', variant: ['outlined', 'text'], class: { root: 'divide-rui-warning/[0.5]' } },\n { color: 'info', variant: ['outlined', 'text'], class: { root: 'divide-rui-info/[0.5]' } },\n { color: 'success', variant: ['outlined', 'text'], class: { root: 'divide-rui-success/[0.5]' } },\n\n // Color outline for outlined variant\n { color: 'primary', variant: 'outlined', class: { root: 'outline-rui-primary/[0.5]' } },\n { color: 'secondary', variant: 'outlined', class: { root: 'outline-rui-secondary/[0.5]' } },\n { color: 'error', variant: 'outlined', class: { root: 'outline-rui-error/[0.5]' } },\n { color: 'warning', variant: 'outlined', class: { root: 'outline-rui-warning/[0.5]' } },\n { color: 'info', variant: 'outlined', class: { root: 'outline-rui-info/[0.5]' } },\n { color: 'success', variant: 'outlined', class: { root: 'outline-rui-success/[0.5]' } },\n ],\n defaultVariants: {\n vertical: false,\n gap: 'none',\n variant: 'default',\n },\n});\n\nconst ui = computed<ReturnType<typeof buttonGroupStyles>>(() => buttonGroupStyles({\n vertical,\n gap: gap ?? 'none',\n variant,\n color,\n}));\n\nconst children = computed<VNode[]>(() => {\n const selectedValue: T | T[] | undefined = get(modelValue);\n const slotContent = slots.default?.() ?? [];\n const resolved = flattenSlotContent(slotContent);\n\n return resolved.map((child, i) => {\n // child props are in kebab-case\n const value = child.props?.['model-value'];\n const active = isActive(value ?? i, selectedValue);\n const resolvedColor = active && activeColor ? activeColor : color;\n\n // Only fall back to the group's size when the child did not set one itself.\n // This lets consumers override size per-button while still benefiting from\n // the group-level default.\n const childSize = child.props?.size;\n\n child.props = {\n ...child.props,\n active,\n ...(disabled && { disabled: true }),\n ...(resolvedColor && { color: resolvedColor }),\n ...(!childSize && size && { size }),\n };\n\n return child;\n });\n});\n\n/**\n * Flattens slot content by unwrapping Fragments (created by v-for).\n * Returns only actual VNode children.\n */\nfunction flattenSlotContent(nodes: VNode[]): VNode[] {\n return nodes.flatMap((node) => {\n if (node.type === Fragment && Array.isArray(node.children) && node.children.length > 0)\n return flattenSlotContent(node.children.filter(isVNode));\n\n return [node];\n });\n}\n\nfunction isActive(id: T, selected?: T | T[]): boolean {\n if (Array.isArray(selected))\n return selected.includes(id);\n\n return selected === id;\n}\n\nfunction onClick(id: T): void {\n const selected = get(modelValue);\n\n if (Array.isArray(selected)) {\n const index = selected.indexOf(id);\n if (index >= 0) {\n if (required && selected.length === 1)\n return;\n\n set(modelValue, selected.filter((_, i) => i !== index));\n }\n else {\n set(modelValue, [...selected, id]);\n }\n }\n else if (required) {\n set(modelValue, id);\n }\n else {\n set(modelValue, isActive(id, selected) ? undefined : id);\n }\n}\n</script>\n\n<template>\n <div\n :class=\"ui.root()\"\n v-bind=\"$attrs\"\n >\n <Component\n :is=\"child\"\n v-for=\"(child, i) in children\"\n :key=\"i\"\n :class=\"ui.button()\"\n :variant=\"variant\"\n @update:model-value=\"onClick($event ?? i)\"\n />\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"RuiButtonGroup.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"sources":["../../../../src/components/buttons/button-group/RuiButtonGroup.vue"],"sourcesContent":["<script lang=\"ts\" generic=\"T = undefined\" setup>\nimport type { ButtonSize } from '@/components/buttons/button/button-props';\nimport type { ContextColorsType } from '@/consts/colors';\nimport { Fragment, isVNode } from 'vue';\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n vertical?: boolean;\n color?: ContextColorsType;\n activeColor?: ContextColorsType;\n variant?: 'default' | 'outlined' | 'text';\n size?: ButtonSize;\n gap?: 'sm' | 'md' | 'lg';\n required?: boolean;\n disabled?: boolean;\n}\n\ndefineOptions({\n name: 'RuiButtonGroup',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<T | T[]>();\n\nconst {\n vertical = false,\n color,\n activeColor,\n variant = 'default',\n size,\n gap,\n required = false,\n disabled = false,\n} = defineProps<Props>();\n\nconst slots = useSlots();\n\nconst buttonGroupStyles = tv({\n slots: {\n root: 'inline-flex rounded divide-x divide-rui-grey-400 outline outline-1 outline-transparent outline-offset-[-1px]',\n button: 'border-0 outline-0 focus:z-[1]',\n },\n variants: {\n vertical: {\n true: {\n root: 'flex-col items-start divide-x-0 divide-y',\n button: 'w-full',\n },\n false: {},\n },\n gap: {\n none: {\n button: '!rounded-none',\n },\n sm: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-2',\n button: 'outline-1',\n },\n md: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-4',\n button: 'outline-1',\n },\n lg: {\n root: 'divide-x-0 divide-y-0 outline-0 gap-6',\n button: 'outline-1',\n },\n },\n variant: {\n default: {},\n outlined: {\n // Matches RuiButton's colourless outlined treatment: a neutral edge at Material's\n // 23%, rather than an outline and dividers as dark as the labels themselves.\n root: 'outline-black/[0.23] divide-black/[0.23] dark:outline-white/[0.23] dark:divide-white/[0.23]',\n },\n text: {},\n },\n color: {\n primary: {},\n secondary: {},\n error: {},\n warning: {},\n info: {},\n success: {},\n },\n },\n compoundVariants: [\n // First/last child rounding when not separated (! needed to override RuiButton's CSS module border-radius)\n { gap: 'none', vertical: false, class: { button: 'first:!rounded-l last:!rounded-r' } },\n { gap: 'none', vertical: true, class: { button: 'first:!rounded-t last:!rounded-b' } },\n\n // Color dividers (default variant)\n { color: 'primary', class: { root: 'divide-rui-primary-darker' } },\n { color: 'secondary', class: { root: 'divide-rui-secondary-darker' } },\n { color: 'error', class: { root: 'divide-rui-error-darker' } },\n { color: 'warning', class: { root: 'divide-rui-warning-darker' } },\n { color: 'info', class: { root: 'divide-rui-info-darker' } },\n { color: 'success', class: { root: 'divide-rui-success-darker' } },\n\n // Color dividers for outlined/text (overrides darker dividers above)\n { color: 'primary', variant: ['outlined', 'text'], class: { root: 'divide-rui-primary/[0.5]' } },\n { color: 'secondary', variant: ['outlined', 'text'], class: { root: 'divide-rui-secondary/[0.5]' } },\n { color: 'error', variant: ['outlined', 'text'], class: { root: 'divide-rui-error/[0.5]' } },\n { color: 'warning', variant: ['outlined', 'text'], class: { root: 'divide-rui-warning/[0.5]' } },\n { color: 'info', variant: ['outlined', 'text'], class: { root: 'divide-rui-info/[0.5]' } },\n { color: 'success', variant: ['outlined', 'text'], class: { root: 'divide-rui-success/[0.5]' } },\n\n // Color outline for outlined variant\n { color: 'primary', variant: 'outlined', class: { root: 'outline-rui-primary/[0.5]' } },\n { color: 'secondary', variant: 'outlined', class: { root: 'outline-rui-secondary/[0.5]' } },\n { color: 'error', variant: 'outlined', class: { root: 'outline-rui-error/[0.5]' } },\n { color: 'warning', variant: 'outlined', class: { root: 'outline-rui-warning/[0.5]' } },\n { color: 'info', variant: 'outlined', class: { root: 'outline-rui-info/[0.5]' } },\n { color: 'success', variant: 'outlined', class: { root: 'outline-rui-success/[0.5]' } },\n ],\n defaultVariants: {\n vertical: false,\n gap: 'none',\n variant: 'default',\n },\n});\n\nconst ui = computed<ReturnType<typeof buttonGroupStyles>>(() => buttonGroupStyles({\n vertical,\n gap: gap ?? 'none',\n variant,\n color,\n}));\n\nconst children = computed<VNode[]>(() => {\n const selectedValue: T | T[] | undefined = get(modelValue);\n const slotContent = slots.default?.() ?? [];\n const resolved = flattenSlotContent(slotContent);\n\n return resolved.map((child, i) => {\n // child props are in kebab-case\n const value = child.props?.['model-value'];\n const active = isActive(value ?? i, selectedValue);\n const resolvedColor = active && activeColor ? activeColor : color;\n\n // Only fall back to the group's size when the child did not set one itself.\n // This lets consumers override size per-button while still benefiting from\n // the group-level default.\n const childSize = child.props?.size;\n\n child.props = {\n ...child.props,\n active,\n ...(disabled && { disabled: true }),\n ...(resolvedColor && { color: resolvedColor }),\n ...(!childSize && size && { size }),\n };\n\n return child;\n });\n});\n\n/**\n * Flattens slot content by unwrapping Fragments (created by v-for).\n * Returns only actual VNode children.\n */\nfunction flattenSlotContent(nodes: VNode[]): VNode[] {\n return nodes.flatMap((node) => {\n if (node.type === Fragment && Array.isArray(node.children) && node.children.length > 0)\n return flattenSlotContent(node.children.filter(isVNode));\n\n return [node];\n });\n}\n\nfunction isActive(id: T, selected?: T | T[]): boolean {\n if (Array.isArray(selected))\n return selected.includes(id);\n\n return selected === id;\n}\n\nfunction onClick(id: T): void {\n const selected = get(modelValue);\n\n if (Array.isArray(selected)) {\n const index = selected.indexOf(id);\n if (index >= 0) {\n if (required && selected.length === 1)\n return;\n\n set(modelValue, selected.filter((_, i) => i !== index));\n }\n else {\n set(modelValue, [...selected, id]);\n }\n }\n else if (required) {\n set(modelValue, id);\n }\n else {\n set(modelValue, isActive(id, selected) ? undefined : id);\n }\n}\n</script>\n\n<template>\n <div\n :class=\"ui.root()\"\n v-bind=\"$attrs\"\n >\n <Component\n :is=\"child\"\n v-for=\"(child, i) in children\"\n :key=\"i\"\n :class=\"ui.button()\"\n :variant=\"variant\"\n @update:model-value=\"onClick($event ?? i)\"\n />\n </div>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RuiCategoryPicker.js","names":[],"sources":["../../../../src/components/forms/category-picker/RuiCategoryPicker.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { KeyOfType } from '@/composables/dropdown-menu';\nimport type { VueClassValue } from '@/types/class-value';\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { categoryPickerActivatorStyles, categoryPickerStyles, type CategoryPickerVariant } from '@/components/forms/category-picker/category-picker-styles';\nimport RuiTextField from '@/components/forms/text-field/RuiTextField.vue';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiDialog, { type DialogProps } from '@/components/overlays/dialog/RuiDialog.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport { Placement } from '@/composables/floating';\nimport { useCategoryPicker } from '@/composables/forms/category-picker/use-category-picker';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn } from '@/utils/tv';\n\nexport interface RuiCategoryPickerClassNames {\n root?: VueClassValue;\n rail?: VueClassValue;\n detail?: VueClassValue;\n search?: VueClassValue;\n}\n\nexport type CategoryPickerBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport interface RuiCategoryPickerProps<TValue, TItem> {\n /** The full catalogue of selectable items. */\n items: TItem[];\n /** Resolve an item's category label. Takes precedence over `categoryAttr`. */\n categoryOf?: (item: TItem) => string;\n /** Resolve an item's category from one of its own properties. */\n categoryAttr?: keyof TItem;\n /** Property whose value becomes the model value. Omit to use the item itself. */\n keyAttr?: KeyOfType<TItem, TValue extends (infer U)[] ? U : TValue>;\n /** Property that produces an item's display text. */\n textAttr?: keyof TItem;\n /** Field label, and the title of the mobile sheet. */\n label?: string;\n /** Placeholder shown in the field while it is empty or being searched. */\n placeholder?: string;\n /** Allow filtering: type in the field (desktop) or the sheet's search box (mobile). */\n searchable?: boolean;\n /**\n * Custom predicate. Receives the resolved category label as the third\n * argument, matching the `RuiAutoComplete` filter signature.\n */\n filter?: (item: TItem, query: string, category: string) => boolean;\n /** Show the synthetic \"All\" pseudo-category as the first rail entry. */\n showAll?: boolean;\n /** Label for the \"All\" pseudo-category. */\n allLabel?: string;\n /** Text shown when no item matches the current search. */\n emptyText?: string;\n disabled?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n loading?: boolean;\n /** Trigger field style: underline (`default`), `outlined`, or `filled`. */\n variant?: CategoryPickerVariant;\n /** Marks the trigger field as required (renders the asterisk). */\n required?: boolean;\n /** Validation errors rendered under the trigger field. */\n errorMessages?: string | string[];\n /** Success messages rendered under the trigger field. */\n successMessages?: string | string[];\n /** Hint rendered under the trigger field. */\n hint?: string;\n /** Hide the details row (errors/success/hint) under the trigger field. */\n hideDetails?: boolean;\n /** Below this breakpoint the picker collapses to a single-pane drill-in. */\n mobileBreakpoint?: CategoryPickerBreakpoint;\n /** Forwarded to the mobile bottom-sheet (`RuiDialog`). */\n dialogOptions?: DialogProps;\n classNames?: RuiCategoryPickerClassNames;\n}\n\ndefineOptions({\n name: 'RuiCategoryPicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<TValue>();\nconst searchInput = defineModel<string>('search', { default: '' });\n\nconst {\n items,\n categoryOf = undefined,\n categoryAttr = undefined,\n keyAttr = undefined,\n textAttr = undefined,\n label = 'Select',\n placeholder = 'Search',\n searchable = true,\n filter = undefined,\n showAll = true,\n allLabel = 'All',\n emptyText = 'No results',\n disabled = false,\n readOnly = false,\n dense = false,\n clearable = false,\n loading = false,\n variant = 'outlined',\n required = false,\n errorMessages = [],\n successMessages = [],\n hint = '',\n hideDetails = false,\n mobileBreakpoint = 'md',\n dialogOptions = undefined,\n classNames = undefined,\n} = defineProps<RuiCategoryPickerProps<TValue, TItem>>();\n\nconst emit = defineEmits<{\n select: [item: TItem];\n}>();\n\ndefineSlots<{\n 'activator'?: (props: { attrs: { onClick?: () => void; onMouseover?: () => void; onMouseleave?: () => void }; isOpen: boolean; open: () => void; item: TItem | undefined; text: string | undefined }) => any;\n 'activator.label'?: (props: { item: TItem | undefined }) => any;\n 'selection'?: (props: { item: TItem }) => any;\n 'selection.prepend'?: (props: { item: TItem }) => any;\n 'category'?: (props: { category: string | null; label: string; active: boolean; count: number }) => any;\n 'item'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.prepend'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.append'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'empty'?: () => any;\n 'header'?: () => any;\n 'footer'?: (props: { close: () => void }) => any;\n}>();\n\nconst isOpen = ref<boolean>(false);\nconst focusedPane = ref<'rail' | 'detail'>('rail');\nconst highlightedItemIndex = ref<number>(0);\nconst drillStep = ref<'category' | 'detail'>('category');\n\nconst railRef = useTemplateRef<HTMLDivElement>('railRef');\nconst detailRef = useTemplateRef<HTMLDivElement>('detailRef');\nconst activatorRef = useTemplateRef<HTMLInputElement>('activatorRef');\nconst fieldRef = useTemplateRef<HTMLElement>('fieldRef');\nconst isHovered = ref<boolean>(false);\n\nconst { focused } = useFocus(activatorRef);\nconst { hasError, hasSuccess } = useFormTextDetail(() => errorMessages, () => successMessages);\n\nconst baseId = useId();\nconst breakpoints = useBreakpoints(breakpointsTailwind);\n\nfunction resolveCategory(item: TItem): string {\n if (categoryOf)\n return categoryOf(item);\n if (categoryAttr !== undefined)\n return String(item[categoryAttr] ?? '');\n return '';\n}\n\nconst {\n activeCategory,\n detailItems,\n detailRows,\n getIdentifier,\n getText,\n isEmpty,\n isSelected,\n railEntries,\n setActiveCategory,\n syncActiveToSelection,\n} = useCategoryPicker<TValue, TItem>({\n allLabel: () => allLabel,\n categoryOf: resolveCategory,\n filter: () => filter,\n items: () => items,\n keyAttr,\n modelValue,\n search: searchInput,\n searchable: () => searchable,\n showAll: () => showAll,\n textAttr,\n});\n\nconst isMobile = computed<boolean>(() => get(breakpoints.smaller(mobileBreakpoint)));\n\nconst mode = computed<'twoPane' | 'drill'>(() => (get(isMobile) ? 'drill' : 'twoPane'));\n\n// Desktop anchors an outside-click popover under the field (RuiMenu); narrow\n// width shows a full-width bottom sheet (RuiDialog). Each branch returns only\n// its own shell's props so the other component's props never leak as attrs.\nconst shell = computed<typeof RuiMenu | typeof RuiDialog>(() => (get(isMobile) ? RuiDialog : RuiMenu));\n\nconst shellProps = computed<Record<string, unknown>>(() => {\n if (get(isMobile)) {\n return {\n ariaLabel: dialogOptions?.ariaLabel ?? label,\n bottomSheet: dialogOptions?.bottomSheet ?? true,\n maxWidth: dialogOptions?.maxWidth,\n persistent: dialogOptions?.persistent,\n width: dialogOptions?.width ?? '100%',\n };\n }\n return {\n // Anchor to the field box, not the wrapper, so the popover sits directly\n // under the input instead of below the reserved details row.\n anchorEl: get(fieldRef) ?? undefined,\n classNames: { menu: 'z-[9999]' },\n closeOnContentClick: false,\n disableAutoFocus: true,\n fullWidth: true,\n options: { placement: Placement.bottomStart },\n persistent: dialogOptions?.persistent,\n persistOnActivatorClick: true,\n };\n});\n\nconst ui = computed<ReturnType<typeof categoryPickerStyles>>(() => categoryPickerStyles({\n dense,\n mode: get(mode),\n}));\n\nconst selectedItem = computed<TItem | undefined>(() => {\n const value = get(modelValue);\n if (value === undefined)\n return undefined;\n return items.find(item => getIdentifier(item) === value);\n});\n\nconst selectedText = computed<string | undefined>(() => {\n const selected = get(selectedItem);\n return selected ? getText(selected) : undefined;\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\n\n// Desktop lets the user type in the field itself (no separate search box);\n// narrow width keeps a read-only trigger that opens the sheet's own search.\nconst canType = computed<boolean>(() => searchable && !get(isMobile) && !readOnly && !disabled);\n\nconst isTyping = computed<boolean>(() => get(isOpen) && get(canType));\n\nconst float = computed<boolean>(() => (get(isOpen) || get(selectedItem) !== undefined || get(focused)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst activatorUi = computed<ReturnType<typeof categoryPickerActivatorStyles>>(() => categoryPickerActivatorStyles({\n dense,\n disabled,\n filled: variant === 'filled',\n float: get(float),\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n hovered: get(isHovered),\n opened: get(isOpen),\n outlined: get(outlined),\n readonly: readOnly,\n}));\n\nconst activeRailIndex = computed<number>(() =>\n get(railEntries).findIndex(entry => entry.category === get(activeCategory)));\n\nconst showRail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'category');\nconst showDetail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'detail');\n\nfunction clamp(value: number, max: number): number {\n if (value < 0)\n return 0;\n if (value > max)\n return max;\n return value;\n}\n\nfunction open(): void {\n if (disabled)\n return;\n set(isOpen, true);\n}\n\nfunction close(): void {\n set(isOpen, false);\n}\n\nfunction onInput(event: Event): void {\n const target = event.target;\n if (!(target instanceof HTMLInputElement))\n return;\n set(searchInput, target.value);\n if (!get(isOpen))\n open();\n}\n\nfunction selectItem(item: TItem): void {\n if (readOnly)\n return;\n set(modelValue, getIdentifier(item));\n emit('select', item);\n close();\n}\n\nfunction clearSelection(): void {\n set(modelValue, undefined);\n}\n\nfunction onCategoryClick(category: string | null): void {\n setActiveCategory(category);\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n else\n focusPane('detail');\n}\n\nfunction focusPane(pane: 'rail' | 'detail'): void {\n set(focusedPane, pane);\n nextTick(() => {\n (pane === 'rail' ? get(railRef) : get(detailRef))?.focus();\n });\n}\n\nfunction moveRail(direction: 1 | -1): void {\n const entries = get(railEntries);\n if (entries.length === 0)\n return;\n const next = clamp(get(activeRailIndex) + direction, entries.length - 1);\n setActiveCategory(entries[next]?.category ?? null);\n}\n\nfunction moveItem(direction: 1 | -1): void {\n const total = get(detailItems).length;\n if (total === 0)\n return;\n set(highlightedItemIndex, clamp(get(highlightedItemIndex) + direction, total - 1));\n}\n\nfunction enterDetail(): void {\n if (get(detailItems).length === 0)\n return;\n resetHighlightToSelection();\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n focusPane('detail');\n}\n\nfunction selectHighlighted(): void {\n const item = get(detailItems)[get(highlightedItemIndex)];\n if (item !== undefined)\n selectItem(item);\n}\n\n// Enter from the field commits the best match for the current query, so a\n// type-to-pick flow (type a few letters, press Enter) works without leaving the\n// input. Does nothing when the field is empty.\nfunction selectTopResult(): void {\n if (!get(searchInput).trim())\n return;\n const item = get(detailItems)[0];\n if (item !== undefined)\n selectItem(item);\n}\n\nfunction resetHighlightToSelection(): void {\n const index = get(detailItems).findIndex(item => isSelected(item));\n set(highlightedItemIndex, index >= 0 ? index : 0);\n}\n\nfunction backToRail(): void {\n if (get(mode) === 'drill')\n set(drillStep, 'category');\n else\n focusPane('rail');\n}\n\nfunction scrollActiveDescendantIntoView(container: HTMLElement | null, id: string): void {\n if (!container)\n return;\n nextTick(() => {\n container.querySelector(`#${id}`)?.scrollIntoView({ block: 'nearest' });\n });\n}\n\n// Reset the item highlight whenever the active category changes.\nwatch(activeCategory, () => {\n resetHighlightToSelection();\n});\n\nwatch(highlightedItemIndex, (index) => {\n scrollActiveDescendantIntoView(get(detailRef), `${baseId}-opt-${index}`);\n});\n\nwatch(activeRailIndex, (index) => {\n if (index >= 0)\n scrollActiveDescendantIntoView(get(railRef), `${baseId}-cat-${index}`);\n});\n\nwatch(isOpen, (value) => {\n if (value) {\n syncActiveToSelection();\n set(focusedPane, 'rail');\n set(drillStep, 'category');\n resetHighlightToSelection();\n // Desktop is a combobox: keep focus in the field so the user can type\n // straight away (ArrowDown moves into the panes). The mobile sheet has no\n // typing field, so focus the rail instead.\n nextTick(() => (get(canType) ? get(activatorRef) : get(railRef))?.focus());\n }\n else {\n // Reset the query so the field shows the selection again next time, and\n // return focus to the trigger (APG dialog contract).\n set(searchInput, '');\n nextTick(() => get(activatorRef)?.focus());\n }\n});\n</script>\n\n<template>\n <component\n :is=\"shell\"\n v-model=\"isOpen\"\n v-bind=\"{ ...$attrs, ...shellProps }\"\n >\n <template #activator=\"{ attrs }\">\n <slot\n name=\"activator\"\n v-bind=\"{ attrs, isOpen, item: selectedItem, open, text: selectedText }\"\n >\n <div :class=\"activatorUi.wrapper()\">\n <div\n ref=\"fieldRef\"\n role=\"combobox\"\n data-id=\"activator\"\n :class=\"activatorUi.activator()\"\n aria-haspopup=\"dialog\"\n :aria-controls=\"isOpen ? `${baseId}-panel` : undefined\"\n :aria-expanded=\"isOpen\"\n :aria-disabled=\"disabled || undefined\"\n :aria-readonly=\"(readOnly || !canType) || undefined\"\n :aria-required=\"required || undefined\"\n :aria-invalid=\"hasError || undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"readOnly ? {} : attrs\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <span\n v-if=\"outlined || (!selectedItem && !isTyping)\"\n :class=\"activatorUi.label()\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ item: selectedItem }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"activatorUi.required()\"\n >\n ﹡\n </span>\n </span>\n <span\n v-if=\"selectedItem && !isTyping && $slots.selection\"\n class=\"absolute inset-y-0 left-4 right-8 flex items-center gap-2 pointer-events-none\"\n :class=\"[activatorUi.value()]\"\n >\n <slot\n name=\"selection.prepend\"\n v-bind=\"{ item: selectedItem }\"\n />\n <slot\n name=\"selection\"\n v-bind=\"{ item: selectedItem }\"\n />\n </span>\n <input\n ref=\"activatorRef\"\n data-id=\"search-input\"\n :value=\"isTyping ? searchInput : (($slots.selection && selectedItem) ? '' : (selectedText ?? ''))\"\n :placeholder=\"isTyping ? placeholder : ''\"\n :readonly=\"!canType\"\n :disabled=\"disabled\"\n :aria-invalid=\"hasError || undefined\"\n class=\"bg-transparent outline-none\"\n :class=\"[activatorUi.value(), !canType && 'cursor-pointer']\"\n @input=\"onInput($event)\"\n @keydown.down.prevent=\"focusPane('rail')\"\n @keydown.enter.prevent=\"selectTopResult()\"\n @keydown.esc=\"close()\"\n />\n <span\n v-if=\"clearable && selectedItem && !disabled && !readOnly\"\n data-id=\"clear\"\n :class=\"[activatorUi.clear(), focused && '!visible']\"\n @click.stop.prevent=\"clearSelection()\"\n >\n <RuiIcon\n color=\"error\"\n name=\"lu-x\"\n size=\"18\"\n />\n </span>\n <span :class=\"activatorUi.iconWrapper()\">\n <RuiIcon\n :class=\"activatorUi.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n <RuiProgress\n v-if=\"loading\"\n :class=\"activatorUi.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n <fieldset\n v-if=\"outlined\"\n :class=\"activatorUi.fieldset()\"\n >\n <legend :class=\"activatorUi.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </div>\n <RuiFormTextDetail\n v-if=\"!hideDetails\"\n class=\"px-3 pt-1\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n </slot>\n </template>\n\n <div\n :id=\"`${baseId}-panel`\"\n :class=\"ui.root({ class: cn(classNames?.root) })\"\n data-id=\"panel\"\n >\n <!-- On desktop the field itself is the search input, so the panel only\n needs its own search box in the mobile sheet. -->\n <div\n v-if=\"isMobile || $slots.header\"\n :class=\"ui.header()\"\n >\n <slot name=\"header\">\n <div :class=\"ui.title()\">\n {{ label }}\n </div>\n </slot>\n <RuiTextField\n v-if=\"searchable && isMobile\"\n v-model=\"searchInput\"\n :class=\"cn(classNames?.search)\"\n :placeholder=\"placeholder\"\n :dense=\"dense\"\n variant=\"outlined\"\n prepend-icon=\"lu-search\"\n hide-details\n clearable\n data-id=\"search\"\n />\n </div>\n\n <div :class=\"ui.body()\">\n <!-- Category rail (master) -->\n <div\n v-show=\"showRail\"\n ref=\"railRef\"\n :class=\"ui.rail({ class: cn(classNames?.rail) })\"\n role=\"tablist\"\n aria-orientation=\"vertical\"\n :aria-label=\"label\"\n :aria-activedescendant=\"focusedPane === 'rail' && activeRailIndex >= 0 ? `${baseId}-cat-${activeRailIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"rail\"\n @focus=\"focusedPane = 'rail'\"\n @keydown.down.prevent=\"moveRail(1)\"\n @keydown.up.prevent=\"moveRail(-1)\"\n @keydown.right.prevent=\"enterDetail()\"\n @keydown.enter.prevent=\"enterDetail()\"\n @keydown.home.prevent=\"setActiveCategory(railEntries[0]?.category ?? null)\"\n @keydown.end.prevent=\"setActiveCategory(railEntries[railEntries.length - 1]?.category ?? null)\"\n >\n <RuiButton\n v-for=\"(entry, index) in railEntries\"\n :id=\"`${baseId}-cat-${index}`\"\n :key=\"entry.category ?? '__all__'\"\n role=\"tab\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"entry.category === activeCategory\"\n :aria-selected=\"entry.category === activeCategory\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'rail' && index === activeRailIndex\"\n :data-category=\"entry.category ?? '__all__'\"\n @click=\"onCategoryClick(entry.category)\"\n >\n <slot\n name=\"category\"\n v-bind=\"{ active: entry.category === activeCategory, category: entry.category, count: entry.count, label: entry.label }\"\n >\n <span class=\"truncate\">{{ entry.label }}</span>\n <span :class=\"ui.railCount()\">{{ entry.count }}</span>\n </slot>\n </RuiButton>\n </div>\n\n <!-- Detail pane -->\n <div\n v-show=\"showDetail\"\n ref=\"detailRef\"\n :class=\"ui.detail({ class: cn(classNames?.detail) })\"\n role=\"listbox\"\n :aria-label=\"activeCategory ?? allLabel\"\n :aria-activedescendant=\"focusedPane === 'detail' && !isEmpty ? `${baseId}-opt-${highlightedItemIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"detail\"\n @focus=\"focusedPane = 'detail'\"\n @keydown.down.prevent=\"moveItem(1)\"\n @keydown.up.prevent=\"moveItem(-1)\"\n @keydown.left.prevent=\"backToRail()\"\n @keydown.home.prevent=\"highlightedItemIndex = 0\"\n @keydown.end.prevent=\"highlightedItemIndex = detailItems.length - 1\"\n @keydown.enter.prevent=\"selectHighlighted()\"\n @keydown.space.prevent=\"selectHighlighted()\"\n >\n <template v-if=\"mode === 'drill' && showDetail\">\n <button\n type=\"button\"\n :class=\"ui.backButton()\"\n data-id=\"back\"\n @click=\"backToRail()\"\n >\n <RuiIcon\n name=\"lu-chevron-left\"\n size=\"18\"\n />\n <span>{{ activeCategory ?? allLabel }}</span>\n </button>\n </template>\n\n <slot\n v-if=\"isEmpty\"\n name=\"empty\"\n >\n <div :class=\"ui.empty()\">\n {{ emptyText }}\n </div>\n </slot>\n\n <template\n v-for=\"row in detailRows\"\n v-else\n :key=\"row.type === 'header' ? `h-${row.category}` : `i-${row.index}`\"\n >\n <div\n v-if=\"row.type === 'header'\"\n :class=\"ui.subheader()\"\n role=\"presentation\"\n >\n {{ row.category }}\n </div>\n <RuiButton\n v-else\n :id=\"`${baseId}-opt-${row.index}`\"\n role=\"option\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"isSelected(row.item)\"\n :aria-selected=\"isSelected(row.item)\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'detail' && row.index === highlightedItemIndex\"\n :class=\"{ [ui.highlighted()]: focusedPane === 'detail' && row.index === highlightedItemIndex && !isSelected(row.item) }\"\n @click=\"selectItem(row.item)\"\n >\n <template\n v-if=\"$slots['item.prepend']\"\n #prepend\n >\n <slot\n name=\"item.prepend\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n >\n {{ getText(row.item) }}\n </slot>\n <template\n v-if=\"$slots['item.append']\"\n #append\n >\n <slot\n name=\"item.append\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n </RuiButton>\n </template>\n </div>\n </div>\n\n <div\n v-if=\"$slots.footer\"\n :class=\"ui.footer()\"\n >\n <slot\n name=\"footer\"\n v-bind=\"{ close }\"\n />\n </div>\n </div>\n </component>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiCategoryPicker.js","names":[],"sources":["../../../../src/components/forms/category-picker/RuiCategoryPicker.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { KeyOfType } from '@/composables/dropdown-menu';\nimport type { VueClassValue } from '@/types/class-value';\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { categoryPickerActivatorStyles, categoryPickerStyles, type CategoryPickerVariant } from '@/components/forms/category-picker/category-picker-styles';\nimport RuiTextField from '@/components/forms/text-field/RuiTextField.vue';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiDialog, { type DialogProps } from '@/components/overlays/dialog/RuiDialog.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport { Placement } from '@/composables/floating';\nimport { useCategoryPicker } from '@/composables/forms/category-picker/use-category-picker';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn } from '@/utils/tv';\n\nexport interface RuiCategoryPickerClassNames {\n root?: VueClassValue;\n rail?: VueClassValue;\n detail?: VueClassValue;\n search?: VueClassValue;\n}\n\nexport type CategoryPickerBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport interface RuiCategoryPickerProps<TValue, TItem> {\n /** The full catalogue of selectable items. */\n items: TItem[];\n /** Resolve an item's category label. Takes precedence over `categoryAttr`. */\n categoryOf?: (item: TItem) => string;\n /** Resolve an item's category from one of its own properties. */\n categoryAttr?: keyof TItem;\n /** Property whose value becomes the model value. Omit to use the item itself. */\n keyAttr?: KeyOfType<TItem, TValue extends (infer U)[] ? U : TValue>;\n /** Property that produces an item's display text. */\n textAttr?: keyof TItem;\n /** Field label, and the title of the mobile sheet. */\n label?: string;\n /** Placeholder shown in the field while it is empty or being searched. */\n placeholder?: string;\n /** Allow filtering: type in the field (desktop) or the sheet's search box (mobile). */\n searchable?: boolean;\n /**\n * Custom predicate. Receives the resolved category label as the third\n * argument, matching the `RuiAutoComplete` filter signature.\n */\n filter?: (item: TItem, query: string, category: string) => boolean;\n /** Show the synthetic \"All\" pseudo-category as the first rail entry. */\n showAll?: boolean;\n /** Label for the \"All\" pseudo-category. */\n allLabel?: string;\n /** Text shown when no item matches the current search. */\n emptyText?: string;\n disabled?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n loading?: boolean;\n /** Trigger field style: underline (`default`), `outlined`, or `filled`. */\n variant?: CategoryPickerVariant;\n /** Marks the trigger field as required (renders the asterisk). */\n required?: boolean;\n /** Validation errors rendered under the trigger field. */\n errorMessages?: string | string[];\n /** Success messages rendered under the trigger field. */\n successMessages?: string | string[];\n /** Hint rendered under the trigger field. */\n hint?: string;\n /** Hide the details row (errors/success/hint) under the trigger field. */\n hideDetails?: boolean;\n /** Below this breakpoint the picker collapses to a single-pane drill-in. */\n mobileBreakpoint?: CategoryPickerBreakpoint;\n /** Forwarded to the mobile bottom-sheet (`RuiDialog`). */\n dialogOptions?: DialogProps;\n classNames?: RuiCategoryPickerClassNames;\n}\n\ndefineOptions({\n name: 'RuiCategoryPicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<TValue>();\nconst searchInput = defineModel<string>('search', { default: '' });\n\nconst {\n items,\n categoryOf = undefined,\n categoryAttr = undefined,\n keyAttr = undefined,\n textAttr = undefined,\n label = 'Select',\n placeholder = 'Search',\n searchable = true,\n filter = undefined,\n showAll = true,\n allLabel = 'All',\n emptyText = 'No results',\n disabled = false,\n readOnly = false,\n dense = false,\n clearable = false,\n loading = false,\n variant = 'outlined',\n required = false,\n errorMessages = [],\n successMessages = [],\n hint = '',\n hideDetails = false,\n mobileBreakpoint = 'md',\n dialogOptions = undefined,\n classNames = undefined,\n} = defineProps<RuiCategoryPickerProps<TValue, TItem>>();\n\nconst emit = defineEmits<{\n select: [item: TItem];\n}>();\n\ndefineSlots<{\n 'activator'?: (props: { attrs: { onClick?: () => void; onMouseover?: () => void; onMouseleave?: () => void }; isOpen: boolean; open: () => void; item: TItem | undefined; text: string | undefined }) => any;\n 'activator.label'?: (props: { item: TItem | undefined }) => any;\n 'selection'?: (props: { item: TItem }) => any;\n 'selection.prepend'?: (props: { item: TItem }) => any;\n 'category'?: (props: { category: string | null; label: string; active: boolean; count: number }) => any;\n 'item'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.prepend'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.append'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'empty'?: () => any;\n 'header'?: () => any;\n 'footer'?: (props: { close: () => void }) => any;\n}>();\n\nconst isOpen = ref<boolean>(false);\nconst focusedPane = ref<'rail' | 'detail'>('rail');\nconst highlightedItemIndex = ref<number>(0);\nconst drillStep = ref<'category' | 'detail'>('category');\n\nconst railRef = useTemplateRef<HTMLDivElement>('railRef');\nconst detailRef = useTemplateRef<HTMLDivElement>('detailRef');\nconst activatorRef = useTemplateRef<HTMLInputElement>('activatorRef');\nconst fieldRef = useTemplateRef<HTMLElement>('fieldRef');\nconst isHovered = ref<boolean>(false);\n\nconst { focused } = useFocus(activatorRef);\nconst { hasError, hasSuccess } = useFormTextDetail(() => errorMessages, () => successMessages);\n\nconst baseId = useId();\nconst breakpoints = useBreakpoints(breakpointsTailwind);\n\nfunction resolveCategory(item: TItem): string {\n if (categoryOf)\n return categoryOf(item);\n if (categoryAttr !== undefined)\n return String(item[categoryAttr] ?? '');\n return '';\n}\n\nconst {\n activeCategory,\n detailItems,\n detailRows,\n getIdentifier,\n getText,\n isEmpty,\n isSelected,\n railEntries,\n setActiveCategory,\n syncActiveToSelection,\n} = useCategoryPicker<TValue, TItem>({\n allLabel: () => allLabel,\n categoryOf: resolveCategory,\n filter: () => filter,\n items: () => items,\n keyAttr,\n modelValue,\n search: searchInput,\n searchable: () => searchable,\n showAll: () => showAll,\n textAttr,\n});\n\nconst isMobile = computed<boolean>(() => get(breakpoints.smaller(mobileBreakpoint)));\n\nconst mode = computed<'twoPane' | 'drill'>(() => (get(isMobile) ? 'drill' : 'twoPane'));\n\n// Desktop anchors an outside-click popover under the field (RuiMenu); narrow\n// width shows a full-width bottom sheet (RuiDialog). Each branch returns only\n// its own shell's props so the other component's props never leak as attrs.\nconst shell = computed<typeof RuiMenu | typeof RuiDialog>(() => (get(isMobile) ? RuiDialog : RuiMenu));\n\nconst shellProps = computed<Record<string, unknown>>(() => {\n if (get(isMobile)) {\n return {\n ariaLabel: dialogOptions?.ariaLabel ?? label,\n bottomSheet: dialogOptions?.bottomSheet ?? true,\n maxWidth: dialogOptions?.maxWidth,\n persistent: dialogOptions?.persistent,\n width: dialogOptions?.width ?? '100%',\n };\n }\n return {\n // Anchor to the field box, not the wrapper, so the popover sits directly\n // under the input instead of below the reserved details row.\n anchorEl: get(fieldRef) ?? undefined,\n // Drop the menu's default vertical padding so the popover box equals our\n // panel: the size middleware caps the panel to the available height, and\n // any extra chrome padding would push the popover past the viewport edge.\n classNames: { content: 'py-0', menu: 'z-[9999]' },\n closeOnContentClick: false,\n disableAutoFocus: true,\n fullWidth: true,\n options: { placement: Placement.bottomStart, size: true },\n persistent: dialogOptions?.persistent,\n persistOnActivatorClick: true,\n };\n});\n\nconst ui = computed<ReturnType<typeof categoryPickerStyles>>(() => categoryPickerStyles({\n dense,\n mode: get(mode),\n}));\n\nconst selectedItem = computed<TItem | undefined>(() => {\n const value = get(modelValue);\n if (value === undefined)\n return undefined;\n return items.find(item => getIdentifier(item) === value);\n});\n\nconst selectedText = computed<string | undefined>(() => {\n const selected = get(selectedItem);\n return selected ? getText(selected) : undefined;\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\n\n// Desktop lets the user type in the field itself (no separate search box);\n// narrow width keeps a read-only trigger that opens the sheet's own search.\nconst canType = computed<boolean>(() => searchable && !get(isMobile) && !readOnly && !disabled);\n\nconst isTyping = computed<boolean>(() => get(isOpen) && get(canType));\n\nconst float = computed<boolean>(() => (get(isOpen) || get(selectedItem) !== undefined || get(focused)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst activatorUi = computed<ReturnType<typeof categoryPickerActivatorStyles>>(() => categoryPickerActivatorStyles({\n dense,\n disabled,\n filled: variant === 'filled',\n float: get(float),\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n hovered: get(isHovered),\n opened: get(isOpen),\n outlined: get(outlined),\n readonly: readOnly,\n}));\n\nconst activeRailIndex = computed<number>(() =>\n get(railEntries).findIndex(entry => entry.category === get(activeCategory)));\n\nconst showRail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'category');\nconst showDetail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'detail');\n\nfunction clamp(value: number, max: number): number {\n if (value < 0)\n return 0;\n if (value > max)\n return max;\n return value;\n}\n\nfunction open(): void {\n if (disabled)\n return;\n set(isOpen, true);\n}\n\nfunction close(): void {\n set(isOpen, false);\n}\n\nfunction onInput(event: Event): void {\n const target = event.target;\n if (!(target instanceof HTMLInputElement))\n return;\n set(searchInput, target.value);\n if (!get(isOpen))\n open();\n}\n\nfunction selectItem(item: TItem): void {\n if (readOnly)\n return;\n set(modelValue, getIdentifier(item));\n emit('select', item);\n close();\n}\n\nfunction clearSelection(): void {\n set(modelValue, undefined);\n}\n\nfunction onCategoryClick(category: string | null): void {\n setActiveCategory(category);\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n else\n focusPane('detail');\n}\n\nfunction focusPane(pane: 'rail' | 'detail'): void {\n set(focusedPane, pane);\n nextTick(() => {\n // preventScroll: focus moves between panes inside a teleported popover;\n // without it the browser scrolls the page behind to reveal the pane.\n (pane === 'rail' ? get(railRef) : get(detailRef))?.focus({ preventScroll: true });\n });\n}\n\nfunction moveRail(direction: 1 | -1): void {\n const entries = get(railEntries);\n if (entries.length === 0)\n return;\n const next = clamp(get(activeRailIndex) + direction, entries.length - 1);\n setActiveCategory(entries[next]?.category ?? null);\n}\n\nfunction moveItem(direction: 1 | -1): void {\n const total = get(detailItems).length;\n if (total === 0)\n return;\n set(highlightedItemIndex, clamp(get(highlightedItemIndex) + direction, total - 1));\n}\n\nfunction enterDetail(): void {\n if (get(detailItems).length === 0)\n return;\n resetHighlightToSelection();\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n focusPane('detail');\n}\n\nfunction selectHighlighted(): void {\n const item = get(detailItems)[get(highlightedItemIndex)];\n if (item !== undefined)\n selectItem(item);\n}\n\n// Enter from the field commits the best match for the current query, so a\n// type-to-pick flow (type a few letters, press Enter) works without leaving the\n// input. Does nothing when the field is empty.\nfunction selectTopResult(): void {\n if (!get(searchInput).trim())\n return;\n const item = get(detailItems)[0];\n if (item !== undefined)\n selectItem(item);\n}\n\nfunction resetHighlightToSelection(): void {\n const index = get(detailItems).findIndex(item => isSelected(item));\n set(highlightedItemIndex, index >= 0 ? index : 0);\n}\n\nfunction backToRail(): void {\n if (get(mode) === 'drill')\n set(drillStep, 'category');\n else\n focusPane('rail');\n}\n\nfunction scrollActiveDescendantIntoView(container: HTMLElement | null, id: string): void {\n if (!container)\n return;\n nextTick(() => {\n container.querySelector(`#${id}`)?.scrollIntoView({ block: 'nearest' });\n });\n}\n\n// Reset the item highlight whenever the active category changes.\nwatch(activeCategory, () => {\n resetHighlightToSelection();\n});\n\nwatch(highlightedItemIndex, (index) => {\n scrollActiveDescendantIntoView(get(detailRef), `${baseId}-opt-${index}`);\n});\n\nwatch(activeRailIndex, (index) => {\n if (index >= 0)\n scrollActiveDescendantIntoView(get(railRef), `${baseId}-cat-${index}`);\n});\n\nwatch(isOpen, (value) => {\n if (value) {\n syncActiveToSelection();\n set(focusedPane, 'rail');\n set(drillStep, 'category');\n resetHighlightToSelection();\n // Desktop is a combobox: keep focus in the field so the user can type\n // straight away (ArrowDown moves into the panes). The mobile sheet has no\n // typing field, so focus the rail instead.\n nextTick(() => (get(canType) ? get(activatorRef) : get(railRef))?.focus({ preventScroll: true }));\n }\n else {\n // Reset the query so the field shows the selection again next time, and\n // return focus to the trigger (APG dialog contract).\n set(searchInput, '');\n nextTick(() => get(activatorRef)?.focus({ preventScroll: true }));\n }\n});\n</script>\n\n<template>\n <component\n :is=\"shell\"\n v-model=\"isOpen\"\n v-bind=\"{ ...$attrs, ...shellProps }\"\n >\n <template #activator=\"{ attrs }\">\n <slot\n name=\"activator\"\n v-bind=\"{ attrs, isOpen, item: selectedItem, open, text: selectedText }\"\n >\n <div :class=\"activatorUi.wrapper()\">\n <div\n ref=\"fieldRef\"\n role=\"combobox\"\n data-id=\"activator\"\n :class=\"activatorUi.activator()\"\n aria-haspopup=\"dialog\"\n :aria-controls=\"isOpen ? `${baseId}-panel` : undefined\"\n :aria-expanded=\"isOpen\"\n :aria-disabled=\"disabled || undefined\"\n :aria-readonly=\"(readOnly || !canType) || undefined\"\n :aria-required=\"required || undefined\"\n :aria-invalid=\"hasError || undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"readOnly ? {} : attrs\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <span\n v-if=\"outlined || (!selectedItem && !isTyping)\"\n :class=\"activatorUi.label()\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ item: selectedItem }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"activatorUi.required()\"\n >\n ﹡\n </span>\n </span>\n <span\n v-if=\"selectedItem && !isTyping && $slots.selection\"\n class=\"absolute inset-y-0 left-4 right-8 flex items-center gap-2 pointer-events-none\"\n :class=\"[activatorUi.value()]\"\n >\n <slot\n name=\"selection.prepend\"\n v-bind=\"{ item: selectedItem }\"\n />\n <slot\n name=\"selection\"\n v-bind=\"{ item: selectedItem }\"\n />\n </span>\n <input\n ref=\"activatorRef\"\n data-id=\"search-input\"\n :value=\"isTyping ? searchInput : (($slots.selection && selectedItem) ? '' : (selectedText ?? ''))\"\n :placeholder=\"isTyping ? placeholder : ''\"\n :readonly=\"!canType\"\n :disabled=\"disabled\"\n :aria-invalid=\"hasError || undefined\"\n class=\"bg-transparent outline-none\"\n :class=\"[activatorUi.value(), !canType && 'cursor-pointer']\"\n @input=\"onInput($event)\"\n @keydown.down.prevent=\"focusPane('rail')\"\n @keydown.enter.prevent=\"selectTopResult()\"\n @keydown.esc=\"close()\"\n />\n <span\n v-if=\"clearable && selectedItem && !disabled && !readOnly\"\n data-id=\"clear\"\n :class=\"[activatorUi.clear(), focused && '!visible']\"\n @click.stop.prevent=\"clearSelection()\"\n >\n <RuiIcon\n color=\"error\"\n name=\"lu-x\"\n size=\"18\"\n />\n </span>\n <span :class=\"activatorUi.iconWrapper()\">\n <RuiIcon\n :class=\"activatorUi.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n <RuiProgress\n v-if=\"loading\"\n :class=\"activatorUi.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n <fieldset\n v-if=\"outlined\"\n :class=\"activatorUi.fieldset()\"\n >\n <legend :class=\"activatorUi.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </div>\n <RuiFormTextDetail\n v-if=\"!hideDetails\"\n class=\"px-3 pt-1\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n </slot>\n </template>\n\n <div\n :id=\"`${baseId}-panel`\"\n :class=\"ui.root({ class: cn(classNames?.root) })\"\n data-id=\"panel\"\n >\n <!-- On desktop the field itself is the search input, so the panel only\n needs its own search box in the mobile sheet. -->\n <div\n v-if=\"isMobile || $slots.header\"\n :class=\"ui.header()\"\n >\n <slot name=\"header\">\n <div :class=\"ui.title()\">\n {{ label }}\n </div>\n </slot>\n <RuiTextField\n v-if=\"searchable && isMobile\"\n v-model=\"searchInput\"\n :class=\"cn(classNames?.search)\"\n :placeholder=\"placeholder\"\n :dense=\"dense\"\n variant=\"outlined\"\n prepend-icon=\"lu-search\"\n hide-details\n clearable\n data-id=\"search\"\n />\n </div>\n\n <div :class=\"ui.body()\">\n <!-- Category rail (master) -->\n <div\n v-show=\"showRail\"\n ref=\"railRef\"\n :class=\"ui.rail({ class: cn(classNames?.rail) })\"\n role=\"tablist\"\n aria-orientation=\"vertical\"\n :aria-label=\"label\"\n :aria-activedescendant=\"focusedPane === 'rail' && activeRailIndex >= 0 ? `${baseId}-cat-${activeRailIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"rail\"\n @focus=\"focusedPane = 'rail'\"\n @keydown.down.prevent=\"moveRail(1)\"\n @keydown.up.prevent=\"moveRail(-1)\"\n @keydown.right.prevent=\"enterDetail()\"\n @keydown.enter.prevent=\"enterDetail()\"\n @keydown.home.prevent=\"setActiveCategory(railEntries[0]?.category ?? null)\"\n @keydown.end.prevent=\"setActiveCategory(railEntries[railEntries.length - 1]?.category ?? null)\"\n >\n <RuiButton\n v-for=\"(entry, index) in railEntries\"\n :id=\"`${baseId}-cat-${index}`\"\n :key=\"entry.category ?? '__all__'\"\n role=\"tab\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"entry.category === activeCategory\"\n :aria-selected=\"entry.category === activeCategory\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'rail' && index === activeRailIndex\"\n :data-category=\"entry.category ?? '__all__'\"\n @click=\"onCategoryClick(entry.category)\"\n >\n <slot\n name=\"category\"\n v-bind=\"{ active: entry.category === activeCategory, category: entry.category, count: entry.count, label: entry.label }\"\n >\n <span class=\"truncate\">{{ entry.label }}</span>\n <span :class=\"ui.railCount()\">{{ entry.count }}</span>\n </slot>\n </RuiButton>\n </div>\n\n <!-- Detail pane -->\n <div\n v-show=\"showDetail\"\n ref=\"detailRef\"\n :class=\"ui.detail({ class: cn(classNames?.detail) })\"\n role=\"listbox\"\n :aria-label=\"activeCategory ?? allLabel\"\n :aria-activedescendant=\"focusedPane === 'detail' && !isEmpty ? `${baseId}-opt-${highlightedItemIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"detail\"\n @focus=\"focusedPane = 'detail'\"\n @keydown.down.prevent=\"moveItem(1)\"\n @keydown.up.prevent=\"moveItem(-1)\"\n @keydown.left.prevent=\"backToRail()\"\n @keydown.home.prevent=\"highlightedItemIndex = 0\"\n @keydown.end.prevent=\"highlightedItemIndex = detailItems.length - 1\"\n @keydown.enter.prevent=\"selectHighlighted()\"\n @keydown.space.prevent=\"selectHighlighted()\"\n >\n <template v-if=\"mode === 'drill' && showDetail\">\n <button\n type=\"button\"\n :class=\"ui.backButton()\"\n data-id=\"back\"\n @click=\"backToRail()\"\n >\n <RuiIcon\n name=\"lu-chevron-left\"\n size=\"18\"\n />\n <span>{{ activeCategory ?? allLabel }}</span>\n </button>\n </template>\n\n <slot\n v-if=\"isEmpty\"\n name=\"empty\"\n >\n <div :class=\"ui.empty()\">\n {{ emptyText }}\n </div>\n </slot>\n\n <template\n v-for=\"row in detailRows\"\n v-else\n :key=\"row.type === 'header' ? `h-${row.category}` : `i-${row.index}`\"\n >\n <div\n v-if=\"row.type === 'header'\"\n :class=\"ui.subheader()\"\n role=\"presentation\"\n >\n {{ row.category }}\n </div>\n <RuiButton\n v-else\n :id=\"`${baseId}-opt-${row.index}`\"\n role=\"option\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"isSelected(row.item)\"\n :aria-selected=\"isSelected(row.item)\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'detail' && row.index === highlightedItemIndex\"\n :class=\"{ [ui.highlighted()]: focusedPane === 'detail' && row.index === highlightedItemIndex && !isSelected(row.item) }\"\n @click=\"selectItem(row.item)\"\n >\n <template\n v-if=\"$slots['item.prepend']\"\n #prepend\n >\n <slot\n name=\"item.prepend\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n >\n {{ getText(row.item) }}\n </slot>\n <template\n v-if=\"$slots['item.append']\"\n #append\n >\n <slot\n name=\"item.append\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n </RuiButton>\n </template>\n </div>\n </div>\n\n <div\n v-if=\"$slots.footer\"\n :class=\"ui.footer()\"\n >\n <slot\n name=\"footer\"\n v-bind=\"{ close }\"\n />\n </div>\n </div>\n </component>\n</template>\n"],"mappings":""}
@@ -152,11 +152,17 @@ var RuiCategoryPicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
152
152
  };
153
153
  return {
154
154
  anchorEl: get$1(fieldRef) ?? void 0,
155
- classNames: { menu: "z-[9999]" },
155
+ classNames: {
156
+ content: "py-0",
157
+ menu: "z-[9999]"
158
+ },
156
159
  closeOnContentClick: false,
157
160
  disableAutoFocus: true,
158
161
  fullWidth: true,
159
- options: { placement: Placement.bottomStart },
162
+ options: {
163
+ placement: Placement.bottomStart,
164
+ size: true
165
+ },
160
166
  persistent: __props.dialogOptions?.persistent,
161
167
  persistOnActivatorClick: true
162
168
  };
@@ -232,7 +238,7 @@ var RuiCategoryPicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
232
238
  function focusPane(pane) {
233
239
  set$1(focusedPane, pane);
234
240
  nextTick(() => {
235
- (pane === "rail" ? get$1(railRef) : get$1(detailRef))?.focus();
241
+ (pane === "rail" ? get$1(railRef) : get$1(detailRef))?.focus({ preventScroll: true });
236
242
  });
237
243
  }
238
244
  function moveRail(direction) {
@@ -289,10 +295,10 @@ var RuiCategoryPicker_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
289
295
  set$1(focusedPane, "rail");
290
296
  set$1(drillStep, "category");
291
297
  resetHighlightToSelection();
292
- nextTick(() => (get$1(canType) ? get$1(activatorRef) : get$1(railRef))?.focus());
298
+ nextTick(() => (get$1(canType) ? get$1(activatorRef) : get$1(railRef))?.focus({ preventScroll: true }));
293
299
  } else {
294
300
  set$1(searchInput, "");
295
- nextTick(() => get$1(activatorRef)?.focus());
301
+ nextTick(() => get$1(activatorRef)?.focus({ preventScroll: true }));
296
302
  }
297
303
  });
298
304
  return (_ctx, _cache) => {
@@ -1 +1 @@
1
- {"version":3,"file":"RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js","names":["$attrs","$slots"],"sources":["../../../../src/components/forms/category-picker/RuiCategoryPicker.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { KeyOfType } from '@/composables/dropdown-menu';\nimport type { VueClassValue } from '@/types/class-value';\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { categoryPickerActivatorStyles, categoryPickerStyles, type CategoryPickerVariant } from '@/components/forms/category-picker/category-picker-styles';\nimport RuiTextField from '@/components/forms/text-field/RuiTextField.vue';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiDialog, { type DialogProps } from '@/components/overlays/dialog/RuiDialog.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport { Placement } from '@/composables/floating';\nimport { useCategoryPicker } from '@/composables/forms/category-picker/use-category-picker';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn } from '@/utils/tv';\n\nexport interface RuiCategoryPickerClassNames {\n root?: VueClassValue;\n rail?: VueClassValue;\n detail?: VueClassValue;\n search?: VueClassValue;\n}\n\nexport type CategoryPickerBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport interface RuiCategoryPickerProps<TValue, TItem> {\n /** The full catalogue of selectable items. */\n items: TItem[];\n /** Resolve an item's category label. Takes precedence over `categoryAttr`. */\n categoryOf?: (item: TItem) => string;\n /** Resolve an item's category from one of its own properties. */\n categoryAttr?: keyof TItem;\n /** Property whose value becomes the model value. Omit to use the item itself. */\n keyAttr?: KeyOfType<TItem, TValue extends (infer U)[] ? U : TValue>;\n /** Property that produces an item's display text. */\n textAttr?: keyof TItem;\n /** Field label, and the title of the mobile sheet. */\n label?: string;\n /** Placeholder shown in the field while it is empty or being searched. */\n placeholder?: string;\n /** Allow filtering: type in the field (desktop) or the sheet's search box (mobile). */\n searchable?: boolean;\n /**\n * Custom predicate. Receives the resolved category label as the third\n * argument, matching the `RuiAutoComplete` filter signature.\n */\n filter?: (item: TItem, query: string, category: string) => boolean;\n /** Show the synthetic \"All\" pseudo-category as the first rail entry. */\n showAll?: boolean;\n /** Label for the \"All\" pseudo-category. */\n allLabel?: string;\n /** Text shown when no item matches the current search. */\n emptyText?: string;\n disabled?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n loading?: boolean;\n /** Trigger field style: underline (`default`), `outlined`, or `filled`. */\n variant?: CategoryPickerVariant;\n /** Marks the trigger field as required (renders the asterisk). */\n required?: boolean;\n /** Validation errors rendered under the trigger field. */\n errorMessages?: string | string[];\n /** Success messages rendered under the trigger field. */\n successMessages?: string | string[];\n /** Hint rendered under the trigger field. */\n hint?: string;\n /** Hide the details row (errors/success/hint) under the trigger field. */\n hideDetails?: boolean;\n /** Below this breakpoint the picker collapses to a single-pane drill-in. */\n mobileBreakpoint?: CategoryPickerBreakpoint;\n /** Forwarded to the mobile bottom-sheet (`RuiDialog`). */\n dialogOptions?: DialogProps;\n classNames?: RuiCategoryPickerClassNames;\n}\n\ndefineOptions({\n name: 'RuiCategoryPicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<TValue>();\nconst searchInput = defineModel<string>('search', { default: '' });\n\nconst {\n items,\n categoryOf = undefined,\n categoryAttr = undefined,\n keyAttr = undefined,\n textAttr = undefined,\n label = 'Select',\n placeholder = 'Search',\n searchable = true,\n filter = undefined,\n showAll = true,\n allLabel = 'All',\n emptyText = 'No results',\n disabled = false,\n readOnly = false,\n dense = false,\n clearable = false,\n loading = false,\n variant = 'outlined',\n required = false,\n errorMessages = [],\n successMessages = [],\n hint = '',\n hideDetails = false,\n mobileBreakpoint = 'md',\n dialogOptions = undefined,\n classNames = undefined,\n} = defineProps<RuiCategoryPickerProps<TValue, TItem>>();\n\nconst emit = defineEmits<{\n select: [item: TItem];\n}>();\n\ndefineSlots<{\n 'activator'?: (props: { attrs: { onClick?: () => void; onMouseover?: () => void; onMouseleave?: () => void }; isOpen: boolean; open: () => void; item: TItem | undefined; text: string | undefined }) => any;\n 'activator.label'?: (props: { item: TItem | undefined }) => any;\n 'selection'?: (props: { item: TItem }) => any;\n 'selection.prepend'?: (props: { item: TItem }) => any;\n 'category'?: (props: { category: string | null; label: string; active: boolean; count: number }) => any;\n 'item'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.prepend'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.append'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'empty'?: () => any;\n 'header'?: () => any;\n 'footer'?: (props: { close: () => void }) => any;\n}>();\n\nconst isOpen = ref<boolean>(false);\nconst focusedPane = ref<'rail' | 'detail'>('rail');\nconst highlightedItemIndex = ref<number>(0);\nconst drillStep = ref<'category' | 'detail'>('category');\n\nconst railRef = useTemplateRef<HTMLDivElement>('railRef');\nconst detailRef = useTemplateRef<HTMLDivElement>('detailRef');\nconst activatorRef = useTemplateRef<HTMLInputElement>('activatorRef');\nconst fieldRef = useTemplateRef<HTMLElement>('fieldRef');\nconst isHovered = ref<boolean>(false);\n\nconst { focused } = useFocus(activatorRef);\nconst { hasError, hasSuccess } = useFormTextDetail(() => errorMessages, () => successMessages);\n\nconst baseId = useId();\nconst breakpoints = useBreakpoints(breakpointsTailwind);\n\nfunction resolveCategory(item: TItem): string {\n if (categoryOf)\n return categoryOf(item);\n if (categoryAttr !== undefined)\n return String(item[categoryAttr] ?? '');\n return '';\n}\n\nconst {\n activeCategory,\n detailItems,\n detailRows,\n getIdentifier,\n getText,\n isEmpty,\n isSelected,\n railEntries,\n setActiveCategory,\n syncActiveToSelection,\n} = useCategoryPicker<TValue, TItem>({\n allLabel: () => allLabel,\n categoryOf: resolveCategory,\n filter: () => filter,\n items: () => items,\n keyAttr,\n modelValue,\n search: searchInput,\n searchable: () => searchable,\n showAll: () => showAll,\n textAttr,\n});\n\nconst isMobile = computed<boolean>(() => get(breakpoints.smaller(mobileBreakpoint)));\n\nconst mode = computed<'twoPane' | 'drill'>(() => (get(isMobile) ? 'drill' : 'twoPane'));\n\n// Desktop anchors an outside-click popover under the field (RuiMenu); narrow\n// width shows a full-width bottom sheet (RuiDialog). Each branch returns only\n// its own shell's props so the other component's props never leak as attrs.\nconst shell = computed<typeof RuiMenu | typeof RuiDialog>(() => (get(isMobile) ? RuiDialog : RuiMenu));\n\nconst shellProps = computed<Record<string, unknown>>(() => {\n if (get(isMobile)) {\n return {\n ariaLabel: dialogOptions?.ariaLabel ?? label,\n bottomSheet: dialogOptions?.bottomSheet ?? true,\n maxWidth: dialogOptions?.maxWidth,\n persistent: dialogOptions?.persistent,\n width: dialogOptions?.width ?? '100%',\n };\n }\n return {\n // Anchor to the field box, not the wrapper, so the popover sits directly\n // under the input instead of below the reserved details row.\n anchorEl: get(fieldRef) ?? undefined,\n classNames: { menu: 'z-[9999]' },\n closeOnContentClick: false,\n disableAutoFocus: true,\n fullWidth: true,\n options: { placement: Placement.bottomStart },\n persistent: dialogOptions?.persistent,\n persistOnActivatorClick: true,\n };\n});\n\nconst ui = computed<ReturnType<typeof categoryPickerStyles>>(() => categoryPickerStyles({\n dense,\n mode: get(mode),\n}));\n\nconst selectedItem = computed<TItem | undefined>(() => {\n const value = get(modelValue);\n if (value === undefined)\n return undefined;\n return items.find(item => getIdentifier(item) === value);\n});\n\nconst selectedText = computed<string | undefined>(() => {\n const selected = get(selectedItem);\n return selected ? getText(selected) : undefined;\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\n\n// Desktop lets the user type in the field itself (no separate search box);\n// narrow width keeps a read-only trigger that opens the sheet's own search.\nconst canType = computed<boolean>(() => searchable && !get(isMobile) && !readOnly && !disabled);\n\nconst isTyping = computed<boolean>(() => get(isOpen) && get(canType));\n\nconst float = computed<boolean>(() => (get(isOpen) || get(selectedItem) !== undefined || get(focused)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst activatorUi = computed<ReturnType<typeof categoryPickerActivatorStyles>>(() => categoryPickerActivatorStyles({\n dense,\n disabled,\n filled: variant === 'filled',\n float: get(float),\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n hovered: get(isHovered),\n opened: get(isOpen),\n outlined: get(outlined),\n readonly: readOnly,\n}));\n\nconst activeRailIndex = computed<number>(() =>\n get(railEntries).findIndex(entry => entry.category === get(activeCategory)));\n\nconst showRail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'category');\nconst showDetail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'detail');\n\nfunction clamp(value: number, max: number): number {\n if (value < 0)\n return 0;\n if (value > max)\n return max;\n return value;\n}\n\nfunction open(): void {\n if (disabled)\n return;\n set(isOpen, true);\n}\n\nfunction close(): void {\n set(isOpen, false);\n}\n\nfunction onInput(event: Event): void {\n const target = event.target;\n if (!(target instanceof HTMLInputElement))\n return;\n set(searchInput, target.value);\n if (!get(isOpen))\n open();\n}\n\nfunction selectItem(item: TItem): void {\n if (readOnly)\n return;\n set(modelValue, getIdentifier(item));\n emit('select', item);\n close();\n}\n\nfunction clearSelection(): void {\n set(modelValue, undefined);\n}\n\nfunction onCategoryClick(category: string | null): void {\n setActiveCategory(category);\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n else\n focusPane('detail');\n}\n\nfunction focusPane(pane: 'rail' | 'detail'): void {\n set(focusedPane, pane);\n nextTick(() => {\n (pane === 'rail' ? get(railRef) : get(detailRef))?.focus();\n });\n}\n\nfunction moveRail(direction: 1 | -1): void {\n const entries = get(railEntries);\n if (entries.length === 0)\n return;\n const next = clamp(get(activeRailIndex) + direction, entries.length - 1);\n setActiveCategory(entries[next]?.category ?? null);\n}\n\nfunction moveItem(direction: 1 | -1): void {\n const total = get(detailItems).length;\n if (total === 0)\n return;\n set(highlightedItemIndex, clamp(get(highlightedItemIndex) + direction, total - 1));\n}\n\nfunction enterDetail(): void {\n if (get(detailItems).length === 0)\n return;\n resetHighlightToSelection();\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n focusPane('detail');\n}\n\nfunction selectHighlighted(): void {\n const item = get(detailItems)[get(highlightedItemIndex)];\n if (item !== undefined)\n selectItem(item);\n}\n\n// Enter from the field commits the best match for the current query, so a\n// type-to-pick flow (type a few letters, press Enter) works without leaving the\n// input. Does nothing when the field is empty.\nfunction selectTopResult(): void {\n if (!get(searchInput).trim())\n return;\n const item = get(detailItems)[0];\n if (item !== undefined)\n selectItem(item);\n}\n\nfunction resetHighlightToSelection(): void {\n const index = get(detailItems).findIndex(item => isSelected(item));\n set(highlightedItemIndex, index >= 0 ? index : 0);\n}\n\nfunction backToRail(): void {\n if (get(mode) === 'drill')\n set(drillStep, 'category');\n else\n focusPane('rail');\n}\n\nfunction scrollActiveDescendantIntoView(container: HTMLElement | null, id: string): void {\n if (!container)\n return;\n nextTick(() => {\n container.querySelector(`#${id}`)?.scrollIntoView({ block: 'nearest' });\n });\n}\n\n// Reset the item highlight whenever the active category changes.\nwatch(activeCategory, () => {\n resetHighlightToSelection();\n});\n\nwatch(highlightedItemIndex, (index) => {\n scrollActiveDescendantIntoView(get(detailRef), `${baseId}-opt-${index}`);\n});\n\nwatch(activeRailIndex, (index) => {\n if (index >= 0)\n scrollActiveDescendantIntoView(get(railRef), `${baseId}-cat-${index}`);\n});\n\nwatch(isOpen, (value) => {\n if (value) {\n syncActiveToSelection();\n set(focusedPane, 'rail');\n set(drillStep, 'category');\n resetHighlightToSelection();\n // Desktop is a combobox: keep focus in the field so the user can type\n // straight away (ArrowDown moves into the panes). The mobile sheet has no\n // typing field, so focus the rail instead.\n nextTick(() => (get(canType) ? get(activatorRef) : get(railRef))?.focus());\n }\n else {\n // Reset the query so the field shows the selection again next time, and\n // return focus to the trigger (APG dialog contract).\n set(searchInput, '');\n nextTick(() => get(activatorRef)?.focus());\n }\n});\n</script>\n\n<template>\n <component\n :is=\"shell\"\n v-model=\"isOpen\"\n v-bind=\"{ ...$attrs, ...shellProps }\"\n >\n <template #activator=\"{ attrs }\">\n <slot\n name=\"activator\"\n v-bind=\"{ attrs, isOpen, item: selectedItem, open, text: selectedText }\"\n >\n <div :class=\"activatorUi.wrapper()\">\n <div\n ref=\"fieldRef\"\n role=\"combobox\"\n data-id=\"activator\"\n :class=\"activatorUi.activator()\"\n aria-haspopup=\"dialog\"\n :aria-controls=\"isOpen ? `${baseId}-panel` : undefined\"\n :aria-expanded=\"isOpen\"\n :aria-disabled=\"disabled || undefined\"\n :aria-readonly=\"(readOnly || !canType) || undefined\"\n :aria-required=\"required || undefined\"\n :aria-invalid=\"hasError || undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"readOnly ? {} : attrs\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <span\n v-if=\"outlined || (!selectedItem && !isTyping)\"\n :class=\"activatorUi.label()\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ item: selectedItem }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"activatorUi.required()\"\n >\n ﹡\n </span>\n </span>\n <span\n v-if=\"selectedItem && !isTyping && $slots.selection\"\n class=\"absolute inset-y-0 left-4 right-8 flex items-center gap-2 pointer-events-none\"\n :class=\"[activatorUi.value()]\"\n >\n <slot\n name=\"selection.prepend\"\n v-bind=\"{ item: selectedItem }\"\n />\n <slot\n name=\"selection\"\n v-bind=\"{ item: selectedItem }\"\n />\n </span>\n <input\n ref=\"activatorRef\"\n data-id=\"search-input\"\n :value=\"isTyping ? searchInput : (($slots.selection && selectedItem) ? '' : (selectedText ?? ''))\"\n :placeholder=\"isTyping ? placeholder : ''\"\n :readonly=\"!canType\"\n :disabled=\"disabled\"\n :aria-invalid=\"hasError || undefined\"\n class=\"bg-transparent outline-none\"\n :class=\"[activatorUi.value(), !canType && 'cursor-pointer']\"\n @input=\"onInput($event)\"\n @keydown.down.prevent=\"focusPane('rail')\"\n @keydown.enter.prevent=\"selectTopResult()\"\n @keydown.esc=\"close()\"\n />\n <span\n v-if=\"clearable && selectedItem && !disabled && !readOnly\"\n data-id=\"clear\"\n :class=\"[activatorUi.clear(), focused && '!visible']\"\n @click.stop.prevent=\"clearSelection()\"\n >\n <RuiIcon\n color=\"error\"\n name=\"lu-x\"\n size=\"18\"\n />\n </span>\n <span :class=\"activatorUi.iconWrapper()\">\n <RuiIcon\n :class=\"activatorUi.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n <RuiProgress\n v-if=\"loading\"\n :class=\"activatorUi.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n <fieldset\n v-if=\"outlined\"\n :class=\"activatorUi.fieldset()\"\n >\n <legend :class=\"activatorUi.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </div>\n <RuiFormTextDetail\n v-if=\"!hideDetails\"\n class=\"px-3 pt-1\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n </slot>\n </template>\n\n <div\n :id=\"`${baseId}-panel`\"\n :class=\"ui.root({ class: cn(classNames?.root) })\"\n data-id=\"panel\"\n >\n <!-- On desktop the field itself is the search input, so the panel only\n needs its own search box in the mobile sheet. -->\n <div\n v-if=\"isMobile || $slots.header\"\n :class=\"ui.header()\"\n >\n <slot name=\"header\">\n <div :class=\"ui.title()\">\n {{ label }}\n </div>\n </slot>\n <RuiTextField\n v-if=\"searchable && isMobile\"\n v-model=\"searchInput\"\n :class=\"cn(classNames?.search)\"\n :placeholder=\"placeholder\"\n :dense=\"dense\"\n variant=\"outlined\"\n prepend-icon=\"lu-search\"\n hide-details\n clearable\n data-id=\"search\"\n />\n </div>\n\n <div :class=\"ui.body()\">\n <!-- Category rail (master) -->\n <div\n v-show=\"showRail\"\n ref=\"railRef\"\n :class=\"ui.rail({ class: cn(classNames?.rail) })\"\n role=\"tablist\"\n aria-orientation=\"vertical\"\n :aria-label=\"label\"\n :aria-activedescendant=\"focusedPane === 'rail' && activeRailIndex >= 0 ? `${baseId}-cat-${activeRailIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"rail\"\n @focus=\"focusedPane = 'rail'\"\n @keydown.down.prevent=\"moveRail(1)\"\n @keydown.up.prevent=\"moveRail(-1)\"\n @keydown.right.prevent=\"enterDetail()\"\n @keydown.enter.prevent=\"enterDetail()\"\n @keydown.home.prevent=\"setActiveCategory(railEntries[0]?.category ?? null)\"\n @keydown.end.prevent=\"setActiveCategory(railEntries[railEntries.length - 1]?.category ?? null)\"\n >\n <RuiButton\n v-for=\"(entry, index) in railEntries\"\n :id=\"`${baseId}-cat-${index}`\"\n :key=\"entry.category ?? '__all__'\"\n role=\"tab\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"entry.category === activeCategory\"\n :aria-selected=\"entry.category === activeCategory\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'rail' && index === activeRailIndex\"\n :data-category=\"entry.category ?? '__all__'\"\n @click=\"onCategoryClick(entry.category)\"\n >\n <slot\n name=\"category\"\n v-bind=\"{ active: entry.category === activeCategory, category: entry.category, count: entry.count, label: entry.label }\"\n >\n <span class=\"truncate\">{{ entry.label }}</span>\n <span :class=\"ui.railCount()\">{{ entry.count }}</span>\n </slot>\n </RuiButton>\n </div>\n\n <!-- Detail pane -->\n <div\n v-show=\"showDetail\"\n ref=\"detailRef\"\n :class=\"ui.detail({ class: cn(classNames?.detail) })\"\n role=\"listbox\"\n :aria-label=\"activeCategory ?? allLabel\"\n :aria-activedescendant=\"focusedPane === 'detail' && !isEmpty ? `${baseId}-opt-${highlightedItemIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"detail\"\n @focus=\"focusedPane = 'detail'\"\n @keydown.down.prevent=\"moveItem(1)\"\n @keydown.up.prevent=\"moveItem(-1)\"\n @keydown.left.prevent=\"backToRail()\"\n @keydown.home.prevent=\"highlightedItemIndex = 0\"\n @keydown.end.prevent=\"highlightedItemIndex = detailItems.length - 1\"\n @keydown.enter.prevent=\"selectHighlighted()\"\n @keydown.space.prevent=\"selectHighlighted()\"\n >\n <template v-if=\"mode === 'drill' && showDetail\">\n <button\n type=\"button\"\n :class=\"ui.backButton()\"\n data-id=\"back\"\n @click=\"backToRail()\"\n >\n <RuiIcon\n name=\"lu-chevron-left\"\n size=\"18\"\n />\n <span>{{ activeCategory ?? allLabel }}</span>\n </button>\n </template>\n\n <slot\n v-if=\"isEmpty\"\n name=\"empty\"\n >\n <div :class=\"ui.empty()\">\n {{ emptyText }}\n </div>\n </slot>\n\n <template\n v-for=\"row in detailRows\"\n v-else\n :key=\"row.type === 'header' ? `h-${row.category}` : `i-${row.index}`\"\n >\n <div\n v-if=\"row.type === 'header'\"\n :class=\"ui.subheader()\"\n role=\"presentation\"\n >\n {{ row.category }}\n </div>\n <RuiButton\n v-else\n :id=\"`${baseId}-opt-${row.index}`\"\n role=\"option\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"isSelected(row.item)\"\n :aria-selected=\"isSelected(row.item)\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'detail' && row.index === highlightedItemIndex\"\n :class=\"{ [ui.highlighted()]: focusedPane === 'detail' && row.index === highlightedItemIndex && !isSelected(row.item) }\"\n @click=\"selectItem(row.item)\"\n >\n <template\n v-if=\"$slots['item.prepend']\"\n #prepend\n >\n <slot\n name=\"item.prepend\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n >\n {{ getText(row.item) }}\n </slot>\n <template\n v-if=\"$slots['item.append']\"\n #append\n >\n <slot\n name=\"item.append\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n </RuiButton>\n </template>\n </div>\n </div>\n\n <div\n v-if=\"$slots.footer\"\n :class=\"ui.footer()\"\n >\n <slot\n name=\"footer\"\n v-bind=\"{ close }\"\n />\n </div>\n </div>\n </component>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmFA,MAAM,aAAa,SAAmB,SAAA,YAAC;EACvC,MAAM,cAAc,SAAmB,SAAC,QAAyB;EA+BjE,MAAM,OAAO;EAkBb,MAAM,SAAS,IAAa,KAAK;EACjC,MAAM,cAAc,IAAuB,MAAM;EACjD,MAAM,uBAAuB,IAAY,CAAC;EAC1C,MAAM,YAAY,IAA2B,UAAU;EAEvD,MAAM,UAAU,eAA+B,SAAS;EACxD,MAAM,YAAY,eAA+B,WAAW;EAC5D,MAAM,eAAe,eAAiC,cAAc;EACpE,MAAM,WAAW,eAA4B,UAAU;EACvD,MAAM,YAAY,IAAa,KAAK;EAEpC,MAAM,EAAE,YAAY,SAAS,YAAY;EACzC,MAAM,EAAE,UAAU,eAAe,wBAAwB,QAAA,qBAAqB,QAAA,eAAe;EAE7F,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,eAAe,mBAAmB;EAEtD,SAAS,gBAAgB,MAAqB;GAC5C,IAAI,QAAA,YACF,OAAO,QAAA,WAAW,IAAI;GACxB,IAAI,QAAA,iBAAiB,KAAA,GACnB,OAAO,OAAO,KAAK,QAAA,iBAAiB,EAAE;GACxC,OAAO;EACT;EAEA,MAAM,EACJ,gBACA,aACA,YACA,eACA,SACA,SACA,YACA,aACA,mBACA,0BACE,kBAAiC;GACnC,gBAAgB,QAAA;GAChB,YAAY;GACZ,cAAc,QAAA;GACd,aAAa,QAAA;GACb,SAAM,QAAA;GACN;GACA,QAAQ;GACR,kBAAkB,QAAA;GAClB,eAAe,QAAA;GACf,UAAO,QAAA;EACT,CAAC;EAED,MAAM,WAAW,eAAwB,MAAI,YAAY,QAAQ,QAAA,gBAAgB,CAAC,CAAC;EAEnF,MAAM,OAAO,eAAqC,MAAI,QAAQ,IAAI,UAAU,SAAU;EAKtF,MAAM,QAAQ,eAAmD,MAAI,QAAQ,IAAI,oBAAY,eAAQ;EAErG,MAAM,aAAa,eAAwC;GACzD,IAAI,MAAI,QAAQ,GACd,OAAO;IACL,WAAW,QAAA,eAAe,aAAa,QAAA;IACvC,aAAa,QAAA,eAAe,eAAe;IAC3C,UAAU,QAAA,eAAe;IACzB,YAAY,QAAA,eAAe;IAC3B,OAAO,QAAA,eAAe,SAAS;GACjC;GAEF,OAAO;IAGL,UAAU,MAAI,QAAQ,KAAK,KAAA;IAC3B,YAAY,EAAE,MAAM,WAAW;IAC/B,qBAAqB;IACrB,kBAAkB;IAClB,WAAW;IACX,SAAS,EAAE,WAAW,UAAU,YAAY;IAC5C,YAAY,QAAA,eAAe;IAC3B,yBAAyB;GAC3B;EACF,CAAC;EAED,MAAM,KAAK,eAAwD,qBAAqB;GACtF,OAAI,QAAA;GACJ,MAAM,MAAI,IAAI;EAChB,CAAC,CAAC;EAEF,MAAM,eAAe,eAAkC;GACrD,MAAM,QAAQ,MAAI,UAAU;GAC5B,IAAI,UAAU,KAAA,GACZ,OAAO,KAAA;GACT,OAAO,QAAA,MAAM,MAAK,SAAQ,cAAc,IAAI,MAAM,KAAK;EACzD,CAAC;EAED,MAAM,eAAe,eAAmC;GACtD,MAAM,WAAW,MAAI,YAAY;GACjC,OAAO,WAAW,QAAQ,QAAQ,IAAI,KAAA;EACxC,CAAC;EAED,MAAM,WAAW,eAAwB,QAAA,YAAY,UAAU;EAI/D,MAAM,UAAU,eAAwB,QAAA,cAAc,CAAC,MAAI,QAAQ,KAAK,CAAC,QAAA,YAAY,CAAC,QAAA,QAAQ;EAE9F,MAAM,WAAW,eAAwB,MAAI,MAAM,KAAK,MAAI,OAAO,CAAC;EAEpE,MAAM,QAAQ,gBAAyB,MAAI,MAAM,KAAK,MAAI,YAAY,MAAM,KAAA,KAAa,MAAI,OAAO,MAAM,MAAI,QAAQ,CAAC;EAEvH,MAAM,aAAa,eAAuB;GACxC,IAAI,CAAC,MAAI,KAAK,KAAK,CAAC,QAAA,OAClB,OAAO;GACT,OAAO,QAAA,WAAW,GAAG,QAAA,MAAM,MAAM,QAAA;EACnC,CAAC;EAED,MAAM,cAAc,eAAiE,8BAA8B;GACjH,OAAI,QAAA;GACJ,UAAO,QAAA;GACP,QAAQ,QAAA,YAAY;GACpB,OAAO,MAAI,KAAK;GAChB,UAAU,MAAI,QAAQ;GACtB,YAAY,MAAI,UAAU,KAAK,CAAC,MAAI,QAAQ;GAC5C,SAAS,MAAI,SAAS;GACtB,QAAQ,MAAI,MAAM;GAClB,UAAU,MAAI,QAAQ;GACtB,UAAU,QAAA;EACZ,CAAC,CAAC;EAEF,MAAM,kBAAkB,eACtB,MAAI,WAAW,CAAC,CAAC,WAAU,UAAS,MAAM,aAAa,MAAI,cAAc,CAAC,CAAC;EAE7E,MAAM,WAAW,eAAwB,MAAI,IAAI,MAAM,aAAa,MAAI,SAAS,MAAM,UAAU;EACjG,MAAM,aAAa,eAAwB,MAAI,IAAI,MAAM,aAAa,MAAI,SAAS,MAAM,QAAQ;EAEjG,SAAS,MAAM,OAAe,KAAqB;GACjD,IAAI,QAAQ,GACV,OAAO;GACT,IAAI,QAAQ,KACV,OAAO;GACT,OAAO;EACT;EAEA,SAAS,OAAa;GACpB,IAAI,QAAA,UACF;GACF,MAAI,QAAQ,IAAI;EAClB;EAEA,SAAS,QAAc;GACrB,MAAI,QAAQ,KAAK;EACnB;EAEA,SAAS,QAAQ,OAAoB;GACnC,MAAM,SAAS,MAAM;GACrB,IAAI,EAAE,kBAAkB,mBACtB;GACF,MAAI,aAAa,OAAO,KAAK;GAC7B,IAAI,CAAC,MAAI,MAAM,GACb,KAAK;EACT;EAEA,SAAS,WAAW,MAAmB;GACrC,IAAI,QAAA,UACF;GACF,MAAI,YAAY,cAAc,IAAI,CAAC;GACnC,KAAK,UAAU,IAAI;GACnB,MAAM;EACR;EAEA,SAAS,iBAAuB;GAC9B,MAAI,YAAY,KAAA,CAAS;EAC3B;EAEA,SAAS,gBAAgB,UAA+B;GACtD,kBAAkB,QAAQ;GAC1B,IAAI,MAAI,IAAI,MAAM,SAChB,MAAI,WAAW,QAAQ;QAEvB,UAAU,QAAQ;EACtB;EAEA,SAAS,UAAU,MAA+B;GAChD,MAAI,aAAa,IAAI;GACrB,eAAe;IACb,CAAC,SAAS,SAAS,MAAI,OAAO,IAAI,MAAI,SAAS,EAAA,EAAI,MAAM;GAC3D,CAAC;EACH;EAEA,SAAS,SAAS,WAAyB;GACzC,MAAM,UAAU,MAAI,WAAW;GAC/B,IAAI,QAAQ,WAAW,GACrB;GAEF,kBAAkB,QADL,MAAM,MAAI,eAAe,IAAI,WAAW,QAAQ,SAAS,CAC5C,EAAK,EAAE,YAAY,IAAI;EACnD;EAEA,SAAS,SAAS,WAAyB;GACzC,MAAM,QAAQ,MAAI,WAAW,CAAC,CAAC;GAC/B,IAAI,UAAU,GACZ;GACF,MAAI,sBAAsB,MAAM,MAAI,oBAAoB,IAAI,WAAW,QAAQ,CAAC,CAAC;EACnF;EAEA,SAAS,cAAoB;GAC3B,IAAI,MAAI,WAAW,CAAC,CAAC,WAAW,GAC9B;GACF,0BAA0B;GAC1B,IAAI,MAAI,IAAI,MAAM,SAChB,MAAI,WAAW,QAAQ;GACzB,UAAU,QAAQ;EACpB;EAEA,SAAS,oBAA0B;GACjC,MAAM,OAAO,MAAI,WAAW,CAAC,CAAC,MAAI,oBAAoB;GACtD,IAAI,SAAS,KAAA,GACX,WAAW,IAAI;EACnB;EAKA,SAAS,kBAAwB;GAC/B,IAAI,CAAC,MAAI,WAAW,CAAC,CAAC,KAAK,GACzB;GACF,MAAM,OAAO,MAAI,WAAW,CAAC,CAAC;GAC9B,IAAI,SAAS,KAAA,GACX,WAAW,IAAI;EACnB;EAEA,SAAS,4BAAkC;GACzC,MAAM,QAAQ,MAAI,WAAW,CAAC,CAAC,WAAU,SAAQ,WAAW,IAAI,CAAC;GACjE,MAAI,sBAAsB,SAAS,IAAI,QAAQ,CAAC;EAClD;EAEA,SAAS,aAAmB;GAC1B,IAAI,MAAI,IAAI,MAAM,SAChB,MAAI,WAAW,UAAU;QAEzB,UAAU,MAAM;EACpB;EAEA,SAAS,+BAA+B,WAA+B,IAAkB;GACvF,IAAI,CAAC,WACH;GACF,eAAe;IACb,UAAU,cAAc,IAAI,IAAI,CAAC,EAAE,eAAe,EAAE,OAAO,UAAU,CAAC;GACxE,CAAC;EACH;EAGA,MAAM,sBAAsB;GAC1B,0BAA0B;EAC5B,CAAC;EAED,MAAM,uBAAuB,UAAU;GACrC,+BAA+B,MAAI,SAAS,GAAG,GAAG,OAAO,OAAO,OAAO;EACzE,CAAC;EAED,MAAM,kBAAkB,UAAU;GAChC,IAAI,SAAS,GACX,+BAA+B,MAAI,OAAO,GAAG,GAAG,OAAO,OAAO,OAAO;EACzE,CAAC;EAED,MAAM,SAAS,UAAU;GACvB,IAAI,OAAO;IACT,sBAAsB;IACtB,MAAI,aAAa,MAAM;IACvB,MAAI,WAAW,UAAU;IACzB,0BAA0B;IAI1B,gBAAgB,MAAI,OAAO,IAAI,MAAI,YAAY,IAAI,MAAI,OAAO,EAAA,EAAI,MAAM,CAAC;GAC3E,OACK;IAGH,MAAI,aAAa,EAAE;IACnB,eAAe,MAAI,YAAY,CAAC,EAAE,MAAM,CAAC;GAC3C;EACF,CAAC;;uBAIC,YA6SY,wBA5SL,MAAA,KAAA,CAAK,GADZ,WA6SY;gBA3SD,MAAA,MAAA;0FAAM,QAAA,SAAA;;OACFA,KAAAA;IAAM,GAAK,MAAA,UAAA;GAAU,CAAA,GAAA;IAEvB,WAAS,SAgHX,EAhHe,YAAK,CAC3B,WA+GO,KAAA,QAAA,aAAA,eAAA,mBAAA;KA7GK;KAAK,QAAE,MAAA,MAAA;KAAM,MAAQ,MAAA,YAAA;KAAc;KAAI,MAAQ,MAAA,YAAA;IAAY,CAAA,CAAA,SA6GhE,CA3GL,mBA0GM,OAAA,EA1GA,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,QAAO,CAAA,EAAA,GAAA,CAC9B,mBAiGM,OAjGN,WAiGM;cAhGA;KAAJ,KAAI;KACJ,MAAK;KACL,WAAQ;KACP,OAAO,MAAA,WAAA,CAAW,CAAC,UAAS;KAC7B,iBAAc;KACb,iBAAe,MAAA,MAAA,IAAM,GAAM,MAAA,MAAA,EAAM,UAAW,KAAA;KAC5C,iBAAe,MAAA,MAAA;KACf,iBAAe,QAAA,YAAY,KAAA;KAC3B,iBAAgB,QAAA,YAAQ,CAAK,MAAA,OAAA,KAAY,KAAA;KACzC,iBAAe,QAAA,YAAY,KAAA;KAC3B,gBAAc,MAAA,QAAA,KAAY,KAAA;KAC1B,aAAW,QAAA,WAAW,KAAA;OACf,QAAA,WAAQ,CAAA,IAAQ,OAAK;KAC5B,cAAU,OAAA,OAAA,OAAA,MAAA,WAAE,UAAA,QAAS;KACrB,cAAU,OAAA,OAAA,OAAA,MAAA,WAAE,UAAA,QAAS;;KAGd,MAAA,QAAA,KAAQ,CAAM,MAAA,YAAA,KAAY,CAAK,MAAA,QAAA,KAAA,UAAA,GADvC,mBAgBO,QAAA;;MAdJ,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,MAAK,CAAA;SAEzB,WAKO,KAAA,QAAA,mBAAA,eAAA,mBAAA,EAAA,MAHW,MAAA,YAAA,EAAY,CAAA,CAAA,SAGvB,CAAA,gBAAA,gBADF,QAAA,KAAK,GAAA,CAAA,CAAA,CAAA,GAGF,QAAA,YAAA,UAAA,GADR,mBAKO,QAAA;;MAHJ,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,SAAQ,CAAA;QAC7B,OAED,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAGM,MAAA,YAAA,KAAY,CAAK,MAAA,QAAA,KAAYC,KAAAA,OAAO,aAAA,UAAA,GAD5C,mBAaO,QAAA;;MAXL,OAAK,eAAA,CAAC,iFAA+E,CAC5E,MAAA,WAAA,CAAW,CAAC,MAAK,CAAA,CAAA,CAAA;SAE1B,WAGE,KAAA,QAAA,qBAAA,eAAA,mBAAA,EAAA,MADgB,MAAA,YAAA,EAAY,CAAA,CAAA,CAAA,GAE9B,WAGE,KAAA,QAAA,aAAA,eAAA,mBAAA,EAAA,MADgB,MAAA,YAAA,EAAY,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAGhC,mBAcE,SAAA;eAbI;MAAJ,KAAI;MACJ,WAAQ;MACP,OAAO,MAAA,QAAA,IAAW,YAAA,QAAgBA,KAAAA,OAAO,aAAa,MAAA,YAAA,IAAY,KAAU,MAAA,YAAA,KAAY;MACxF,aAAa,MAAA,QAAA,IAAW,QAAA,cAAW;MACnC,UAAQ,CAAG,MAAA,OAAA;MACX,UAAU,QAAA;MACV,gBAAc,MAAA,QAAA,KAAY,KAAA;MAC3B,OAAK,eAAA,CAAC,+BAA6B,CAC1B,MAAA,WAAA,CAAW,CAAC,MAAK,GAAA,CAAK,MAAA,OAAA,KAAO,gBAAA,CAAA,CAAA;MACrC,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,QAAQ,MAAM;MACrB,WAAO;oEAAe,UAAS,MAAA,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,MAAA,CAAA;oEACR,gBAAe,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;sDACzB,MAAK,GAAA,CAAA,KAAA,CAAA;;;KAGb,QAAA,aAAa,MAAA,YAAA,KAAY,CAAK,QAAA,YAAQ,CAAK,QAAA,YAAA,UAAA,GADnD,mBAWO,QAAA;;MATL,WAAQ;MACP,OAAK,eAAA,CAAG,MAAA,WAAA,CAAW,CAAC,MAAK,GAAI,MAAA,OAAA,KAAO,UAAA,CAAA;MACpC,SAAK,OAAA,OAAA,OAAA,KAAA,eAAA,WAAe,eAAc,GAAA,CAAA,QAAA,SAAA,CAAA;SAEnC,YAIE,iBAAA;MAHA,OAAM;MACN,MAAK;MACL,MAAK;;KAGT,mBAMO,QAAA,EANA,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,YAAW,CAAA,EAAA,GAAA,CACnC,YAIE,iBAAA;MAHC,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,KAAI,CAAA;MACvB,MAAM,QAAA,QAAK,KAAA;MACZ,MAAK;;KAID,QAAA,WAAA,UAAA,GADR,YAME,qBAAA;;MAJC,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,SAAQ,CAAA;MAC5B,OAAM;MACN,WAAU;MACV,SAAQ;;KAGF,MAAA,QAAA,KAAA,UAAA,GADR,mBAOW,YAAA;;MALR,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,SAAQ,CAAA;SAE5B,mBAES,UAAA,EAFA,OAAK,eAAE,MAAA,WAAA,CAAW,CAAC,OAAM,CAAA,EAAA,GAAA,gBAC7B,MAAA,UAAA,CAAU,GAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;yBAKV,QAAA,eAAA,UAAA,GADT,YAME,2BAAA;;KAJA,OAAM;KACL,kBAAgB,QAAA;KAChB,oBAAkB,QAAA;KAClB,MAAM,QAAA;;;;;;2BA0LT,CApLN,mBAoLM,OAAA;KAnLH,IAAE,GAAK,MAAA,MAAA,EAAM;KACb,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,KAAI,EAAA,OAAU,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,IAAI,EAAA,CAAA,CAAA;KAC5C,WAAQ;;KAKA,MAAA,QAAA,KAAYA,KAAAA,OAAO,UAAA,UAAA,GAD3B,mBAqBM,OAAA;;MAnBH,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,OAAM,CAAA;SAEjB,WAIO,KAAA,QAAA,UAAA,CAAA,SAAA,CAHL,mBAEM,OAAA,EAFA,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,MAAK,CAAA,EAAA,GAAA,gBAChB,QAAA,KAAK,GAAA,CAAA,CAAA,CAAA,GAIJ,QAAA,cAAc,MAAA,QAAA,KAAA,UAAA,GADtB,YAWE,sBAAA;;kBATS,YAAA;+EAAW,QAAA;MACnB,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,MAAM,CAAA;MAC5B,aAAa,QAAA;MACb,OAAO,QAAA;MACR,SAAQ;MACR,gBAAa;MACb,gBAAA;MACA,WAAA;MACA,WAAQ;;;;;;;KAIZ,mBA2IM,OAAA,EA3IA,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,KAAI,CAAA,EAAA,GAAA,CAAA,eAElB,mBAwCM,OAAA;eAtCA;MAAJ,KAAI;MACH,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,KAAI,EAAA,OAAU,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,IAAI,EAAA,CAAA,CAAA;MAC5C,MAAK;MACL,oBAAiB;MAChB,cAAY,QAAA;MACZ,yBAAuB,MAAA,WAAA,MAAW,UAAe,MAAA,eAAA,KAAe,IAAA,GAAW,MAAA,MAAA,EAAM,OAAQ,MAAA,eAAA,MAAoB,KAAA;MAC9G,UAAS;MACT,WAAQ;MACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,YAAA,QAAW;MAClB,WAAO;oEAAe,SAAQ,CAAA,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,MAAA,CAAA;sEACV,SAAQ,EAAA,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,IAAA,CAAA;sEACL,YAAW,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;sEACX,YAAW,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;sEACZ,MAAA,iBAAA,CAAiB,CAAC,MAAA,WAAA,CAAW,CAAA,EAAA,EAAK,YAAQ,IAAA,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,MAAA,CAAA;sEAC3C,MAAA,iBAAA,CAAiB,CAAC,MAAA,WAAA,CAAW,CAAC,MAAA,WAAA,CAAW,CAAC,SAAM,EAAA,EAAO,YAAQ,IAAA,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,KAAA,CAAA;;2BAErF,mBAqBY,UAAA,MAAA,WApBe,MAAA,WAAA,IAAjB,OAAO,UAAK;0BADtB,YAqBY,mBAAA;OAnBT,IAAE,GAAK,MAAA,MAAA,EAAM,OAAQ;OACrB,KAAK,MAAM,YAAQ;OACpB,MAAK;OACL,SAAQ;OACP,MAAM,QAAA,QAAK,OAAU,KAAA;OACrB,QAAQ,MAAM,aAAa,MAAA,cAAA;OAC3B,iBAAe,MAAM,aAAa,MAAA,cAAA;OACnC,UAAS;OACR,oBAAkB,MAAA,WAAA,MAAW,UAAe,UAAU,MAAA,eAAA;OACtD,iBAAe,MAAM,YAAQ;OAC7B,UAAK,WAAE,gBAAgB,MAAM,QAAQ;;8BAQ/B,CANP,WAMO,KAAA,QAAA,YANP,WAMO,EAAA,SAAA,KAAA,GAAA;QAAA,QAJa,MAAM,aAAa,MAAA,cAAA;QAAc,UAAY,MAAM;QAAQ,OAAS,MAAM;QAAK,OAAS,MAAM;OAAK,CAAA,SAIhH,CAFL,mBAA+C,QAA/C,YAA+C,gBAArB,MAAM,KAAK,GAAA,CAAA,GACrC,mBAAsD,QAAA,EAA/C,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,UAAS,CAAA,EAAA,GAAA,gBAAO,MAAM,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;2CApCxC,MAAA,QAAA,CAAQ,CAAA,CAAA,GAAA,eA0ClB,mBA6FM,OAAA;eA3FA;MAAJ,KAAI;MACH,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,OAAM,EAAA,OAAU,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,MAAM,EAAA,CAAA,CAAA;MAChD,MAAK;MACJ,cAAY,MAAA,cAAA,KAAkB,QAAA;MAC9B,yBAAuB,MAAA,WAAA,MAAW,YAAA,CAAkB,MAAA,OAAA,IAAO,GAAM,MAAA,MAAA,EAAM,OAAQ,MAAA,oBAAA,MAAyB,KAAA;MACzG,UAAS;MACT,WAAQ;MACP,SAAK,OAAA,QAAA,OAAA,OAAA,WAAE,YAAA,QAAW;MAClB,WAAO;sEAAe,SAAQ,CAAA,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,MAAA,CAAA;sEACV,SAAQ,EAAA,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,IAAA,CAAA;sEACN,WAAU,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,MAAA,CAAA;sEACV,qBAAA,QAAoB,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,MAAA,CAAA;sEACrB,qBAAA,QAAuB,MAAA,WAAA,CAAW,CAAC,SAAM,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,KAAA,CAAA;sEACvC,kBAAiB,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;sEACjB,kBAAiB,GAAA,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;;SAEzB,MAAA,IAAA,MAAI,WAAgB,MAAA,UAAA,KAAA,UAAA,GAClC,mBAWS,UAAA;;MAVP,MAAK;MACJ,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,WAAU,CAAA;MACrB,WAAQ;MACP,SAAK,OAAA,QAAA,OAAA,OAAA,WAAE,WAAU;SAElB,YAGE,iBAAA;MAFA,MAAK;MACL,MAAK;SAEP,mBAA6C,QAAA,MAAA,gBAApC,MAAA,cAAA,KAAkB,QAAA,QAAQ,GAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAK/B,MAAA,OAAA,IADR,WAOO,KAAA,QAAA,SAAA,EAAA,KAAA,EAAA,SAAA,CAHL,mBAEM,OAAA,EAFA,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,MAAK,CAAA,EAAA,GAAA,gBAChB,QAAA,SAAS,GAAA,CAAA,CAAA,CAAA,KAAA,UAAA,IAAA,GAIhB,mBAkDW,UAAA,EAAA,KAAA,EAAA,GAAA,WAjDK,MAAA,UAAA,IAAP,QAAG;8DAEJ,IAAI,SAAI,WAAA,KAAqB,IAAI,aAAQ,KAAU,IAAI,QAAA,GAAA,CAGrD,IAAI,SAAI,YAAA,UAAA,GADhB,mBAMM,OAAA;;OAJH,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,UAAS,CAAA;OACpB,MAAK;yBAEF,IAAI,QAAQ,GAAA,CAAA,MAAA,UAAA,GAEjB,YAqCY,mBAAA;;OAnCT,IAAE,GAAK,MAAA,MAAA,EAAM,OAAQ,IAAI;OAC1B,MAAK;OACL,SAAQ;OACP,MAAM,QAAA,QAAK,OAAU,KAAA;OACrB,QAAQ,MAAA,UAAA,CAAU,CAAC,IAAI,IAAI;OAC3B,iBAAe,MAAA,UAAA,CAAU,CAAC,IAAI,IAAI;OACnC,UAAS;OACR,oBAAkB,MAAA,WAAA,MAAW,YAAiB,IAAI,UAAU,MAAA,oBAAA;OAC5D,OAAK,eAAA,GAAK,MAAA,EAAA,CAAE,CAAC,YAAW,IAAK,MAAA,WAAA,MAAW,YAAiB,IAAI,UAAU,MAAA,oBAAA,KAAoB,CAAK,MAAA,UAAA,CAAU,CAAC,IAAI,IAAI,EAAA,CAAA;OACnH,UAAK,WAAE,WAAW,IAAI,IAAI;;8BAgBpB,CALP,WAKO,KAAA,QAAA,QALP,WAKO,EAAA,SAAA,KAAA,GAAA;QAAA,QAHa,IAAI,UAAU,MAAA,oBAAA;QAAoB,MAAQ,IAAI;QAAI,UAAY,MAAA,UAAA,CAAU,CAAC,IAAI,IAAI;OAAA,CAAA,SAG9F,CAAA,gBAAA,gBADF,MAAA,OAAA,CAAO,CAAC,IAAI,IAAI,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;UAZbA,KAAAA,OAAM,kBAAA;aACX;yBAKC,CAHF,WAGE,KAAA,QAAA,gBAHF,WAGE,EAAA,SAAA,KAAA,GAAA;QAAA,QADkB,IAAI,UAAU,MAAA,oBAAA;QAAoB,MAAQ,IAAI;QAAI,UAAY,MAAA,UAAA,CAAU,CAAC,IAAI,IAAI;OAAA,CAAA,CAAA,CAAA,CAAA;;kBAU/FA,KAAAA,OAAM,iBAAA;aACX;yBAKC,CAHF,WAGE,KAAA,QAAA,eAHF,WAGE,EAAA,SAAA,KAAA,GAAA;QAAA,QADkB,IAAI,UAAU,MAAA,oBAAA;QAAoB,MAAQ,IAAI;QAAI,UAAY,MAAA,UAAA,CAAU,CAAC,IAAI,IAAI;OAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;2CAvFnG,MAAA,UAAA,CAAU,CAAA,CAAA,CAAA,GAAA,CAAA;KAgGdA,KAAAA,OAAO,UAAA,UAAA,GADf,mBAQM,OAAA;;MANH,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,OAAM,CAAA;SAEjB,WAGE,KAAA,QAAA,UAAA,eAAA,mBAAA,EADU,MAAK,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA"}
1
+ {"version":3,"file":"RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js","names":["$attrs","$slots"],"sources":["../../../../src/components/forms/category-picker/RuiCategoryPicker.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { KeyOfType } from '@/composables/dropdown-menu';\nimport type { VueClassValue } from '@/types/class-value';\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { categoryPickerActivatorStyles, categoryPickerStyles, type CategoryPickerVariant } from '@/components/forms/category-picker/category-picker-styles';\nimport RuiTextField from '@/components/forms/text-field/RuiTextField.vue';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiDialog, { type DialogProps } from '@/components/overlays/dialog/RuiDialog.vue';\nimport RuiMenu from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport { Placement } from '@/composables/floating';\nimport { useCategoryPicker } from '@/composables/forms/category-picker/use-category-picker';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn } from '@/utils/tv';\n\nexport interface RuiCategoryPickerClassNames {\n root?: VueClassValue;\n rail?: VueClassValue;\n detail?: VueClassValue;\n search?: VueClassValue;\n}\n\nexport type CategoryPickerBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport interface RuiCategoryPickerProps<TValue, TItem> {\n /** The full catalogue of selectable items. */\n items: TItem[];\n /** Resolve an item's category label. Takes precedence over `categoryAttr`. */\n categoryOf?: (item: TItem) => string;\n /** Resolve an item's category from one of its own properties. */\n categoryAttr?: keyof TItem;\n /** Property whose value becomes the model value. Omit to use the item itself. */\n keyAttr?: KeyOfType<TItem, TValue extends (infer U)[] ? U : TValue>;\n /** Property that produces an item's display text. */\n textAttr?: keyof TItem;\n /** Field label, and the title of the mobile sheet. */\n label?: string;\n /** Placeholder shown in the field while it is empty or being searched. */\n placeholder?: string;\n /** Allow filtering: type in the field (desktop) or the sheet's search box (mobile). */\n searchable?: boolean;\n /**\n * Custom predicate. Receives the resolved category label as the third\n * argument, matching the `RuiAutoComplete` filter signature.\n */\n filter?: (item: TItem, query: string, category: string) => boolean;\n /** Show the synthetic \"All\" pseudo-category as the first rail entry. */\n showAll?: boolean;\n /** Label for the \"All\" pseudo-category. */\n allLabel?: string;\n /** Text shown when no item matches the current search. */\n emptyText?: string;\n disabled?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n loading?: boolean;\n /** Trigger field style: underline (`default`), `outlined`, or `filled`. */\n variant?: CategoryPickerVariant;\n /** Marks the trigger field as required (renders the asterisk). */\n required?: boolean;\n /** Validation errors rendered under the trigger field. */\n errorMessages?: string | string[];\n /** Success messages rendered under the trigger field. */\n successMessages?: string | string[];\n /** Hint rendered under the trigger field. */\n hint?: string;\n /** Hide the details row (errors/success/hint) under the trigger field. */\n hideDetails?: boolean;\n /** Below this breakpoint the picker collapses to a single-pane drill-in. */\n mobileBreakpoint?: CategoryPickerBreakpoint;\n /** Forwarded to the mobile bottom-sheet (`RuiDialog`). */\n dialogOptions?: DialogProps;\n classNames?: RuiCategoryPickerClassNames;\n}\n\ndefineOptions({\n name: 'RuiCategoryPicker',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<TValue>();\nconst searchInput = defineModel<string>('search', { default: '' });\n\nconst {\n items,\n categoryOf = undefined,\n categoryAttr = undefined,\n keyAttr = undefined,\n textAttr = undefined,\n label = 'Select',\n placeholder = 'Search',\n searchable = true,\n filter = undefined,\n showAll = true,\n allLabel = 'All',\n emptyText = 'No results',\n disabled = false,\n readOnly = false,\n dense = false,\n clearable = false,\n loading = false,\n variant = 'outlined',\n required = false,\n errorMessages = [],\n successMessages = [],\n hint = '',\n hideDetails = false,\n mobileBreakpoint = 'md',\n dialogOptions = undefined,\n classNames = undefined,\n} = defineProps<RuiCategoryPickerProps<TValue, TItem>>();\n\nconst emit = defineEmits<{\n select: [item: TItem];\n}>();\n\ndefineSlots<{\n 'activator'?: (props: { attrs: { onClick?: () => void; onMouseover?: () => void; onMouseleave?: () => void }; isOpen: boolean; open: () => void; item: TItem | undefined; text: string | undefined }) => any;\n 'activator.label'?: (props: { item: TItem | undefined }) => any;\n 'selection'?: (props: { item: TItem }) => any;\n 'selection.prepend'?: (props: { item: TItem }) => any;\n 'category'?: (props: { category: string | null; label: string; active: boolean; count: number }) => any;\n 'item'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.prepend'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'item.append'?: (props: { item: TItem; active: boolean; selected: boolean }) => any;\n 'empty'?: () => any;\n 'header'?: () => any;\n 'footer'?: (props: { close: () => void }) => any;\n}>();\n\nconst isOpen = ref<boolean>(false);\nconst focusedPane = ref<'rail' | 'detail'>('rail');\nconst highlightedItemIndex = ref<number>(0);\nconst drillStep = ref<'category' | 'detail'>('category');\n\nconst railRef = useTemplateRef<HTMLDivElement>('railRef');\nconst detailRef = useTemplateRef<HTMLDivElement>('detailRef');\nconst activatorRef = useTemplateRef<HTMLInputElement>('activatorRef');\nconst fieldRef = useTemplateRef<HTMLElement>('fieldRef');\nconst isHovered = ref<boolean>(false);\n\nconst { focused } = useFocus(activatorRef);\nconst { hasError, hasSuccess } = useFormTextDetail(() => errorMessages, () => successMessages);\n\nconst baseId = useId();\nconst breakpoints = useBreakpoints(breakpointsTailwind);\n\nfunction resolveCategory(item: TItem): string {\n if (categoryOf)\n return categoryOf(item);\n if (categoryAttr !== undefined)\n return String(item[categoryAttr] ?? '');\n return '';\n}\n\nconst {\n activeCategory,\n detailItems,\n detailRows,\n getIdentifier,\n getText,\n isEmpty,\n isSelected,\n railEntries,\n setActiveCategory,\n syncActiveToSelection,\n} = useCategoryPicker<TValue, TItem>({\n allLabel: () => allLabel,\n categoryOf: resolveCategory,\n filter: () => filter,\n items: () => items,\n keyAttr,\n modelValue,\n search: searchInput,\n searchable: () => searchable,\n showAll: () => showAll,\n textAttr,\n});\n\nconst isMobile = computed<boolean>(() => get(breakpoints.smaller(mobileBreakpoint)));\n\nconst mode = computed<'twoPane' | 'drill'>(() => (get(isMobile) ? 'drill' : 'twoPane'));\n\n// Desktop anchors an outside-click popover under the field (RuiMenu); narrow\n// width shows a full-width bottom sheet (RuiDialog). Each branch returns only\n// its own shell's props so the other component's props never leak as attrs.\nconst shell = computed<typeof RuiMenu | typeof RuiDialog>(() => (get(isMobile) ? RuiDialog : RuiMenu));\n\nconst shellProps = computed<Record<string, unknown>>(() => {\n if (get(isMobile)) {\n return {\n ariaLabel: dialogOptions?.ariaLabel ?? label,\n bottomSheet: dialogOptions?.bottomSheet ?? true,\n maxWidth: dialogOptions?.maxWidth,\n persistent: dialogOptions?.persistent,\n width: dialogOptions?.width ?? '100%',\n };\n }\n return {\n // Anchor to the field box, not the wrapper, so the popover sits directly\n // under the input instead of below the reserved details row.\n anchorEl: get(fieldRef) ?? undefined,\n // Drop the menu's default vertical padding so the popover box equals our\n // panel: the size middleware caps the panel to the available height, and\n // any extra chrome padding would push the popover past the viewport edge.\n classNames: { content: 'py-0', menu: 'z-[9999]' },\n closeOnContentClick: false,\n disableAutoFocus: true,\n fullWidth: true,\n options: { placement: Placement.bottomStart, size: true },\n persistent: dialogOptions?.persistent,\n persistOnActivatorClick: true,\n };\n});\n\nconst ui = computed<ReturnType<typeof categoryPickerStyles>>(() => categoryPickerStyles({\n dense,\n mode: get(mode),\n}));\n\nconst selectedItem = computed<TItem | undefined>(() => {\n const value = get(modelValue);\n if (value === undefined)\n return undefined;\n return items.find(item => getIdentifier(item) === value);\n});\n\nconst selectedText = computed<string | undefined>(() => {\n const selected = get(selectedItem);\n return selected ? getText(selected) : undefined;\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\n\n// Desktop lets the user type in the field itself (no separate search box);\n// narrow width keeps a read-only trigger that opens the sheet's own search.\nconst canType = computed<boolean>(() => searchable && !get(isMobile) && !readOnly && !disabled);\n\nconst isTyping = computed<boolean>(() => get(isOpen) && get(canType));\n\nconst float = computed<boolean>(() => (get(isOpen) || get(selectedItem) !== undefined || get(focused)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst activatorUi = computed<ReturnType<typeof categoryPickerActivatorStyles>>(() => categoryPickerActivatorStyles({\n dense,\n disabled,\n filled: variant === 'filled',\n float: get(float),\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n hovered: get(isHovered),\n opened: get(isOpen),\n outlined: get(outlined),\n readonly: readOnly,\n}));\n\nconst activeRailIndex = computed<number>(() =>\n get(railEntries).findIndex(entry => entry.category === get(activeCategory)));\n\nconst showRail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'category');\nconst showDetail = computed<boolean>(() => get(mode) === 'twoPane' || get(drillStep) === 'detail');\n\nfunction clamp(value: number, max: number): number {\n if (value < 0)\n return 0;\n if (value > max)\n return max;\n return value;\n}\n\nfunction open(): void {\n if (disabled)\n return;\n set(isOpen, true);\n}\n\nfunction close(): void {\n set(isOpen, false);\n}\n\nfunction onInput(event: Event): void {\n const target = event.target;\n if (!(target instanceof HTMLInputElement))\n return;\n set(searchInput, target.value);\n if (!get(isOpen))\n open();\n}\n\nfunction selectItem(item: TItem): void {\n if (readOnly)\n return;\n set(modelValue, getIdentifier(item));\n emit('select', item);\n close();\n}\n\nfunction clearSelection(): void {\n set(modelValue, undefined);\n}\n\nfunction onCategoryClick(category: string | null): void {\n setActiveCategory(category);\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n else\n focusPane('detail');\n}\n\nfunction focusPane(pane: 'rail' | 'detail'): void {\n set(focusedPane, pane);\n nextTick(() => {\n // preventScroll: focus moves between panes inside a teleported popover;\n // without it the browser scrolls the page behind to reveal the pane.\n (pane === 'rail' ? get(railRef) : get(detailRef))?.focus({ preventScroll: true });\n });\n}\n\nfunction moveRail(direction: 1 | -1): void {\n const entries = get(railEntries);\n if (entries.length === 0)\n return;\n const next = clamp(get(activeRailIndex) + direction, entries.length - 1);\n setActiveCategory(entries[next]?.category ?? null);\n}\n\nfunction moveItem(direction: 1 | -1): void {\n const total = get(detailItems).length;\n if (total === 0)\n return;\n set(highlightedItemIndex, clamp(get(highlightedItemIndex) + direction, total - 1));\n}\n\nfunction enterDetail(): void {\n if (get(detailItems).length === 0)\n return;\n resetHighlightToSelection();\n if (get(mode) === 'drill')\n set(drillStep, 'detail');\n focusPane('detail');\n}\n\nfunction selectHighlighted(): void {\n const item = get(detailItems)[get(highlightedItemIndex)];\n if (item !== undefined)\n selectItem(item);\n}\n\n// Enter from the field commits the best match for the current query, so a\n// type-to-pick flow (type a few letters, press Enter) works without leaving the\n// input. Does nothing when the field is empty.\nfunction selectTopResult(): void {\n if (!get(searchInput).trim())\n return;\n const item = get(detailItems)[0];\n if (item !== undefined)\n selectItem(item);\n}\n\nfunction resetHighlightToSelection(): void {\n const index = get(detailItems).findIndex(item => isSelected(item));\n set(highlightedItemIndex, index >= 0 ? index : 0);\n}\n\nfunction backToRail(): void {\n if (get(mode) === 'drill')\n set(drillStep, 'category');\n else\n focusPane('rail');\n}\n\nfunction scrollActiveDescendantIntoView(container: HTMLElement | null, id: string): void {\n if (!container)\n return;\n nextTick(() => {\n container.querySelector(`#${id}`)?.scrollIntoView({ block: 'nearest' });\n });\n}\n\n// Reset the item highlight whenever the active category changes.\nwatch(activeCategory, () => {\n resetHighlightToSelection();\n});\n\nwatch(highlightedItemIndex, (index) => {\n scrollActiveDescendantIntoView(get(detailRef), `${baseId}-opt-${index}`);\n});\n\nwatch(activeRailIndex, (index) => {\n if (index >= 0)\n scrollActiveDescendantIntoView(get(railRef), `${baseId}-cat-${index}`);\n});\n\nwatch(isOpen, (value) => {\n if (value) {\n syncActiveToSelection();\n set(focusedPane, 'rail');\n set(drillStep, 'category');\n resetHighlightToSelection();\n // Desktop is a combobox: keep focus in the field so the user can type\n // straight away (ArrowDown moves into the panes). The mobile sheet has no\n // typing field, so focus the rail instead.\n nextTick(() => (get(canType) ? get(activatorRef) : get(railRef))?.focus({ preventScroll: true }));\n }\n else {\n // Reset the query so the field shows the selection again next time, and\n // return focus to the trigger (APG dialog contract).\n set(searchInput, '');\n nextTick(() => get(activatorRef)?.focus({ preventScroll: true }));\n }\n});\n</script>\n\n<template>\n <component\n :is=\"shell\"\n v-model=\"isOpen\"\n v-bind=\"{ ...$attrs, ...shellProps }\"\n >\n <template #activator=\"{ attrs }\">\n <slot\n name=\"activator\"\n v-bind=\"{ attrs, isOpen, item: selectedItem, open, text: selectedText }\"\n >\n <div :class=\"activatorUi.wrapper()\">\n <div\n ref=\"fieldRef\"\n role=\"combobox\"\n data-id=\"activator\"\n :class=\"activatorUi.activator()\"\n aria-haspopup=\"dialog\"\n :aria-controls=\"isOpen ? `${baseId}-panel` : undefined\"\n :aria-expanded=\"isOpen\"\n :aria-disabled=\"disabled || undefined\"\n :aria-readonly=\"(readOnly || !canType) || undefined\"\n :aria-required=\"required || undefined\"\n :aria-invalid=\"hasError || undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"readOnly ? {} : attrs\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <span\n v-if=\"outlined || (!selectedItem && !isTyping)\"\n :class=\"activatorUi.label()\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ item: selectedItem }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"activatorUi.required()\"\n >\n ﹡\n </span>\n </span>\n <span\n v-if=\"selectedItem && !isTyping && $slots.selection\"\n class=\"absolute inset-y-0 left-4 right-8 flex items-center gap-2 pointer-events-none\"\n :class=\"[activatorUi.value()]\"\n >\n <slot\n name=\"selection.prepend\"\n v-bind=\"{ item: selectedItem }\"\n />\n <slot\n name=\"selection\"\n v-bind=\"{ item: selectedItem }\"\n />\n </span>\n <input\n ref=\"activatorRef\"\n data-id=\"search-input\"\n :value=\"isTyping ? searchInput : (($slots.selection && selectedItem) ? '' : (selectedText ?? ''))\"\n :placeholder=\"isTyping ? placeholder : ''\"\n :readonly=\"!canType\"\n :disabled=\"disabled\"\n :aria-invalid=\"hasError || undefined\"\n class=\"bg-transparent outline-none\"\n :class=\"[activatorUi.value(), !canType && 'cursor-pointer']\"\n @input=\"onInput($event)\"\n @keydown.down.prevent=\"focusPane('rail')\"\n @keydown.enter.prevent=\"selectTopResult()\"\n @keydown.esc=\"close()\"\n />\n <span\n v-if=\"clearable && selectedItem && !disabled && !readOnly\"\n data-id=\"clear\"\n :class=\"[activatorUi.clear(), focused && '!visible']\"\n @click.stop.prevent=\"clearSelection()\"\n >\n <RuiIcon\n color=\"error\"\n name=\"lu-x\"\n size=\"18\"\n />\n </span>\n <span :class=\"activatorUi.iconWrapper()\">\n <RuiIcon\n :class=\"activatorUi.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n <RuiProgress\n v-if=\"loading\"\n :class=\"activatorUi.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n <fieldset\n v-if=\"outlined\"\n :class=\"activatorUi.fieldset()\"\n >\n <legend :class=\"activatorUi.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </div>\n <RuiFormTextDetail\n v-if=\"!hideDetails\"\n class=\"px-3 pt-1\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n </slot>\n </template>\n\n <div\n :id=\"`${baseId}-panel`\"\n :class=\"ui.root({ class: cn(classNames?.root) })\"\n data-id=\"panel\"\n >\n <!-- On desktop the field itself is the search input, so the panel only\n needs its own search box in the mobile sheet. -->\n <div\n v-if=\"isMobile || $slots.header\"\n :class=\"ui.header()\"\n >\n <slot name=\"header\">\n <div :class=\"ui.title()\">\n {{ label }}\n </div>\n </slot>\n <RuiTextField\n v-if=\"searchable && isMobile\"\n v-model=\"searchInput\"\n :class=\"cn(classNames?.search)\"\n :placeholder=\"placeholder\"\n :dense=\"dense\"\n variant=\"outlined\"\n prepend-icon=\"lu-search\"\n hide-details\n clearable\n data-id=\"search\"\n />\n </div>\n\n <div :class=\"ui.body()\">\n <!-- Category rail (master) -->\n <div\n v-show=\"showRail\"\n ref=\"railRef\"\n :class=\"ui.rail({ class: cn(classNames?.rail) })\"\n role=\"tablist\"\n aria-orientation=\"vertical\"\n :aria-label=\"label\"\n :aria-activedescendant=\"focusedPane === 'rail' && activeRailIndex >= 0 ? `${baseId}-cat-${activeRailIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"rail\"\n @focus=\"focusedPane = 'rail'\"\n @keydown.down.prevent=\"moveRail(1)\"\n @keydown.up.prevent=\"moveRail(-1)\"\n @keydown.right.prevent=\"enterDetail()\"\n @keydown.enter.prevent=\"enterDetail()\"\n @keydown.home.prevent=\"setActiveCategory(railEntries[0]?.category ?? null)\"\n @keydown.end.prevent=\"setActiveCategory(railEntries[railEntries.length - 1]?.category ?? null)\"\n >\n <RuiButton\n v-for=\"(entry, index) in railEntries\"\n :id=\"`${baseId}-cat-${index}`\"\n :key=\"entry.category ?? '__all__'\"\n role=\"tab\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"entry.category === activeCategory\"\n :aria-selected=\"entry.category === activeCategory\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'rail' && index === activeRailIndex\"\n :data-category=\"entry.category ?? '__all__'\"\n @click=\"onCategoryClick(entry.category)\"\n >\n <slot\n name=\"category\"\n v-bind=\"{ active: entry.category === activeCategory, category: entry.category, count: entry.count, label: entry.label }\"\n >\n <span class=\"truncate\">{{ entry.label }}</span>\n <span :class=\"ui.railCount()\">{{ entry.count }}</span>\n </slot>\n </RuiButton>\n </div>\n\n <!-- Detail pane -->\n <div\n v-show=\"showDetail\"\n ref=\"detailRef\"\n :class=\"ui.detail({ class: cn(classNames?.detail) })\"\n role=\"listbox\"\n :aria-label=\"activeCategory ?? allLabel\"\n :aria-activedescendant=\"focusedPane === 'detail' && !isEmpty ? `${baseId}-opt-${highlightedItemIndex}` : undefined\"\n tabindex=\"0\"\n data-id=\"detail\"\n @focus=\"focusedPane = 'detail'\"\n @keydown.down.prevent=\"moveItem(1)\"\n @keydown.up.prevent=\"moveItem(-1)\"\n @keydown.left.prevent=\"backToRail()\"\n @keydown.home.prevent=\"highlightedItemIndex = 0\"\n @keydown.end.prevent=\"highlightedItemIndex = detailItems.length - 1\"\n @keydown.enter.prevent=\"selectHighlighted()\"\n @keydown.space.prevent=\"selectHighlighted()\"\n >\n <template v-if=\"mode === 'drill' && showDetail\">\n <button\n type=\"button\"\n :class=\"ui.backButton()\"\n data-id=\"back\"\n @click=\"backToRail()\"\n >\n <RuiIcon\n name=\"lu-chevron-left\"\n size=\"18\"\n />\n <span>{{ activeCategory ?? allLabel }}</span>\n </button>\n </template>\n\n <slot\n v-if=\"isEmpty\"\n name=\"empty\"\n >\n <div :class=\"ui.empty()\">\n {{ emptyText }}\n </div>\n </slot>\n\n <template\n v-for=\"row in detailRows\"\n v-else\n :key=\"row.type === 'header' ? `h-${row.category}` : `i-${row.index}`\"\n >\n <div\n v-if=\"row.type === 'header'\"\n :class=\"ui.subheader()\"\n role=\"presentation\"\n >\n {{ row.category }}\n </div>\n <RuiButton\n v-else\n :id=\"`${baseId}-opt-${row.index}`\"\n role=\"option\"\n variant=\"list\"\n :size=\"dense ? 'sm' : undefined\"\n :active=\"isSelected(row.item)\"\n :aria-selected=\"isSelected(row.item)\"\n tabindex=\"-1\"\n :data-highlighted=\"focusedPane === 'detail' && row.index === highlightedItemIndex\"\n :class=\"{ [ui.highlighted()]: focusedPane === 'detail' && row.index === highlightedItemIndex && !isSelected(row.item) }\"\n @click=\"selectItem(row.item)\"\n >\n <template\n v-if=\"$slots['item.prepend']\"\n #prepend\n >\n <slot\n name=\"item.prepend\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n >\n {{ getText(row.item) }}\n </slot>\n <template\n v-if=\"$slots['item.append']\"\n #append\n >\n <slot\n name=\"item.append\"\n v-bind=\"{ active: row.index === highlightedItemIndex, item: row.item, selected: isSelected(row.item) }\"\n />\n </template>\n </RuiButton>\n </template>\n </div>\n </div>\n\n <div\n v-if=\"$slots.footer\"\n :class=\"ui.footer()\"\n >\n <slot\n name=\"footer\"\n v-bind=\"{ close }\"\n />\n </div>\n </div>\n </component>\n</template>\n"],"mappings":"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"}
@@ -17,17 +17,17 @@ var categoryPickerActivatorStyles = tv({
17
17
  */
18
18
  var categoryPickerStyles = tv({
19
19
  slots: {
20
- root: "flex flex-col min-w-0 bg-white dark:bg-rui-grey-900 rounded-md overflow-hidden",
20
+ root: "flex flex-col min-w-0 max-h-[var(--rui-floating-max-height,60vh)] bg-white dark:bg-rui-grey-900 rounded-md overflow-hidden",
21
21
  header: "flex flex-col gap-3 p-4 border-b border-rui-grey-200 dark:border-rui-grey-800",
22
22
  title: "text-h6 text-rui-text",
23
- body: "grid min-h-0 max-h-[60vh]",
23
+ body: "grid min-h-0 flex-1",
24
24
  rail: "flex flex-col gap-0.5 p-2 overflow-y-auto outline-none border-rui-grey-200 dark:border-rui-grey-800",
25
25
  railCount: "ml-auto pl-2 text-caption tabular-nums text-rui-text-secondary",
26
26
  detail: "flex flex-col gap-0.5 p-2 overflow-y-auto outline-none min-w-0",
27
27
  highlighted: "!bg-rui-grey-100 dark:!bg-rui-grey-800",
28
28
  subheader: "px-3 pt-3 pb-1 text-overline text-rui-text-secondary uppercase",
29
29
  empty: "flex flex-1 items-center justify-center p-8 text-body-2 text-rui-text-secondary text-center",
30
- footer: "p-3 border-t border-rui-grey-200 dark:border-rui-grey-800",
30
+ footer: "shrink-0 p-3 border-t border-rui-grey-200 dark:border-rui-grey-800 bg-white dark:bg-rui-grey-900",
31
31
  backButton: "flex items-center gap-2"
32
32
  },
33
33
  variants: {
@@ -1 +1 @@
1
- {"version":3,"file":"category-picker-styles.js","names":[],"sources":["../../../../src/components/forms/category-picker/category-picker-styles.ts"],"sourcesContent":["import { activatorStyles, type TextInputVariant } from '@/components/forms/text-input-styles';\nimport { tv } from '@/utils/tv';\n\nexport type CategoryPickerVariant = TextInputVariant;\n\n/**\n * Form-field trigger, shared with `RuiAutoComplete` / `RuiMenuSelect` via\n * `activatorStyles`: floating label, outlined/filled/underline variants,\n * error + hint details, required marker.\n */\nexport const categoryPickerActivatorStyles = tv({\n extend: activatorStyles,\n variants: {\n // Re-declare for type inference — actual styles live in activatorStyles.\n filled: { true: {} },\n },\n});\n\n/**\n * Layout styles for `RuiCategoryPicker`. Item and category rows lean on\n * `RuiButton variant=\"list\"` for their interactive/active visuals; these slots\n * cover only the surrounding two-pane (and mobile drill-in) scaffold.\n */\nexport const categoryPickerStyles = tv({\n slots: {\n root: 'flex flex-col min-w-0 bg-white dark:bg-rui-grey-900 rounded-md overflow-hidden',\n header: 'flex flex-col gap-3 p-4 border-b border-rui-grey-200 dark:border-rui-grey-800',\n title: 'text-h6 text-rui-text',\n body: 'grid min-h-0 max-h-[60vh]',\n rail: 'flex flex-col gap-0.5 p-2 overflow-y-auto outline-none border-rui-grey-200 dark:border-rui-grey-800',\n railCount: 'ml-auto pl-2 text-caption tabular-nums text-rui-text-secondary',\n detail: 'flex flex-col gap-0.5 p-2 overflow-y-auto outline-none min-w-0',\n // Keyboard highlight for the active-descendant item (focus lives on the\n // pane container, so the highlight is the only per-item focus cue).\n highlighted: '!bg-rui-grey-100 dark:!bg-rui-grey-800',\n subheader: 'px-3 pt-3 pb-1 text-overline text-rui-text-secondary uppercase',\n empty: 'flex flex-1 items-center justify-center p-8 text-body-2 text-rui-text-secondary text-center',\n footer: 'p-3 border-t border-rui-grey-200 dark:border-rui-grey-800',\n backButton: 'flex items-center gap-2',\n },\n variants: {\n mode: {\n // Desktop: category rail beside the detail pane, inside an anchored\n // popover. The explicit width lets the `1fr` detail column resolve\n // (a `w-max` popover would otherwise collapse it to its content).\n twoPane: {\n root: 'w-[40rem] max-w-[calc(100vw-2rem)]',\n body: 'grid-cols-[minmax(9.5rem,13rem)_1fr]',\n rail: 'border-r',\n },\n // Narrow width: one column at a time, drill in and back out, inside a\n // full-width bottom sheet.\n drill: {\n root: 'w-full',\n body: 'grid-cols-1',\n rail: 'border-r-0',\n },\n },\n dense: {\n true: {\n header: 'p-3',\n title: 'text-subtitle-1',\n },\n },\n },\n defaultVariants: {\n mode: 'twoPane',\n dense: false,\n },\n});\n"],"mappings":";;;;;;;;AAUA,IAAa,gCAAgC,GAAG;CAC9C,QAAQ;CACR,UAAU,EAER,QAAQ,EAAE,MAAM,CAAC,EAAE,EACrB;AACF,CAAC;;;;;;AAOD,IAAa,uBAAuB,GAAG;CACrC,OAAO;EACL,MAAM;EACN,QAAQ;EACR,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW;EACX,QAAQ;EAGR,aAAa;EACb,WAAW;EACX,OAAO;EACP,QAAQ;EACR,YAAY;CACd;CACA,UAAU;EACR,MAAM;GAIJ,SAAS;IACP,MAAM;IACN,MAAM;IACN,MAAM;GACR;GAGA,OAAO;IACL,MAAM;IACN,MAAM;IACN,MAAM;GACR;EACF;EACA,OAAO,EACL,MAAM;GACJ,QAAQ;GACR,OAAO;EACT,EACF;CACF;CACA,iBAAiB;EACf,MAAM;EACN,OAAO;CACT;AACF,CAAC"}
1
+ {"version":3,"file":"category-picker-styles.js","names":[],"sources":["../../../../src/components/forms/category-picker/category-picker-styles.ts"],"sourcesContent":["import { activatorStyles, type TextInputVariant } from '@/components/forms/text-input-styles';\nimport { tv } from '@/utils/tv';\n\nexport type CategoryPickerVariant = TextInputVariant;\n\n/**\n * Form-field trigger, shared with `RuiAutoComplete` / `RuiMenuSelect` via\n * `activatorStyles`: floating label, outlined/filled/underline variants,\n * error + hint details, required marker.\n */\nexport const categoryPickerActivatorStyles = tv({\n extend: activatorStyles,\n variants: {\n // Re-declare for type inference — actual styles live in activatorStyles.\n filled: { true: {} },\n },\n});\n\n/**\n * Layout styles for `RuiCategoryPicker`. Item and category rows lean on\n * `RuiButton variant=\"list\"` for their interactive/active visuals; these slots\n * cover only the surrounding two-pane (and mobile drill-in) scaffold.\n */\nexport const categoryPickerStyles = tv({\n slots: {\n // Cap to the space the floating layer measured toward the viewport edge\n // (set as --rui-floating-max-height by the menu's size middleware), falling\n // back to 60vh. The body flex-shrinks within this so the footer stays put.\n root: 'flex flex-col min-w-0 max-h-[var(--rui-floating-max-height,60vh)] bg-white dark:bg-rui-grey-900 rounded-md overflow-hidden',\n header: 'flex flex-col gap-3 p-4 border-b border-rui-grey-200 dark:border-rui-grey-800',\n title: 'text-h6 text-rui-text',\n body: 'grid min-h-0 flex-1',\n rail: 'flex flex-col gap-0.5 p-2 overflow-y-auto outline-none border-rui-grey-200 dark:border-rui-grey-800',\n railCount: 'ml-auto pl-2 text-caption tabular-nums text-rui-text-secondary',\n detail: 'flex flex-col gap-0.5 p-2 overflow-y-auto outline-none min-w-0',\n // Keyboard highlight for the active-descendant item (focus lives on the\n // pane container, so the highlight is the only per-item focus cue).\n highlighted: '!bg-rui-grey-100 dark:!bg-rui-grey-800',\n subheader: 'px-3 pt-3 pb-1 text-overline text-rui-text-secondary uppercase',\n empty: 'flex flex-1 items-center justify-center p-8 text-body-2 text-rui-text-secondary text-center',\n // Opaque panel surface so a scrolled pane never bleeds through the footer.\n footer: 'shrink-0 p-3 border-t border-rui-grey-200 dark:border-rui-grey-800 bg-white dark:bg-rui-grey-900',\n backButton: 'flex items-center gap-2',\n },\n variants: {\n mode: {\n // Desktop: category rail beside the detail pane, inside an anchored\n // popover. The explicit width lets the `1fr` detail column resolve\n // (a `w-max` popover would otherwise collapse it to its content).\n twoPane: {\n root: 'w-[40rem] max-w-[calc(100vw-2rem)]',\n body: 'grid-cols-[minmax(9.5rem,13rem)_1fr]',\n rail: 'border-r',\n },\n // Narrow width: one column at a time, drill in and back out, inside a\n // full-width bottom sheet.\n drill: {\n root: 'w-full',\n body: 'grid-cols-1',\n rail: 'border-r-0',\n },\n },\n dense: {\n true: {\n header: 'p-3',\n title: 'text-subtitle-1',\n },\n },\n },\n defaultVariants: {\n mode: 'twoPane',\n dense: false,\n },\n});\n"],"mappings":";;;;;;;;AAUA,IAAa,gCAAgC,GAAG;CAC9C,QAAQ;CACR,UAAU,EAER,QAAQ,EAAE,MAAM,CAAC,EAAE,EACrB;AACF,CAAC;;;;;;AAOD,IAAa,uBAAuB,GAAG;CACrC,OAAO;EAIL,MAAM;EACN,QAAQ;EACR,OAAO;EACP,MAAM;EACN,MAAM;EACN,WAAW;EACX,QAAQ;EAGR,aAAa;EACb,WAAW;EACX,OAAO;EAEP,QAAQ;EACR,YAAY;CACd;CACA,UAAU;EACR,MAAM;GAIJ,SAAS;IACP,MAAM;IACN,MAAM;IACN,MAAM;GACR;GAGA,OAAO;IACL,MAAM;IACN,MAAM;IACN,MAAM;GACR;EACF;EACA,OAAO,EACL,MAAM;GACJ,QAAQ;GACR,OAAO;EACT,EACF;CACF;CACA,iBAAiB;EACf,MAAM;EACN,OAAO;CACT;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"RuiMenuSelect.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"sources":["../../../../src/components/forms/select/RuiMenuSelect.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { menuSelectStyles, type MenuSelectVariant } from '@/components/forms/select/menu-select-styles';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu, { type MenuProps } from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport { type KeyOfType, useDropdownMenu } from '@/composables/dropdown-menu';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\nexport interface RuiMenuSelectClassNames {\n root?: VueClassValue;\n label?: VueClassValue;\n menu?: VueClassValue;\n option?: VueClassValue;\n}\n\nexport interface MenuSelectProps<TValue, TItem> {\n options: TItem[];\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem;\n disabled?: boolean;\n loading?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n label?: string;\n menuOptions?: MenuProps;\n classNames?: RuiMenuSelectClassNames;\n /** @deprecated Use `classNames.label` instead */\n labelClass?: string;\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string;\n /** @deprecated Use `classNames.option` instead */\n optionClass?: string;\n prependWidth?: number;\n appendWidth?: number;\n itemHeight?: number;\n variant?: MenuSelectVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n autoSelectFirst?: boolean;\n hideNoData?: boolean;\n noDataText?: string;\n required?: boolean;\n}\n\ndefineOptions({\n name: 'RuiMenuSelect',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<TValue | undefined>({ required: true });\n\nconst {\n options,\n disabled = false,\n loading = false,\n readOnly = false,\n dense = false,\n clearable = false,\n hideDetails = false,\n label = 'Select',\n menuOptions,\n classNames,\n labelClass,\n menuClass,\n variant = 'default',\n hint,\n keyAttr,\n textAttr,\n itemHeight,\n errorMessages = [],\n successMessages = [],\n autoSelectFirst = false,\n hideNoData = false,\n noDataText = 'No data available',\n required = false,\n} = defineProps<MenuSelectProps<TValue, TItem>>();\n\ndefineSlots<{\n 'activator'?: (props: {\n disabled: boolean;\n value: TItem | undefined;\n variant: string;\n readOnly: boolean;\n attrs: Record<string, unknown>;\n open: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n 'activator.label'?: (props: { value: TItem | undefined }) => any;\n 'selection.prepend'?: (props: { item: TItem }) => any;\n 'selection'?: (props: { item: TItem }) => any;\n 'item.prepend'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item.append'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'no-data'?: () => any;\n}>();\n\nconst menuRef = useTemplateRef<HTMLDivElement>('menuRef');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst { focused } = useFocus(activator);\nconst isHovered = ref<boolean>(false);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst value = computed<TItem | undefined>({\n get: () => {\n const value = get(modelValue);\n if (keyAttr)\n return options.find(option => option[keyAttr] === value);\n return value as unknown as TItem;\n },\n set: (selected?: TItem) => {\n const selection = keyAttr && selected ? selected[keyAttr] : selected;\n set(modelValue, selection as TValue);\n },\n});\n\nfunction setValue(val: TItem): void {\n set(value, val);\n set(focused, true);\n}\n\nconst {\n containerProps,\n wrapperProps,\n renderedData,\n isOpen,\n menuWidth,\n getText,\n getIdentifier,\n isActiveItem,\n highlightedIndex,\n moveHighlight,\n applyHighlighted,\n valueKey,\n} = useDropdownMenu<TValue, TItem>({\n itemHeight: itemHeight ?? (dense ? 30 : 48),\n keyAttr,\n textAttr,\n options: () => options,\n dense: () => dense,\n value,\n menuRef,\n disabled: () => disabled,\n autoSelectFirst,\n setValue,\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\nconst float = computed<boolean>(() => (get(isOpen) || !!get(value)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst ui = computed<ReturnType<typeof menuSelectStyles>>(() => menuSelectStyles({\n filled: variant === 'filled',\n outlined: get(outlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly: readOnly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst highlightedClass = menuSelectStyles({}).highlighted();\n\nfunction clear(): void {\n set(modelValue, undefined);\n}\n\nconst menuFloatingOptions = computed<FloatingOptions>(() => ({\n placement: Placement.bottomStart,\n ...menuOptions?.options,\n}));\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"{ ...getRootAttrs($attrs, []), ...menuOptions }\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"menuFloatingOptions\"\n :close-on-content-click=\"true\"\n :full-width=\"true\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n :dense=\"dense\"\n :show-details=\"!hideDetails\"\n :disabled=\"disabled\"\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\">\n <slot\n name=\"activator\"\n v-bind=\"{ disabled, value, variant, readOnly, attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\"\n >\n <button\n ref=\"activator\"\n :disabled=\"disabled\"\n :aria-disabled=\"disabled\"\n :aria-expanded=\"isOpen\"\n :aria-readonly=\"readOnly || undefined\"\n :aria-required=\"required || undefined\"\n :aria-busy=\"loading || undefined\"\n type=\"button\"\n :tabindex=\"disabled || readOnly ? -1 : 0\"\n :class=\"ui.activator({ class: cn(classNames?.label) ?? labelClass })\"\n v-bind=\"{\n ...getNonRootAttrs($attrs),\n ...(readOnly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n @keydown.enter.prevent=\"applyHighlighted()\"\n @keydown.space.prevent=\"applyHighlighted()\"\n @keydown.home.prevent=\"highlightedIndex = 0\"\n @keydown.end.prevent=\"highlightedIndex = options.length - 1\"\n >\n <span\n v-if=\"outlined || !value\"\n :class=\"[\n ui.label(),\n { 'pr-2': !value && !open && outlined },\n ]\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ value }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n <span\n v-if=\"value\"\n :class=\"ui.value()\"\n >\n <slot\n name=\"selection.prepend\"\n v-bind=\"{ item: value }\"\n />\n <slot\n name=\"selection\"\n v-bind=\"{ item: value }\"\n >\n {{ getText(value) }}\n </slot>\n </span>\n\n <span\n v-if=\"clearable && value && !disabled\"\n data-id=\"clear\"\n :class=\"[ui.clear(), focused && '!visible']\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n color=\"error\"\n name=\"lu-x\"\n size=\"18\"\n />\n </span>\n\n <span :class=\"ui.iconWrapper()\">\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n\n <RuiProgress\n v-if=\"loading\"\n :class=\"ui.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n </button>\n <fieldset\n v-if=\"outlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </slot>\n <input\n :value=\"valueKey\"\n class=\"hidden\"\n type=\"hidden\"\n />\n </template>\n <template #default=\"{ width }\">\n <div\n v-if=\"options.length > 0\"\n :ref=\"containerProps.ref\"\n :class=\"ui.menu({ class: cn(classNames?.menu) ?? menuClass })\"\n :style=\"[containerProps.style, { width: `${width}px`, minWidth: menuWidth }]\"\n @scroll=\"containerProps.onScroll\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n >\n <div\n v-bind=\"wrapperProps\"\n ref=\"menuRef\"\n >\n <RuiButton\n v-for=\"{ item, _index } in renderedData\"\n :key=\"getIdentifier(item)\"\n :active=\"isActiveItem(item)\"\n :aria-selected=\"isActiveItem(item)\"\n :size=\"dense ? 'sm' : undefined\"\n variant=\"list\"\n :data-highlighted=\"highlightedIndex === _index\"\n :class=\"{\n [highlightedClass]: !isActiveItem(item) && highlightedIndex === _index,\n }\"\n @click=\"setValue(item)\"\n >\n <template #prepend>\n <slot\n name=\"item.prepend\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n >\n {{ getText(item) }}\n </slot>\n <template #append>\n <slot\n name=\"item.append\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n />\n </template>\n </RuiButton>\n </div>\n </div>\n\n <div\n v-else-if=\"!hideNoData\"\n data-id=\"no-data\"\n :style=\"{ width: `${width}px`, minWidth: menuWidth }\"\n :class=\"classNames?.menu ?? menuClass\"\n >\n <slot name=\"no-data\">\n <div class=\"p-4\">\n {{ noDataText }}\n </div>\n </slot>\n </div>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"RuiMenuSelect.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"sources":["../../../../src/components/forms/select/RuiMenuSelect.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"TValue, TItem\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport { menuSelectStyles, type MenuSelectVariant } from '@/components/forms/select/menu-select-styles';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport RuiMenu, { type MenuProps } from '@/components/overlays/menu/RuiMenu.vue';\nimport RuiProgress from '@/components/progress/RuiProgress.vue';\nimport { type KeyOfType, useDropdownMenu } from '@/composables/dropdown-menu';\nimport { type FloatingOptions, Placement } from '@/composables/floating';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\n\nexport interface RuiMenuSelectClassNames {\n root?: VueClassValue;\n label?: VueClassValue;\n menu?: VueClassValue;\n option?: VueClassValue;\n}\n\nexport interface MenuSelectProps<TValue, TItem> {\n options: TItem[];\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem;\n disabled?: boolean;\n loading?: boolean;\n readOnly?: boolean;\n dense?: boolean;\n clearable?: boolean;\n label?: string;\n menuOptions?: MenuProps;\n classNames?: RuiMenuSelectClassNames;\n /** @deprecated Use `classNames.label` instead */\n labelClass?: string;\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string;\n /** @deprecated Use `classNames.option` instead */\n optionClass?: string;\n prependWidth?: number;\n appendWidth?: number;\n itemHeight?: number;\n variant?: MenuSelectVariant;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n autoSelectFirst?: boolean;\n hideNoData?: boolean;\n noDataText?: string;\n required?: boolean;\n}\n\ndefineOptions({\n name: 'RuiMenuSelect',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<TValue | undefined>({ required: true });\n\nconst {\n options,\n disabled = false,\n loading = false,\n readOnly = false,\n dense = false,\n clearable = false,\n hideDetails = false,\n label = 'Select',\n menuOptions,\n classNames,\n labelClass,\n menuClass,\n variant = 'default',\n hint,\n keyAttr,\n textAttr,\n itemHeight,\n errorMessages = [],\n successMessages = [],\n autoSelectFirst = false,\n hideNoData = false,\n noDataText = 'No data available',\n required = false,\n} = defineProps<MenuSelectProps<TValue, TItem>>();\n\ndefineSlots<{\n 'activator'?: (props: {\n disabled: boolean;\n value: TItem | undefined;\n variant: string;\n readOnly: boolean;\n attrs: Record<string, unknown>;\n open: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n 'activator.label'?: (props: { value: TItem | undefined }) => any;\n 'selection.prepend'?: (props: { item: TItem }) => any;\n 'selection'?: (props: { item: TItem }) => any;\n 'item.prepend'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'item.append'?: (props: { disabled: boolean; item: TItem; active: boolean }) => any;\n 'no-data'?: () => any;\n}>();\n\nconst menuRef = useTemplateRef<HTMLDivElement>('menuRef');\nconst activator = useTemplateRef<HTMLDivElement>('activator');\nconst { focused } = useFocus(activator);\nconst isHovered = ref<boolean>(false);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst value = computed<TItem | undefined>({\n get: () => {\n const value = get(modelValue);\n if (keyAttr)\n return options.find(option => option[keyAttr] === value);\n return value as unknown as TItem;\n },\n set: (selected?: TItem) => {\n const selection = keyAttr && selected ? selected[keyAttr] : selected;\n set(modelValue, selection as TValue);\n },\n});\n\nfunction setValue(val: TItem): void {\n set(value, val);\n set(focused, true);\n}\n\nconst {\n containerProps,\n wrapperProps,\n renderedData,\n isOpen,\n menuWidth,\n getText,\n getIdentifier,\n isActiveItem,\n highlightedIndex,\n moveHighlight,\n applyHighlighted,\n valueKey,\n} = useDropdownMenu<TValue, TItem>({\n itemHeight: itemHeight ?? (dense ? 30 : 48),\n keyAttr,\n textAttr,\n options: () => options,\n dense: () => dense,\n value,\n menuRef,\n disabled: () => disabled,\n autoSelectFirst,\n setValue,\n});\n\nconst outlined = computed<boolean>(() => variant === 'outlined');\nconst float = computed<boolean>(() => (get(isOpen) || !!get(value)) && get(outlined));\n\nconst legendText = computed<string>(() => {\n if (!get(float) || !label)\n return '';\n return required ? `${label} ﹡` : label;\n});\n\nconst ui = computed<ReturnType<typeof menuSelectStyles>>(() => menuSelectStyles({\n filled: variant === 'filled',\n outlined: get(outlined),\n float: get(float),\n opened: get(isOpen),\n hovered: get(isHovered),\n dense,\n disabled,\n readonly: readOnly,\n hasError: get(hasError),\n hasSuccess: get(hasSuccess) && !get(hasError),\n}));\n\nconst highlightedClass = menuSelectStyles({}).highlighted();\n\nfunction clear(): void {\n set(modelValue, undefined);\n}\n\nconst menuFloatingOptions = computed<FloatingOptions>(() => ({\n placement: Placement.bottomStart,\n ...menuOptions?.options,\n}));\n</script>\n\n<template>\n <RuiMenu\n v-model=\"isOpen\"\n v-bind=\"{ ...getRootAttrs($attrs, []), ...menuOptions }\"\n :class=\"ui.wrapper({ class: cn($attrs.class) })\"\n :options=\"menuFloatingOptions\"\n :close-on-content-click=\"true\"\n :full-width=\"true\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n :dense=\"dense\"\n :show-details=\"!hideDetails\"\n :disabled=\"disabled\"\n disable-auto-focus\n >\n <template #activator=\"{ attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\">\n <slot\n name=\"activator\"\n v-bind=\"{ disabled, value, variant, readOnly, attrs, open, hasError: slotHasError, hasSuccess: slotHasSuccess }\"\n >\n <button\n ref=\"activator\"\n :disabled=\"disabled\"\n :aria-disabled=\"disabled\"\n :aria-expanded=\"isOpen\"\n :aria-readonly=\"readOnly || undefined\"\n :aria-required=\"required || undefined\"\n :aria-busy=\"loading || undefined\"\n type=\"button\"\n :tabindex=\"disabled || readOnly ? -1 : 0\"\n :class=\"ui.activator({ class: cn(classNames?.label) ?? labelClass })\"\n v-bind=\"{\n ...getNonRootAttrs($attrs),\n ...(readOnly ? {} : attrs),\n }\"\n data-id=\"activator\"\n :aria-invalid=\"hasError\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n @keydown.enter.prevent=\"applyHighlighted()\"\n @keydown.space.prevent=\"applyHighlighted()\"\n @keydown.home.prevent=\"highlightedIndex = 0\"\n @keydown.end.prevent=\"highlightedIndex = options.length - 1\"\n >\n <span\n v-if=\"outlined || !value\"\n :class=\"[\n ui.label(),\n { 'pr-2': !value && !open && outlined },\n ]\"\n >\n <slot\n name=\"activator.label\"\n v-bind=\"{ value }\"\n >\n {{ label }}\n </slot>\n <span\n v-if=\"required\"\n :class=\"ui.required()\"\n >\n ﹡\n </span>\n </span>\n <span\n v-if=\"value\"\n :class=\"ui.value()\"\n >\n <slot\n name=\"selection.prepend\"\n v-bind=\"{ item: value }\"\n />\n <slot\n name=\"selection\"\n v-bind=\"{ item: value }\"\n >\n {{ getText(value) }}\n </slot>\n </span>\n\n <span\n v-if=\"clearable && value && !disabled\"\n data-id=\"clear\"\n :class=\"[ui.clear(), focused && '!visible']\"\n @click.stop.prevent=\"clear()\"\n >\n <RuiIcon\n color=\"error\"\n name=\"lu-x\"\n size=\"18\"\n />\n </span>\n\n <span :class=\"ui.iconWrapper()\">\n <RuiIcon\n :class=\"ui.icon()\"\n :size=\"dense ? 16 : 24\"\n name=\"lu-chevron-down\"\n />\n </span>\n\n <RuiProgress\n v-if=\"loading\"\n :class=\"ui.progress()\"\n color=\"primary\"\n thickness=\"3\"\n variant=\"indeterminate\"\n />\n </button>\n <fieldset\n v-if=\"outlined\"\n :class=\"ui.fieldset()\"\n >\n <legend :class=\"ui.legend()\">\n {{ legendText }}\n </legend>\n </fieldset>\n </slot>\n <input\n :value=\"valueKey\"\n class=\"hidden\"\n type=\"hidden\"\n />\n </template>\n <template #default=\"{ width }\">\n <div\n v-if=\"options.length > 0\"\n :ref=\"containerProps.ref\"\n :class=\"ui.menu({ class: cn(classNames?.menu) ?? menuClass })\"\n :style=\"[containerProps.style, { width: `${width}px`, minWidth: menuWidth }]\"\n @scroll=\"containerProps.onScroll\"\n @keydown.up.prevent=\"moveHighlight(true)\"\n @keydown.down.prevent=\"moveHighlight(false)\"\n >\n <div\n v-bind=\"wrapperProps\"\n ref=\"menuRef\"\n >\n <RuiButton\n v-for=\"{ item, _index } in renderedData\"\n :key=\"getIdentifier(item)\"\n :active=\"isActiveItem(item)\"\n :aria-selected=\"isActiveItem(item)\"\n :size=\"dense ? 'sm' : undefined\"\n variant=\"list\"\n :data-highlighted=\"highlightedIndex === _index\"\n :class=\"{\n [highlightedClass]: !isActiveItem(item) && highlightedIndex === _index,\n }\"\n @click=\"setValue(item)\"\n >\n <template #prepend>\n <slot\n name=\"item.prepend\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n />\n </template>\n <slot\n name=\"item\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n >\n {{ getText(item) }}\n </slot>\n <template #append>\n <slot\n name=\"item.append\"\n v-bind=\"{ disabled, item, active: isActiveItem(item) }\"\n />\n </template>\n </RuiButton>\n </div>\n </div>\n\n <div\n v-else-if=\"!hideNoData\"\n data-id=\"no-data\"\n :style=\"{ width: `${width}px`, minWidth: menuWidth }\"\n :class=\"classNames?.menu ?? menuClass\"\n >\n <slot name=\"no-data\">\n <div class=\"p-4\">\n {{ noDataText }}\n </div>\n </slot>\n </div>\n </template>\n </RuiMenu>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RuiMenu.js","names":[],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type PopperOptions, toFloatingOptions } from '@/composables/popper';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\ninterface MenuAttrs extends BaseMenuAttrs { onClick?: () => void }\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n}\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n /** @deprecated Use `options` instead */\n popper?: PopperOptions;\n classNames?: RuiMenuClassNames;\n /** @deprecated Use `classNames.wrapper` instead */\n wrapperClass?: string | object | string[];\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string | object | string[];\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n dense?: boolean;\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n popper,\n classNames,\n wrapperClass = '',\n menuClass = '',\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n attrs: { onMouseover?: () => void; onMouseleave?: () => void; onClick?: () => void };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => popper ? toFloatingOptions(popper) : (options ?? {}),\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-[9999]',\n content: 'rounded overflow-hidden shadow-8 bg-white dark:bg-[#2E2E2E] text-rui-text focus:outline-none py-2',\n details: 'pt-1',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// NOTE: both computed functions must return the *same* object shape from\n// every branch — otherwise vue-tsc infers the slot's `attrs` type as a\n// discriminated union where each key is either \"all defined\" or \"all\n// undefined\", which breaks consumer code that types its own `attrs`\n// handler with individually-optional keys.\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n onClick: disabled ? undefined : checkClick,\n}));\n\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n // Focus on the menu container itself\n content.focus();\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus();\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) ?? cn(wrapperClass as VueClassValue) })\"\n :data-menu-disabled=\"disabled\"\n aria-haspopup=\"true\"\n :aria-expanded=\"open\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) ?? cn(menuClass as VueClassValue) })\"\n role=\"menu\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content()\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <slot v-bind=\"{ width }\" />\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :class=\"ui.details()\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiMenu.js","names":[],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type PopperOptions, toFloatingOptions } from '@/composables/popper';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\ninterface MenuAttrs extends BaseMenuAttrs { onClick?: () => void }\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n}\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n /** @deprecated Use `options` instead */\n popper?: PopperOptions;\n classNames?: RuiMenuClassNames;\n /** @deprecated Use `classNames.wrapper` instead */\n wrapperClass?: string | object | string[];\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string | object | string[];\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n dense?: boolean;\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n popper,\n classNames,\n wrapperClass = '',\n menuClass = '',\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n attrs: { onMouseover?: () => void; onMouseleave?: () => void; onClick?: () => void };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => popper ? toFloatingOptions(popper) : (options ?? {}),\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-[9999]',\n content: 'rounded overflow-hidden shadow-8 bg-white dark:bg-[#2E2E2E] text-rui-text focus:outline-none py-2',\n details: 'pt-1',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// NOTE: both computed functions must return the *same* object shape from\n// every branch — otherwise vue-tsc infers the slot's `attrs` type as a\n// discriminated union where each key is either \"all defined\" or \"all\n// undefined\", which breaks consumer code that types its own `attrs`\n// handler with individually-optional keys.\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n onClick: disabled ? undefined : checkClick,\n}));\n\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n // Focus on the menu container itself\n content.focus();\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus();\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) ?? cn(wrapperClass as VueClassValue) })\"\n :data-menu-disabled=\"disabled\"\n aria-haspopup=\"true\"\n :aria-expanded=\"open\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) ?? cn(menuClass as VueClassValue) })\"\n role=\"menu\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <slot v-bind=\"{ width }\" />\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :class=\"ui.details()\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":""}
@@ -5,6 +5,8 @@ export interface RuiMenuClassNames {
5
5
  root?: VueClassValue;
6
6
  wrapper?: VueClassValue;
7
7
  menu?: VueClassValue;
8
+ /** Overrides applied to the popover content box (e.g. to drop its default padding). */
9
+ content?: VueClassValue;
8
10
  }
9
11
  export interface MenuProps {
10
12
  openOnHover?: boolean;
@@ -206,7 +206,7 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
206
206
  ref: menuContent,
207
207
  key: "menu",
208
208
  "data-id": "content",
209
- class: unref(ui).content(),
209
+ class: unref(ui).content({ class: unref(cn)(__props.classNames?.content) }),
210
210
  tabindex: "-1"
211
211
  }, unref(baseMenuAttrs)), [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps({ width: unref(width) })))], 16)) : createCommentVNode("", true)]),
212
212
  _: 3
@@ -1 +1 @@
1
- {"version":3,"file":"RuiMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type PopperOptions, toFloatingOptions } from '@/composables/popper';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\ninterface MenuAttrs extends BaseMenuAttrs { onClick?: () => void }\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n}\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n /** @deprecated Use `options` instead */\n popper?: PopperOptions;\n classNames?: RuiMenuClassNames;\n /** @deprecated Use `classNames.wrapper` instead */\n wrapperClass?: string | object | string[];\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string | object | string[];\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n dense?: boolean;\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n popper,\n classNames,\n wrapperClass = '',\n menuClass = '',\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n attrs: { onMouseover?: () => void; onMouseleave?: () => void; onClick?: () => void };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => popper ? toFloatingOptions(popper) : (options ?? {}),\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-[9999]',\n content: 'rounded overflow-hidden shadow-8 bg-white dark:bg-[#2E2E2E] text-rui-text focus:outline-none py-2',\n details: 'pt-1',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// NOTE: both computed functions must return the *same* object shape from\n// every branch — otherwise vue-tsc infers the slot's `attrs` type as a\n// discriminated union where each key is either \"all defined\" or \"all\n// undefined\", which breaks consumer code that types its own `attrs`\n// handler with individually-optional keys.\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n onClick: disabled ? undefined : checkClick,\n}));\n\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n // Focus on the menu container itself\n content.focus();\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus();\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) ?? cn(wrapperClass as VueClassValue) })\"\n :data-menu-disabled=\"disabled\"\n aria-haspopup=\"true\"\n :aria-expanded=\"open\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) ?? cn(menuClass as VueClassValue) })\"\n role=\"menu\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content()\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <slot v-bind=\"{ width }\" />\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :class=\"ui.details()\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"RuiMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type PopperOptions, toFloatingOptions } from '@/composables/popper';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\ninterface MenuAttrs extends BaseMenuAttrs { onClick?: () => void }\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n}\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n /** @deprecated Use `options` instead */\n popper?: PopperOptions;\n classNames?: RuiMenuClassNames;\n /** @deprecated Use `classNames.wrapper` instead */\n wrapperClass?: string | object | string[];\n /** @deprecated Use `classNames.menu` instead */\n menuClass?: string | object | string[];\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n dense?: boolean;\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n popper,\n classNames,\n wrapperClass = '',\n menuClass = '',\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n attrs: { onMouseover?: () => void; onMouseleave?: () => void; onClick?: () => void };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => popper ? toFloatingOptions(popper) : (options ?? {}),\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-[9999]',\n content: 'rounded overflow-hidden shadow-8 bg-white dark:bg-[#2E2E2E] text-rui-text focus:outline-none py-2',\n details: 'pt-1',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// NOTE: both computed functions must return the *same* object shape from\n// every branch — otherwise vue-tsc infers the slot's `attrs` type as a\n// discriminated union where each key is either \"all defined\" or \"all\n// undefined\", which breaks consumer code that types its own `attrs`\n// handler with individually-optional keys.\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n onClick: disabled ? undefined : checkClick,\n}));\n\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n // Focus on the menu container itself\n content.focus();\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus();\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) ?? cn(wrapperClass as VueClassValue) })\"\n :data-menu-disabled=\"disabled\"\n aria-haspopup=\"true\"\n :aria-expanded=\"open\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) ?? cn(menuClass as VueClassValue) })\"\n role=\"menu\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc.stop=\"onLeave()\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <slot v-bind=\"{ width }\" />\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :class=\"ui.details()\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":"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"}
@@ -41,7 +41,18 @@ export interface FloatingOptions {
41
41
  scroll?: boolean;
42
42
  resize?: boolean;
43
43
  };
44
+ /**
45
+ * Cap the floating element to the space available toward the viewport edge so
46
+ * it never overflows the window. Exposes the measured space as the
47
+ * `--rui-floating-max-height` custom property on the floating element; the
48
+ * content is responsible for consuming it by capping its own max-height to
49
+ * that variable and scrolling internally.
50
+ * @default false
51
+ */
52
+ size?: boolean;
44
53
  }
54
+ /** Custom property carrying the space-aware max height set by the size middleware. */
55
+ export declare const FLOATING_MAX_HEIGHT_VAR = "--rui-floating-max-height";
45
56
  export declare const DEFAULT_FLOATING_OPTIONS: Required<FloatingOptions>;
46
57
  export interface UseFloatingReturn {
47
58
  currentPlacement: Readonly<Ref<Placement>>;
@@ -2,7 +2,7 @@ import { useTimeoutManager } from "./timeout-manager.js";
2
2
  import { onMounted, readonly, ref, shallowRef, toValue, watchEffect } from "vue";
3
3
  import { unrefElement } from "@vueuse/core";
4
4
  import { get as get$1, set as set$1 } from "@vueuse/shared";
5
- import { arrow, autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
5
+ import { arrow, autoUpdate, computePosition, flip, offset, shift, size } from "@floating-ui/dom";
6
6
  //#region src/composables/floating.ts
7
7
  var Placement = {
8
8
  bottom: "bottom",
@@ -22,14 +22,32 @@ var Strategy = {
22
22
  absolute: "absolute",
23
23
  fixed: "fixed"
24
24
  };
25
+ /** Custom property carrying the space-aware max height set by the size middleware. */
26
+ var FLOATING_MAX_HEIGHT_VAR = "--rui-floating-max-height";
25
27
  var DEFAULT_FLOATING_OPTIONS = {
26
28
  autoUpdate: true,
27
29
  flip: true,
28
30
  offset: 2,
29
31
  placement: Placement.bottom,
30
32
  shiftPadding: 8,
33
+ size: false,
31
34
  strategy: Strategy.absolute
32
35
  };
36
+ /**
37
+ * Cap the floating element to the space available toward the viewport edge and
38
+ * expose it as {@link FLOATING_MAX_HEIGHT_VAR}. The content decides how to use
39
+ * it (cap its own height and scroll internally); writing a custom property
40
+ * instead of a hard max-height keeps this non-invasive for elements that opt in
41
+ * but size themselves differently.
42
+ */
43
+ function buildSizeMiddleware(padding) {
44
+ return size({
45
+ padding,
46
+ apply({ availableHeight, elements }) {
47
+ elements.floating.style.setProperty(FLOATING_MAX_HEIGHT_VAR, `${Math.max(0, Math.floor(availableHeight))}px`);
48
+ }
49
+ });
50
+ }
33
51
  function buildMiddleware(opts, arrowEl) {
34
52
  const mw = [];
35
53
  const off = opts.offset ?? DEFAULT_FLOATING_OPTIONS.offset;
@@ -47,6 +65,7 @@ function buildMiddleware(opts, arrowEl) {
47
65
  element: arrowEl,
48
66
  padding: 4
49
67
  }));
68
+ if (opts.size === true) mw.push(buildSizeMiddleware(opts.shiftPadding ?? DEFAULT_FLOATING_OPTIONS.shiftPadding));
50
69
  return mw;
51
70
  }
52
71
  function applyArrowStyles(arrowEl, result) {
@@ -162,6 +181,6 @@ function useFloating(options, disabled = false, openDelay = 0, closeDelay = 0, v
162
181
  };
163
182
  }
164
183
  //#endregion
165
- export { DEFAULT_FLOATING_OPTIONS, Placement, Strategy, useFloating };
184
+ export { DEFAULT_FLOATING_OPTIONS, FLOATING_MAX_HEIGHT_VAR, Placement, Strategy, useFloating };
166
185
 
167
186
  //# sourceMappingURL=floating.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"floating.js","names":[],"sources":["../../src/composables/floating.ts"],"sourcesContent":["import {\n arrow,\n autoUpdate,\n computePosition,\n type ComputePositionReturn,\n flip,\n type VirtualElement as FloatingVirtualElement,\n type Middleware,\n offset,\n shift,\n} from '@floating-ui/dom';\nimport { type MaybeElement, unrefElement } from '@vueuse/core';\nimport { type MaybeRefOrGetter, onMounted, type Ref, ref, watchEffect } from 'vue';\nimport { useTimeoutManager } from './timeout-manager';\n\ntype VirtualElement = FloatingVirtualElement;\n\nexport const Placement = {\n bottom: 'bottom',\n bottomEnd: 'bottom-end',\n bottomStart: 'bottom-start',\n left: 'left',\n leftEnd: 'left-end',\n leftStart: 'left-start',\n right: 'right',\n rightEnd: 'right-end',\n rightStart: 'right-start',\n top: 'top',\n topEnd: 'top-end',\n topStart: 'top-start',\n} as const;\n\nexport type Placement = (typeof Placement)[keyof typeof Placement];\n\nexport const Strategy = {\n absolute: 'absolute',\n fixed: 'fixed',\n} as const;\n\nexport type Strategy = (typeof Strategy)[keyof typeof Strategy];\n\nexport interface FloatingOptions {\n /** Preferred placement relative to the reference element. @default Placement.bottom */\n placement?: Placement;\n /** CSS positioning strategy. @default Strategy.absolute */\n strategy?: Strategy;\n /** Main-axis and/or cross-axis offset from the reference element. @default 2 */\n offset?: number | { mainAxis?: number; crossAxis?: number };\n /** Whether to flip to the opposite side when overflowing. @default true */\n flip?: boolean;\n /** Padding from viewport edges for shift middleware. @default 8 */\n shiftPadding?: number;\n /** Whether to auto-update position on scroll/resize. @default true */\n autoUpdate?: boolean | { scroll?: boolean; resize?: boolean };\n}\n\nexport const DEFAULT_FLOATING_OPTIONS: Required<FloatingOptions> = {\n autoUpdate: true,\n flip: true,\n offset: 2,\n placement: Placement.bottom,\n shiftPadding: 8,\n strategy: Strategy.absolute,\n};\n\nexport interface UseFloatingReturn {\n currentPlacement: Readonly<Ref<Placement>>;\n leavePending: Ref<boolean>;\n onClose: (immediate?: boolean) => void;\n onLeaveComplete: () => void;\n onLeavePending: () => void;\n onOpen: (immediate?: boolean) => void;\n open: Ref<boolean>;\n popover: Ref<MaybeElement>;\n reference: Ref<HTMLElement | undefined>;\n updatePosition: () => Promise<void>;\n visible: Ref<boolean>;\n}\n\nfunction buildMiddleware(opts: FloatingOptions, arrowEl: HTMLElement | null): Middleware[] {\n const mw: Middleware[] = [];\n\n const off = opts.offset ?? DEFAULT_FLOATING_OPTIONS.offset;\n if (typeof off === 'number') {\n mw.push(offset(off));\n }\n else {\n const defaultOffset = typeof DEFAULT_FLOATING_OPTIONS.offset === 'number' ? DEFAULT_FLOATING_OPTIONS.offset : 0;\n mw.push(offset({ mainAxis: off.mainAxis ?? defaultOffset, crossAxis: off.crossAxis ?? 0 }));\n }\n\n if (opts.flip !== false)\n mw.push(flip());\n\n mw.push(shift({ padding: opts.shiftPadding ?? DEFAULT_FLOATING_OPTIONS.shiftPadding }));\n\n if (arrowEl)\n mw.push(arrow({ element: arrowEl, padding: 4 }));\n\n return mw;\n}\n\nfunction applyArrowStyles(arrowEl: HTMLElement | null, result: ComputePositionReturn): void {\n const arrowData = result.middlewareData.arrow;\n if (!arrowEl || !arrowData)\n return;\n\n Object.assign(arrowEl.style, {\n left: arrowData.x != null ? `${arrowData.x}px` : '',\n top: arrowData.y != null ? `${arrowData.y}px` : '',\n });\n}\n\nexport function useFloating(\n options: MaybeRefOrGetter<FloatingOptions>,\n disabled: MaybeRefOrGetter<boolean> = false,\n openDelay: MaybeRefOrGetter<number> = 0,\n closeDelay: MaybeRefOrGetter<number> = 0,\n virtualReference?: MaybeRefOrGetter<Element | VirtualElement | undefined>,\n): UseFloatingReturn {\n const reference = ref<HTMLElement>();\n const popover = ref<MaybeElement>();\n const open = shallowRef<boolean>(false);\n const visible = shallowRef<boolean>(false);\n const leavePending = shallowRef<boolean>(false);\n const currentPlacement = shallowRef<Placement>(toValue(options).placement ?? DEFAULT_FLOATING_OPTIONS.placement);\n\n let cleanupAutoUpdate: (() => void) | null = null;\n\n const openTimeoutManager = useTimeoutManager();\n const closeTimeoutManager = useTimeoutManager();\n\n function onLeaveComplete(): void {\n set(visible, false);\n }\n\n function onOpen(immediate: boolean = false): void {\n if (toValue(disabled))\n return;\n\n closeTimeoutManager.clear();\n\n set(leavePending, false);\n if (!openTimeoutManager.isActive()) {\n set(visible, true);\n\n openTimeoutManager.create(\n () => {\n set(open, true);\n },\n immediate ? 0 : toValue(openDelay),\n );\n }\n }\n\n function onClose(immediate: boolean = false): void {\n if (toValue(disabled))\n return;\n\n openTimeoutManager.clear();\n\n if (!closeTimeoutManager.isActive()) {\n closeTimeoutManager.create(\n () => {\n if (!get(open))\n onLeaveComplete();\n\n set(open, false);\n },\n immediate ? 0 : toValue(closeDelay),\n );\n }\n }\n\n function onLeavePending(): void {\n set(leavePending, true);\n }\n\n function getValidElements(): { popoverEl: HTMLElement; referenceEl: Element | VirtualElement } | null {\n if (!get(popover) || (!get(reference) && !toValue(virtualReference)))\n return null;\n\n const popoverEl = unrefElement(popover);\n const referenceEl = toValue(virtualReference) || unrefElement(reference);\n\n if (!(popoverEl instanceof HTMLElement) || !referenceEl)\n return null;\n\n return { popoverEl, referenceEl };\n }\n\n async function updatePosition(): Promise<void> {\n const elements = getValidElements();\n if (!elements)\n return;\n\n const { popoverEl, referenceEl } = elements;\n const opts = toValue(options);\n const arrowEl = popoverEl.querySelector<HTMLElement>('[data-id=\"arrow\"]');\n const strategy = opts.strategy ?? DEFAULT_FLOATING_OPTIONS.strategy;\n\n const result = await computePosition(referenceEl, popoverEl, {\n middleware: buildMiddleware(opts, arrowEl),\n placement: opts.placement ?? DEFAULT_FLOATING_OPTIONS.placement,\n strategy,\n });\n\n Object.assign(popoverEl.style, {\n left: `${result.x}px`,\n position: strategy,\n top: `${result.y}px`,\n });\n\n set(currentPlacement, result.placement);\n applyArrowStyles(arrowEl, result);\n }\n\n function startAutoUpdate(): void {\n stopAutoUpdate();\n const elements = getValidElements();\n if (!elements)\n return;\n\n const { popoverEl, referenceEl } = elements;\n const opts = toValue(options);\n const autoUpdateOpt = opts.autoUpdate ?? DEFAULT_FLOATING_OPTIONS.autoUpdate;\n\n if (autoUpdateOpt === false)\n return;\n\n const autoUpdateOptions = typeof autoUpdateOpt === 'object'\n ? {\n ancestorResize: autoUpdateOpt.resize ?? true,\n ancestorScroll: autoUpdateOpt.scroll ?? true,\n elementResize: true,\n }\n : undefined;\n\n cleanupAutoUpdate = autoUpdate(\n referenceEl,\n popoverEl,\n () => { updatePosition().catch(() => {}); },\n autoUpdateOptions,\n );\n }\n\n function stopAutoUpdate(): void {\n cleanupAutoUpdate?.();\n cleanupAutoUpdate = null;\n }\n\n onMounted(() => {\n watchEffect((onCleanup) => {\n startAutoUpdate();\n onCleanup(stopAutoUpdate);\n });\n });\n\n return {\n currentPlacement: readonly(currentPlacement),\n leavePending, // eslint-disable-line @rotki/composable-return-readonly -- written by consumer transition hooks\n onClose,\n onLeaveComplete,\n onLeavePending,\n onOpen,\n open, // eslint-disable-line @rotki/composable-return-readonly -- v-model bound by consumers\n popover, // eslint-disable-line @rotki/composable-return-readonly -- template ref bound by consumers\n reference, // eslint-disable-line @rotki/composable-return-readonly -- template ref bound by consumers\n updatePosition,\n visible, // eslint-disable-line @rotki/composable-return-readonly -- used as v-if condition by consumers\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"floating.js","names":[],"sources":["../../src/composables/floating.ts"],"sourcesContent":["import {\n arrow,\n autoUpdate,\n computePosition,\n type ComputePositionReturn,\n flip,\n type VirtualElement as FloatingVirtualElement,\n type Middleware,\n offset,\n shift,\n size,\n} from '@floating-ui/dom';\nimport { type MaybeElement, unrefElement } from '@vueuse/core';\nimport { type MaybeRefOrGetter, onMounted, type Ref, ref, watchEffect } from 'vue';\nimport { useTimeoutManager } from './timeout-manager';\n\ntype VirtualElement = FloatingVirtualElement;\n\nexport const Placement = {\n bottom: 'bottom',\n bottomEnd: 'bottom-end',\n bottomStart: 'bottom-start',\n left: 'left',\n leftEnd: 'left-end',\n leftStart: 'left-start',\n right: 'right',\n rightEnd: 'right-end',\n rightStart: 'right-start',\n top: 'top',\n topEnd: 'top-end',\n topStart: 'top-start',\n} as const;\n\nexport type Placement = (typeof Placement)[keyof typeof Placement];\n\nexport const Strategy = {\n absolute: 'absolute',\n fixed: 'fixed',\n} as const;\n\nexport type Strategy = (typeof Strategy)[keyof typeof Strategy];\n\nexport interface FloatingOptions {\n /** Preferred placement relative to the reference element. @default Placement.bottom */\n placement?: Placement;\n /** CSS positioning strategy. @default Strategy.absolute */\n strategy?: Strategy;\n /** Main-axis and/or cross-axis offset from the reference element. @default 2 */\n offset?: number | { mainAxis?: number; crossAxis?: number };\n /** Whether to flip to the opposite side when overflowing. @default true */\n flip?: boolean;\n /** Padding from viewport edges for shift middleware. @default 8 */\n shiftPadding?: number;\n /** Whether to auto-update position on scroll/resize. @default true */\n autoUpdate?: boolean | { scroll?: boolean; resize?: boolean };\n /**\n * Cap the floating element to the space available toward the viewport edge so\n * it never overflows the window. Exposes the measured space as the\n * `--rui-floating-max-height` custom property on the floating element; the\n * content is responsible for consuming it by capping its own max-height to\n * that variable and scrolling internally.\n * @default false\n */\n size?: boolean;\n}\n\n/** Custom property carrying the space-aware max height set by the size middleware. */\nexport const FLOATING_MAX_HEIGHT_VAR = '--rui-floating-max-height';\n\nexport const DEFAULT_FLOATING_OPTIONS: Required<FloatingOptions> = {\n autoUpdate: true,\n flip: true,\n offset: 2,\n placement: Placement.bottom,\n shiftPadding: 8,\n size: false,\n strategy: Strategy.absolute,\n};\n\nexport interface UseFloatingReturn {\n currentPlacement: Readonly<Ref<Placement>>;\n leavePending: Ref<boolean>;\n onClose: (immediate?: boolean) => void;\n onLeaveComplete: () => void;\n onLeavePending: () => void;\n onOpen: (immediate?: boolean) => void;\n open: Ref<boolean>;\n popover: Ref<MaybeElement>;\n reference: Ref<HTMLElement | undefined>;\n updatePosition: () => Promise<void>;\n visible: Ref<boolean>;\n}\n\n/**\n * Cap the floating element to the space available toward the viewport edge and\n * expose it as {@link FLOATING_MAX_HEIGHT_VAR}. The content decides how to use\n * it (cap its own height and scroll internally); writing a custom property\n * instead of a hard max-height keeps this non-invasive for elements that opt in\n * but size themselves differently.\n */\nfunction buildSizeMiddleware(padding: number): Middleware {\n return size({\n padding,\n apply({ availableHeight, elements }) {\n elements.floating.style.setProperty(FLOATING_MAX_HEIGHT_VAR, `${Math.max(0, Math.floor(availableHeight))}px`);\n },\n });\n}\n\nfunction buildMiddleware(opts: FloatingOptions, arrowEl: HTMLElement | null): Middleware[] {\n const mw: Middleware[] = [];\n\n const off = opts.offset ?? DEFAULT_FLOATING_OPTIONS.offset;\n if (typeof off === 'number') {\n mw.push(offset(off));\n }\n else {\n const defaultOffset = typeof DEFAULT_FLOATING_OPTIONS.offset === 'number' ? DEFAULT_FLOATING_OPTIONS.offset : 0;\n mw.push(offset({ mainAxis: off.mainAxis ?? defaultOffset, crossAxis: off.crossAxis ?? 0 }));\n }\n\n if (opts.flip !== false)\n mw.push(flip());\n\n mw.push(shift({ padding: opts.shiftPadding ?? DEFAULT_FLOATING_OPTIONS.shiftPadding }));\n\n if (arrowEl)\n mw.push(arrow({ element: arrowEl, padding: 4 }));\n\n if (opts.size === true)\n mw.push(buildSizeMiddleware(opts.shiftPadding ?? DEFAULT_FLOATING_OPTIONS.shiftPadding));\n\n return mw;\n}\n\nfunction applyArrowStyles(arrowEl: HTMLElement | null, result: ComputePositionReturn): void {\n const arrowData = result.middlewareData.arrow;\n if (!arrowEl || !arrowData)\n return;\n\n Object.assign(arrowEl.style, {\n left: arrowData.x != null ? `${arrowData.x}px` : '',\n top: arrowData.y != null ? `${arrowData.y}px` : '',\n });\n}\n\nexport function useFloating(\n options: MaybeRefOrGetter<FloatingOptions>,\n disabled: MaybeRefOrGetter<boolean> = false,\n openDelay: MaybeRefOrGetter<number> = 0,\n closeDelay: MaybeRefOrGetter<number> = 0,\n virtualReference?: MaybeRefOrGetter<Element | VirtualElement | undefined>,\n): UseFloatingReturn {\n const reference = ref<HTMLElement>();\n const popover = ref<MaybeElement>();\n const open = shallowRef<boolean>(false);\n const visible = shallowRef<boolean>(false);\n const leavePending = shallowRef<boolean>(false);\n const currentPlacement = shallowRef<Placement>(toValue(options).placement ?? DEFAULT_FLOATING_OPTIONS.placement);\n\n let cleanupAutoUpdate: (() => void) | null = null;\n\n const openTimeoutManager = useTimeoutManager();\n const closeTimeoutManager = useTimeoutManager();\n\n function onLeaveComplete(): void {\n set(visible, false);\n }\n\n function onOpen(immediate: boolean = false): void {\n if (toValue(disabled))\n return;\n\n closeTimeoutManager.clear();\n\n set(leavePending, false);\n if (!openTimeoutManager.isActive()) {\n set(visible, true);\n\n openTimeoutManager.create(\n () => {\n set(open, true);\n },\n immediate ? 0 : toValue(openDelay),\n );\n }\n }\n\n function onClose(immediate: boolean = false): void {\n if (toValue(disabled))\n return;\n\n openTimeoutManager.clear();\n\n if (!closeTimeoutManager.isActive()) {\n closeTimeoutManager.create(\n () => {\n if (!get(open))\n onLeaveComplete();\n\n set(open, false);\n },\n immediate ? 0 : toValue(closeDelay),\n );\n }\n }\n\n function onLeavePending(): void {\n set(leavePending, true);\n }\n\n function getValidElements(): { popoverEl: HTMLElement; referenceEl: Element | VirtualElement } | null {\n if (!get(popover) || (!get(reference) && !toValue(virtualReference)))\n return null;\n\n const popoverEl = unrefElement(popover);\n const referenceEl = toValue(virtualReference) || unrefElement(reference);\n\n if (!(popoverEl instanceof HTMLElement) || !referenceEl)\n return null;\n\n return { popoverEl, referenceEl };\n }\n\n async function updatePosition(): Promise<void> {\n const elements = getValidElements();\n if (!elements)\n return;\n\n const { popoverEl, referenceEl } = elements;\n const opts = toValue(options);\n const arrowEl = popoverEl.querySelector<HTMLElement>('[data-id=\"arrow\"]');\n const strategy = opts.strategy ?? DEFAULT_FLOATING_OPTIONS.strategy;\n\n const result = await computePosition(referenceEl, popoverEl, {\n middleware: buildMiddleware(opts, arrowEl),\n placement: opts.placement ?? DEFAULT_FLOATING_OPTIONS.placement,\n strategy,\n });\n\n Object.assign(popoverEl.style, {\n left: `${result.x}px`,\n position: strategy,\n top: `${result.y}px`,\n });\n\n set(currentPlacement, result.placement);\n applyArrowStyles(arrowEl, result);\n }\n\n function startAutoUpdate(): void {\n stopAutoUpdate();\n const elements = getValidElements();\n if (!elements)\n return;\n\n const { popoverEl, referenceEl } = elements;\n const opts = toValue(options);\n const autoUpdateOpt = opts.autoUpdate ?? DEFAULT_FLOATING_OPTIONS.autoUpdate;\n\n if (autoUpdateOpt === false)\n return;\n\n const autoUpdateOptions = typeof autoUpdateOpt === 'object'\n ? {\n ancestorResize: autoUpdateOpt.resize ?? true,\n ancestorScroll: autoUpdateOpt.scroll ?? true,\n elementResize: true,\n }\n : undefined;\n\n cleanupAutoUpdate = autoUpdate(\n referenceEl,\n popoverEl,\n () => { updatePosition().catch(() => {}); },\n autoUpdateOptions,\n );\n }\n\n function stopAutoUpdate(): void {\n cleanupAutoUpdate?.();\n cleanupAutoUpdate = null;\n }\n\n onMounted(() => {\n watchEffect((onCleanup) => {\n startAutoUpdate();\n onCleanup(stopAutoUpdate);\n });\n });\n\n return {\n currentPlacement: readonly(currentPlacement),\n leavePending, // eslint-disable-line @rotki/composable-return-readonly -- written by consumer transition hooks\n onClose,\n onLeaveComplete,\n onLeavePending,\n onOpen,\n open, // eslint-disable-line @rotki/composable-return-readonly -- v-model bound by consumers\n popover, // eslint-disable-line @rotki/composable-return-readonly -- template ref bound by consumers\n reference, // eslint-disable-line @rotki/composable-return-readonly -- template ref bound by consumers\n updatePosition,\n visible, // eslint-disable-line @rotki/composable-return-readonly -- used as v-if condition by consumers\n };\n}\n"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1443,10 +1443,6 @@ html[data-theme="dark"], html.dark {
1443
1443
  max-height: 15rem;
1444
1444
  }
1445
1445
 
1446
- .max-h-\[60vh\] {
1447
- max-height: 60vh;
1448
- }
1449
-
1450
1446
  .max-h-\[65vh\] {
1451
1447
  max-height: 65vh;
1452
1448
  }
@@ -1455,6 +1451,10 @@ html[data-theme="dark"], html.dark {
1455
1451
  max-height: 90vh;
1456
1452
  }
1457
1453
 
1454
+ .max-h-\[var\(--rui-floating-max-height\2c 60vh\)\] {
1455
+ max-height: var(--rui-floating-max-height,60vh);
1456
+ }
1457
+
1458
1458
  .max-h-full {
1459
1459
  max-height: 100%;
1460
1460
  }
@@ -2226,6 +2226,10 @@ html[data-theme="dark"], html.dark {
2226
2226
  border-color: rgb(0 0 0 / 0.12);
2227
2227
  }
2228
2228
 
2229
+ .divide-black\/\[0\.23\] > :not([hidden]) ~ :not([hidden]) {
2230
+ border-color: rgb(0 0 0 / 0.23);
2231
+ }
2232
+
2229
2233
  .divide-rui-error-darker > :not([hidden]) ~ :not([hidden]) {
2230
2234
  --tw-divide-opacity: 1;
2231
2235
  border-color: rgba(var(--rui-error-darker), var(--tw-divide-opacity, 1));
@@ -2281,10 +2285,6 @@ html[data-theme="dark"], html.dark {
2281
2285
  border-color: rgba(var(--rui-success-main), 0.5);
2282
2286
  }
2283
2287
 
2284
- .divide-rui-text > :not([hidden]) ~ :not([hidden]) {
2285
- border-color: var(--rui-text-primary);
2286
- }
2287
-
2288
2288
  .divide-rui-warning-darker > :not([hidden]) ~ :not([hidden]) {
2289
2289
  --tw-divide-opacity: 1;
2290
2290
  border-color: rgba(var(--rui-warning-darker), var(--tw-divide-opacity, 1));
@@ -3174,6 +3174,11 @@ html[data-theme="dark"], html.dark {
3174
3174
  padding-right: 1.5rem;
3175
3175
  }
3176
3176
 
3177
+ .py-0 {
3178
+ padding-top: 0px;
3179
+ padding-bottom: 0px;
3180
+ }
3181
+
3177
3182
  .py-0\.5 {
3178
3183
  padding-top: 0.125rem;
3179
3184
  padding-bottom: 0.125rem;
@@ -3889,6 +3894,10 @@ html[data-theme="dark"], html.dark {
3889
3894
  outline-offset: -1px;
3890
3895
  }
3891
3896
 
3897
+ .outline-black\/\[0\.23\] {
3898
+ outline-color: rgb(0 0 0 / 0.23);
3899
+ }
3900
+
3892
3901
  .outline-rui-error\/\[0\.5\] {
3893
3902
  outline-color: rgba(var(--rui-error-main), 0.5);
3894
3903
  }
@@ -3909,10 +3918,6 @@ html[data-theme="dark"], html.dark {
3909
3918
  outline-color: rgba(var(--rui-success-main), 0.5);
3910
3919
  }
3911
3920
 
3912
- .outline-rui-text {
3913
- outline-color: var(--rui-text-primary);
3914
- }
3915
-
3916
3921
  .outline-rui-warning\/\[0\.5\] {
3917
3922
  outline-color: rgba(var(--rui-warning-main), 0.5);
3918
3923
  }
@@ -5204,6 +5209,10 @@ body {
5204
5209
  border-color: rgb(255 255 255 / 0.12);
5205
5210
  }
5206
5211
 
5212
+ .dark\:divide-white\/\[0\.23\]:is([data-theme="dark"] *) > :not([hidden]) ~ :not([hidden]) {
5213
+ border-color: rgb(255 255 255 / 0.23);
5214
+ }
5215
+
5207
5216
  .dark\:\!border-white\/\[0\.23\]:is([data-theme="dark"] *) {
5208
5217
  border-color: rgb(255 255 255 / 0.23) !important;
5209
5218
  }
@@ -5546,6 +5555,10 @@ body {
5546
5555
  color: rgb(255 255 255 / 0.56);
5547
5556
  }
5548
5557
 
5558
+ .dark\:outline-white\/\[0\.23\]:is([data-theme="dark"] *) {
5559
+ outline-color: rgb(255 255 255 / 0.23);
5560
+ }
5561
+
5549
5562
  .dark\:ring-rui-error\/60:is([data-theme="dark"] *) {
5550
5563
  --tw-ring-color: rgba(var(--rui-error-main), 0.6);
5551
5564
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json.schemastore.org/web-types",
3
3
  "framework": "vue",
4
4
  "name": "@rotki/ui-library",
5
- "version": "2.22.0",
5
+ "version": "2.22.2",
6
6
  "js-types-syntax": "typescript",
7
7
  "description-markup": "markdown",
8
8
  "contributions": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rotki/ui-library",
3
- "version": "2.22.0",
3
+ "version": "2.22.2",
4
4
  "description": "A vue design system and component library for rotki",
5
5
  "type": "module",
6
6
  "keywords": [