@rotki/ui-library 2.26.0 → 2.26.1

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.
@@ -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 /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\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 /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\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 * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\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 role = 'menu',\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// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\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\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\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 <!--\n Escape is swallowed only while the menu is open and not persistent\n (`onLeave` stops the event itself); a closed or persistent menu must let it\n through, or a consumer that closes its own editor on Escape never sees the\n key.\n -->\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\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=\"ariaHasPopup\"\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=\"role\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\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":""}
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\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\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 /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\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 * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\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 role = 'menu',\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// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\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\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\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({ preventScroll: true });\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\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\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 <!--\n Escape is swallowed only while the menu is open and not persistent\n (`onLeave` stops the event itself); a closed or persistent menu must let it\n through, or a consumer that closes its own editor on Escape never sees the\n key.\n -->\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\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=\"ariaHasPopup\"\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=\"role\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\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":""}
@@ -129,20 +129,26 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
129
129
  ...get$1(baseMenuAttrs),
130
130
  onClick: __props.disabled ? void 0 : checkClick
131
131
  }));
132
+ /**
133
+ * Moves focus into the open menu without scrolling the page. A menu taller than the space below its
134
+ * activator would otherwise make the browser scroll the whole document to reveal the focused
135
+ * container, sliding the page out from under the activator.
136
+ */
132
137
  function focusOnContent() {
133
138
  const content = get$1(menuContent);
134
139
  if (!content) return;
135
- content.focus();
140
+ content.focus({ preventScroll: true });
136
141
  }
137
142
  function focusMenu() {
138
143
  if (__props.disableAutoFocus) return;
139
144
  nextTick(() => focusOnContent());
140
145
  }
146
+ /** Hands focus back to the activator on close, likewise without scrolling to it. */
141
147
  function focusOnActivator() {
142
148
  const activatorEl = get$1(activator);
143
149
  if (!activatorEl) return;
144
150
  const focusableEl = activatorEl.querySelector(FOCUSABLE_ELEMENTS_SELECTOR);
145
- if (focusableEl) focusableEl.focus();
151
+ if (focusableEl) focusableEl.focus({ preventScroll: true });
146
152
  }
147
153
  function onLeave(event) {
148
154
  if (!get$1(open)) return;
@@ -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 /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\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 /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\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 * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\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 role = 'menu',\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// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\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\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\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 <!--\n Escape is swallowed only while the menu is open and not persistent\n (`onLeave` stops the event itself); a closed or persistent menu must let it\n through, or a consumer that closes its own editor on Escape never sees the\n key.\n -->\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\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=\"ariaHasPopup\"\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=\"role\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\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":";;;;;;;;;;;;;;;AAiHA,IAAM,8BACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAzCF,MAAM,aAAa,SAAoB,SAAA,YAAmB;EAqC1D,MAAM,QAAQ,IAAa,KAAK;EAChC,MAAM,cAAc,eAA4B,aAAa;EAK7D,MAAM,EACJ,WAAW,WACX,SAAS,MACT,MACA,SACA,kBACA,cACA,gBACA,QACA,SACA,iBACA,mBACE,kBACI,QAAA,SAAS,kBAAkB,QAAA,MAAM,IAAK,QAAA,WAAW,CAAC,SAClD,QAAA,gBACA,QAAA,iBACA,QAAA,kBACA,QAAA,QACR;EAEA,MAAM,EAAE,UAAU,eAAe,SAAS;EAE1C,MAAM,EAAE,UAAU,eAAe,wBACzB,QAAA,qBACA,QAAA,eACR;EAEA,MAAM,aAAa,GAAG;GACpB,OAAO;IACL,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;GACX;GACA,UAAU;IACR,WAAW,EACT,MAAM,EAAE,SAAS,SAAS,EAC5B;IACA,OAAO;KACL,MAAM,EAAE,SAAS,OAAO;KACxB,OAAO,EAAE,SAAS,OAAO;IAC3B;GACF;GACA,iBAAiB;IAAE,WAAW;IAAO,OAAO;GAAM;EACpD,CAAC;EAED,MAAM,KAAK,eAA8C,WAAW;GAClE,WAAQ,QAAA;GACR,OAAI,QAAA;EACN,CAAC,CAAC;EAGF,MAAM,eAAe,eAAqC,QAAA,SAAS,SAAS,SAAS,QAAA,IAAI;;;;;;;;;;EAWzF,MAAM,gBAAgB,eAA8B;GAClD,MAAM,WAAW,MAAI,KAAK;GAC1B,OAAO;IACL,aAAa,QAAA,WACT,KAAA,UACM;KACJ,IAAI,QAAA,aACF,OAAO;IACX;IACJ,cAAc,QAAA,WACV,KAAA,UACM;KACJ,IAAI,QAAA,eAAe,CAAC,UAClB,QAAQ;IACZ;GACN;EACF,CAAC;EAED,MAAM,YAAY,gBAA2B;GAC3C,GAAG,MAAI,aAAa;GACpB,SAAS,QAAA,WAAW,KAAA,IAAY;EAClC,EAAE;EAEF,SAAS,iBAAiB;GACxB,MAAM,UAAU,MAAI,WAAW;GAC/B,IAAI,CAAC,SACH;GAGF,QAAQ,MAAM;EAChB;EAEA,SAAS,YAAkB;GACzB,IAAI,QAAA,kBACF;GAEF,eAAe,eAAe,CAAC;EACjC;EAEA,SAAS,mBAAmB;GAC1B,MAAM,cAAc,MAAI,SAAS;GACjC,IAAI,CAAC,aACH;GAEF,MAAM,cAAc,YAAY,cAA2B,2BAA2B;GACtF,IAAI,aACF,YAAY,MAAM;EAEtB;EAEA,SAAS,QAAQ,OAA6B;GAC5C,IAAI,CAAC,MAAI,IAAI,GACX;GACF,QAAQ;GACR,MAAI,OAAO,KAAK;GAChB,OAAO,gBAAgB;GAGvB,IAAI,QAAA,kBACF;GAGF,eAAe,iBAAiB,CAAC;EACnC;;;;;;;;;;;;EAaA,SAAS,SAAS,OAA4B;GAC5C,IAAI,QAAA,YACF;GAEF,QAAQ,KAAK;EACf;EAEA,SAAS,aAAmB;GAC1B,IAAI,MAAI,IAAI,KAAK,MAAI,KAAK,GACpB;QAAA,CAAC,QAAA,yBACH,QAAQ;GAAA,OAEP;IACH,OAAO;IACP,MAAI,OAAO,IAAI;GACjB;EACF;EAEA,MAAM,aAAa,UAAU;GAC3B,IAAI,MAAI,IAAI,MAAM,OAChB;GAEF,IAAI,OAAO;IACT,OAAO;IACP,MAAI,OAAO,IAAI;GACjB,OAEE,QAAQ;EAEZ,CAAC;EAED,MAAM,OAAO,SAAS;GACpB,MAAI,YAAY,IAAI;GACpB,IAAI,MACF,UAAU;EAEd,CAAC;EAED,eAAe,YAAY;GACzB,IAAI,MAAI,IAAI,KAAK,CAAC,QAAA,YAChB,QAAQ;EACZ,GAAG,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC;;GAUxB,OAAA,UAAA,GAAA,mBA6DM,OAAA;IA5DH,OAAK,eAAE,QAAA,YAAY,IAAI;IACvB,WAAO,OAAA,OAAA,OAAA,KAAA,UAAA,WAAM,SAAS,MAAM,GAAA,CAAA,KAAA,CAAA;;IAE7B,mBAWM,OAAA;KAVA,SAAA;KAAJ,KAAI;KACH,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,QAAO,EAAA,OAAU,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,OAAO,KAAK,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,EAAA,CAAA,CAAA;KACrE,sBAAoB,QAAA;KACpB,iBAAe,MAAA,YAAA;KACf,iBAAe,MAAA,IAAA;IAEhB,GAAA,CAAA,WAGE,KAAA,QAAA,aAAA,eAAA,mBAAA;KAAA,OADiB,MAAA,SAAA;KAAS,MAAE,MAAA,IAAA;KAAI,UAAE,QAAA;KAAQ,UAAE,MAAA,QAAA;KAAQ,YAAE,MAAA,UAAA;IAAU,CAAA,CAAA,CAAA,CAAA,GAAA,IAAA,UAAA;IAI3D,CAAA,QAAA,YADT,UAAA,GAAA,YAqCW,UAAA;;KAnCT,IAAG;IAGK,GAAA,CAAA,MAAA,OAAA,KADR,UAAA,GAAA,mBAgCM,OAAA;;KA9BA,SAAA;KAAJ,KAAI;KACH,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,QAAO,EAAA,OAAU,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,IAAI,KAAK,MAAA,EAAA,CAAE,CAAC,QAAA,SAAS,EAAA,CAAA,CAAA;KAC/D,MAAM,QAAA;KACN,kBAAgB,MAAA,gBAAA;KAChB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,QAAA,sBAAsB,QAAO,IAAK,KAAA;KACzC,WAAO,OAAA,OAAA,OAAA,KAAA,UAAA,WAAM,SAAS,MAAM,GAAA,CAAA,KAAA,CAAA;IAE7B,GAAA,CAAA,YAsBkB,iBAAA;KArBhB,sBAAmB;KACnB,oBAAiB;KACjB,kBAAe;KACf,sBAAmB;KACnB,oBAAiB;KACjB,kBAAe;KACd,eAAY,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,cAAA,CAAc,CAAA;KAC5B,cAAW,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,YAAA,IAAe,MAAA,eAAA,CAAe,CAAA,IAAK,KAAA;KAChD,eAAY,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,cAAA,CAAc,CAAA;;KAE7B,SAAA,cAUM,CATE,MAAA,IAAA,KADR,UAAA,GAAA,mBAUM,OAVN,WAUM;MARA,SAAA;MAAJ,KAAI;MACJ,KAAI;MACJ,WAAQ;MACP,OAAO,MAAA,EAAA,CAAE,CAAC,QAAO,EAAA,OAAU,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,OAAO,EAAA,CAAA;MAClD,UAAS;KACD,GAAA,MAAA,aAAA,CAAa,GAAA,CAErB,WAA2B,KAAA,QAAA,WAAA,eAAA,mBAAA,EAAA,OAAX,MAAA,KAAA,EAAK,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA;;;IAMrB,QAAA,eADR,UAAA,GAAA,YAME,2BAAA;;KAJC,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,QAAO,CAAA;KACjB,kBAAgB,QAAA;KAChB,oBAAkB,QAAA;KAClB,MAAM,QAAA"}
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\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\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 /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\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 * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\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 role = 'menu',\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// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\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\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\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({ preventScroll: true });\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\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\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 <!--\n Escape is swallowed only while the menu is open and not persistent\n (`onLeave` stops the event itself); a closed or persistent menu must let it\n through, or a consumer that closes its own editor on Escape never sees the\n key.\n -->\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\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=\"ariaHasPopup\"\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=\"role\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RuiDataTable.js","names":[],"sources":["../../../src/components/tables/RuiDataTable.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"T extends object, IdType extends keyof T = keyof T\">\nimport type { GroupHeader } from '@/composables/tables/data-table/types';\nimport { dataTableStyles } from '@/components/tables/data-table-styles';\nimport { type DataTableClasses, type DataTableGroupLabel, provideDataTableContext, provideDataTableNested, useDataTableNested } from '@/components/tables/data-table/context';\nimport RuiDataTableBody from '@/components/tables/data-table/RuiDataTableBody.vue';\nimport RuiDataTableMobileSort from '@/components/tables/data-table/RuiDataTableMobileSort.vue';\nimport RuiTableHead, {\n type GroupData,\n type TableColumn,\n type TableRowKeyData,\n type TableSortData,\n} from '@/components/tables/RuiTableHead.vue';\nimport RuiTablePagination, {\n type TablePaginationData,\n} from '@/components/tables/RuiTablePagination.vue';\nimport { GroupExpandButtonPosition } from '@/components/tables/table-props';\nimport { useTable } from '@/composables/defaults/table';\nimport { useStickyTableHeader } from '@/composables/sticky-header';\nimport { useTableColumns } from '@/composables/tables/data-table/columns';\nimport { useTableExpansion } from '@/composables/tables/data-table/expansion';\nimport { useTableGrouping } from '@/composables/tables/data-table/grouping';\nimport { useTablePagination } from '@/composables/tables/data-table/pagination';\nimport { useTableSelection } from '@/composables/tables/data-table/selection';\nimport { useTableSort } from '@/composables/tables/data-table/sort';\n\nexport type { GroupedTableRow, GroupHeader, TableOptions } from '@/composables/tables/data-table/types';\n\nexport interface Props<T, K extends keyof T> {\n /**\n * list of items for each row\n */\n rows: readonly T[];\n /**\n * the attribute used to identify each row uniquely for selection, usually `id`\n */\n rowAttr: K;\n /**\n * model for internal searching\n */\n search?: string;\n /**\n * model for items per page\n * will be used if the `pagination` model isn't specified\n */\n itemsPerPage?: number;\n /**\n * list of column definitions\n */\n cols?: TableColumn<T>[];\n /**\n * attribute to use from column definitions to display column titles\n */\n columnAttr?: keyof TableColumn<T>;\n /**\n * flag to show a more or less spacious table\n */\n dense?: boolean;\n /**\n * flag to outline the flag with a border\n */\n outlined?: boolean;\n /**\n * should add zebra-striping to the table row\n */\n striped?: boolean;\n /**\n * flag to show loading state of the table\n * triggers indefinite progress at the bottom of the table header\n */\n loading?: boolean;\n disablePerPage?: boolean;\n /**\n * Maximum number of pages before the jump-to-page dropdown is replaced with\n * a numeric input. Defaults to `500`. Forwarded to `RuiTablePagination`.\n */\n rangesThreshold?: number;\n /**\n * The text and icon shown when there are no rows, e.g.\n * `:empty=\"\\{ icon: 'transactions-line', label: 'No transactions found' \\}\"`\n */\n empty?: {\n label?: string;\n description?: string;\n };\n /**\n * Why the last read failed. Shown above any rows kept from an earlier read,\n * or in place of the empty state; `loading` still takes precedence.\n */\n error?: string;\n /** Heading for that failure. */\n errorTitle?: string;\n /** Label for the retry control; without one, no control is offered. */\n retryText?: string;\n /**\n * should hide the header navigation\n */\n hideDefaultHeader?: boolean;\n /**\n * should hide the footer navigation\n */\n hideDefaultFooter?: boolean;\n\n rounded?: 'sm' | 'md' | 'lg';\n /**\n * make expansion work like accordion\n */\n singleExpand?: boolean;\n /**\n * make table head stick to top on scroll\n */\n stickyHeader?: boolean;\n stickyOffset?: number;\n /**\n * Affects how the items per page work across the app.\n * When true, changing the items per page setting in one table will affect other tables.\n */\n globalItemsPerPage?: boolean;\n groupExpandButtonPosition?: GroupExpandButtonPosition;\n disabledRows?: readonly T[];\n multiPageSelect?: boolean;\n itemClass?: ((item: T) => string) | string;\n /**\n * Render each row as a stacked card instead of a table row once the measured\n * width is narrower than this width (in pixels). Columns flagged with\n * `mobileHidden` are dropped in this layout. Falls back to the global\n * `mobileBreakpoint` table default; leave both unset to keep the default table\n * layout at every width.\n */\n mobileBreakpoint?: number;\n /**\n * What the `mobileBreakpoint` is measured against: the browser `viewport`\n * (default) or this table's own `container` width. Use `container` when the\n * table lives in a narrow region of a wider window. Falls back to the global\n * `mobileBreakpointBasis` table default.\n */\n mobileBreakpointBasis?: 'viewport' | 'container';\n /**\n * Force the stacked mobile card layout on (`true`) or off (`false`),\n * regardless of `mobileBreakpoint`. Leave unset to derive it from the width.\n */\n mobile?: boolean;\n}\n\ndefineOptions({\n name: 'RuiDataTable',\n});\n\nconst selectedData = defineModel<T[IdType][]>();\n\nconst expanded = defineModel<T[]>('expanded');\n\nconst [pagination, paginationModifiers] = defineModel<TablePaginationData, 'external'>('pagination');\n\nconst [sort, sortModifiers] = defineModel<TableSortData<T>, 'external'>('sort');\n\nconst group = defineModel<TableRowKeyData<T>>('group');\n\nconst collapsed = defineModel<T[]>('collapsed');\n\nconst {\n rows,\n rowAttr,\n search = '',\n itemsPerPage = 10,\n cols,\n columnAttr = 'label',\n dense = false,\n outlined = false,\n striped = false,\n loading = false,\n disablePerPage = false,\n rangesThreshold = 500,\n empty = { label: 'No item found' },\n error = '',\n errorTitle = '',\n retryText = '',\n hideDefaultHeader = false,\n hideDefaultFooter = false,\n rounded = 'md',\n singleExpand = false,\n stickyHeader = false,\n stickyOffset,\n globalItemsPerPage = undefined,\n groupExpandButtonPosition = GroupExpandButtonPosition.start,\n disabledRows,\n multiPageSelect = false,\n itemClass = '',\n mobileBreakpoint,\n mobileBreakpointBasis,\n mobile,\n} = defineProps<Props<T, IdType>>();\n\nconst emit = defineEmits<{\n 'update:options': [value: { pagination?: TablePaginationData; sort?: TableSortData<T> }];\n 'copy:group': [value: GroupData<T>];\n 'retry': [];\n}>();\n\nconst slots = defineSlots<Partial<\n Record<`header.${string}`, (props: { column: TableColumn<T> }) => any> &\n Record<`item.${string}`, (props: { column: TableColumn<T>; row: T; index: number }) => any> & {\n 'body.prepend': (props: { colspan: number }) => any;\n 'body.append': (props: { colspan: number }) => any;\n 'group.header': (props: {\n colspan: number;\n header: GroupHeader<T>;\n isOpen: boolean;\n toggle: () => void;\n }) => any;\n 'group.header.content': (props: { header: GroupHeader<T>; groupKey: string }) => any;\n 'expanded-item': (props: { row: T; index: number }) => any;\n 'no-data': () => any;\n 'empty-description': () => any;\n 'error': () => any;\n 'tfoot': () => any;\n }\n>>();\n\nconst tableDefaults = useTable();\n\nconst tableWrapper = useTemplateRef<HTMLElement>('tableWrapper');\nconst { width: windowWidth } = useWindowSize();\nconst { width: containerWidth } = useElementSize(tableWrapper);\n\n// Per-table props win, then the global table defaults.\nconst resolvedMobileBreakpoint = computed<number | undefined>(() => {\n if (mobileBreakpoint !== undefined)\n return mobileBreakpoint;\n const globalBreakpoint = tableDefaults.mobileBreakpoint;\n return globalBreakpoint === undefined ? undefined : get(globalBreakpoint);\n});\nconst resolvedMobileBasis = computed<'viewport' | 'container'>(() => {\n if (mobileBreakpointBasis !== undefined)\n return mobileBreakpointBasis;\n const globalBasis = tableDefaults.mobileBreakpointBasis;\n return globalBasis === undefined ? 'viewport' : get(globalBasis);\n});\n\n/**\n * Whether the stacked card layout is showing.\n *\n * Vue coerces an absent Boolean prop to `false`, so `mobile` cannot say\n * \"unset\": with no breakpoint configured it is the manual switch, defaulting\n * to desktop, and with one configured the breakpoint drives the layout while\n * `mobile === true` can still force the stacked layout on.\n */\nconst isMobile = computed<boolean>(() => {\n const breakpoint = get(resolvedMobileBreakpoint);\n if (breakpoint === undefined)\n return mobile;\n if (mobile)\n return true;\n // `containerWidth` is 0 until measured; the viewport keeps the first paint off mobile\n const width = get(resolvedMobileBasis) === 'container'\n ? (get(containerWidth) || get(windowWidth))\n : get(windowWidth);\n return width < breakpoint;\n});\n\nconst stickyHeaderOffset = computed<number | undefined>(() =>\n stickyOffset !== undefined ? stickyOffset : get(tableDefaults.stickyOffset),\n);\n\n/**\n * A table rendered inside an expanded row must not pin its own toolbar: only\n * the outermost table sticks, otherwise the bars stack and collide.\n */\nconst isNestedTable = useDataTableNested();\nprovideDataTableNested();\n\n/**\n * On mobile there is no column header to pin, so `stickyHeader` pins the\n * pagination and sort toolbar instead while the card list scrolls.\n */\nconst stickyMobileToolbar = computed<boolean>(() => stickyHeader && get(isMobile) && !isNestedTable);\n\nconst table = useTemplateRef<HTMLTableElement>('table');\nconst tableScroller = useTemplateRef<HTMLElement>('tableScroller');\n\nconst { stick } = useStickyTableHeader(\n () => stickyHeader,\n stickyHeaderOffset,\n { table, tableScroller },\n);\n\nconst { width: scrollerWidth } = useElementSize(tableScroller);\n\n/**\n * The visible width of the table, which an expanded row caps its content to.\n * Without the cap a wide panel, such as a nested table, sets the minimum width\n * of the whole table and pushes every column past the scroller. `initial`\n * resets the variable, so a nested table never inherits its parent's width.\n */\nconst viewportStyle = computed<Record<string, string>>(() => {\n const width = get(scrollerWidth);\n return { '--rui-table-viewport': width > 0 && !get(isMobile) ? `${width}px` : 'initial' };\n});\n\nconst { expandable, isExpanded, onToggleExpand } = useTableExpansion<T, IdType>(\n { rowAttr, singleExpand },\n { expanded },\n);\n\nfunction emitUpdateOptions(opts: { sort?: TableSortData<T>; pagination?: TablePaginationData }): void {\n emit('update:options', opts);\n}\n\nconst {\n sortData,\n sortedMap,\n sorted,\n onSort: applySort,\n} = useTableSort<T>(\n { rows: () => rows, search: () => search, sortModifiersExternal: sortModifiers.external },\n {\n sort,\n pagination,\n emitUpdateOptions,\n },\n);\n\nconst {\n groupKeys,\n groupKey,\n grouped,\n mappedGroups,\n isExpandedGroup,\n isHiddenRow,\n onToggleExpandGroup,\n onUngroup,\n} = useTableGrouping<T, IdType>(\n { rowAttr },\n {\n group,\n collapsed,\n sorted,\n },\n);\n\nconst {\n paginationData,\n filtered,\n setInternalTotal,\n resetPagination,\n} = useTablePagination<T>(\n {\n itemsPerPage,\n paginationModifiersExternal: paginationModifiers.external,\n globalItemsPerPage,\n },\n {\n pagination,\n grouped,\n isHiddenRow,\n sort,\n emitUpdateOptions,\n tableDefaults,\n },\n);\n\nconst noData = computed<boolean>(() => get(filtered).length === 0);\n\n/**\n * With the header bar showing, an empty table drops its footer bar: two rows\n * of disabled controls around an empty state only add noise.\n */\nconst showFooter = computed<boolean>(() => {\n const data = get(paginationData);\n if (!data || hideDefaultFooter)\n return false;\n return data.total > 0 || hideDefaultHeader;\n});\n\n/**\n * Grouped columns leave the column list, so their labels come from `cols`.\n */\nconst groupLabels = computed<DataTableGroupLabel[]>(() => get(groupKeys).map((key) => {\n const label = cols?.find(column => column.key === key)?.[columnAttr];\n return {\n key: String(key),\n label: label === undefined || label === null ? String(key) : String(label),\n };\n}));\n\n/** Each group's list starts with its header entry, which is not a row. */\nfunction getGroupSize(identifier: string): number {\n return Math.max((get(mappedGroups)[identifier]?.length ?? 0) - 1, 0);\n}\n\nconst ui = computed<ReturnType<typeof dataTableStyles>>(() => dataTableStyles({\n outlined,\n rounded,\n dense,\n striped,\n mobile: get(isMobile),\n}));\n\nconst { columns, colspan, headerSlots, cellValue } = useTableColumns<T, IdType>({\n cols: () => cols,\n columnAttr,\n rows: () => rows,\n expandable,\n groupKeys,\n selectedData,\n slots,\n isMobile,\n tdResolver: options => get(ui).td(options),\n});\n\nconst showMobileSort = computed<boolean>(() =>\n get(isMobile) && !!get(sortData) && get(columns).some(column => column.sortable),\n);\n\nconst ITEM_SLOT_PREFIX = 'item.';\n\n// Slot keys are static for the component lifetime — no reactivity needed\nconst itemSlotKeys: Set<string> = new Set(\n Object.keys(slots)\n .filter((key): key is `item.${string}` => key.startsWith(ITEM_SLOT_PREFIX))\n .map(key => key.slice(ITEM_SLOT_PREFIX.length)),\n);\n\nconst {\n isAllSelected,\n indeterminate,\n isSelected,\n isDisabledRow,\n onToggleAll,\n onSelect,\n onCheckboxClick,\n deselectRemovedRows,\n resetCheckboxShiftState,\n} = useTableSelection<T, IdType>(\n { rowAttr, multiPageSelect, disabledRows: () => disabledRows },\n { selectedData, filtered },\n);\n\n/**\n * Sorting clears the selection. That lives here rather than in either\n * composable, which would make sort and selection depend on each other.\n */\nfunction onSort(payload: Parameters<typeof applySort>[0]): void {\n applySort(payload);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\nfunction onPaginate(): void {\n if ((get(expanded)?.length ?? 0) > 0)\n set(expanded, []);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\n// Reset pagination page to 1 on search query change\nwatch(() => search, () => {\n resetPagination();\n onToggleAll(false);\n resetCheckboxShiftState();\n});\n\nwatch(sorted, (items) => {\n if (!multiPageSelect)\n deselectRemovedRows();\n setInternalTotal(items);\n});\n\nonMounted(() => {\n setInternalTotal(get(sorted));\n});\n\nconst classes = computed<DataTableClasses>(() => {\n const s = get(ui);\n return {\n td: s.td(),\n tr: s.tr(),\n trSelected: s.tr({ rowVariant: 'selected' }),\n trExpandable: s.tr({ rowVariant: 'expandable' }),\n trExpandedParent: s.tr({ rowVariant: 'expandedParent' }),\n trGroup: s.tr({ rowVariant: 'group' }),\n trEmpty: s.tr({ rowVariant: 'empty' }),\n checkbox: s.checkbox(),\n tbody: s.tbody(),\n tbodyLoader: s.tbodyLoader(),\n };\n});\n\nprovideDataTableContext<T, IdType>({\n // Reactive (ComputedRef/Ref) — destructuring preserves reactivity via Vue template auto-unwrap\n classes,\n columns,\n colspan,\n expandable,\n groupKey,\n groupLabels,\n isMobile,\n selectedData,\n // Static values\n cellValue,\n columnAttr,\n dense,\n getGroupSize,\n getRowId: (row: T) => row[rowAttr],\n itemSlotKeys,\n groupExpandButtonPosition,\n isDisabledRow,\n isExpanded,\n isExpandedGroup,\n isSelected,\n itemClass,\n onCheckboxClick,\n onCopyGroup: (header: GroupHeader<T>) => emit('copy:group', { key: get(groupKey) ?? '', value: header.group }),\n onSelect,\n onToggleExpand,\n onToggleExpandGroup,\n onUngroup,\n});\n</script>\n\n<template>\n <div\n ref=\"tableWrapper\"\n :class=\"ui.wrapper()\"\n data-id=\"table-wrapper\"\n >\n <div\n :class=\"stickyMobileToolbar ? 'sticky z-10 bg-white dark:bg-[#121212] pt-2' : 'contents'\"\n :style=\"stickyMobileToolbar ? { top: `${stickyHeaderOffset ?? 0}px` } : undefined\"\n data-id=\"table-mobile-toolbar-sticky\"\n >\n <RuiTablePagination\n v-if=\"paginationData && !hideDefaultHeader\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n <div\n v-if=\"showMobileSort\"\n class=\"flex justify-end p-2\"\n data-id=\"table-mobile-toolbar\"\n >\n <RuiDataTableMobileSort\n :columns=\"columns\"\n :column-attr=\"columnAttr\"\n :sorted-map=\"sortedMap\"\n :dense=\"dense\"\n @sort=\"onSort($event)\"\n />\n </div>\n </div>\n <div\n ref=\"tableScroller\"\n :class=\"[ui.scroller(), isMobile && !showMobileSort ? 'pt-2' : '']\"\n data-id=\"table-scroller\"\n >\n <table\n ref=\"table\"\n :class=\"ui.table()\"\n :style=\"viewportStyle\"\n :aria-busy=\"loading\"\n >\n <RuiTableHead\n v-if=\"!isMobile\"\n :loading=\"loading\"\n :indeterminate=\"indeterminate\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :disable-check-all=\"!filtered?.length\"\n :is-all-selected=\"isAllSelected\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n :stick=\"stick\"\n :sticky-header=\"stickyHeader\"\n @sort=\"onSort($event)\"\n @select:all=\"onToggleAll($event)\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiTableHead\n v-if=\"stickyHeader && !isMobile\"\n :loading=\"loading\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n data-id=\"head-clone\"\n class=\"opacity-0 invisible\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiDataTableBody\n :filtered=\"filtered\"\n :loading=\"loading\"\n :no-data=\"noData\"\n :empty=\"empty\"\n :error=\"error\"\n :error-title=\"errorTitle\"\n :retry-text=\"retryText\"\n @retry=\"emit('retry')\"\n >\n <template\n v-if=\"slots.error\"\n #error\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"error\" />\n </template>\n <template\n v-if=\"slots['body.prepend']\"\n #body.prepend=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.prepend\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header']\"\n #group.header=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header.content']\"\n #group.header.content=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header.content\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-for=\"key in itemSlotKeys\"\n #[`item.${key}`]=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n :name=\"`item.${key}`\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['expanded-item']\"\n #expanded-item=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"expanded-item\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['no-data']\"\n #no-data\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"no-data\" />\n </template>\n <template\n v-if=\"slots['empty-description']\"\n #empty-description\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"empty-description\" />\n </template>\n <template\n v-if=\"slots['body.append']\"\n #body.append=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.append\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiDataTableBody>\n <tfoot>\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"tfoot\" />\n </tfoot>\n </table>\n </div>\n <RuiTablePagination\n v-if=\"paginationData && showFooter\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiDataTable.js","names":[],"sources":["../../../src/components/tables/RuiDataTable.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"T extends object, IdType extends keyof T = keyof T\">\nimport type { GroupHeader } from '@/composables/tables/data-table/types';\nimport { dataTableStyles } from '@/components/tables/data-table-styles';\nimport { type DataTableClasses, type DataTableGroupLabel, provideDataTableContext, provideDataTableNested, useDataTableNested } from '@/components/tables/data-table/context';\nimport RuiDataTableBody from '@/components/tables/data-table/RuiDataTableBody.vue';\nimport RuiDataTableMobileSort from '@/components/tables/data-table/RuiDataTableMobileSort.vue';\nimport RuiTableHead, {\n type GroupData,\n type TableColumn,\n type TableRowKeyData,\n type TableSortData,\n} from '@/components/tables/RuiTableHead.vue';\nimport RuiTablePagination, {\n type TablePaginationData,\n} from '@/components/tables/RuiTablePagination.vue';\nimport { GroupExpandButtonPosition } from '@/components/tables/table-props';\nimport { useTable } from '@/composables/defaults/table';\nimport { useStickyTableHeader } from '@/composables/sticky-header';\nimport { useTableColumns } from '@/composables/tables/data-table/columns';\nimport { useTableExpansion } from '@/composables/tables/data-table/expansion';\nimport { useTableGrouping } from '@/composables/tables/data-table/grouping';\nimport { useTablePagination } from '@/composables/tables/data-table/pagination';\nimport { useTableSelection } from '@/composables/tables/data-table/selection';\nimport { useTableSort } from '@/composables/tables/data-table/sort';\n\nexport type { GroupedTableRow, GroupHeader, TableOptions } from '@/composables/tables/data-table/types';\n\nexport interface Props<T, K extends keyof T> {\n /**\n * list of items for each row\n */\n rows: readonly T[];\n /**\n * the attribute used to identify each row uniquely for selection, usually `id`\n */\n rowAttr: K;\n /**\n * model for internal searching\n */\n search?: string;\n /**\n * model for items per page\n * will be used if the `pagination` model isn't specified\n */\n itemsPerPage?: number;\n /**\n * list of column definitions\n */\n cols?: TableColumn<T>[];\n /**\n * attribute to use from column definitions to display column titles\n */\n columnAttr?: keyof TableColumn<T>;\n /**\n * flag to show a more or less spacious table\n */\n dense?: boolean;\n /**\n * flag to outline the flag with a border\n */\n outlined?: boolean;\n /**\n * should add zebra-striping to the table row\n */\n striped?: boolean;\n /**\n * flag to show loading state of the table\n * triggers indefinite progress at the bottom of the table header\n */\n loading?: boolean;\n disablePerPage?: boolean;\n /**\n * Maximum number of pages before the jump-to-page dropdown is replaced with\n * a numeric input. Defaults to `500`. Forwarded to `RuiTablePagination`.\n */\n rangesThreshold?: number;\n /**\n * The text and icon shown when there are no rows, e.g.\n * `:empty=\"\\{ icon: 'transactions-line', label: 'No transactions found' \\}\"`\n */\n empty?: {\n label?: string;\n description?: string;\n };\n /**\n * Why the last read failed. Shown above any rows kept from an earlier read,\n * or in place of the empty state; `loading` still takes precedence.\n */\n error?: string;\n /** Heading for that failure. */\n errorTitle?: string;\n /** Label for the retry control; without one, no control is offered. */\n retryText?: string;\n /**\n * should hide the header navigation\n */\n hideDefaultHeader?: boolean;\n /**\n * should hide the footer navigation\n */\n hideDefaultFooter?: boolean;\n\n rounded?: 'sm' | 'md' | 'lg';\n /**\n * make expansion work like accordion\n */\n singleExpand?: boolean;\n /**\n * make table head stick to top on scroll\n */\n stickyHeader?: boolean;\n stickyOffset?: number;\n /**\n * Affects how the items per page work across the app.\n * When true, changing the items per page setting in one table will affect other tables.\n */\n globalItemsPerPage?: boolean;\n groupExpandButtonPosition?: GroupExpandButtonPosition;\n disabledRows?: readonly T[];\n multiPageSelect?: boolean;\n itemClass?: ((item: T) => string) | string;\n /**\n * Render each row as a stacked card instead of a table row once the measured\n * width is narrower than this width (in pixels). Columns flagged with\n * `mobileHidden` are dropped in this layout. Falls back to the global\n * `mobileBreakpoint` table default; leave both unset to keep the default table\n * layout at every width.\n */\n mobileBreakpoint?: number;\n /**\n * What the `mobileBreakpoint` is measured against: the browser `viewport`\n * (default) or this table's own `container` width. Use `container` when the\n * table lives in a narrow region of a wider window. Falls back to the global\n * `mobileBreakpointBasis` table default.\n */\n mobileBreakpointBasis?: 'viewport' | 'container';\n /**\n * Force the stacked mobile card layout on (`true`) or off (`false`),\n * regardless of `mobileBreakpoint`. Leave unset to derive it from the width.\n */\n mobile?: boolean;\n}\n\ndefineOptions({\n name: 'RuiDataTable',\n});\n\nconst selectedData = defineModel<T[IdType][]>();\n\nconst expanded = defineModel<T[]>('expanded');\n\nconst [pagination, paginationModifiers] = defineModel<TablePaginationData, 'external'>('pagination');\n\nconst [sort, sortModifiers] = defineModel<TableSortData<T>, 'external'>('sort');\n\nconst group = defineModel<TableRowKeyData<T>>('group');\n\nconst collapsed = defineModel<T[]>('collapsed');\n\nconst {\n rows,\n rowAttr,\n search = '',\n itemsPerPage = 10,\n cols,\n columnAttr = 'label',\n dense = false,\n outlined = false,\n striped = false,\n loading = false,\n disablePerPage = false,\n rangesThreshold = 500,\n empty = { label: 'No item found' },\n error = '',\n errorTitle = '',\n retryText = '',\n hideDefaultHeader = false,\n hideDefaultFooter = false,\n rounded = 'md',\n singleExpand = false,\n stickyHeader = false,\n stickyOffset,\n globalItemsPerPage = undefined,\n groupExpandButtonPosition = GroupExpandButtonPosition.start,\n disabledRows,\n multiPageSelect = false,\n itemClass = '',\n mobileBreakpoint,\n mobileBreakpointBasis,\n mobile,\n} = defineProps<Props<T, IdType>>();\n\nconst emit = defineEmits<{\n 'update:options': [value: { pagination?: TablePaginationData; sort?: TableSortData<T> }];\n 'copy:group': [value: GroupData<T>];\n 'retry': [];\n}>();\n\nconst slots = defineSlots<Partial<\n Record<`header.${string}`, (props: { column: TableColumn<T> }) => any> &\n Record<`item.${string}`, (props: { column: TableColumn<T>; row: T; index: number }) => any> & {\n 'body.prepend': (props: { colspan: number }) => any;\n 'body.append': (props: { colspan: number }) => any;\n 'group.header': (props: {\n colspan: number;\n header: GroupHeader<T>;\n isOpen: boolean;\n toggle: () => void;\n }) => any;\n 'group.header.content': (props: { header: GroupHeader<T>; groupKey: string }) => any;\n 'expanded-item': (props: { row: T; index: number }) => any;\n 'no-data': () => any;\n 'empty-description': () => any;\n 'error': () => any;\n 'tfoot': () => any;\n }\n>>();\n\nconst tableDefaults = useTable();\n\nconst tableWrapper = useTemplateRef<HTMLElement>('tableWrapper');\nconst { width: windowWidth } = useWindowSize();\nconst { width: containerWidth } = useElementSize(tableWrapper);\n\n// Per-table props win, then the global table defaults.\nconst resolvedMobileBreakpoint = computed<number | undefined>(() => {\n if (mobileBreakpoint !== undefined)\n return mobileBreakpoint;\n const globalBreakpoint = tableDefaults.mobileBreakpoint;\n return globalBreakpoint === undefined ? undefined : get(globalBreakpoint);\n});\nconst resolvedMobileBasis = computed<'viewport' | 'container'>(() => {\n if (mobileBreakpointBasis !== undefined)\n return mobileBreakpointBasis;\n const globalBasis = tableDefaults.mobileBreakpointBasis;\n return globalBasis === undefined ? 'viewport' : get(globalBasis);\n});\n\n/**\n * Whether the stacked card layout is showing.\n *\n * Vue coerces an absent Boolean prop to `false`, so `mobile` cannot say\n * \"unset\": with no breakpoint configured it is the manual switch, defaulting\n * to desktop, and with one configured the breakpoint drives the layout while\n * `mobile === true` can still force the stacked layout on.\n */\nconst isMobile = computed<boolean>(() => {\n const breakpoint = get(resolvedMobileBreakpoint);\n if (breakpoint === undefined)\n return mobile;\n if (mobile)\n return true;\n // `containerWidth` is 0 until measured; the viewport keeps the first paint off mobile\n const width = get(resolvedMobileBasis) === 'container'\n ? (get(containerWidth) || get(windowWidth))\n : get(windowWidth);\n return width < breakpoint;\n});\n\nconst stickyHeaderOffset = computed<number | undefined>(() =>\n stickyOffset !== undefined ? stickyOffset : get(tableDefaults.stickyOffset),\n);\n\n/**\n * A table rendered inside an expanded row must not pin its own toolbar: only\n * the outermost table sticks, otherwise the bars stack and collide.\n */\nconst isNestedTable = useDataTableNested();\nprovideDataTableNested();\n\n/**\n * On mobile there is no column header to pin, so `stickyHeader` pins the\n * pagination and sort toolbar instead while the card list scrolls.\n */\nconst stickyMobileToolbar = computed<boolean>(() => stickyHeader && get(isMobile) && !isNestedTable);\n\nconst table = useTemplateRef<HTMLTableElement>('table');\nconst tableScroller = useTemplateRef<HTMLElement>('tableScroller');\n\nconst { stick } = useStickyTableHeader(\n () => stickyHeader,\n stickyHeaderOffset,\n { table },\n);\n\nconst { width: scrollerWidth } = useElementSize(tableScroller);\n\n/**\n * The visible width of the table, which an expanded row caps its content to.\n * Without the cap a wide panel, such as a nested table, sets the minimum width\n * of the whole table and pushes every column past the scroller. `initial`\n * resets the variable, so a nested table never inherits its parent's width.\n */\nconst viewportStyle = computed<Record<string, string>>(() => {\n const width = get(scrollerWidth);\n return { '--rui-table-viewport': width > 0 && !get(isMobile) ? `${width}px` : 'initial' };\n});\n\nconst { expandable, isExpanded, onToggleExpand } = useTableExpansion<T, IdType>(\n { rowAttr, singleExpand },\n { expanded },\n);\n\nfunction emitUpdateOptions(opts: { sort?: TableSortData<T>; pagination?: TablePaginationData }): void {\n emit('update:options', opts);\n}\n\nconst {\n sortData,\n sortedMap,\n sorted,\n onSort: applySort,\n} = useTableSort<T>(\n { rows: () => rows, search: () => search, sortModifiersExternal: sortModifiers.external },\n {\n sort,\n pagination,\n emitUpdateOptions,\n },\n);\n\nconst {\n groupKeys,\n groupKey,\n grouped,\n mappedGroups,\n isExpandedGroup,\n isHiddenRow,\n onToggleExpandGroup,\n onUngroup,\n} = useTableGrouping<T, IdType>(\n { rowAttr },\n {\n group,\n collapsed,\n sorted,\n },\n);\n\nconst {\n paginationData,\n filtered,\n setInternalTotal,\n resetPagination,\n} = useTablePagination<T>(\n {\n itemsPerPage,\n paginationModifiersExternal: paginationModifiers.external,\n globalItemsPerPage,\n },\n {\n pagination,\n grouped,\n isHiddenRow,\n sort,\n emitUpdateOptions,\n tableDefaults,\n },\n);\n\nconst noData = computed<boolean>(() => get(filtered).length === 0);\n\n/**\n * With the header bar showing, an empty table drops its footer bar: two rows\n * of disabled controls around an empty state only add noise.\n */\nconst showFooter = computed<boolean>(() => {\n const data = get(paginationData);\n if (!data || hideDefaultFooter)\n return false;\n return data.total > 0 || hideDefaultHeader;\n});\n\n/**\n * Grouped columns leave the column list, so their labels come from `cols`.\n */\nconst groupLabels = computed<DataTableGroupLabel[]>(() => get(groupKeys).map((key) => {\n const label = cols?.find(column => column.key === key)?.[columnAttr];\n return {\n key: String(key),\n label: label === undefined || label === null ? String(key) : String(label),\n };\n}));\n\n/** Each group's list starts with its header entry, which is not a row. */\nfunction getGroupSize(identifier: string): number {\n return Math.max((get(mappedGroups)[identifier]?.length ?? 0) - 1, 0);\n}\n\nconst ui = computed<ReturnType<typeof dataTableStyles>>(() => dataTableStyles({\n outlined,\n rounded,\n dense,\n striped,\n mobile: get(isMobile),\n}));\n\nconst { columns, colspan, headerSlots, cellValue } = useTableColumns<T, IdType>({\n cols: () => cols,\n columnAttr,\n rows: () => rows,\n expandable,\n groupKeys,\n selectedData,\n slots,\n isMobile,\n tdResolver: options => get(ui).td(options),\n});\n\nconst showMobileSort = computed<boolean>(() =>\n get(isMobile) && !!get(sortData) && get(columns).some(column => column.sortable),\n);\n\nconst ITEM_SLOT_PREFIX = 'item.';\n\n// Slot keys are static for the component lifetime — no reactivity needed\nconst itemSlotKeys: Set<string> = new Set(\n Object.keys(slots)\n .filter((key): key is `item.${string}` => key.startsWith(ITEM_SLOT_PREFIX))\n .map(key => key.slice(ITEM_SLOT_PREFIX.length)),\n);\n\nconst {\n isAllSelected,\n indeterminate,\n isSelected,\n isDisabledRow,\n onToggleAll,\n onSelect,\n onCheckboxClick,\n deselectRemovedRows,\n resetCheckboxShiftState,\n} = useTableSelection<T, IdType>(\n { rowAttr, multiPageSelect, disabledRows: () => disabledRows },\n { selectedData, filtered },\n);\n\n/**\n * Sorting clears the selection. That lives here rather than in either\n * composable, which would make sort and selection depend on each other.\n */\nfunction onSort(payload: Parameters<typeof applySort>[0]): void {\n applySort(payload);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\nfunction onPaginate(): void {\n if ((get(expanded)?.length ?? 0) > 0)\n set(expanded, []);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\n// Reset pagination page to 1 on search query change\nwatch(() => search, () => {\n resetPagination();\n onToggleAll(false);\n resetCheckboxShiftState();\n});\n\nwatch(sorted, (items) => {\n if (!multiPageSelect)\n deselectRemovedRows();\n setInternalTotal(items);\n});\n\nonMounted(() => {\n setInternalTotal(get(sorted));\n});\n\nconst classes = computed<DataTableClasses>(() => {\n const s = get(ui);\n return {\n td: s.td(),\n tr: s.tr(),\n trSelected: s.tr({ rowVariant: 'selected' }),\n trExpandable: s.tr({ rowVariant: 'expandable' }),\n trExpandedParent: s.tr({ rowVariant: 'expandedParent' }),\n trGroup: s.tr({ rowVariant: 'group' }),\n trEmpty: s.tr({ rowVariant: 'empty' }),\n checkbox: s.checkbox(),\n tbody: s.tbody(),\n tbodyLoader: s.tbodyLoader(),\n };\n});\n\nprovideDataTableContext<T, IdType>({\n // Reactive (ComputedRef/Ref) — destructuring preserves reactivity via Vue template auto-unwrap\n classes,\n columns,\n colspan,\n expandable,\n groupKey,\n groupLabels,\n isMobile,\n selectedData,\n // Static values\n cellValue,\n columnAttr,\n dense,\n getGroupSize,\n getRowId: (row: T) => row[rowAttr],\n itemSlotKeys,\n groupExpandButtonPosition,\n isDisabledRow,\n isExpanded,\n isExpandedGroup,\n isSelected,\n itemClass,\n onCheckboxClick,\n onCopyGroup: (header: GroupHeader<T>) => emit('copy:group', { key: get(groupKey) ?? '', value: header.group }),\n onSelect,\n onToggleExpand,\n onToggleExpandGroup,\n onUngroup,\n});\n</script>\n\n<template>\n <div\n ref=\"tableWrapper\"\n :class=\"ui.wrapper()\"\n data-id=\"table-wrapper\"\n >\n <div\n :class=\"stickyMobileToolbar ? 'sticky z-10 bg-white dark:bg-[#121212] pt-2' : 'contents'\"\n :style=\"stickyMobileToolbar ? { top: `${stickyHeaderOffset ?? 0}px` } : undefined\"\n data-id=\"table-mobile-toolbar-sticky\"\n >\n <RuiTablePagination\n v-if=\"paginationData && !hideDefaultHeader\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n <div\n v-if=\"showMobileSort\"\n class=\"flex justify-end p-2\"\n data-id=\"table-mobile-toolbar\"\n >\n <RuiDataTableMobileSort\n :columns=\"columns\"\n :column-attr=\"columnAttr\"\n :sorted-map=\"sortedMap\"\n :dense=\"dense\"\n @sort=\"onSort($event)\"\n />\n </div>\n </div>\n <div\n ref=\"tableScroller\"\n :class=\"[ui.scroller(), isMobile && !showMobileSort ? 'pt-2' : '']\"\n data-id=\"table-scroller\"\n >\n <table\n ref=\"table\"\n :class=\"ui.table()\"\n :style=\"viewportStyle\"\n :aria-busy=\"loading\"\n >\n <RuiTableHead\n v-if=\"!isMobile\"\n :loading=\"loading\"\n :indeterminate=\"indeterminate\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :disable-check-all=\"!filtered?.length\"\n :is-all-selected=\"isAllSelected\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n :stick=\"stick\"\n :sticky-header=\"stickyHeader\"\n @sort=\"onSort($event)\"\n @select:all=\"onToggleAll($event)\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiTableHead\n v-if=\"stickyHeader && !isMobile\"\n :loading=\"loading\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n data-id=\"head-clone\"\n class=\"opacity-0 invisible\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiDataTableBody\n :filtered=\"filtered\"\n :loading=\"loading\"\n :no-data=\"noData\"\n :empty=\"empty\"\n :error=\"error\"\n :error-title=\"errorTitle\"\n :retry-text=\"retryText\"\n @retry=\"emit('retry')\"\n >\n <template\n v-if=\"slots.error\"\n #error\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"error\" />\n </template>\n <template\n v-if=\"slots['body.prepend']\"\n #body.prepend=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.prepend\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header']\"\n #group.header=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header.content']\"\n #group.header.content=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header.content\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-for=\"key in itemSlotKeys\"\n #[`item.${key}`]=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n :name=\"`item.${key}`\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['expanded-item']\"\n #expanded-item=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"expanded-item\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['no-data']\"\n #no-data\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"no-data\" />\n </template>\n <template\n v-if=\"slots['empty-description']\"\n #empty-description\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"empty-description\" />\n </template>\n <template\n v-if=\"slots['body.append']\"\n #body.append=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.append\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiDataTableBody>\n <tfoot>\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"tfoot\" />\n </tfoot>\n </table>\n </div>\n <RuiTablePagination\n v-if=\"paginationData && showFooter\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n </div>\n</template>\n"],"mappings":""}
@@ -171,10 +171,7 @@ var RuiDataTable_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
171
171
  const stickyMobileToolbar = computed(() => __props.stickyHeader && get$1(isMobile) && !isNestedTable);
172
172
  const table = useTemplateRef("table");
173
173
  const tableScroller = useTemplateRef("tableScroller");
174
- const { stick } = useStickyTableHeader(() => __props.stickyHeader, stickyHeaderOffset, {
175
- table,
176
- tableScroller
177
- });
174
+ const { stick } = useStickyTableHeader(() => __props.stickyHeader, stickyHeaderOffset, { table });
178
175
  const { width: scrollerWidth } = useElementSize(tableScroller);
179
176
  /**
180
177
  * The visible width of the table, which an expanded row caps its content to.
@@ -1 +1 @@
1
- {"version":3,"file":"RuiDataTable.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/tables/RuiDataTable.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"T extends object, IdType extends keyof T = keyof T\">\nimport type { GroupHeader } from '@/composables/tables/data-table/types';\nimport { dataTableStyles } from '@/components/tables/data-table-styles';\nimport { type DataTableClasses, type DataTableGroupLabel, provideDataTableContext, provideDataTableNested, useDataTableNested } from '@/components/tables/data-table/context';\nimport RuiDataTableBody from '@/components/tables/data-table/RuiDataTableBody.vue';\nimport RuiDataTableMobileSort from '@/components/tables/data-table/RuiDataTableMobileSort.vue';\nimport RuiTableHead, {\n type GroupData,\n type TableColumn,\n type TableRowKeyData,\n type TableSortData,\n} from '@/components/tables/RuiTableHead.vue';\nimport RuiTablePagination, {\n type TablePaginationData,\n} from '@/components/tables/RuiTablePagination.vue';\nimport { GroupExpandButtonPosition } from '@/components/tables/table-props';\nimport { useTable } from '@/composables/defaults/table';\nimport { useStickyTableHeader } from '@/composables/sticky-header';\nimport { useTableColumns } from '@/composables/tables/data-table/columns';\nimport { useTableExpansion } from '@/composables/tables/data-table/expansion';\nimport { useTableGrouping } from '@/composables/tables/data-table/grouping';\nimport { useTablePagination } from '@/composables/tables/data-table/pagination';\nimport { useTableSelection } from '@/composables/tables/data-table/selection';\nimport { useTableSort } from '@/composables/tables/data-table/sort';\n\nexport type { GroupedTableRow, GroupHeader, TableOptions } from '@/composables/tables/data-table/types';\n\nexport interface Props<T, K extends keyof T> {\n /**\n * list of items for each row\n */\n rows: readonly T[];\n /**\n * the attribute used to identify each row uniquely for selection, usually `id`\n */\n rowAttr: K;\n /**\n * model for internal searching\n */\n search?: string;\n /**\n * model for items per page\n * will be used if the `pagination` model isn't specified\n */\n itemsPerPage?: number;\n /**\n * list of column definitions\n */\n cols?: TableColumn<T>[];\n /**\n * attribute to use from column definitions to display column titles\n */\n columnAttr?: keyof TableColumn<T>;\n /**\n * flag to show a more or less spacious table\n */\n dense?: boolean;\n /**\n * flag to outline the flag with a border\n */\n outlined?: boolean;\n /**\n * should add zebra-striping to the table row\n */\n striped?: boolean;\n /**\n * flag to show loading state of the table\n * triggers indefinite progress at the bottom of the table header\n */\n loading?: boolean;\n disablePerPage?: boolean;\n /**\n * Maximum number of pages before the jump-to-page dropdown is replaced with\n * a numeric input. Defaults to `500`. Forwarded to `RuiTablePagination`.\n */\n rangesThreshold?: number;\n /**\n * The text and icon shown when there are no rows, e.g.\n * `:empty=\"\\{ icon: 'transactions-line', label: 'No transactions found' \\}\"`\n */\n empty?: {\n label?: string;\n description?: string;\n };\n /**\n * Why the last read failed. Shown above any rows kept from an earlier read,\n * or in place of the empty state; `loading` still takes precedence.\n */\n error?: string;\n /** Heading for that failure. */\n errorTitle?: string;\n /** Label for the retry control; without one, no control is offered. */\n retryText?: string;\n /**\n * should hide the header navigation\n */\n hideDefaultHeader?: boolean;\n /**\n * should hide the footer navigation\n */\n hideDefaultFooter?: boolean;\n\n rounded?: 'sm' | 'md' | 'lg';\n /**\n * make expansion work like accordion\n */\n singleExpand?: boolean;\n /**\n * make table head stick to top on scroll\n */\n stickyHeader?: boolean;\n stickyOffset?: number;\n /**\n * Affects how the items per page work across the app.\n * When true, changing the items per page setting in one table will affect other tables.\n */\n globalItemsPerPage?: boolean;\n groupExpandButtonPosition?: GroupExpandButtonPosition;\n disabledRows?: readonly T[];\n multiPageSelect?: boolean;\n itemClass?: ((item: T) => string) | string;\n /**\n * Render each row as a stacked card instead of a table row once the measured\n * width is narrower than this width (in pixels). Columns flagged with\n * `mobileHidden` are dropped in this layout. Falls back to the global\n * `mobileBreakpoint` table default; leave both unset to keep the default table\n * layout at every width.\n */\n mobileBreakpoint?: number;\n /**\n * What the `mobileBreakpoint` is measured against: the browser `viewport`\n * (default) or this table's own `container` width. Use `container` when the\n * table lives in a narrow region of a wider window. Falls back to the global\n * `mobileBreakpointBasis` table default.\n */\n mobileBreakpointBasis?: 'viewport' | 'container';\n /**\n * Force the stacked mobile card layout on (`true`) or off (`false`),\n * regardless of `mobileBreakpoint`. Leave unset to derive it from the width.\n */\n mobile?: boolean;\n}\n\ndefineOptions({\n name: 'RuiDataTable',\n});\n\nconst selectedData = defineModel<T[IdType][]>();\n\nconst expanded = defineModel<T[]>('expanded');\n\nconst [pagination, paginationModifiers] = defineModel<TablePaginationData, 'external'>('pagination');\n\nconst [sort, sortModifiers] = defineModel<TableSortData<T>, 'external'>('sort');\n\nconst group = defineModel<TableRowKeyData<T>>('group');\n\nconst collapsed = defineModel<T[]>('collapsed');\n\nconst {\n rows,\n rowAttr,\n search = '',\n itemsPerPage = 10,\n cols,\n columnAttr = 'label',\n dense = false,\n outlined = false,\n striped = false,\n loading = false,\n disablePerPage = false,\n rangesThreshold = 500,\n empty = { label: 'No item found' },\n error = '',\n errorTitle = '',\n retryText = '',\n hideDefaultHeader = false,\n hideDefaultFooter = false,\n rounded = 'md',\n singleExpand = false,\n stickyHeader = false,\n stickyOffset,\n globalItemsPerPage = undefined,\n groupExpandButtonPosition = GroupExpandButtonPosition.start,\n disabledRows,\n multiPageSelect = false,\n itemClass = '',\n mobileBreakpoint,\n mobileBreakpointBasis,\n mobile,\n} = defineProps<Props<T, IdType>>();\n\nconst emit = defineEmits<{\n 'update:options': [value: { pagination?: TablePaginationData; sort?: TableSortData<T> }];\n 'copy:group': [value: GroupData<T>];\n 'retry': [];\n}>();\n\nconst slots = defineSlots<Partial<\n Record<`header.${string}`, (props: { column: TableColumn<T> }) => any> &\n Record<`item.${string}`, (props: { column: TableColumn<T>; row: T; index: number }) => any> & {\n 'body.prepend': (props: { colspan: number }) => any;\n 'body.append': (props: { colspan: number }) => any;\n 'group.header': (props: {\n colspan: number;\n header: GroupHeader<T>;\n isOpen: boolean;\n toggle: () => void;\n }) => any;\n 'group.header.content': (props: { header: GroupHeader<T>; groupKey: string }) => any;\n 'expanded-item': (props: { row: T; index: number }) => any;\n 'no-data': () => any;\n 'empty-description': () => any;\n 'error': () => any;\n 'tfoot': () => any;\n }\n>>();\n\nconst tableDefaults = useTable();\n\nconst tableWrapper = useTemplateRef<HTMLElement>('tableWrapper');\nconst { width: windowWidth } = useWindowSize();\nconst { width: containerWidth } = useElementSize(tableWrapper);\n\n// Per-table props win, then the global table defaults.\nconst resolvedMobileBreakpoint = computed<number | undefined>(() => {\n if (mobileBreakpoint !== undefined)\n return mobileBreakpoint;\n const globalBreakpoint = tableDefaults.mobileBreakpoint;\n return globalBreakpoint === undefined ? undefined : get(globalBreakpoint);\n});\nconst resolvedMobileBasis = computed<'viewport' | 'container'>(() => {\n if (mobileBreakpointBasis !== undefined)\n return mobileBreakpointBasis;\n const globalBasis = tableDefaults.mobileBreakpointBasis;\n return globalBasis === undefined ? 'viewport' : get(globalBasis);\n});\n\n/**\n * Whether the stacked card layout is showing.\n *\n * Vue coerces an absent Boolean prop to `false`, so `mobile` cannot say\n * \"unset\": with no breakpoint configured it is the manual switch, defaulting\n * to desktop, and with one configured the breakpoint drives the layout while\n * `mobile === true` can still force the stacked layout on.\n */\nconst isMobile = computed<boolean>(() => {\n const breakpoint = get(resolvedMobileBreakpoint);\n if (breakpoint === undefined)\n return mobile;\n if (mobile)\n return true;\n // `containerWidth` is 0 until measured; the viewport keeps the first paint off mobile\n const width = get(resolvedMobileBasis) === 'container'\n ? (get(containerWidth) || get(windowWidth))\n : get(windowWidth);\n return width < breakpoint;\n});\n\nconst stickyHeaderOffset = computed<number | undefined>(() =>\n stickyOffset !== undefined ? stickyOffset : get(tableDefaults.stickyOffset),\n);\n\n/**\n * A table rendered inside an expanded row must not pin its own toolbar: only\n * the outermost table sticks, otherwise the bars stack and collide.\n */\nconst isNestedTable = useDataTableNested();\nprovideDataTableNested();\n\n/**\n * On mobile there is no column header to pin, so `stickyHeader` pins the\n * pagination and sort toolbar instead while the card list scrolls.\n */\nconst stickyMobileToolbar = computed<boolean>(() => stickyHeader && get(isMobile) && !isNestedTable);\n\nconst table = useTemplateRef<HTMLTableElement>('table');\nconst tableScroller = useTemplateRef<HTMLElement>('tableScroller');\n\nconst { stick } = useStickyTableHeader(\n () => stickyHeader,\n stickyHeaderOffset,\n { table, tableScroller },\n);\n\nconst { width: scrollerWidth } = useElementSize(tableScroller);\n\n/**\n * The visible width of the table, which an expanded row caps its content to.\n * Without the cap a wide panel, such as a nested table, sets the minimum width\n * of the whole table and pushes every column past the scroller. `initial`\n * resets the variable, so a nested table never inherits its parent's width.\n */\nconst viewportStyle = computed<Record<string, string>>(() => {\n const width = get(scrollerWidth);\n return { '--rui-table-viewport': width > 0 && !get(isMobile) ? `${width}px` : 'initial' };\n});\n\nconst { expandable, isExpanded, onToggleExpand } = useTableExpansion<T, IdType>(\n { rowAttr, singleExpand },\n { expanded },\n);\n\nfunction emitUpdateOptions(opts: { sort?: TableSortData<T>; pagination?: TablePaginationData }): void {\n emit('update:options', opts);\n}\n\nconst {\n sortData,\n sortedMap,\n sorted,\n onSort: applySort,\n} = useTableSort<T>(\n { rows: () => rows, search: () => search, sortModifiersExternal: sortModifiers.external },\n {\n sort,\n pagination,\n emitUpdateOptions,\n },\n);\n\nconst {\n groupKeys,\n groupKey,\n grouped,\n mappedGroups,\n isExpandedGroup,\n isHiddenRow,\n onToggleExpandGroup,\n onUngroup,\n} = useTableGrouping<T, IdType>(\n { rowAttr },\n {\n group,\n collapsed,\n sorted,\n },\n);\n\nconst {\n paginationData,\n filtered,\n setInternalTotal,\n resetPagination,\n} = useTablePagination<T>(\n {\n itemsPerPage,\n paginationModifiersExternal: paginationModifiers.external,\n globalItemsPerPage,\n },\n {\n pagination,\n grouped,\n isHiddenRow,\n sort,\n emitUpdateOptions,\n tableDefaults,\n },\n);\n\nconst noData = computed<boolean>(() => get(filtered).length === 0);\n\n/**\n * With the header bar showing, an empty table drops its footer bar: two rows\n * of disabled controls around an empty state only add noise.\n */\nconst showFooter = computed<boolean>(() => {\n const data = get(paginationData);\n if (!data || hideDefaultFooter)\n return false;\n return data.total > 0 || hideDefaultHeader;\n});\n\n/**\n * Grouped columns leave the column list, so their labels come from `cols`.\n */\nconst groupLabels = computed<DataTableGroupLabel[]>(() => get(groupKeys).map((key) => {\n const label = cols?.find(column => column.key === key)?.[columnAttr];\n return {\n key: String(key),\n label: label === undefined || label === null ? String(key) : String(label),\n };\n}));\n\n/** Each group's list starts with its header entry, which is not a row. */\nfunction getGroupSize(identifier: string): number {\n return Math.max((get(mappedGroups)[identifier]?.length ?? 0) - 1, 0);\n}\n\nconst ui = computed<ReturnType<typeof dataTableStyles>>(() => dataTableStyles({\n outlined,\n rounded,\n dense,\n striped,\n mobile: get(isMobile),\n}));\n\nconst { columns, colspan, headerSlots, cellValue } = useTableColumns<T, IdType>({\n cols: () => cols,\n columnAttr,\n rows: () => rows,\n expandable,\n groupKeys,\n selectedData,\n slots,\n isMobile,\n tdResolver: options => get(ui).td(options),\n});\n\nconst showMobileSort = computed<boolean>(() =>\n get(isMobile) && !!get(sortData) && get(columns).some(column => column.sortable),\n);\n\nconst ITEM_SLOT_PREFIX = 'item.';\n\n// Slot keys are static for the component lifetime — no reactivity needed\nconst itemSlotKeys: Set<string> = new Set(\n Object.keys(slots)\n .filter((key): key is `item.${string}` => key.startsWith(ITEM_SLOT_PREFIX))\n .map(key => key.slice(ITEM_SLOT_PREFIX.length)),\n);\n\nconst {\n isAllSelected,\n indeterminate,\n isSelected,\n isDisabledRow,\n onToggleAll,\n onSelect,\n onCheckboxClick,\n deselectRemovedRows,\n resetCheckboxShiftState,\n} = useTableSelection<T, IdType>(\n { rowAttr, multiPageSelect, disabledRows: () => disabledRows },\n { selectedData, filtered },\n);\n\n/**\n * Sorting clears the selection. That lives here rather than in either\n * composable, which would make sort and selection depend on each other.\n */\nfunction onSort(payload: Parameters<typeof applySort>[0]): void {\n applySort(payload);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\nfunction onPaginate(): void {\n if ((get(expanded)?.length ?? 0) > 0)\n set(expanded, []);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\n// Reset pagination page to 1 on search query change\nwatch(() => search, () => {\n resetPagination();\n onToggleAll(false);\n resetCheckboxShiftState();\n});\n\nwatch(sorted, (items) => {\n if (!multiPageSelect)\n deselectRemovedRows();\n setInternalTotal(items);\n});\n\nonMounted(() => {\n setInternalTotal(get(sorted));\n});\n\nconst classes = computed<DataTableClasses>(() => {\n const s = get(ui);\n return {\n td: s.td(),\n tr: s.tr(),\n trSelected: s.tr({ rowVariant: 'selected' }),\n trExpandable: s.tr({ rowVariant: 'expandable' }),\n trExpandedParent: s.tr({ rowVariant: 'expandedParent' }),\n trGroup: s.tr({ rowVariant: 'group' }),\n trEmpty: s.tr({ rowVariant: 'empty' }),\n checkbox: s.checkbox(),\n tbody: s.tbody(),\n tbodyLoader: s.tbodyLoader(),\n };\n});\n\nprovideDataTableContext<T, IdType>({\n // Reactive (ComputedRef/Ref) — destructuring preserves reactivity via Vue template auto-unwrap\n classes,\n columns,\n colspan,\n expandable,\n groupKey,\n groupLabels,\n isMobile,\n selectedData,\n // Static values\n cellValue,\n columnAttr,\n dense,\n getGroupSize,\n getRowId: (row: T) => row[rowAttr],\n itemSlotKeys,\n groupExpandButtonPosition,\n isDisabledRow,\n isExpanded,\n isExpandedGroup,\n isSelected,\n itemClass,\n onCheckboxClick,\n onCopyGroup: (header: GroupHeader<T>) => emit('copy:group', { key: get(groupKey) ?? '', value: header.group }),\n onSelect,\n onToggleExpand,\n onToggleExpandGroup,\n onUngroup,\n});\n</script>\n\n<template>\n <div\n ref=\"tableWrapper\"\n :class=\"ui.wrapper()\"\n data-id=\"table-wrapper\"\n >\n <div\n :class=\"stickyMobileToolbar ? 'sticky z-10 bg-white dark:bg-[#121212] pt-2' : 'contents'\"\n :style=\"stickyMobileToolbar ? { top: `${stickyHeaderOffset ?? 0}px` } : undefined\"\n data-id=\"table-mobile-toolbar-sticky\"\n >\n <RuiTablePagination\n v-if=\"paginationData && !hideDefaultHeader\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n <div\n v-if=\"showMobileSort\"\n class=\"flex justify-end p-2\"\n data-id=\"table-mobile-toolbar\"\n >\n <RuiDataTableMobileSort\n :columns=\"columns\"\n :column-attr=\"columnAttr\"\n :sorted-map=\"sortedMap\"\n :dense=\"dense\"\n @sort=\"onSort($event)\"\n />\n </div>\n </div>\n <div\n ref=\"tableScroller\"\n :class=\"[ui.scroller(), isMobile && !showMobileSort ? 'pt-2' : '']\"\n data-id=\"table-scroller\"\n >\n <table\n ref=\"table\"\n :class=\"ui.table()\"\n :style=\"viewportStyle\"\n :aria-busy=\"loading\"\n >\n <RuiTableHead\n v-if=\"!isMobile\"\n :loading=\"loading\"\n :indeterminate=\"indeterminate\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :disable-check-all=\"!filtered?.length\"\n :is-all-selected=\"isAllSelected\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n :stick=\"stick\"\n :sticky-header=\"stickyHeader\"\n @sort=\"onSort($event)\"\n @select:all=\"onToggleAll($event)\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiTableHead\n v-if=\"stickyHeader && !isMobile\"\n :loading=\"loading\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n data-id=\"head-clone\"\n class=\"opacity-0 invisible\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiDataTableBody\n :filtered=\"filtered\"\n :loading=\"loading\"\n :no-data=\"noData\"\n :empty=\"empty\"\n :error=\"error\"\n :error-title=\"errorTitle\"\n :retry-text=\"retryText\"\n @retry=\"emit('retry')\"\n >\n <template\n v-if=\"slots.error\"\n #error\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"error\" />\n </template>\n <template\n v-if=\"slots['body.prepend']\"\n #body.prepend=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.prepend\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header']\"\n #group.header=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header.content']\"\n #group.header.content=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header.content\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-for=\"key in itemSlotKeys\"\n #[`item.${key}`]=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n :name=\"`item.${key}`\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['expanded-item']\"\n #expanded-item=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"expanded-item\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['no-data']\"\n #no-data\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"no-data\" />\n </template>\n <template\n v-if=\"slots['empty-description']\"\n #empty-description\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"empty-description\" />\n </template>\n <template\n v-if=\"slots['body.append']\"\n #body.append=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.append\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiDataTableBody>\n <tfoot>\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"tfoot\" />\n </tfoot>\n </table>\n </div>\n <RuiTablePagination\n v-if=\"paginationData && showFooter\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"RuiDataTable.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/tables/RuiDataTable.vue"],"sourcesContent":["<script lang=\"ts\" setup generic=\"T extends object, IdType extends keyof T = keyof T\">\nimport type { GroupHeader } from '@/composables/tables/data-table/types';\nimport { dataTableStyles } from '@/components/tables/data-table-styles';\nimport { type DataTableClasses, type DataTableGroupLabel, provideDataTableContext, provideDataTableNested, useDataTableNested } from '@/components/tables/data-table/context';\nimport RuiDataTableBody from '@/components/tables/data-table/RuiDataTableBody.vue';\nimport RuiDataTableMobileSort from '@/components/tables/data-table/RuiDataTableMobileSort.vue';\nimport RuiTableHead, {\n type GroupData,\n type TableColumn,\n type TableRowKeyData,\n type TableSortData,\n} from '@/components/tables/RuiTableHead.vue';\nimport RuiTablePagination, {\n type TablePaginationData,\n} from '@/components/tables/RuiTablePagination.vue';\nimport { GroupExpandButtonPosition } from '@/components/tables/table-props';\nimport { useTable } from '@/composables/defaults/table';\nimport { useStickyTableHeader } from '@/composables/sticky-header';\nimport { useTableColumns } from '@/composables/tables/data-table/columns';\nimport { useTableExpansion } from '@/composables/tables/data-table/expansion';\nimport { useTableGrouping } from '@/composables/tables/data-table/grouping';\nimport { useTablePagination } from '@/composables/tables/data-table/pagination';\nimport { useTableSelection } from '@/composables/tables/data-table/selection';\nimport { useTableSort } from '@/composables/tables/data-table/sort';\n\nexport type { GroupedTableRow, GroupHeader, TableOptions } from '@/composables/tables/data-table/types';\n\nexport interface Props<T, K extends keyof T> {\n /**\n * list of items for each row\n */\n rows: readonly T[];\n /**\n * the attribute used to identify each row uniquely for selection, usually `id`\n */\n rowAttr: K;\n /**\n * model for internal searching\n */\n search?: string;\n /**\n * model for items per page\n * will be used if the `pagination` model isn't specified\n */\n itemsPerPage?: number;\n /**\n * list of column definitions\n */\n cols?: TableColumn<T>[];\n /**\n * attribute to use from column definitions to display column titles\n */\n columnAttr?: keyof TableColumn<T>;\n /**\n * flag to show a more or less spacious table\n */\n dense?: boolean;\n /**\n * flag to outline the flag with a border\n */\n outlined?: boolean;\n /**\n * should add zebra-striping to the table row\n */\n striped?: boolean;\n /**\n * flag to show loading state of the table\n * triggers indefinite progress at the bottom of the table header\n */\n loading?: boolean;\n disablePerPage?: boolean;\n /**\n * Maximum number of pages before the jump-to-page dropdown is replaced with\n * a numeric input. Defaults to `500`. Forwarded to `RuiTablePagination`.\n */\n rangesThreshold?: number;\n /**\n * The text and icon shown when there are no rows, e.g.\n * `:empty=\"\\{ icon: 'transactions-line', label: 'No transactions found' \\}\"`\n */\n empty?: {\n label?: string;\n description?: string;\n };\n /**\n * Why the last read failed. Shown above any rows kept from an earlier read,\n * or in place of the empty state; `loading` still takes precedence.\n */\n error?: string;\n /** Heading for that failure. */\n errorTitle?: string;\n /** Label for the retry control; without one, no control is offered. */\n retryText?: string;\n /**\n * should hide the header navigation\n */\n hideDefaultHeader?: boolean;\n /**\n * should hide the footer navigation\n */\n hideDefaultFooter?: boolean;\n\n rounded?: 'sm' | 'md' | 'lg';\n /**\n * make expansion work like accordion\n */\n singleExpand?: boolean;\n /**\n * make table head stick to top on scroll\n */\n stickyHeader?: boolean;\n stickyOffset?: number;\n /**\n * Affects how the items per page work across the app.\n * When true, changing the items per page setting in one table will affect other tables.\n */\n globalItemsPerPage?: boolean;\n groupExpandButtonPosition?: GroupExpandButtonPosition;\n disabledRows?: readonly T[];\n multiPageSelect?: boolean;\n itemClass?: ((item: T) => string) | string;\n /**\n * Render each row as a stacked card instead of a table row once the measured\n * width is narrower than this width (in pixels). Columns flagged with\n * `mobileHidden` are dropped in this layout. Falls back to the global\n * `mobileBreakpoint` table default; leave both unset to keep the default table\n * layout at every width.\n */\n mobileBreakpoint?: number;\n /**\n * What the `mobileBreakpoint` is measured against: the browser `viewport`\n * (default) or this table's own `container` width. Use `container` when the\n * table lives in a narrow region of a wider window. Falls back to the global\n * `mobileBreakpointBasis` table default.\n */\n mobileBreakpointBasis?: 'viewport' | 'container';\n /**\n * Force the stacked mobile card layout on (`true`) or off (`false`),\n * regardless of `mobileBreakpoint`. Leave unset to derive it from the width.\n */\n mobile?: boolean;\n}\n\ndefineOptions({\n name: 'RuiDataTable',\n});\n\nconst selectedData = defineModel<T[IdType][]>();\n\nconst expanded = defineModel<T[]>('expanded');\n\nconst [pagination, paginationModifiers] = defineModel<TablePaginationData, 'external'>('pagination');\n\nconst [sort, sortModifiers] = defineModel<TableSortData<T>, 'external'>('sort');\n\nconst group = defineModel<TableRowKeyData<T>>('group');\n\nconst collapsed = defineModel<T[]>('collapsed');\n\nconst {\n rows,\n rowAttr,\n search = '',\n itemsPerPage = 10,\n cols,\n columnAttr = 'label',\n dense = false,\n outlined = false,\n striped = false,\n loading = false,\n disablePerPage = false,\n rangesThreshold = 500,\n empty = { label: 'No item found' },\n error = '',\n errorTitle = '',\n retryText = '',\n hideDefaultHeader = false,\n hideDefaultFooter = false,\n rounded = 'md',\n singleExpand = false,\n stickyHeader = false,\n stickyOffset,\n globalItemsPerPage = undefined,\n groupExpandButtonPosition = GroupExpandButtonPosition.start,\n disabledRows,\n multiPageSelect = false,\n itemClass = '',\n mobileBreakpoint,\n mobileBreakpointBasis,\n mobile,\n} = defineProps<Props<T, IdType>>();\n\nconst emit = defineEmits<{\n 'update:options': [value: { pagination?: TablePaginationData; sort?: TableSortData<T> }];\n 'copy:group': [value: GroupData<T>];\n 'retry': [];\n}>();\n\nconst slots = defineSlots<Partial<\n Record<`header.${string}`, (props: { column: TableColumn<T> }) => any> &\n Record<`item.${string}`, (props: { column: TableColumn<T>; row: T; index: number }) => any> & {\n 'body.prepend': (props: { colspan: number }) => any;\n 'body.append': (props: { colspan: number }) => any;\n 'group.header': (props: {\n colspan: number;\n header: GroupHeader<T>;\n isOpen: boolean;\n toggle: () => void;\n }) => any;\n 'group.header.content': (props: { header: GroupHeader<T>; groupKey: string }) => any;\n 'expanded-item': (props: { row: T; index: number }) => any;\n 'no-data': () => any;\n 'empty-description': () => any;\n 'error': () => any;\n 'tfoot': () => any;\n }\n>>();\n\nconst tableDefaults = useTable();\n\nconst tableWrapper = useTemplateRef<HTMLElement>('tableWrapper');\nconst { width: windowWidth } = useWindowSize();\nconst { width: containerWidth } = useElementSize(tableWrapper);\n\n// Per-table props win, then the global table defaults.\nconst resolvedMobileBreakpoint = computed<number | undefined>(() => {\n if (mobileBreakpoint !== undefined)\n return mobileBreakpoint;\n const globalBreakpoint = tableDefaults.mobileBreakpoint;\n return globalBreakpoint === undefined ? undefined : get(globalBreakpoint);\n});\nconst resolvedMobileBasis = computed<'viewport' | 'container'>(() => {\n if (mobileBreakpointBasis !== undefined)\n return mobileBreakpointBasis;\n const globalBasis = tableDefaults.mobileBreakpointBasis;\n return globalBasis === undefined ? 'viewport' : get(globalBasis);\n});\n\n/**\n * Whether the stacked card layout is showing.\n *\n * Vue coerces an absent Boolean prop to `false`, so `mobile` cannot say\n * \"unset\": with no breakpoint configured it is the manual switch, defaulting\n * to desktop, and with one configured the breakpoint drives the layout while\n * `mobile === true` can still force the stacked layout on.\n */\nconst isMobile = computed<boolean>(() => {\n const breakpoint = get(resolvedMobileBreakpoint);\n if (breakpoint === undefined)\n return mobile;\n if (mobile)\n return true;\n // `containerWidth` is 0 until measured; the viewport keeps the first paint off mobile\n const width = get(resolvedMobileBasis) === 'container'\n ? (get(containerWidth) || get(windowWidth))\n : get(windowWidth);\n return width < breakpoint;\n});\n\nconst stickyHeaderOffset = computed<number | undefined>(() =>\n stickyOffset !== undefined ? stickyOffset : get(tableDefaults.stickyOffset),\n);\n\n/**\n * A table rendered inside an expanded row must not pin its own toolbar: only\n * the outermost table sticks, otherwise the bars stack and collide.\n */\nconst isNestedTable = useDataTableNested();\nprovideDataTableNested();\n\n/**\n * On mobile there is no column header to pin, so `stickyHeader` pins the\n * pagination and sort toolbar instead while the card list scrolls.\n */\nconst stickyMobileToolbar = computed<boolean>(() => stickyHeader && get(isMobile) && !isNestedTable);\n\nconst table = useTemplateRef<HTMLTableElement>('table');\nconst tableScroller = useTemplateRef<HTMLElement>('tableScroller');\n\nconst { stick } = useStickyTableHeader(\n () => stickyHeader,\n stickyHeaderOffset,\n { table },\n);\n\nconst { width: scrollerWidth } = useElementSize(tableScroller);\n\n/**\n * The visible width of the table, which an expanded row caps its content to.\n * Without the cap a wide panel, such as a nested table, sets the minimum width\n * of the whole table and pushes every column past the scroller. `initial`\n * resets the variable, so a nested table never inherits its parent's width.\n */\nconst viewportStyle = computed<Record<string, string>>(() => {\n const width = get(scrollerWidth);\n return { '--rui-table-viewport': width > 0 && !get(isMobile) ? `${width}px` : 'initial' };\n});\n\nconst { expandable, isExpanded, onToggleExpand } = useTableExpansion<T, IdType>(\n { rowAttr, singleExpand },\n { expanded },\n);\n\nfunction emitUpdateOptions(opts: { sort?: TableSortData<T>; pagination?: TablePaginationData }): void {\n emit('update:options', opts);\n}\n\nconst {\n sortData,\n sortedMap,\n sorted,\n onSort: applySort,\n} = useTableSort<T>(\n { rows: () => rows, search: () => search, sortModifiersExternal: sortModifiers.external },\n {\n sort,\n pagination,\n emitUpdateOptions,\n },\n);\n\nconst {\n groupKeys,\n groupKey,\n grouped,\n mappedGroups,\n isExpandedGroup,\n isHiddenRow,\n onToggleExpandGroup,\n onUngroup,\n} = useTableGrouping<T, IdType>(\n { rowAttr },\n {\n group,\n collapsed,\n sorted,\n },\n);\n\nconst {\n paginationData,\n filtered,\n setInternalTotal,\n resetPagination,\n} = useTablePagination<T>(\n {\n itemsPerPage,\n paginationModifiersExternal: paginationModifiers.external,\n globalItemsPerPage,\n },\n {\n pagination,\n grouped,\n isHiddenRow,\n sort,\n emitUpdateOptions,\n tableDefaults,\n },\n);\n\nconst noData = computed<boolean>(() => get(filtered).length === 0);\n\n/**\n * With the header bar showing, an empty table drops its footer bar: two rows\n * of disabled controls around an empty state only add noise.\n */\nconst showFooter = computed<boolean>(() => {\n const data = get(paginationData);\n if (!data || hideDefaultFooter)\n return false;\n return data.total > 0 || hideDefaultHeader;\n});\n\n/**\n * Grouped columns leave the column list, so their labels come from `cols`.\n */\nconst groupLabels = computed<DataTableGroupLabel[]>(() => get(groupKeys).map((key) => {\n const label = cols?.find(column => column.key === key)?.[columnAttr];\n return {\n key: String(key),\n label: label === undefined || label === null ? String(key) : String(label),\n };\n}));\n\n/** Each group's list starts with its header entry, which is not a row. */\nfunction getGroupSize(identifier: string): number {\n return Math.max((get(mappedGroups)[identifier]?.length ?? 0) - 1, 0);\n}\n\nconst ui = computed<ReturnType<typeof dataTableStyles>>(() => dataTableStyles({\n outlined,\n rounded,\n dense,\n striped,\n mobile: get(isMobile),\n}));\n\nconst { columns, colspan, headerSlots, cellValue } = useTableColumns<T, IdType>({\n cols: () => cols,\n columnAttr,\n rows: () => rows,\n expandable,\n groupKeys,\n selectedData,\n slots,\n isMobile,\n tdResolver: options => get(ui).td(options),\n});\n\nconst showMobileSort = computed<boolean>(() =>\n get(isMobile) && !!get(sortData) && get(columns).some(column => column.sortable),\n);\n\nconst ITEM_SLOT_PREFIX = 'item.';\n\n// Slot keys are static for the component lifetime — no reactivity needed\nconst itemSlotKeys: Set<string> = new Set(\n Object.keys(slots)\n .filter((key): key is `item.${string}` => key.startsWith(ITEM_SLOT_PREFIX))\n .map(key => key.slice(ITEM_SLOT_PREFIX.length)),\n);\n\nconst {\n isAllSelected,\n indeterminate,\n isSelected,\n isDisabledRow,\n onToggleAll,\n onSelect,\n onCheckboxClick,\n deselectRemovedRows,\n resetCheckboxShiftState,\n} = useTableSelection<T, IdType>(\n { rowAttr, multiPageSelect, disabledRows: () => disabledRows },\n { selectedData, filtered },\n);\n\n/**\n * Sorting clears the selection. That lives here rather than in either\n * composable, which would make sort and selection depend on each other.\n */\nfunction onSort(payload: Parameters<typeof applySort>[0]): void {\n applySort(payload);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\nfunction onPaginate(): void {\n if ((get(expanded)?.length ?? 0) > 0)\n set(expanded, []);\n if (!multiPageSelect)\n onToggleAll(false);\n resetCheckboxShiftState();\n}\n\n// Reset pagination page to 1 on search query change\nwatch(() => search, () => {\n resetPagination();\n onToggleAll(false);\n resetCheckboxShiftState();\n});\n\nwatch(sorted, (items) => {\n if (!multiPageSelect)\n deselectRemovedRows();\n setInternalTotal(items);\n});\n\nonMounted(() => {\n setInternalTotal(get(sorted));\n});\n\nconst classes = computed<DataTableClasses>(() => {\n const s = get(ui);\n return {\n td: s.td(),\n tr: s.tr(),\n trSelected: s.tr({ rowVariant: 'selected' }),\n trExpandable: s.tr({ rowVariant: 'expandable' }),\n trExpandedParent: s.tr({ rowVariant: 'expandedParent' }),\n trGroup: s.tr({ rowVariant: 'group' }),\n trEmpty: s.tr({ rowVariant: 'empty' }),\n checkbox: s.checkbox(),\n tbody: s.tbody(),\n tbodyLoader: s.tbodyLoader(),\n };\n});\n\nprovideDataTableContext<T, IdType>({\n // Reactive (ComputedRef/Ref) — destructuring preserves reactivity via Vue template auto-unwrap\n classes,\n columns,\n colspan,\n expandable,\n groupKey,\n groupLabels,\n isMobile,\n selectedData,\n // Static values\n cellValue,\n columnAttr,\n dense,\n getGroupSize,\n getRowId: (row: T) => row[rowAttr],\n itemSlotKeys,\n groupExpandButtonPosition,\n isDisabledRow,\n isExpanded,\n isExpandedGroup,\n isSelected,\n itemClass,\n onCheckboxClick,\n onCopyGroup: (header: GroupHeader<T>) => emit('copy:group', { key: get(groupKey) ?? '', value: header.group }),\n onSelect,\n onToggleExpand,\n onToggleExpandGroup,\n onUngroup,\n});\n</script>\n\n<template>\n <div\n ref=\"tableWrapper\"\n :class=\"ui.wrapper()\"\n data-id=\"table-wrapper\"\n >\n <div\n :class=\"stickyMobileToolbar ? 'sticky z-10 bg-white dark:bg-[#121212] pt-2' : 'contents'\"\n :style=\"stickyMobileToolbar ? { top: `${stickyHeaderOffset ?? 0}px` } : undefined\"\n data-id=\"table-mobile-toolbar-sticky\"\n >\n <RuiTablePagination\n v-if=\"paginationData && !hideDefaultHeader\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n <div\n v-if=\"showMobileSort\"\n class=\"flex justify-end p-2\"\n data-id=\"table-mobile-toolbar\"\n >\n <RuiDataTableMobileSort\n :columns=\"columns\"\n :column-attr=\"columnAttr\"\n :sorted-map=\"sortedMap\"\n :dense=\"dense\"\n @sort=\"onSort($event)\"\n />\n </div>\n </div>\n <div\n ref=\"tableScroller\"\n :class=\"[ui.scroller(), isMobile && !showMobileSort ? 'pt-2' : '']\"\n data-id=\"table-scroller\"\n >\n <table\n ref=\"table\"\n :class=\"ui.table()\"\n :style=\"viewportStyle\"\n :aria-busy=\"loading\"\n >\n <RuiTableHead\n v-if=\"!isMobile\"\n :loading=\"loading\"\n :indeterminate=\"indeterminate\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :disable-check-all=\"!filtered?.length\"\n :is-all-selected=\"isAllSelected\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n :stick=\"stick\"\n :sticky-header=\"stickyHeader\"\n @sort=\"onSort($event)\"\n @select:all=\"onToggleAll($event)\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiTableHead\n v-if=\"stickyHeader && !isMobile\"\n :loading=\"loading\"\n :capitalize-headers=\"!cols\"\n :colspan=\"colspan\"\n :column-attr=\"columnAttr\"\n :columns=\"columns\"\n :dense=\"dense\"\n :selectable=\"!!selectedData\"\n :sort-data=\"sortData\"\n :sorted-map=\"sortedMap\"\n data-id=\"head-clone\"\n class=\"opacity-0 invisible\"\n >\n <template\n v-for=\"headerSlot in headerSlots\"\n #[headerSlot]=\"slotData\"\n >\n <slot\n :name=\"headerSlot\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiTableHead>\n <RuiDataTableBody\n :filtered=\"filtered\"\n :loading=\"loading\"\n :no-data=\"noData\"\n :empty=\"empty\"\n :error=\"error\"\n :error-title=\"errorTitle\"\n :retry-text=\"retryText\"\n @retry=\"emit('retry')\"\n >\n <template\n v-if=\"slots.error\"\n #error\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"error\" />\n </template>\n <template\n v-if=\"slots['body.prepend']\"\n #body.prepend=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.prepend\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header']\"\n #group.header=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['group.header.content']\"\n #group.header.content=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"group.header.content\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-for=\"key in itemSlotKeys\"\n #[`item.${key}`]=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n :name=\"`item.${key}`\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['expanded-item']\"\n #expanded-item=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"expanded-item\"\n v-bind=\"slotData\"\n />\n </template>\n <template\n v-if=\"slots['no-data']\"\n #no-data\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"no-data\" />\n </template>\n <template\n v-if=\"slots['empty-description']\"\n #empty-description\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"empty-description\" />\n </template>\n <template\n v-if=\"slots['body.append']\"\n #body.append=\"slotData\"\n >\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot\n name=\"body.append\"\n v-bind=\"slotData\"\n />\n </template>\n </RuiDataTableBody>\n <tfoot>\n <!-- eslint-disable-next-line vue/require-explicit-slots -- defined via Partial<Record<...>> in defineSlots -->\n <slot name=\"tfoot\" />\n </tfoot>\n </table>\n </div>\n <RuiTablePagination\n v-if=\"paginationData && showFooter\"\n v-model=\"paginationData\"\n :dense=\"dense\"\n :loading=\"loading\"\n :mobile=\"isMobile\"\n :disable-per-page=\"disablePerPage\"\n :ranges-threshold=\"rangesThreshold\"\n data-id=\"table-pagination\"\n @update:model-value=\"onPaginate()\"\n />\n </div>\n</template>\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { tv } from "../../utils/tv.js";
2
2
  //#region src/components/tables/data-table-styles.ts
3
3
  var dataTableStyles = tv({
4
4
  slots: {
5
- wrapper: "relative divide-y divide-black/[0.12] dark:divide-white/[0.12] overflow-hidden",
5
+ wrapper: "relative divide-y divide-black/[0.12] dark:divide-white/[0.12] overflow-clip",
6
6
  scroller: "overflow-x-auto overflow-y-hidden [clip-path:inset(0_0_0_0)]",
7
7
  table: "min-w-full table-fixed divide-y divide-black/[0.12] dark:divide-white/[0.12] whitespace-nowrap mx-auto my-0 max-w-fit relative border-black/[0.12] dark:border-white/[0.12]",
8
8
  tbody: "divide-y divide-black/[0.12] dark:divide-white/[0.12]",
@@ -1 +1 @@
1
- {"version":3,"file":"data-table-styles.js","names":[],"sources":["../../../src/components/tables/data-table-styles.ts"],"sourcesContent":["import { tv } from '@/utils/tv';\n\nexport const dataTableStyles = tv({\n slots: {\n wrapper: 'relative divide-y divide-black/[0.12] dark:divide-white/[0.12] overflow-hidden',\n scroller: 'overflow-x-auto overflow-y-hidden [clip-path:inset(0_0_0_0)]',\n table: 'min-w-full table-fixed divide-y divide-black/[0.12] dark:divide-white/[0.12] whitespace-nowrap mx-auto my-0 max-w-fit relative border-black/[0.12] dark:border-white/[0.12]',\n tbody: 'divide-y divide-black/[0.12] dark:divide-white/[0.12]',\n tr: 'hover:bg-black/[0.04] dark:hover:bg-white/[0.04]',\n td: '[:where(&)]:px-4 text-rui-text text-body-2 tabular-nums [text-wrap:initial]',\n checkbox: 'px-2 w-[3.625rem] max-w-[3.625rem] [&_label]:ml-0',\n tbodyLoader: 'text-center',\n },\n variants: {\n outlined: {\n true: { wrapper: 'border border-black/[0.12] dark:border-white/[0.12]' },\n },\n rounded: {\n sm: { wrapper: 'rounded-[.25rem]' },\n md: { wrapper: 'rounded-[.75rem]' },\n lg: { wrapper: 'rounded-[1rem]' },\n },\n dense: {\n true: {},\n false: {},\n },\n // `:where()` keeps the stripe below every row state (selected, hover, expanded, group)\n striped: {\n true: { tbody: '[:where(&>tr:nth-child(even))]:bg-rui-grey-50 dark:[:where(&>tr:nth-child(even))]:bg-rui-grey-900' },\n },\n rowVariant: {\n selected: { tr: 'bg-rui-primary/[0.08] dark:bg-rui-dark-primary/[0.08]' },\n empty: { tr: 'hover:bg-transparent' },\n // `!border-t-0` drops the divider so the panel joins the row that opened it\n expandable: { tr: 'bg-rui-grey-50 hover:bg-rui-grey-50 dark:bg-white/[0.03] dark:hover:bg-white/[0.03] !border-t-0' },\n expandedParent: { tr: 'bg-rui-grey-50 dark:bg-white/[0.03]' },\n group: { tr: 'bg-rui-grey-100 dark:bg-white/[0.05]' },\n },\n mobile: {\n true: {\n wrapper: 'divide-y-0 border-0 rounded-none overflow-visible',\n scroller: 'overflow-visible [clip-path:none]',\n table: 'block min-w-0 max-w-none w-full whitespace-normal divide-y-0 border-0',\n tbody: 'block divide-y-0',\n tr: 'block',\n td: 'block px-4 py-2',\n },\n },\n },\n /**\n * A fixed row height keeps a row of plain text as tall as one holding an\n * icon button. Padding and floor sit in `:where()` so a consumer's `cellClass`\n * (`py-0` is common) still wins at any stylesheet order, and each density\n * sets its own because the class merger cannot dedupe these.\n */\n compoundVariants: [\n { dense: false, mobile: false, class: { td: '[:where(&)]:py-3 [:where(&)]:h-[3.25rem]' } },\n { dense: true, mobile: false, class: { td: '[:where(&)]:py-1 [:where(&)]:h-9' } },\n ],\n defaultVariants: {\n dense: false,\n mobile: false,\n },\n});\n"],"mappings":";;AAEA,IAAa,kBAAkB,GAAG;CAChC,OAAO;EACL,SAAS;EACT,UAAU;EACV,OAAO;EACP,OAAO;EACP,IAAI;EACJ,IAAI;EACJ,UAAU;EACV,aAAa;CACf;CACA,UAAU;EACR,UAAU,EACR,MAAM,EAAE,SAAS,sDAAsD,EACzE;EACA,SAAS;GACP,IAAI,EAAE,SAAS,mBAAmB;GAClC,IAAI,EAAE,SAAS,mBAAmB;GAClC,IAAI,EAAE,SAAS,iBAAiB;EAClC;EACA,OAAO;GACL,MAAM,CAAC;GACP,OAAO,CAAC;EACV;EAEA,SAAS,EACP,MAAM,EAAE,OAAO,oGAAoG,EACrH;EACA,YAAY;GACV,UAAU,EAAE,IAAI,wDAAwD;GACxE,OAAO,EAAE,IAAI,uBAAuB;GAEpC,YAAY,EAAE,IAAI,kGAAkG;GACpH,gBAAgB,EAAE,IAAI,sCAAsC;GAC5D,OAAO,EAAE,IAAI,uCAAuC;EACtD;EACA,QAAQ,EACN,MAAM;GACJ,SAAS;GACT,UAAU;GACV,OAAO;GACP,OAAO;GACP,IAAI;GACJ,IAAI;EACN,EACF;CACF;;;;;;;CAOA,kBAAkB,CAChB;EAAE,OAAO;EAAO,QAAQ;EAAO,OAAO,EAAE,IAAI,2CAA2C;CAAE,GACzF;EAAE,OAAO;EAAM,QAAQ;EAAO,OAAO,EAAE,IAAI,mCAAmC;CAAE,CAClF;CACA,iBAAiB;EACf,OAAO;EACP,QAAQ;CACV;AACF,CAAC"}
1
+ {"version":3,"file":"data-table-styles.js","names":[],"sources":["../../../src/components/tables/data-table-styles.ts"],"sourcesContent":["import { tv } from '@/utils/tv';\n\nexport const dataTableStyles = tv({\n slots: {\n // `clip`, not `hidden`: a hidden-overflow flex item may shrink below its rows and hide them\n wrapper: 'relative divide-y divide-black/[0.12] dark:divide-white/[0.12] overflow-clip',\n scroller: 'overflow-x-auto overflow-y-hidden [clip-path:inset(0_0_0_0)]',\n table: 'min-w-full table-fixed divide-y divide-black/[0.12] dark:divide-white/[0.12] whitespace-nowrap mx-auto my-0 max-w-fit relative border-black/[0.12] dark:border-white/[0.12]',\n tbody: 'divide-y divide-black/[0.12] dark:divide-white/[0.12]',\n tr: 'hover:bg-black/[0.04] dark:hover:bg-white/[0.04]',\n td: '[:where(&)]:px-4 text-rui-text text-body-2 tabular-nums [text-wrap:initial]',\n checkbox: 'px-2 w-[3.625rem] max-w-[3.625rem] [&_label]:ml-0',\n tbodyLoader: 'text-center',\n },\n variants: {\n outlined: {\n true: { wrapper: 'border border-black/[0.12] dark:border-white/[0.12]' },\n },\n rounded: {\n sm: { wrapper: 'rounded-[.25rem]' },\n md: { wrapper: 'rounded-[.75rem]' },\n lg: { wrapper: 'rounded-[1rem]' },\n },\n dense: {\n true: {},\n false: {},\n },\n // `:where()` keeps the stripe below every row state (selected, hover, expanded, group)\n striped: {\n true: { tbody: '[:where(&>tr:nth-child(even))]:bg-rui-grey-50 dark:[:where(&>tr:nth-child(even))]:bg-rui-grey-900' },\n },\n rowVariant: {\n selected: { tr: 'bg-rui-primary/[0.08] dark:bg-rui-dark-primary/[0.08]' },\n empty: { tr: 'hover:bg-transparent' },\n // `!border-t-0` drops the divider so the panel joins the row that opened it\n expandable: { tr: 'bg-rui-grey-50 hover:bg-rui-grey-50 dark:bg-white/[0.03] dark:hover:bg-white/[0.03] !border-t-0' },\n expandedParent: { tr: 'bg-rui-grey-50 dark:bg-white/[0.03]' },\n group: { tr: 'bg-rui-grey-100 dark:bg-white/[0.05]' },\n },\n mobile: {\n true: {\n wrapper: 'divide-y-0 border-0 rounded-none overflow-visible',\n scroller: 'overflow-visible [clip-path:none]',\n table: 'block min-w-0 max-w-none w-full whitespace-normal divide-y-0 border-0',\n tbody: 'block divide-y-0',\n tr: 'block',\n td: 'block px-4 py-2',\n },\n },\n },\n /**\n * A fixed row height keeps a row of plain text as tall as one holding an\n * icon button. Padding and floor sit in `:where()` so a consumer's `cellClass`\n * (`py-0` is common) still wins at any stylesheet order, and each density\n * sets its own because the class merger cannot dedupe these.\n */\n compoundVariants: [\n { dense: false, mobile: false, class: { td: '[:where(&)]:py-3 [:where(&)]:h-[3.25rem]' } },\n { dense: true, mobile: false, class: { td: '[:where(&)]:py-1 [:where(&)]:h-9' } },\n ],\n defaultVariants: {\n dense: false,\n mobile: false,\n },\n});\n"],"mappings":";;AAEA,IAAa,kBAAkB,GAAG;CAChC,OAAO;EAEL,SAAS;EACT,UAAU;EACV,OAAO;EACP,OAAO;EACP,IAAI;EACJ,IAAI;EACJ,UAAU;EACV,aAAa;CACf;CACA,UAAU;EACR,UAAU,EACR,MAAM,EAAE,SAAS,sDAAsD,EACzE;EACA,SAAS;GACP,IAAI,EAAE,SAAS,mBAAmB;GAClC,IAAI,EAAE,SAAS,mBAAmB;GAClC,IAAI,EAAE,SAAS,iBAAiB;EAClC;EACA,OAAO;GACL,MAAM,CAAC;GACP,OAAO,CAAC;EACV;EAEA,SAAS,EACP,MAAM,EAAE,OAAO,oGAAoG,EACrH;EACA,YAAY;GACV,UAAU,EAAE,IAAI,wDAAwD;GACxE,OAAO,EAAE,IAAI,uBAAuB;GAEpC,YAAY,EAAE,IAAI,kGAAkG;GACpH,gBAAgB,EAAE,IAAI,sCAAsC;GAC5D,OAAO,EAAE,IAAI,uCAAuC;EACtD;EACA,QAAQ,EACN,MAAM;GACJ,SAAS;GACT,UAAU;GACV,OAAO;GACP,OAAO;GACP,IAAI;GACJ,IAAI;EACN,EACF;CACF;;;;;;;CAOA,kBAAkB,CAChB;EAAE,OAAO;EAAO,QAAQ;EAAO,OAAO,EAAE,IAAI,2CAA2C;CAAE,GACzF;EAAE,OAAO;EAAM,QAAQ;EAAO,OAAO,EAAE,IAAI,mCAAmC;CAAE,CAClF;CACA,iBAAiB;EACf,OAAO;EACP,QAAQ;CACV;AACF,CAAC"}
@@ -1,7 +1,6 @@
1
1
  import type { MaybeRefOrGetter, Ref, ShallowRef } from 'vue';
2
2
  export interface UseStickyTableHeaderRefs {
3
3
  table: Readonly<ShallowRef<HTMLTableElement | null>>;
4
- tableScroller: Readonly<ShallowRef<HTMLElement | null>>;
5
4
  }
6
5
  interface UseStickyTableHeaderReturn {
7
6
  stick: Readonly<Ref<boolean>>;
@@ -29,14 +29,51 @@ function positionHeadAtRest(head) {
29
29
  head.style.left = `${BORDER_PRECISION}px`;
30
30
  head.style.top = "0";
31
31
  }
32
- function positionHeadStuck(head, { clonedRect, lastRowHeight, tableRect, top }) {
33
- head.style.left = `${clonedRect.left + BORDER_PRECISION}px`;
34
- const isNearBottom = tableRect.bottom <= lastRowHeight + clonedRect.height + top;
35
- head.style.top = isNearBottom ? `${tableRect.bottom - lastRowHeight - clonedRect.height}px` : `${top}px`;
32
+ function positionHeadStuck(head, { clonedRect, lastRowHeight, origin, tableRect, top }) {
33
+ head.style.left = `${clonedRect.left - origin.left + BORDER_PRECISION}px`;
34
+ const viewportTop = tableRect.bottom <= lastRowHeight + clonedRect.height + top ? tableRect.bottom - lastRowHeight - clonedRect.height : top;
35
+ head.style.top = `${viewportTop - origin.top}px`;
36
36
  }
37
37
  function isInStickyRange(tableRect, top) {
38
38
  return tableRect.top <= top && tableRect.bottom > top;
39
39
  }
40
+ /**
41
+ * The nearest ancestor that currently scrolls vertically, e.g. a dialog or drawer body, or
42
+ * `undefined` when only the page does. One that could scroll but has nothing to (a card body on a
43
+ * page) is skipped, as is the table's own scroller, which only scrolls sideways.
44
+ */
45
+ function findScrollParent(element) {
46
+ let parent = element.parentElement;
47
+ while (parent && parent !== document.body && parent !== document.documentElement) {
48
+ const { overflowY } = getComputedStyle(parent);
49
+ if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) return parent;
50
+ parent = parent.parentElement;
51
+ }
52
+ }
53
+ function createsFixedContainingBlock(style) {
54
+ return style.transform !== "none" || style.perspective !== "none" || style.filter !== "none" || style.backdropFilter !== void 0 && style.backdropFilter !== "none" || /transform|perspective|filter/.test(style.willChange) || /paint|layout|strict|content/.test(style.contain);
55
+ }
56
+ /**
57
+ * Where a `position: fixed` descendant's `top: 0; left: 0` lands in the viewport. That is the
58
+ * viewport origin, unless an ancestor (a centred dialog is translated) becomes its containing block.
59
+ */
60
+ function getFixedOrigin(element) {
61
+ let parent = element.parentElement;
62
+ while (parent) {
63
+ if (createsFixedContainingBlock(getComputedStyle(parent))) {
64
+ const rect = parent.getBoundingClientRect();
65
+ return {
66
+ left: rect.left + parent.clientLeft,
67
+ top: rect.top + parent.clientTop
68
+ };
69
+ }
70
+ parent = parent.parentElement;
71
+ }
72
+ return {
73
+ left: 0,
74
+ top: 0
75
+ };
76
+ }
40
77
  function getLastRowHeight(root) {
41
78
  const rows = root.querySelectorAll(SELECTORS.row);
42
79
  if (rows.length <= 1) return void 0;
@@ -46,7 +83,7 @@ function getLastRowHeight(root) {
46
83
  * Setup sticky table header
47
84
  */
48
85
  function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs) {
49
- const { table, tableScroller } = tableRefs;
86
+ const { table } = tableRefs;
50
87
  const stick = shallowRef(false);
51
88
  let resizeCleanups = [];
52
89
  let syncRafId = null;
@@ -109,7 +146,9 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
109
146
  const { head, root, theadClone } = elements;
110
147
  const clonedRect = theadClone.getBoundingClientRect();
111
148
  const tableRect = root.getBoundingClientRect();
112
- const top = toValue(offsetTop) ?? 0;
149
+ const pageTop = toValue(offsetTop) ?? 0;
150
+ const scrollParent = findScrollParent(root);
151
+ const top = scrollParent ? Math.max(scrollParent.getBoundingClientRect().top + scrollParent.clientTop, pageTop) : pageTop;
113
152
  head.style.width = `${clonedRect.width}px`;
114
153
  const lastRowHeight = getLastRowHeight(root);
115
154
  if (lastRowHeight === void 0) {
@@ -122,6 +161,7 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
122
161
  positionHeadStuck(head, {
123
162
  clonedRect,
124
163
  lastRowHeight,
164
+ origin: getFixedOrigin(head),
125
165
  tableRect,
126
166
  top
127
167
  });
@@ -137,10 +177,18 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
137
177
  rafId = null;
138
178
  });
139
179
  }
180
+ function onScroll(event) {
181
+ const root = get$1(table);
182
+ const target = event.target;
183
+ if (!root || !(target instanceof Node) || !target.contains(root)) return;
184
+ throttledToggleStickyClass();
185
+ }
140
186
  onMounted(() => {
141
187
  toggleStickyClass();
142
- useEventListener(tableScroller, "scroll", throttledToggleStickyClass);
143
- useEventListener(document.body, "scroll", throttledToggleStickyClass);
188
+ useEventListener(document, "scroll", onScroll, {
189
+ capture: true,
190
+ passive: true
191
+ });
144
192
  useEventListener(window, "resize", throttledToggleStickyClass);
145
193
  watchCellWidth();
146
194
  });
@@ -1 +1 @@
1
- {"version":3,"file":"sticky-header.js","names":[],"sources":["../../src/composables/sticky-header.ts"],"sourcesContent":["import type { MaybeRefOrGetter, Ref, ShallowRef } from 'vue';\n\nexport interface UseStickyTableHeaderRefs {\n table: Readonly<ShallowRef<HTMLTableElement | null>>;\n tableScroller: Readonly<ShallowRef<HTMLElement | null>>;\n}\n\ninterface StickyElements {\n head: HTMLElement;\n theadClone: HTMLElement;\n root: HTMLTableElement;\n}\n\ninterface UseStickyTableHeaderReturn {\n stick: Readonly<Ref<boolean>>;\n}\n\nconst SELECTORS = {\n head: ':scope > thead[data-id=head-main]',\n headClone: ':scope > thead[data-id=head-clone]',\n row: ':scope > tbody > tr:not([hidden])',\n th: ':scope > th',\n} as const;\n\nconst BORDER_PRECISION = -0.5;\n\nfunction queryHeadRow(root: HTMLTableElement, selector: string): HTMLTableRowElement | null {\n return root.querySelector(`${selector} > tr`);\n}\n\nfunction queryColumns(row: HTMLTableRowElement | null): HTMLElement[] {\n if (!row)\n return [];\n return Array.from(row.querySelectorAll<HTMLElement>(SELECTORS.th));\n}\n\nfunction readColumnWidths(columns: HTMLElement[]): number[] {\n return columns.map(col => col.getBoundingClientRect().width);\n}\n\nfunction applyColumnWidths(columns: HTMLElement[], widths: number[]): void {\n for (const [i, column] of columns.entries()) {\n const width = widths[i];\n if (width !== undefined)\n column.style.width = `${width}px`;\n }\n}\n\nfunction positionHeadAtRest(head: HTMLElement): void {\n head.style.left = `${BORDER_PRECISION}px`;\n head.style.top = '0';\n}\n\ninterface StuckPosition {\n clonedRect: DOMRect;\n tableRect: DOMRect;\n lastRowHeight: number;\n top: number;\n}\n\nfunction positionHeadStuck(head: HTMLElement, { clonedRect, lastRowHeight, tableRect, top }: StuckPosition): void {\n head.style.left = `${clonedRect.left + BORDER_PRECISION}px`;\n\n const isNearBottom = tableRect.bottom <= lastRowHeight + clonedRect.height + top;\n head.style.top = isNearBottom\n ? `${tableRect.bottom - lastRowHeight - clonedRect.height}px`\n : `${top}px`;\n}\n\nfunction isInStickyRange(tableRect: DOMRect, top: number): boolean {\n return tableRect.top <= top && tableRect.bottom > top;\n}\n\nfunction getLastRowHeight(root: HTMLTableElement): number | undefined {\n const rows = root.querySelectorAll(SELECTORS.row);\n if (rows.length <= 1)\n return undefined;\n return rows.item(rows.length - 1)?.getBoundingClientRect().height ?? 0;\n}\n\n/**\n * Setup sticky table header\n */\nexport function useStickyTableHeader(\n sticky: MaybeRefOrGetter<boolean> = shallowRef(false),\n offsetTop: MaybeRefOrGetter<number | undefined>,\n tableRefs: UseStickyTableHeaderRefs,\n): UseStickyTableHeaderReturn {\n const { table, tableScroller } = tableRefs;\n const stick = shallowRef<boolean>(false);\n\n let resizeCleanups: (() => void)[] = [];\n let syncRafId: number | null = null;\n let rafId: number | null = null;\n\n function cleanupResizeObservers(): void {\n for (const cleanup of resizeCleanups)\n cleanup();\n resizeCleanups = [];\n }\n\n function cancelPendingSync(): void {\n if (syncRafId !== null)\n cancelAnimationFrame(syncRafId);\n syncRafId = null;\n }\n\n function scheduleSyncWidths(cloneColumns: HTMLElement[], mainColumns: HTMLElement[]): void {\n cancelPendingSync();\n\n // One frame is enough: the main thead is positioned, so its widths never feed back into the clone's\n syncRafId = requestAnimationFrame(() => {\n syncRafId = null;\n applyColumnWidths(mainColumns, readColumnWidths(cloneColumns));\n });\n }\n\n function observeColumnResizes(cloneColumns: HTMLElement[], mainColumns: HTMLElement[]): void {\n cleanupResizeObservers();\n\n for (const th of cloneColumns) {\n const { stop } = useResizeObserver(th, () => {\n scheduleSyncWidths(cloneColumns, mainColumns);\n });\n resizeCleanups.push(stop);\n }\n }\n\n function watchCellWidth(): void {\n const root = get(table);\n if (!toValue(sticky) || !root)\n return;\n\n const cloneRow = queryHeadRow(root, SELECTORS.headClone);\n const mainRow = queryHeadRow(root, SELECTORS.head);\n const cloneColumns = queryColumns(cloneRow);\n const mainColumns = queryColumns(mainRow);\n\n useMutationObserver(cloneRow, (mutations) => {\n const hasRelevantMutation = mutations.some(\n m => m.type === 'childList' || m.type === 'attributes',\n );\n if (hasRelevantMutation)\n observeColumnResizes(queryColumns(cloneRow), queryColumns(mainRow));\n }, { attributes: true, childList: true, subtree: true });\n\n observeColumnResizes(cloneColumns, mainColumns);\n }\n\n function queryStickyElements(): StickyElements | undefined {\n const root = get(table);\n if (!toValue(sticky) || !root)\n return undefined;\n\n const theadClone = root.querySelector<HTMLElement>(SELECTORS.headClone);\n const head = root.querySelector<HTMLElement>(SELECTORS.head);\n if (!theadClone || !head)\n return undefined;\n\n return { head, root, theadClone };\n }\n\n function toggleStickyClass(): void {\n const elements = queryStickyElements();\n if (!elements)\n return;\n\n const { head, root, theadClone } = elements;\n const clonedRect = theadClone.getBoundingClientRect();\n const tableRect = root.getBoundingClientRect();\n const top = toValue(offsetTop) ?? 0;\n\n head.style.width = `${clonedRect.width}px`;\n\n const lastRowHeight = getLastRowHeight(root);\n if (lastRowHeight === undefined) {\n set(stick, false);\n positionHeadAtRest(head);\n return;\n }\n\n if (isInStickyRange(tableRect, top)) {\n set(stick, true);\n positionHeadStuck(head, { clonedRect, lastRowHeight, tableRect, top });\n }\n else {\n set(stick, false);\n positionHeadAtRest(head);\n }\n }\n\n function throttledToggleStickyClass(): void {\n if (rafId !== null)\n return;\n rafId = requestAnimationFrame(() => {\n toggleStickyClass();\n rafId = null;\n });\n }\n\n onMounted(() => {\n toggleStickyClass();\n useEventListener(tableScroller, 'scroll', throttledToggleStickyClass);\n useEventListener(document.body, 'scroll', throttledToggleStickyClass);\n useEventListener(window, 'resize', throttledToggleStickyClass);\n watchCellWidth();\n });\n\n return {\n stick: readonly(stick),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sticky-header.js","names":[],"sources":["../../src/composables/sticky-header.ts"],"sourcesContent":["import type { MaybeRefOrGetter, Ref, ShallowRef } from 'vue';\n\nexport interface UseStickyTableHeaderRefs {\n table: Readonly<ShallowRef<HTMLTableElement | null>>;\n}\n\ninterface StickyElements {\n head: HTMLElement;\n theadClone: HTMLElement;\n root: HTMLTableElement;\n}\n\ninterface UseStickyTableHeaderReturn {\n stick: Readonly<Ref<boolean>>;\n}\n\nconst SELECTORS = {\n head: ':scope > thead[data-id=head-main]',\n headClone: ':scope > thead[data-id=head-clone]',\n row: ':scope > tbody > tr:not([hidden])',\n th: ':scope > th',\n} as const;\n\nconst BORDER_PRECISION = -0.5;\n\nfunction queryHeadRow(root: HTMLTableElement, selector: string): HTMLTableRowElement | null {\n return root.querySelector(`${selector} > tr`);\n}\n\nfunction queryColumns(row: HTMLTableRowElement | null): HTMLElement[] {\n if (!row)\n return [];\n return Array.from(row.querySelectorAll<HTMLElement>(SELECTORS.th));\n}\n\nfunction readColumnWidths(columns: HTMLElement[]): number[] {\n return columns.map(col => col.getBoundingClientRect().width);\n}\n\nfunction applyColumnWidths(columns: HTMLElement[], widths: number[]): void {\n for (const [i, column] of columns.entries()) {\n const width = widths[i];\n if (width !== undefined)\n column.style.width = `${width}px`;\n }\n}\n\nfunction positionHeadAtRest(head: HTMLElement): void {\n head.style.left = `${BORDER_PRECISION}px`;\n head.style.top = '0';\n}\n\ninterface StuckPosition {\n clonedRect: DOMRect;\n tableRect: DOMRect;\n lastRowHeight: number;\n top: number;\n origin: Point;\n}\n\nfunction positionHeadStuck(head: HTMLElement, { clonedRect, lastRowHeight, origin, tableRect, top }: StuckPosition): void {\n head.style.left = `${clonedRect.left - origin.left + BORDER_PRECISION}px`;\n\n const isNearBottom = tableRect.bottom <= lastRowHeight + clonedRect.height + top;\n const viewportTop = isNearBottom ? tableRect.bottom - lastRowHeight - clonedRect.height : top;\n head.style.top = `${viewportTop - origin.top}px`;\n}\n\nfunction isInStickyRange(tableRect: DOMRect, top: number): boolean {\n return tableRect.top <= top && tableRect.bottom > top;\n}\n\ninterface Point {\n left: number;\n top: number;\n}\n\n/**\n * The nearest ancestor that currently scrolls vertically, e.g. a dialog or drawer body, or\n * `undefined` when only the page does. One that could scroll but has nothing to (a card body on a\n * page) is skipped, as is the table's own scroller, which only scrolls sideways.\n */\nfunction findScrollParent(element: HTMLElement): HTMLElement | undefined {\n let parent = element.parentElement;\n while (parent && parent !== document.body && parent !== document.documentElement) {\n const { overflowY } = getComputedStyle(parent);\n if ((overflowY === 'auto' || overflowY === 'scroll') && parent.scrollHeight > parent.clientHeight)\n return parent;\n parent = parent.parentElement;\n }\n return undefined;\n}\n\nfunction createsFixedContainingBlock(style: CSSStyleDeclaration): boolean {\n return style.transform !== 'none'\n || style.perspective !== 'none'\n || style.filter !== 'none'\n || (style.backdropFilter !== undefined && style.backdropFilter !== 'none')\n || /transform|perspective|filter/.test(style.willChange)\n || /paint|layout|strict|content/.test(style.contain);\n}\n\n/**\n * Where a `position: fixed` descendant's `top: 0; left: 0` lands in the viewport. That is the\n * viewport origin, unless an ancestor (a centred dialog is translated) becomes its containing block.\n */\nfunction getFixedOrigin(element: HTMLElement): Point {\n let parent = element.parentElement;\n while (parent) {\n if (createsFixedContainingBlock(getComputedStyle(parent))) {\n const rect = parent.getBoundingClientRect();\n return { left: rect.left + parent.clientLeft, top: rect.top + parent.clientTop };\n }\n parent = parent.parentElement;\n }\n return { left: 0, top: 0 };\n}\n\nfunction getLastRowHeight(root: HTMLTableElement): number | undefined {\n const rows = root.querySelectorAll(SELECTORS.row);\n if (rows.length <= 1)\n return undefined;\n return rows.item(rows.length - 1)?.getBoundingClientRect().height ?? 0;\n}\n\n/**\n * Setup sticky table header\n */\nexport function useStickyTableHeader(\n sticky: MaybeRefOrGetter<boolean> = shallowRef(false),\n offsetTop: MaybeRefOrGetter<number | undefined>,\n tableRefs: UseStickyTableHeaderRefs,\n): UseStickyTableHeaderReturn {\n const { table } = tableRefs;\n const stick = shallowRef<boolean>(false);\n\n let resizeCleanups: (() => void)[] = [];\n let syncRafId: number | null = null;\n let rafId: number | null = null;\n\n function cleanupResizeObservers(): void {\n for (const cleanup of resizeCleanups)\n cleanup();\n resizeCleanups = [];\n }\n\n function cancelPendingSync(): void {\n if (syncRafId !== null)\n cancelAnimationFrame(syncRafId);\n syncRafId = null;\n }\n\n function scheduleSyncWidths(cloneColumns: HTMLElement[], mainColumns: HTMLElement[]): void {\n cancelPendingSync();\n\n // One frame is enough: the main thead is positioned, so its widths never feed back into the clone's\n syncRafId = requestAnimationFrame(() => {\n syncRafId = null;\n applyColumnWidths(mainColumns, readColumnWidths(cloneColumns));\n });\n }\n\n function observeColumnResizes(cloneColumns: HTMLElement[], mainColumns: HTMLElement[]): void {\n cleanupResizeObservers();\n\n for (const th of cloneColumns) {\n const { stop } = useResizeObserver(th, () => {\n scheduleSyncWidths(cloneColumns, mainColumns);\n });\n resizeCleanups.push(stop);\n }\n }\n\n function watchCellWidth(): void {\n const root = get(table);\n if (!toValue(sticky) || !root)\n return;\n\n const cloneRow = queryHeadRow(root, SELECTORS.headClone);\n const mainRow = queryHeadRow(root, SELECTORS.head);\n const cloneColumns = queryColumns(cloneRow);\n const mainColumns = queryColumns(mainRow);\n\n useMutationObserver(cloneRow, (mutations) => {\n const hasRelevantMutation = mutations.some(\n m => m.type === 'childList' || m.type === 'attributes',\n );\n if (hasRelevantMutation)\n observeColumnResizes(queryColumns(cloneRow), queryColumns(mainRow));\n }, { attributes: true, childList: true, subtree: true });\n\n observeColumnResizes(cloneColumns, mainColumns);\n }\n\n function queryStickyElements(): StickyElements | undefined {\n const root = get(table);\n if (!toValue(sticky) || !root)\n return undefined;\n\n const theadClone = root.querySelector<HTMLElement>(SELECTORS.headClone);\n const head = root.querySelector<HTMLElement>(SELECTORS.head);\n if (!theadClone || !head)\n return undefined;\n\n return { head, root, theadClone };\n }\n\n function toggleStickyClass(): void {\n const elements = queryStickyElements();\n if (!elements)\n return;\n\n const { head, root, theadClone } = elements;\n const clonedRect = theadClone.getBoundingClientRect();\n const tableRect = root.getBoundingClientRect();\n const pageTop = toValue(offsetTop) ?? 0;\n const scrollParent = findScrollParent(root);\n const top = scrollParent\n ? Math.max(scrollParent.getBoundingClientRect().top + scrollParent.clientTop, pageTop)\n : pageTop;\n\n head.style.width = `${clonedRect.width}px`;\n\n const lastRowHeight = getLastRowHeight(root);\n if (lastRowHeight === undefined) {\n set(stick, false);\n positionHeadAtRest(head);\n return;\n }\n\n if (isInStickyRange(tableRect, top)) {\n set(stick, true);\n positionHeadStuck(head, { clonedRect, lastRowHeight, origin: getFixedOrigin(head), tableRect, top });\n }\n else {\n set(stick, false);\n positionHeadAtRest(head);\n }\n }\n\n function throttledToggleStickyClass(): void {\n if (rafId !== null)\n return;\n rafId = requestAnimationFrame(() => {\n toggleStickyClass();\n rafId = null;\n });\n }\n\n function onScroll(event: Event): void {\n const root = get(table);\n const target = event.target;\n // scroll does not bubble, so this runs in capture for every scroll: only an ancestor's matters\n if (!root || !(target instanceof Node) || !target.contains(root))\n return;\n throttledToggleStickyClass();\n }\n\n onMounted(() => {\n toggleStickyClass();\n useEventListener(document, 'scroll', onScroll, { capture: true, passive: true });\n useEventListener(window, 'resize', throttledToggleStickyClass);\n watchCellWidth();\n });\n\n return {\n stick: readonly(stick),\n };\n}\n"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1570,6 +1570,9 @@ html[data-theme="dark"], html.dark {
1570
1570
  .flex-\[1_1_0\] {
1571
1571
  flex: 1 1 0;
1572
1572
  }
1573
+ .shrink {
1574
+ flex-shrink: 1;
1575
+ }
1573
1576
  .shrink-0 {
1574
1577
  flex-shrink: 0;
1575
1578
  }
@@ -2018,6 +2021,9 @@ html[data-theme="dark"], html.dark {
2018
2021
  .overflow-hidden {
2019
2022
  overflow: hidden;
2020
2023
  }
2024
+ .overflow-clip {
2025
+ overflow: clip;
2026
+ }
2021
2027
  .\!overflow-visible {
2022
2028
  overflow: visible !important;
2023
2029
  }
@@ -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.26.0",
5
+ "version": "2.26.1",
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.26.0",
3
+ "version": "2.26.1",
4
4
  "description": "A vue design system and component library for rotki",
5
5
  "type": "module",
6
6
  "keywords": [