@rotki/ui-library 3.0.0-beta.1 → 3.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/accordions/accordions/RuiAccordions.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/avatars/RuiAvatar.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/avatars/RuiAvatarGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/buttons/button/RuiButton.js.map +1 -1
- package/dist/components/buttons/button/RuiButton.vue_vue_type_script_setup_true_lang.js +10 -10
- package/dist/components/buttons/button/RuiButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/buttons/button/button-styles.js +10 -2
- package/dist/components/buttons/button/button-styles.js.map +1 -1
- package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/calendar/RuiCalendar.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/calendar/RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/calendar/RuiCalendarHeader.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/color-picker/RuiColorBoard.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/color-picker/RuiColorDisplay.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/color-picker/RuiColorHue.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/color-picker/RuiColorInput.vue_vue_type_script_setup_true_lang.js +9 -10
- package/dist/components/color-picker/RuiColorInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-picker/RuiColorPicker.vue_vue_type_script_setup_true_lang.js +3 -4
- package/dist/components/color-picker/RuiColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js +33 -33
- package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/date-time-picker/use-date-bounds.js +1 -1
- package/dist/components/date-time-picker/use-date-time-selection.js +65 -66
- package/dist/components/date-time-picker/use-date-time-selection.js.map +1 -1
- package/dist/components/date-time-picker/use-input-handler.js +1 -1
- package/dist/components/date-time-picker/use-keyboard-handler.js +29 -30
- package/dist/components/date-time-picker/use-keyboard-handler.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoComplete.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js +25 -24
- package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/auto-complete/auto-complete-styles.d.ts +3 -53
- package/dist/components/forms/auto-complete/auto-complete-styles.js +2 -1
- package/dist/components/forms/auto-complete/auto-complete-styles.js.map +1 -1
- package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js +49 -49
- package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/checkbox/RuiCheckbox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/forms/file-upload/RuiFileUpload.js.map +1 -1
- package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js +19 -17
- package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/radio-button/radio/RuiRadio.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js +11 -11
- package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/slider/RuiSlider.vue_vue_type_script_setup_true_lang.js +17 -17
- package/dist/components/forms/slider/RuiSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js +21 -21
- package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/text-field/RuiTextField.js.map +1 -1
- package/dist/components/forms/text-field/RuiTextField.vue.d.ts +2 -0
- package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js +24 -20
- package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/text-input-styles.js +1 -1
- package/dist/components/forms/text-input-styles.js.map +1 -1
- package/dist/components/helpers/RuiFormTextDetail.js.map +1 -1
- package/dist/components/helpers/RuiFormTextDetail.vue.d.ts +11 -1
- package/dist/components/helpers/RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/helpers/RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/icons/RuiIcon.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/logos/RuiLogo.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/overlays/badge/RuiBadge.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/overlays/dialog/RuiDialog.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js +17 -17
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.js.map +1 -1
- package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/notification/RuiNotification.vue_vue_type_script_setup_true_lang.js +3 -4
- package/dist/components/overlays/notification/RuiNotification.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/tooltip/RuiTooltip.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/progress/RuiProgress.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/steppers/RuiFooterStepper.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/steppers/RuiStepper.js.map +1 -1
- package/dist/components/steppers/RuiStepper.vue_vue_type_script_setup_true_lang.js +13 -8
- package/dist/components/steppers/RuiStepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiDataTable.vue_vue_type_script_setup_true_lang.js +27 -27
- package/dist/components/tables/RuiDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tables/data-table/RuiDataTableCell.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tables/data-table/RuiDataTableGroupRow.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tables/data-table/RuiDataTableMobileSort.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js +23 -23
- package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/use-pagination-navigation.js +1 -1
- package/dist/components/tabs/tab/RuiTab.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tabs/tab/use-tab-link.js +1 -1
- package/dist/components/tabs/tab-items/RuiTabItems.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/components/tabs/tab-items/RuiTabItems.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tabs/tabs/RuiTabs.vue_vue_type_script_setup_true_lang.js +11 -12
- package/dist/components/tabs/tabs/RuiTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tabs/tabs/use-tab-indicator.js +7 -7
- package/dist/components/tabs/tabs/use-tab-indicator.js.map +1 -1
- package/dist/components/tabs/tabs/use-tab-keyboard.js +1 -1
- package/dist/components/tabs/tabs/use-tab-scroll.js +15 -15
- package/dist/components/tabs/tabs/use-tab-scroll.js.map +1 -1
- package/dist/components/time-picker/RuiTimePicker.vue_vue_type_script_setup_true_lang.js +60 -61
- package/dist/components/time-picker/RuiTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/breakpoint.js +3 -3
- package/dist/composables/breakpoint.js.map +1 -1
- package/dist/composables/dropdown-menu.d.ts +9 -9
- package/dist/composables/dropdown-menu.js +32 -32
- package/dist/composables/dropdown-menu.js.map +1 -1
- package/dist/composables/floating.d.ts +1 -1
- package/dist/composables/floating.js +11 -11
- package/dist/composables/floating.js.map +1 -1
- package/dist/composables/forms/auto-complete/focus.js +16 -17
- package/dist/composables/forms/auto-complete/focus.js.map +1 -1
- package/dist/composables/forms/auto-complete/keyboard-navigation.js +1 -1
- package/dist/composables/forms/auto-complete/search.js +6 -7
- package/dist/composables/forms/auto-complete/search.js.map +1 -1
- package/dist/composables/forms/auto-complete/selection.js +1 -1
- package/dist/composables/forms/auto-complete/value.js +1 -1
- package/dist/composables/forms/category-picker/use-category-picker.js +1 -1
- package/dist/composables/forms/use-prepend-append-width.js +6 -6
- package/dist/composables/forms/use-prepend-append-width.js.map +1 -1
- package/dist/composables/keyboard-scroll.js +5 -5
- package/dist/composables/keyboard-scroll.js.map +1 -1
- package/dist/composables/overlay-stack.js +1 -1
- package/dist/composables/sticky-header.js +8 -8
- package/dist/composables/sticky-header.js.map +1 -1
- package/dist/composables/tables/data-table/columns.js +1 -1
- package/dist/composables/tables/data-table/expansion.js +1 -1
- package/dist/composables/tables/data-table/grouping.js +27 -28
- package/dist/composables/tables/data-table/grouping.js.map +1 -1
- package/dist/composables/tables/data-table/page-scroll.d.ts +7 -5
- package/dist/composables/tables/data-table/page-scroll.js +48 -17
- package/dist/composables/tables/data-table/page-scroll.js.map +1 -1
- package/dist/composables/tables/data-table/pagination.js +1 -1
- package/dist/composables/tables/data-table/selection.js +37 -38
- package/dist/composables/tables/data-table/selection.js.map +1 -1
- package/dist/composables/tables/data-table/sort.js +1 -1
- package/dist/composables/theme.js +12 -12
- package/dist/composables/theme.js.map +1 -1
- package/dist/composables/timeout-manager.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/style.css +6 -18
- package/dist/types/dom.d.ts +9 -0
- package/dist/types/theme.d.ts +2 -3
- package/dist/types/theme.js.map +1 -1
- package/dist/utils/form-text-detail.js +1 -1
- package/dist/utils/helpers.js +1 -1
- package/dist/vendor/@vueuse/core/dist/index.js +1577 -0
- package/dist/vendor/@vueuse/core/dist/index.js.map +1 -0
- package/dist/vendor/@vueuse/shared/dist/index.js +563 -0
- package/dist/vendor/@vueuse/shared/dist/index.js.map +1 -0
- package/dist/vite-plugin/index.js +5 -6
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/web-types.json +9 -2
- package/package.json +5 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assert } from "../../utils/assert.js";
|
|
2
|
+
import { get, set } from "../../vendor/@vueuse/shared/dist/index.js";
|
|
3
|
+
import { useResizeObserver } from "../../vendor/@vueuse/core/dist/index.js";
|
|
2
4
|
import { readonly, shallowRef } from "vue";
|
|
3
|
-
import { useResizeObserver } from "@vueuse/core";
|
|
4
|
-
import { get as get$1, set as set$1 } from "@vueuse/shared";
|
|
5
5
|
//#region src/composables/forms/use-prepend-append-width.ts
|
|
6
6
|
/**
|
|
7
7
|
* Tracks how much room a field's prepend and append regions take.
|
|
@@ -22,14 +22,14 @@ function usePrependAppendWidth(prepend, append, offset = 0) {
|
|
|
22
22
|
const prependWidth = shallowRef("0px");
|
|
23
23
|
const appendWidth = shallowRef("0px");
|
|
24
24
|
useResizeObserver(prepend, () => {
|
|
25
|
-
const target = get
|
|
25
|
+
const target = get(prepend);
|
|
26
26
|
assert(target);
|
|
27
|
-
set
|
|
27
|
+
set(prependWidth, `${target.offsetWidth + offset}px`);
|
|
28
28
|
});
|
|
29
29
|
useResizeObserver(append, () => {
|
|
30
|
-
const target = get
|
|
30
|
+
const target = get(append);
|
|
31
31
|
assert(target);
|
|
32
|
-
set
|
|
32
|
+
set(appendWidth, `${target.offsetWidth + offset}px`);
|
|
33
33
|
});
|
|
34
34
|
return {
|
|
35
35
|
appendWidth: readonly(appendWidth),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-prepend-append-width.js","names":[],"sources":["../../../src/composables/forms/use-prepend-append-width.ts"],"sourcesContent":["import type { ShallowRef } from 'vue';\nimport { assert } from '@/utils/assert';\n\nexport interface PrependAppendWidth {\n prependWidth: Readonly<ShallowRef<string>>;\n appendWidth: Readonly<ShallowRef<string>>;\n}\n\n/**\n * Tracks how much room a field's prepend and append regions take.\n *\n * Each is measured by its outer border-box width, so a caller gets the whole\n * space the region occupies, both paddings included. The earlier\n * `contentRect.width + contentRect.left` form added padding-left but dropped\n * padding-right, so a prepend slot styled with `pr-3` alone reported 12px\n * short and the floated label landed under the icon's trailing padding\n * instead of aligned with the input text.\n *\n * @param prepend - the prepend region's element\n * @param append - the append region's element\n * @param offset - extra width to add to the prepend measurement\n * @returns both widths, in px, as CSS lengths\n */\nexport function usePrependAppendWidth(\n prepend: Readonly<ShallowRef<HTMLDivElement | null>>,\n append: Readonly<ShallowRef<HTMLDivElement | null>>,\n offset: number = 0,\n): PrependAppendWidth {\n const prependWidth = shallowRef<string>('0px');\n const appendWidth = shallowRef<string>('0px');\n\n useResizeObserver(prepend, () => {\n const target = get(prepend);\n assert(target);\n set(prependWidth, `${target.offsetWidth + offset}px`);\n });\n\n useResizeObserver(append, () => {\n const target = get(append);\n assert(target);\n set(appendWidth, `${target.offsetWidth + offset}px`);\n });\n\n return {\n appendWidth: readonly(appendWidth),\n prependWidth: readonly(prependWidth),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,sBACd,SACA,QACA,SAAiB,GACG;CACpB,MAAM,eAAe,WAAmB,KAAK;CAC7C,MAAM,cAAc,WAAmB,KAAK;CAE5C,kBAAkB,eAAe;EAC/B,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"use-prepend-append-width.js","names":[],"sources":["../../../src/composables/forms/use-prepend-append-width.ts"],"sourcesContent":["import type { ShallowRef } from 'vue';\nimport { assert } from '@/utils/assert';\n\nexport interface PrependAppendWidth {\n prependWidth: Readonly<ShallowRef<string>>;\n appendWidth: Readonly<ShallowRef<string>>;\n}\n\n/**\n * Tracks how much room a field's prepend and append regions take.\n *\n * Each is measured by its outer border-box width, so a caller gets the whole\n * space the region occupies, both paddings included. The earlier\n * `contentRect.width + contentRect.left` form added padding-left but dropped\n * padding-right, so a prepend slot styled with `pr-3` alone reported 12px\n * short and the floated label landed under the icon's trailing padding\n * instead of aligned with the input text.\n *\n * @param prepend - the prepend region's element\n * @param append - the append region's element\n * @param offset - extra width to add to the prepend measurement\n * @returns both widths, in px, as CSS lengths\n */\nexport function usePrependAppendWidth(\n prepend: Readonly<ShallowRef<HTMLDivElement | null>>,\n append: Readonly<ShallowRef<HTMLDivElement | null>>,\n offset: number = 0,\n): PrependAppendWidth {\n const prependWidth = shallowRef<string>('0px');\n const appendWidth = shallowRef<string>('0px');\n\n useResizeObserver(prepend, () => {\n const target = get(prepend);\n assert(target);\n set(prependWidth, `${target.offsetWidth + offset}px`);\n });\n\n useResizeObserver(append, () => {\n const target = get(append);\n assert(target);\n set(appendWidth, `${target.offsetWidth + offset}px`);\n });\n\n return {\n appendWidth: readonly(appendWidth),\n prependWidth: readonly(prependWidth),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,sBACd,SACA,QACA,SAAiB,GACG;CACpB,MAAM,eAAe,WAAmB,KAAK;CAC7C,MAAM,cAAc,WAAmB,KAAK;CAE5C,kBAAkB,eAAe;EAC/B,MAAM,SAAS,IAAI,OAAO;EAC1B,OAAO,MAAM;EACb,IAAI,cAAc,GAAG,OAAO,cAAc,OAAO,GAAG;CACtD,CAAC;CAED,kBAAkB,cAAc;EAC9B,MAAM,SAAS,IAAI,MAAM;EACzB,OAAO,MAAM;EACb,IAAI,aAAa,GAAG,OAAO,cAAc,OAAO,GAAG;CACrD,CAAC;CAED,OAAO;EACL,aAAa,SAAS,WAAW;EACjC,cAAc,SAAS,YAAY;CACrC;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { get, set } from "../vendor/@vueuse/shared/dist/index.js";
|
|
2
|
+
import { useMutationObserver, useResizeObserver } from "../vendor/@vueuse/core/dist/index.js";
|
|
1
3
|
import { computed, onMounted, shallowRef } from "vue";
|
|
2
|
-
import { useMutationObserver, useResizeObserver } from "@vueuse/core";
|
|
3
|
-
import { get as get$1, set as set$1 } from "@vueuse/shared";
|
|
4
4
|
//#region src/composables/keyboard-scroll.ts
|
|
5
5
|
/** What a keyboard user can tab to inside a scrolling box, and so scroll it by. */
|
|
6
6
|
var FOCUSABLE = [
|
|
@@ -33,8 +33,8 @@ function useKeyboardScroll(target) {
|
|
|
33
33
|
let scheduled = false;
|
|
34
34
|
function update() {
|
|
35
35
|
scheduled = false;
|
|
36
|
-
const element = get
|
|
37
|
-
set
|
|
36
|
+
const element = get(target);
|
|
37
|
+
set(focusable, element ? needsOwnFocus(element) : false);
|
|
38
38
|
}
|
|
39
39
|
/** Reading the scroll sizes forces layout, so a burst of resizes and mutations is checked once a frame. */
|
|
40
40
|
function schedule() {
|
|
@@ -50,7 +50,7 @@ function useKeyboardScroll(target) {
|
|
|
50
50
|
attributes: true,
|
|
51
51
|
attributeFilter: ["disabled", "tabindex"]
|
|
52
52
|
});
|
|
53
|
-
return computed(() => get
|
|
53
|
+
return computed(() => get(focusable) ? 0 : void 0);
|
|
54
54
|
}
|
|
55
55
|
//#endregion
|
|
56
56
|
export { needsOwnFocus, useKeyboardScroll };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard-scroll.js","names":[],"sources":["../../src/composables/keyboard-scroll.ts"],"sourcesContent":["import type { ComputedRef, Ref } from 'vue';\nimport { useMutationObserver, useResizeObserver } from '@vueuse/core';\n\n/** What a keyboard user can tab to inside a scrolling box, and so scroll it by. */\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=hidden])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/**\n * Whether a box scrolls while holding nothing a keyboard user can reach, the case WCAG 2.1.1 and\n * axe's scrollable-region-focusable rule cover: the arrow keys only scroll what has focus.\n *\n * @param element - the scrolling box\n * @returns true when the box overflows and has no focusable content\n */\nexport function needsOwnFocus(element: HTMLElement): boolean {\n const overflows = element.scrollWidth > element.clientWidth + 1 || element.scrollHeight > element.clientHeight + 1;\n return overflows && element.querySelector(FOCUSABLE) === null;\n}\n\n/**\n * Makes a scrolling box a tab stop while, and only while, it overflows with nothing focusable\n * inside, so keyboard users can scroll it without a stray tab stop on every box that fits.\n *\n * @param target - the scrolling box\n * @returns the `tabindex` to bind on the box: 0 when it needs focus, otherwise undefined\n */\nexport function useKeyboardScroll(target: Readonly<Ref<HTMLElement | null | undefined>>): ComputedRef<0 | undefined> {\n const focusable = shallowRef<boolean>(false);\n let scheduled = false;\n\n function update(): void {\n scheduled = false;\n const element = get(target);\n set(focusable, element ? needsOwnFocus(element) : false);\n }\n\n /** Reading the scroll sizes forces layout, so a burst of resizes and mutations is checked once a frame. */\n function schedule(): void {\n if (scheduled)\n return;\n scheduled = true;\n requestAnimationFrame(update);\n }\n\n // the first check runs at mount, so the box is reachable from its first render, not a frame later\n onMounted(update);\n useResizeObserver(target, schedule);\n // the box keeps its size while its content grows or swaps, which changes whether it overflows\n useMutationObserver(target, schedule, { childList: true, subtree: true, attributes: true, attributeFilter: ['disabled', 'tabindex'] });\n\n return computed<0 | undefined>(() => (get(focusable) ? 0 : undefined));\n}\n"],"mappings":";;;;;AAIA,IAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,GAAG;;;;;;;;AASV,SAAgB,cAAc,SAA+B;CAE3D,QADkB,QAAQ,cAAc,QAAQ,cAAc,KAAK,QAAQ,eAAe,QAAQ,eAAe,MAC7F,QAAQ,cAAc,SAAS,MAAM;AAC3D;;;;;;;;AASA,SAAgB,kBAAkB,QAAmF;CACnH,MAAM,YAAY,WAAoB,KAAK;CAC3C,IAAI,YAAY;CAEhB,SAAS,SAAe;EACtB,YAAY;EACZ,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"keyboard-scroll.js","names":[],"sources":["../../src/composables/keyboard-scroll.ts"],"sourcesContent":["import type { ComputedRef, Ref } from 'vue';\nimport { useMutationObserver, useResizeObserver } from '@vueuse/core';\n\n/** What a keyboard user can tab to inside a scrolling box, and so scroll it by. */\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=hidden])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/**\n * Whether a box scrolls while holding nothing a keyboard user can reach, the case WCAG 2.1.1 and\n * axe's scrollable-region-focusable rule cover: the arrow keys only scroll what has focus.\n *\n * @param element - the scrolling box\n * @returns true when the box overflows and has no focusable content\n */\nexport function needsOwnFocus(element: HTMLElement): boolean {\n const overflows = element.scrollWidth > element.clientWidth + 1 || element.scrollHeight > element.clientHeight + 1;\n return overflows && element.querySelector(FOCUSABLE) === null;\n}\n\n/**\n * Makes a scrolling box a tab stop while, and only while, it overflows with nothing focusable\n * inside, so keyboard users can scroll it without a stray tab stop on every box that fits.\n *\n * @param target - the scrolling box\n * @returns the `tabindex` to bind on the box: 0 when it needs focus, otherwise undefined\n */\nexport function useKeyboardScroll(target: Readonly<Ref<HTMLElement | null | undefined>>): ComputedRef<0 | undefined> {\n const focusable = shallowRef<boolean>(false);\n let scheduled = false;\n\n function update(): void {\n scheduled = false;\n const element = get(target);\n set(focusable, element ? needsOwnFocus(element) : false);\n }\n\n /** Reading the scroll sizes forces layout, so a burst of resizes and mutations is checked once a frame. */\n function schedule(): void {\n if (scheduled)\n return;\n scheduled = true;\n requestAnimationFrame(update);\n }\n\n // the first check runs at mount, so the box is reachable from its first render, not a frame later\n onMounted(update);\n useResizeObserver(target, schedule);\n // the box keeps its size while its content grows or swaps, which changes whether it overflows\n useMutationObserver(target, schedule, { childList: true, subtree: true, attributes: true, attributeFilter: ['disabled', 'tabindex'] });\n\n return computed<0 | undefined>(() => (get(focusable) ? 0 : undefined));\n}\n"],"mappings":";;;;;AAIA,IAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,GAAG;;;;;;;;AASV,SAAgB,cAAc,SAA+B;CAE3D,QADkB,QAAQ,cAAc,QAAQ,cAAc,KAAK,QAAQ,eAAe,QAAQ,eAAe,MAC7F,QAAQ,cAAc,SAAS,MAAM;AAC3D;;;;;;;;AASA,SAAgB,kBAAkB,QAAmF;CACnH,MAAM,YAAY,WAAoB,KAAK;CAC3C,IAAI,YAAY;CAEhB,SAAS,SAAe;EACtB,YAAY;EACZ,MAAM,UAAU,IAAI,MAAM;EAC1B,IAAI,WAAW,UAAU,cAAc,OAAO,IAAI,KAAK;CACzD;;CAGA,SAAS,WAAiB;EACxB,IAAI,WACF;EACF,YAAY;EACZ,sBAAsB,MAAM;CAC9B;CAGA,UAAU,MAAM;CAChB,kBAAkB,QAAQ,QAAQ;CAElC,oBAAoB,QAAQ,UAAU;EAAE,WAAW;EAAM,SAAS;EAAM,YAAY;EAAM,iBAAiB,CAAC,YAAY,UAAU;CAAE,CAAC;CAErI,OAAO,eAA+B,IAAI,SAAS,IAAI,IAAI,KAAA,CAAU;AACvE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { get, set } from "../vendor/@vueuse/shared/dist/index.js";
|
|
1
2
|
import { computed, onScopeDispose, ref, toValue, watch } from "vue";
|
|
2
|
-
import { get, set } from "@vueuse/shared";
|
|
3
3
|
//#region src/composables/overlay-stack.ts
|
|
4
4
|
/**
|
|
5
5
|
* Module scoped rather than per call. Layers stack in the window, not in a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { get, set } from "../vendor/@vueuse/shared/dist/index.js";
|
|
2
|
+
import { useEventListener, useMutationObserver, useResizeObserver } from "../vendor/@vueuse/core/dist/index.js";
|
|
1
3
|
import { onMounted, readonly, shallowRef, toValue } from "vue";
|
|
2
|
-
import { useEventListener, useMutationObserver, useResizeObserver } from "@vueuse/core";
|
|
3
|
-
import { get as get$1, set as set$1 } from "@vueuse/shared";
|
|
4
4
|
//#region src/composables/sticky-header.ts
|
|
5
5
|
var SELECTORS = {
|
|
6
6
|
head: ":scope > thead[data-id=head-main]",
|
|
@@ -124,7 +124,7 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
function watchCellWidth() {
|
|
127
|
-
const root = get
|
|
127
|
+
const root = get(table);
|
|
128
128
|
if (!toValue(sticky) || !root) return;
|
|
129
129
|
const cloneRow = queryHeadRow(root, SELECTORS.headClone);
|
|
130
130
|
const mainRow = queryHeadRow(root, SELECTORS.head);
|
|
@@ -140,7 +140,7 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
|
|
|
140
140
|
observeColumnResizes(cloneColumns, mainColumns);
|
|
141
141
|
}
|
|
142
142
|
function queryStickyElements() {
|
|
143
|
-
const root = get
|
|
143
|
+
const root = get(table);
|
|
144
144
|
if (!toValue(sticky) || !root) return void 0;
|
|
145
145
|
const theadClone = root.querySelector(SELECTORS.headClone);
|
|
146
146
|
const head = root.querySelector(SELECTORS.head);
|
|
@@ -163,12 +163,12 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
|
|
|
163
163
|
head.style.width = `${clonedRect.width}px`;
|
|
164
164
|
const lastRowHeight = getLastRowHeight(root);
|
|
165
165
|
if (lastRowHeight === void 0) {
|
|
166
|
-
set
|
|
166
|
+
set(stick, false);
|
|
167
167
|
positionHeadAtRest(head);
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
if (isInStickyRange(tableRect, top)) {
|
|
171
|
-
set
|
|
171
|
+
set(stick, true);
|
|
172
172
|
positionHeadStuck(head, {
|
|
173
173
|
clonedRect,
|
|
174
174
|
lastRowHeight,
|
|
@@ -177,7 +177,7 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
|
|
|
177
177
|
top
|
|
178
178
|
});
|
|
179
179
|
} else {
|
|
180
|
-
set
|
|
180
|
+
set(stick, false);
|
|
181
181
|
positionHeadAtRest(head);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
@@ -189,7 +189,7 @@ function useStickyTableHeader(sticky = shallowRef(false), offsetTop, tableRefs)
|
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
function onScroll(event) {
|
|
192
|
-
const root = get
|
|
192
|
+
const root = get(table);
|
|
193
193
|
const target = event.target;
|
|
194
194
|
if (!root || !(target instanceof Node) || !target.contains(root)) return;
|
|
195
195
|
throttledToggleStickyClass();
|
|
@@ -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}\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 isSet(value: string | undefined): boolean {\n return value !== undefined && value !== '' && value !== 'none';\n}\n\n/**\n * Whether an element becomes the containing block of its `position: fixed`\n * descendants. The individual `translate`, `rotate` and `scale` properties\n * count like `transform`: Tailwind 4 writes its translate utilities to\n * `translate`, so a dialog centred with `-translate-x-1/2` leaves `transform`\n * at `none`.\n */\nexport function createsFixedContainingBlock(style: CSSStyleDeclaration): boolean {\n return isSet(style.transform)\n || isSet(style.translate)\n || isSet(style.rotate)\n || isSet(style.scale)\n || isSet(style.perspective)\n || isSet(style.filter)\n || isSet(style.backdropFilter)\n || /transform|translate|rotate|scale|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 // the head's own width too: left stale, it squeezes the columns and leaves an untinted strip at the end\n toggleStickyClass();\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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"}
|
|
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 isSet(value: string | undefined): boolean {\n return value !== undefined && value !== '' && value !== 'none';\n}\n\n/**\n * Whether an element becomes the containing block of its `position: fixed`\n * descendants. The individual `translate`, `rotate` and `scale` properties\n * count like `transform`: Tailwind 4 writes its translate utilities to\n * `translate`, so a dialog centred with `-translate-x-1/2` leaves `transform`\n * at `none`.\n */\nexport function createsFixedContainingBlock(style: CSSStyleDeclaration): boolean {\n return isSet(style.transform)\n || isSet(style.translate)\n || isSet(style.rotate)\n || isSet(style.scale)\n || isSet(style.perspective)\n || isSet(style.filter)\n || isSet(style.backdropFilter)\n || /transform|translate|rotate|scale|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 // the head's own width too: left stale, it squeezes the columns and leaves an untinted strip at the end\n toggleStickyClass();\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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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { get } from "../../../vendor/@vueuse/shared/dist/index.js";
|
|
1
2
|
import { TableAlign, getAlignClass } from "../../../components/tables/table-props.js";
|
|
2
3
|
import { getObjectKeys, isHeaderSlot } from "./types.js";
|
|
3
4
|
import { computed, toValue } from "vue";
|
|
4
|
-
import { get } from "@vueuse/shared";
|
|
5
5
|
//#region src/composables/tables/data-table/columns.ts
|
|
6
6
|
function useTableColumns(options) {
|
|
7
7
|
const { cols, columnAttr, expandable, groupKeys, isMobile, rows, selectedData, slots, tdResolver } = options;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { get, set } from "../../../vendor/@vueuse/shared/dist/index.js";
|
|
1
2
|
import { computed } from "vue";
|
|
2
|
-
import { get, set } from "@vueuse/shared";
|
|
3
3
|
//#region src/composables/tables/data-table/expansion.ts
|
|
4
4
|
function useTableExpansion(options, deps) {
|
|
5
5
|
const { rowAttr, singleExpand } = options;
|
|
@@ -1,57 +1,56 @@
|
|
|
1
1
|
import { assert } from "../../../utils/assert.js";
|
|
2
|
+
import { get, isDefined, set } from "../../../vendor/@vueuse/shared/dist/index.js";
|
|
2
3
|
import { GROUP_HEADER_BRAND, isRow } from "./types.js";
|
|
3
4
|
import { computed, ref, watch } from "vue";
|
|
4
|
-
import { isDefined } from "@vueuse/core";
|
|
5
|
-
import { get as get$1, set as set$1 } from "@vueuse/shared";
|
|
6
5
|
//#region src/composables/tables/data-table/grouping.ts
|
|
7
6
|
var GROUP_KEY_SEPARATOR = "\0";
|
|
8
7
|
function useTableGrouping(options, deps) {
|
|
9
8
|
const { rowAttr } = options;
|
|
10
9
|
const { group, collapsed, sorted } = deps;
|
|
11
10
|
const collapsedRows = ref([]);
|
|
12
|
-
watch(collapsed, (value) => set
|
|
11
|
+
watch(collapsed, (value) => set(collapsedRows, value ?? []), { immediate: true });
|
|
13
12
|
const groupKeys = computed(() => {
|
|
14
|
-
const groupBy = get
|
|
13
|
+
const groupBy = get(group);
|
|
15
14
|
if (!groupBy) return [];
|
|
16
15
|
if (!Array.isArray(groupBy)) return [groupBy];
|
|
17
16
|
return groupBy;
|
|
18
17
|
});
|
|
19
18
|
const groupKey = computed(() => {
|
|
20
|
-
return get
|
|
19
|
+
return get(groupKeys).join(":") || void 0;
|
|
21
20
|
});
|
|
22
|
-
const isGrouped = computed(() => get
|
|
23
|
-
const collapsedIdentifierSet = computed(() => new Set(get
|
|
21
|
+
const isGrouped = computed(() => get(groupKey) !== void 0);
|
|
22
|
+
const collapsedIdentifierSet = computed(() => new Set(get(collapsedRows).map((row) => row[rowAttr])));
|
|
24
23
|
const collapsedGroupKeySet = computed(() => {
|
|
25
|
-
const grouping = get
|
|
24
|
+
const grouping = get(groupKeys);
|
|
26
25
|
if (grouping.length === 0) return /* @__PURE__ */ new Set();
|
|
27
26
|
const seen = /* @__PURE__ */ new Set();
|
|
28
|
-
for (const row of get
|
|
27
|
+
for (const row of get(collapsedRows)) seen.add(computeGroupKey(row, grouping));
|
|
29
28
|
return seen;
|
|
30
29
|
});
|
|
31
30
|
function getRowGroup(row) {
|
|
32
31
|
const result = {};
|
|
33
|
-
for (const key of get
|
|
32
|
+
for (const key of get(groupKeys)) result[key] = row[key];
|
|
34
33
|
return result;
|
|
35
34
|
}
|
|
36
35
|
function compareGroupsFn(a, b) {
|
|
37
|
-
const grouping = get
|
|
36
|
+
const grouping = get(groupKeys);
|
|
38
37
|
if (grouping.length === 0) return false;
|
|
39
38
|
return grouping.every((key) => a[key] === b[key]);
|
|
40
39
|
}
|
|
41
40
|
function isExpandedGroup(value) {
|
|
42
|
-
const grouping = get
|
|
41
|
+
const grouping = get(groupKeys);
|
|
43
42
|
let groupVal = "";
|
|
44
43
|
for (const gk of grouping) {
|
|
45
44
|
if (groupVal) groupVal += GROUP_KEY_SEPARATOR;
|
|
46
45
|
const val = value[gk];
|
|
47
46
|
groupVal += isDefined(val) ? val : "";
|
|
48
47
|
}
|
|
49
|
-
return !get
|
|
48
|
+
return !get(collapsedGroupKeySet).has(groupVal);
|
|
50
49
|
}
|
|
51
50
|
function isHiddenRow(row) {
|
|
52
|
-
if (!get
|
|
51
|
+
if (!get(isGrouped)) return false;
|
|
53
52
|
if (!isRow(row)) return false;
|
|
54
|
-
return get
|
|
53
|
+
return get(collapsedIdentifierSet).has(row[rowAttr]);
|
|
55
54
|
}
|
|
56
55
|
function computeGroupKey(row, grouping) {
|
|
57
56
|
let key = "";
|
|
@@ -63,9 +62,9 @@ function useTableGrouping(options, deps) {
|
|
|
63
62
|
return key;
|
|
64
63
|
}
|
|
65
64
|
const mappedGroups = computed(() => {
|
|
66
|
-
const grouping = get
|
|
65
|
+
const grouping = get(groupKeys);
|
|
67
66
|
if (grouping.length === 0) return {};
|
|
68
|
-
const result = get
|
|
67
|
+
const result = get(sorted);
|
|
69
68
|
const identifier = rowAttr;
|
|
70
69
|
const acc = {};
|
|
71
70
|
for (const row of result) {
|
|
@@ -81,31 +80,31 @@ function useTableGrouping(options, deps) {
|
|
|
81
80
|
return acc;
|
|
82
81
|
});
|
|
83
82
|
const grouped = computed(() => {
|
|
84
|
-
const result = get
|
|
85
|
-
if (!get
|
|
86
|
-
const groups = get
|
|
83
|
+
const result = get(sorted);
|
|
84
|
+
if (!get(groupKey)) return result;
|
|
85
|
+
const groups = get(mappedGroups);
|
|
87
86
|
const out = [];
|
|
88
87
|
for (const items of Object.values(groups)) for (const item of items) out.push(item);
|
|
89
88
|
return out;
|
|
90
89
|
});
|
|
91
90
|
function getGroupRows(groupVal) {
|
|
92
|
-
if (!get
|
|
93
|
-
const groupRows = get
|
|
91
|
+
if (!get(isGrouped)) return [];
|
|
92
|
+
const groupRows = get(mappedGroups)[groupVal];
|
|
94
93
|
assert(groupRows);
|
|
95
94
|
return groupRows.filter(isRow);
|
|
96
95
|
}
|
|
97
96
|
function onToggleExpandGroup(groupPartial, value) {
|
|
98
97
|
if (!value) return;
|
|
99
|
-
const currentCollapsed = get
|
|
98
|
+
const currentCollapsed = get(collapsedRows);
|
|
100
99
|
const groupExpanded = isExpandedGroup(groupPartial);
|
|
101
100
|
const groupRows = getGroupRows(value);
|
|
102
|
-
set
|
|
103
|
-
set
|
|
101
|
+
set(collapsedRows, groupExpanded ? [...currentCollapsed, ...groupRows] : currentCollapsed.filter((row) => !compareGroupsFn(row, groupPartial)));
|
|
102
|
+
set(collapsed, get(collapsedRows));
|
|
104
103
|
}
|
|
105
104
|
function onUngroup() {
|
|
106
|
-
set
|
|
107
|
-
set
|
|
108
|
-
set
|
|
105
|
+
set(collapsedRows, []);
|
|
106
|
+
set(collapsed, []);
|
|
107
|
+
set(group, Array.isArray(get(group)) ? [] : void 0);
|
|
109
108
|
}
|
|
110
109
|
return {
|
|
111
110
|
groupKeys,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouping.js","names":[],"sources":["../../../../src/composables/tables/data-table/grouping.ts"],"sourcesContent":["import type { ComputedRef, Ref } from 'vue';\nimport type { TableRowKey, TableRowKeyData } from '@/components/tables/RuiTableHead.vue';\nimport {\n GROUP_HEADER_BRAND,\n type GroupedTableRow,\n type GroupHeader,\n isRow,\n} from '@/composables/tables/data-table/types';\nimport { assert } from '@/utils/assert';\n\nexport interface UseTableGroupingOptions<T extends object, IdType extends keyof T> {\n /** The property on each row used as the unique row identifier. */\n rowAttr: IdType;\n}\n\nexport interface UseTableGroupingDeps<T extends object> {\n group: Ref<TableRowKeyData<T>>;\n collapsed: Ref<T[] | undefined>;\n sorted: ComputedRef<readonly T[]>;\n}\n\nexport interface UseTableGroupingReturn<T extends object> {\n groupKeys: ComputedRef<TableRowKey<T>[]>;\n groupKey: ComputedRef<string | undefined>;\n isGrouped: ComputedRef<boolean>;\n mappedGroups: ComputedRef<Record<string, GroupedTableRow<T>[]>>;\n grouped: ComputedRef<readonly GroupedTableRow<T>[]>;\n getRowGroup: (row: T) => Partial<T>;\n getGroupRows: (groupVal: string) => T[];\n compareGroupsFn: (a: T, b: Partial<T>) => boolean;\n isExpandedGroup: (value: Partial<T>) => boolean;\n isHiddenRow: (row: GroupedTableRow<T>) => boolean;\n onToggleExpandGroup: (group: Partial<T>, value?: string) => void;\n onUngroup: () => void;\n}\n\n// Null character separator prevents false collisions when group values contain commas\nconst GROUP_KEY_SEPARATOR = '\\0';\n\nexport function useTableGrouping<T extends object, IdType extends keyof T>(\n options: UseTableGroupingOptions<T, IdType>,\n deps: UseTableGroupingDeps<T>,\n): UseTableGroupingReturn<T> {\n const { rowAttr } = options;\n const { group, collapsed, sorted } = deps;\n\n const collapsedRows: Ref<T[]> = ref([]);\n\n watch(collapsed, value => set(collapsedRows, value ?? []), { immediate: true });\n\n const groupKeys: ComputedRef<TableRowKey<T>[]> = computed(() => {\n const groupBy = get(group);\n\n if (!groupBy)\n return [];\n\n if (!Array.isArray(groupBy))\n return [groupBy];\n\n return groupBy;\n });\n\n const groupKey = computed<string | undefined>(() => {\n const key = get(groupKeys).join(':');\n return key || undefined;\n });\n\n const isGrouped = computed<boolean>(() => get(groupKey) !== undefined);\n\n const collapsedIdentifierSet = computed<Set<T[IdType]>>(\n () => new Set(get(collapsedRows).map(row => row[rowAttr])),\n );\n\n const collapsedGroupKeySet = computed<Set<string>>(() => {\n const grouping = get(groupKeys);\n if (grouping.length === 0)\n return new Set<string>();\n\n const seen = new Set<string>();\n for (const row of get(collapsedRows))\n seen.add(computeGroupKey(row, grouping));\n return seen;\n });\n\n function getRowGroup(row: T): Partial<T> {\n const result: Partial<T> = {};\n for (const key of get(groupKeys)) {\n result[key] = row[key];\n }\n return result;\n }\n\n function compareGroupsFn(a: T, b: Partial<T>): boolean {\n const grouping = get(groupKeys);\n if (grouping.length === 0)\n return false;\n\n return grouping.every(key => a[key] === b[key]);\n }\n\n function isExpandedGroup(value: Partial<T>): boolean {\n const grouping = get(groupKeys);\n let groupVal = '';\n for (const gk of grouping) {\n if (groupVal)\n groupVal += GROUP_KEY_SEPARATOR;\n const val = value[gk];\n groupVal += isDefined(val) ? val : '';\n }\n return !get(collapsedGroupKeySet).has(groupVal);\n }\n\n function isHiddenRow(row: GroupedTableRow<T>): boolean {\n if (!get(isGrouped))\n return false;\n if (!isRow(row))\n return false;\n return get(collapsedIdentifierSet).has(row[rowAttr]);\n }\n\n function computeGroupKey(row: T, grouping: TableRowKey<T>[]): string {\n let key = '';\n for (const gk of grouping) {\n if (key)\n key += GROUP_KEY_SEPARATOR;\n const val = row[gk];\n key += isDefined(val) ? val : '';\n }\n return key;\n }\n\n const mappedGroups = computed<Record<string, GroupedTableRow<T>[]>>(() => {\n const grouping = get(groupKeys);\n if (grouping.length === 0)\n return {};\n\n const result = get(sorted);\n const identifier = rowAttr;\n const acc: Record<string, GroupedTableRow<T>[]> = {};\n\n for (const row of result) {\n if (!isDefined(row[identifier]) || row[identifier] === '')\n continue;\n\n const groupVal = computeGroupKey(row, grouping);\n if (!acc[groupVal]) {\n acc[groupVal] = [\n {\n [GROUP_HEADER_BRAND]: true,\n group: getRowGroup(row),\n identifier: groupVal,\n } satisfies GroupHeader<T>,\n ];\n }\n\n acc[groupVal].push(row);\n }\n\n return acc;\n });\n\n const grouped = computed<readonly GroupedTableRow<T>[]>(() => {\n const result = get(sorted);\n const groupByKey = get(groupKey);\n\n if (!groupByKey)\n return result;\n\n const groups = get(mappedGroups);\n const out: GroupedTableRow<T>[] = [];\n for (const items of Object.values(groups)) {\n for (const item of items)\n out.push(item);\n }\n return out;\n });\n\n function getGroupRows(groupVal: string): T[] {\n if (!get(isGrouped))\n return [];\n\n const groupRows = get(mappedGroups)[groupVal];\n assert(groupRows);\n return groupRows.filter(isRow);\n }\n\n function onToggleExpandGroup(groupPartial: Partial<T>, value?: string): void {\n if (!value)\n return;\n\n const currentCollapsed = get(collapsedRows);\n\n const groupExpanded = isExpandedGroup(groupPartial);\n\n const groupRows = getGroupRows(value);\n\n set(\n collapsedRows,\n groupExpanded\n ? [...currentCollapsed, ...groupRows]\n : currentCollapsed.filter(row => !compareGroupsFn(row, groupPartial)),\n );\n\n set(collapsed, get(collapsedRows));\n }\n\n function onUngroup(): void {\n set(collapsedRows, []);\n\n set(collapsed, []);\n set(group, Array.isArray(get(group)) ? [] : undefined);\n }\n\n return {\n groupKeys,\n groupKey,\n isGrouped,\n mappedGroups,\n grouped,\n getRowGroup,\n getGroupRows,\n compareGroupsFn,\n isExpandedGroup,\n isHiddenRow,\n onToggleExpandGroup,\n onUngroup,\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"grouping.js","names":[],"sources":["../../../../src/composables/tables/data-table/grouping.ts"],"sourcesContent":["import type { ComputedRef, Ref } from 'vue';\nimport type { TableRowKey, TableRowKeyData } from '@/components/tables/RuiTableHead.vue';\nimport {\n GROUP_HEADER_BRAND,\n type GroupedTableRow,\n type GroupHeader,\n isRow,\n} from '@/composables/tables/data-table/types';\nimport { assert } from '@/utils/assert';\n\nexport interface UseTableGroupingOptions<T extends object, IdType extends keyof T> {\n /** The property on each row used as the unique row identifier. */\n rowAttr: IdType;\n}\n\nexport interface UseTableGroupingDeps<T extends object> {\n group: Ref<TableRowKeyData<T>>;\n collapsed: Ref<T[] | undefined>;\n sorted: ComputedRef<readonly T[]>;\n}\n\nexport interface UseTableGroupingReturn<T extends object> {\n groupKeys: ComputedRef<TableRowKey<T>[]>;\n groupKey: ComputedRef<string | undefined>;\n isGrouped: ComputedRef<boolean>;\n mappedGroups: ComputedRef<Record<string, GroupedTableRow<T>[]>>;\n grouped: ComputedRef<readonly GroupedTableRow<T>[]>;\n getRowGroup: (row: T) => Partial<T>;\n getGroupRows: (groupVal: string) => T[];\n compareGroupsFn: (a: T, b: Partial<T>) => boolean;\n isExpandedGroup: (value: Partial<T>) => boolean;\n isHiddenRow: (row: GroupedTableRow<T>) => boolean;\n onToggleExpandGroup: (group: Partial<T>, value?: string) => void;\n onUngroup: () => void;\n}\n\n// Null character separator prevents false collisions when group values contain commas\nconst GROUP_KEY_SEPARATOR = '\\0';\n\nexport function useTableGrouping<T extends object, IdType extends keyof T>(\n options: UseTableGroupingOptions<T, IdType>,\n deps: UseTableGroupingDeps<T>,\n): UseTableGroupingReturn<T> {\n const { rowAttr } = options;\n const { group, collapsed, sorted } = deps;\n\n const collapsedRows: Ref<T[]> = ref([]);\n\n watch(collapsed, value => set(collapsedRows, value ?? []), { immediate: true });\n\n const groupKeys: ComputedRef<TableRowKey<T>[]> = computed(() => {\n const groupBy = get(group);\n\n if (!groupBy)\n return [];\n\n if (!Array.isArray(groupBy))\n return [groupBy];\n\n return groupBy;\n });\n\n const groupKey = computed<string | undefined>(() => {\n const key = get(groupKeys).join(':');\n return key || undefined;\n });\n\n const isGrouped = computed<boolean>(() => get(groupKey) !== undefined);\n\n const collapsedIdentifierSet = computed<Set<T[IdType]>>(\n () => new Set(get(collapsedRows).map(row => row[rowAttr])),\n );\n\n const collapsedGroupKeySet = computed<Set<string>>(() => {\n const grouping = get(groupKeys);\n if (grouping.length === 0)\n return new Set<string>();\n\n const seen = new Set<string>();\n for (const row of get(collapsedRows))\n seen.add(computeGroupKey(row, grouping));\n return seen;\n });\n\n function getRowGroup(row: T): Partial<T> {\n const result: Partial<T> = {};\n for (const key of get(groupKeys)) {\n result[key] = row[key];\n }\n return result;\n }\n\n function compareGroupsFn(a: T, b: Partial<T>): boolean {\n const grouping = get(groupKeys);\n if (grouping.length === 0)\n return false;\n\n return grouping.every(key => a[key] === b[key]);\n }\n\n function isExpandedGroup(value: Partial<T>): boolean {\n const grouping = get(groupKeys);\n let groupVal = '';\n for (const gk of grouping) {\n if (groupVal)\n groupVal += GROUP_KEY_SEPARATOR;\n const val = value[gk];\n groupVal += isDefined(val) ? val : '';\n }\n return !get(collapsedGroupKeySet).has(groupVal);\n }\n\n function isHiddenRow(row: GroupedTableRow<T>): boolean {\n if (!get(isGrouped))\n return false;\n if (!isRow(row))\n return false;\n return get(collapsedIdentifierSet).has(row[rowAttr]);\n }\n\n function computeGroupKey(row: T, grouping: TableRowKey<T>[]): string {\n let key = '';\n for (const gk of grouping) {\n if (key)\n key += GROUP_KEY_SEPARATOR;\n const val = row[gk];\n key += isDefined(val) ? val : '';\n }\n return key;\n }\n\n const mappedGroups = computed<Record<string, GroupedTableRow<T>[]>>(() => {\n const grouping = get(groupKeys);\n if (grouping.length === 0)\n return {};\n\n const result = get(sorted);\n const identifier = rowAttr;\n const acc: Record<string, GroupedTableRow<T>[]> = {};\n\n for (const row of result) {\n if (!isDefined(row[identifier]) || row[identifier] === '')\n continue;\n\n const groupVal = computeGroupKey(row, grouping);\n if (!acc[groupVal]) {\n acc[groupVal] = [\n {\n [GROUP_HEADER_BRAND]: true,\n group: getRowGroup(row),\n identifier: groupVal,\n } satisfies GroupHeader<T>,\n ];\n }\n\n acc[groupVal].push(row);\n }\n\n return acc;\n });\n\n const grouped = computed<readonly GroupedTableRow<T>[]>(() => {\n const result = get(sorted);\n const groupByKey = get(groupKey);\n\n if (!groupByKey)\n return result;\n\n const groups = get(mappedGroups);\n const out: GroupedTableRow<T>[] = [];\n for (const items of Object.values(groups)) {\n for (const item of items)\n out.push(item);\n }\n return out;\n });\n\n function getGroupRows(groupVal: string): T[] {\n if (!get(isGrouped))\n return [];\n\n const groupRows = get(mappedGroups)[groupVal];\n assert(groupRows);\n return groupRows.filter(isRow);\n }\n\n function onToggleExpandGroup(groupPartial: Partial<T>, value?: string): void {\n if (!value)\n return;\n\n const currentCollapsed = get(collapsedRows);\n\n const groupExpanded = isExpandedGroup(groupPartial);\n\n const groupRows = getGroupRows(value);\n\n set(\n collapsedRows,\n groupExpanded\n ? [...currentCollapsed, ...groupRows]\n : currentCollapsed.filter(row => !compareGroupsFn(row, groupPartial)),\n );\n\n set(collapsed, get(collapsedRows));\n }\n\n function onUngroup(): void {\n set(collapsedRows, []);\n\n set(collapsed, []);\n set(group, Array.isArray(get(group)) ? [] : undefined);\n }\n\n return {\n groupKeys,\n groupKey,\n isGrouped,\n mappedGroups,\n grouped,\n getRowGroup,\n getGroupRows,\n compareGroupsFn,\n isExpandedGroup,\n isHiddenRow,\n onToggleExpandGroup,\n onUngroup,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { MaybeComputedElementRef } from '@vueuse/core';
|
|
2
1
|
import type { MaybeRefOrGetter, Ref } from 'vue';
|
|
2
|
+
import type { MaybeElementRef } from '../../../types/dom';
|
|
3
3
|
export interface UsePageScrollReturn {
|
|
4
4
|
/** Scroll the table's top into view, if it has scrolled above the visible area. */
|
|
5
5
|
revealTableTop: () => Promise<void>;
|
|
@@ -20,11 +20,13 @@ export declare function useStickyPaginationBar(wrapper: Readonly<Ref<HTMLElement
|
|
|
20
20
|
/**
|
|
21
21
|
* Publishes how much of the view's bottom a stuck pagination bar covers, as `--rui-sticky-bottom` on
|
|
22
22
|
* the document root, so the page's own floating parts (a dock, a FAB) can rise above the bar instead of
|
|
23
|
-
* covering its page buttons.
|
|
24
|
-
* sits
|
|
23
|
+
* covering its page buttons. A bar resting at its own place in the bottom band of the view counts too,
|
|
24
|
+
* since it sits in the same corner; it covers from its top down, which is more than its height when it
|
|
25
|
+
* rests above the view's bottom, so the parts keep the same gap above it as above a stuck bar. Only a
|
|
26
|
+
* bar at page level counts: one inside a scrolling area sits under nothing the page floats.
|
|
25
27
|
*
|
|
26
|
-
* @param sentinel - an empty element at the bar's own place after the rows; while it is below the view, the bar
|
|
28
|
+
* @param sentinel - an empty element at the bar's own place after the rows; while it is below the view's bottom band, the bar covers that corner
|
|
27
29
|
* @param bar - the bar, measured for its height
|
|
28
30
|
* @param canStick - whether the bar may stick at all
|
|
29
31
|
*/
|
|
30
|
-
export declare function useStickyBottomInset(sentinel: Readonly<Ref<HTMLElement | null | undefined>>, bar:
|
|
32
|
+
export declare function useStickyBottomInset(sentinel: Readonly<Ref<HTMLElement | null | undefined>>, bar: MaybeElementRef, canStick: MaybeRefOrGetter<boolean>): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useElementSize, useIntersectionObserver } from "
|
|
3
|
-
import {
|
|
1
|
+
import { get, set } from "../../../vendor/@vueuse/shared/dist/index.js";
|
|
2
|
+
import { unrefElement, useElementSize, useEventListener, useIntersectionObserver, useWindowSize } from "../../../vendor/@vueuse/core/dist/index.js";
|
|
3
|
+
import { nextTick, onMounted, onScopeDispose, shallowRef, toValue, watch, watchEffect } from "vue";
|
|
4
4
|
//#region src/composables/tables/data-table/page-scroll.ts
|
|
5
5
|
/**
|
|
6
6
|
* How much of a table, from its top, must be on screen before its pagination bar sticks: the
|
|
@@ -34,7 +34,7 @@ function scrollPageBy(top) {
|
|
|
34
34
|
function usePageScroll(wrapper, stickyOffset) {
|
|
35
35
|
async function revealTableTop() {
|
|
36
36
|
await nextTick();
|
|
37
|
-
const element = get
|
|
37
|
+
const element = get(wrapper);
|
|
38
38
|
if (!element) return;
|
|
39
39
|
const scroller = findScroller(element);
|
|
40
40
|
const viewTop = scroller ? scroller.getBoundingClientRect().top : toValue(stickyOffset) ?? 0;
|
|
@@ -54,19 +54,24 @@ function useStickyPaginationBar(wrapper, enabled) {
|
|
|
54
54
|
const canStick = shallowRef(true);
|
|
55
55
|
const root = shallowRef();
|
|
56
56
|
onMounted(() => {
|
|
57
|
-
const element = get
|
|
58
|
-
if (element) set
|
|
57
|
+
const element = get(wrapper);
|
|
58
|
+
if (element) set(root, findScroller(element));
|
|
59
59
|
});
|
|
60
|
-
useIntersectionObserver(() => toValue(enabled) ? get
|
|
60
|
+
useIntersectionObserver(() => toValue(enabled) ? get(wrapper) : void 0, ([entry]) => {
|
|
61
61
|
if (!entry) return;
|
|
62
|
-
set
|
|
62
|
+
set(canStick, entry.isIntersecting || entry.boundingClientRect.bottom <= (entry.rootBounds?.top ?? 0));
|
|
63
63
|
}, {
|
|
64
64
|
root,
|
|
65
65
|
rootMargin: `0px 0px -${STICK_AFTER}px 0px`
|
|
66
66
|
});
|
|
67
67
|
return canStick;
|
|
68
68
|
}
|
|
69
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* How far above the bar a bar resting at its own place still counts as covered: a part docked in the
|
|
71
|
+
* bottom corner (rotki's task dock is 82px with its offset) reaches over the bar's top by about this much.
|
|
72
|
+
*/
|
|
73
|
+
var RESTING_CLEARANCE = 48;
|
|
74
|
+
/** How much of the view's bottom each pagination bar covers, by table; the page's floating parts clear the most. */
|
|
70
75
|
var stuckBarHeights = /* @__PURE__ */ new Map();
|
|
71
76
|
function publishStickyBottom() {
|
|
72
77
|
if (typeof document === "undefined") return;
|
|
@@ -77,10 +82,12 @@ function publishStickyBottom() {
|
|
|
77
82
|
/**
|
|
78
83
|
* Publishes how much of the view's bottom a stuck pagination bar covers, as `--rui-sticky-bottom` on
|
|
79
84
|
* the document root, so the page's own floating parts (a dock, a FAB) can rise above the bar instead of
|
|
80
|
-
* covering its page buttons.
|
|
81
|
-
* sits
|
|
85
|
+
* covering its page buttons. A bar resting at its own place in the bottom band of the view counts too,
|
|
86
|
+
* since it sits in the same corner; it covers from its top down, which is more than its height when it
|
|
87
|
+
* rests above the view's bottom, so the parts keep the same gap above it as above a stuck bar. Only a
|
|
88
|
+
* bar at page level counts: one inside a scrolling area sits under nothing the page floats.
|
|
82
89
|
*
|
|
83
|
-
* @param sentinel - an empty element at the bar's own place after the rows; while it is below the view, the bar
|
|
90
|
+
* @param sentinel - an empty element at the bar's own place after the rows; while it is below the view's bottom band, the bar covers that corner
|
|
84
91
|
* @param bar - the bar, measured for its height
|
|
85
92
|
* @param canStick - whether the bar may stick at all
|
|
86
93
|
*/
|
|
@@ -88,21 +95,45 @@ function useStickyBottomInset(sentinel, bar, canStick) {
|
|
|
88
95
|
const id = Symbol("sticky-pagination-bar");
|
|
89
96
|
const belowView = shallowRef(false);
|
|
90
97
|
const pageLevel = shallowRef(false);
|
|
98
|
+
const covered = shallowRef(0);
|
|
91
99
|
const { height } = useElementSize(bar, void 0, { box: "border-box" });
|
|
100
|
+
const { height: viewHeight } = useWindowSize();
|
|
101
|
+
let frame = 0;
|
|
102
|
+
/** From the bar's top to the view's bottom; a stuck bar sits on that bottom, so this is its height. */
|
|
103
|
+
function measureCovered() {
|
|
104
|
+
frame = 0;
|
|
105
|
+
const element = unrefElement(bar);
|
|
106
|
+
if (!element) return;
|
|
107
|
+
set(covered, Math.max(get(height), Math.ceil(get(viewHeight) - element.getBoundingClientRect().top)));
|
|
108
|
+
}
|
|
109
|
+
/** A resting bar moves with the page, so while it is in the band, follow it once per frame. */
|
|
110
|
+
function scheduleMeasure() {
|
|
111
|
+
if (get(belowView) && !frame) frame = requestAnimationFrame(measureCovered);
|
|
112
|
+
}
|
|
113
|
+
useEventListener("scroll", scheduleMeasure, {
|
|
114
|
+
capture: true,
|
|
115
|
+
passive: true
|
|
116
|
+
});
|
|
92
117
|
onMounted(() => {
|
|
93
|
-
const element = get
|
|
94
|
-
set
|
|
118
|
+
const element = get(sentinel);
|
|
119
|
+
set(pageLevel, !!element && !findScroller(element));
|
|
95
120
|
});
|
|
96
121
|
useIntersectionObserver(sentinel, ([entry]) => {
|
|
97
122
|
if (!entry) return;
|
|
98
|
-
set
|
|
99
|
-
});
|
|
123
|
+
set(belowView, !entry.isIntersecting && entry.boundingClientRect.top >= (entry.rootBounds?.bottom ?? Number.POSITIVE_INFINITY));
|
|
124
|
+
}, { rootMargin: () => `0px 0px -${Math.ceil(get(height)) + RESTING_CLEARANCE}px 0px` });
|
|
125
|
+
watch([
|
|
126
|
+
belowView,
|
|
127
|
+
height,
|
|
128
|
+
viewHeight
|
|
129
|
+
], () => measureCovered());
|
|
100
130
|
watchEffect(() => {
|
|
101
|
-
if (get
|
|
131
|
+
if (get(pageLevel) && toValue(canStick) && get(belowView) && get(height) > 0) stuckBarHeights.set(id, Math.max(get(height), get(covered)));
|
|
102
132
|
else stuckBarHeights.delete(id);
|
|
103
133
|
publishStickyBottom();
|
|
104
134
|
});
|
|
105
135
|
onScopeDispose(() => {
|
|
136
|
+
if (frame) cancelAnimationFrame(frame);
|
|
106
137
|
stuckBarHeights.delete(id);
|
|
107
138
|
publishStickyBottom();
|
|
108
139
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-scroll.js","names":[],"sources":["../../../../src/composables/tables/data-table/page-scroll.ts"],"sourcesContent":["import type { MaybeComputedElementRef } from '@vueuse/core';\nimport type { MaybeRefOrGetter, Ref } from 'vue';\n\nexport interface UsePageScrollReturn {\n /** Scroll the table's top into view, if it has scrolled above the visible area. */\n revealTableTop: () => Promise<void>;\n}\n\n/**\n * How much of a table, from its top, must be on screen before its pagination bar sticks: the\n * column header and the first rows. A table just scrolling in from below keeps its bar under the\n * rows, rather than showing the bar on top of the header.\n */\nconst STICK_AFTER = 128;\n\n/**\n * The nearest ancestor that scrolls vertically. `undefined` means the page itself does, through\n * the window or a scrolling `<body>`, which is where a sticky offset such as an app bar applies.\n */\nfunction findScroller(element: HTMLElement): HTMLElement | undefined {\n let node = element.parentElement;\n while (node && node !== document.body && node !== document.documentElement) {\n const { overflowY } = getComputedStyle(node);\n if ((overflowY === 'auto' || overflowY === 'scroll') && node.scrollHeight > node.clientHeight)\n return node;\n node = node.parentElement;\n }\n return undefined;\n}\n\nfunction scrollPageBy(top: number): void {\n const body = document.body;\n if (body.scrollHeight > body.clientHeight && ['auto', 'scroll'].includes(getComputedStyle(body).overflowY))\n body.scrollBy({ top });\n else\n window.scrollBy({ top });\n}\n\n/**\n * After a page change, brings the table's first row back into view so the new page reads from the\n * top. It scrolls only when the table's top sits above the visible area, as it does after paging\n * from the bottom bar of a long page; a table that already shows its top stays put, so paging never\n * makes the view jump.\n */\nexport function usePageScroll(\n wrapper: Readonly<Ref<HTMLElement | null | undefined>>,\n stickyOffset: MaybeRefOrGetter<number | undefined>,\n): UsePageScrollReturn {\n async function revealTableTop(): Promise<void> {\n await nextTick();\n const element = get(wrapper);\n if (!element)\n return;\n\n const scroller = findScroller(element);\n const viewTop = scroller ? scroller.getBoundingClientRect().top : (toValue(stickyOffset) ?? 0);\n const distance = element.getBoundingClientRect().top - viewTop;\n if (distance >= 0)\n return;\n\n if (scroller)\n scroller.scrollBy({ top: distance });\n else\n scrollPageBy(distance);\n }\n\n return { revealTableTop };\n}\n\n/**\n * Whether the pagination bar may stick: true once the top {@link STICK_AFTER}px of the table are\n * inside its scrolling area. It starts true, so a page without IntersectionObserver keeps the bar\n * sticky.\n */\nexport function useStickyPaginationBar(\n wrapper: Readonly<Ref<HTMLElement | null | undefined>>,\n enabled: MaybeRefOrGetter<boolean>,\n): Readonly<Ref<boolean>> {\n const canStick = shallowRef<boolean>(true);\n const root = shallowRef<HTMLElement>();\n\n onMounted(() => {\n const element = get(wrapper);\n if (element)\n set(root, findScroller(element));\n });\n\n useIntersectionObserver(\n () => (toValue(enabled) ? get(wrapper) : undefined),\n ([entry]) => {\n if (!entry)\n return;\n // also true once the table has scrolled past the top, when the bar is out of view anyway\n set(canStick, entry.isIntersecting || entry.boundingClientRect.bottom <= (entry.rootBounds?.top ?? 0));\n },\n { root, rootMargin: `0px 0px -${STICK_AFTER}px 0px` },\n );\n\n return canStick;\n}\n\n/** The height of each pagination bar stuck to the page, by table; the page's floating parts clear the tallest. */\nconst stuckBarHeights = new Map<symbol, number>();\n\nfunction publishStickyBottom(): void {\n if (typeof document === 'undefined')\n return;\n const style = document.documentElement.style;\n if (stuckBarHeights.size === 0)\n style.removeProperty('--rui-sticky-bottom');\n else\n style.setProperty('--rui-sticky-bottom', `${Math.max(...stuckBarHeights.values())}px`);\n}\n\n/**\n * Publishes how much of the view's bottom a stuck pagination bar covers, as `--rui-sticky-bottom` on\n * the document root, so the page's own floating parts (a dock, a FAB) can rise above the bar instead of\n * covering its page buttons. Only a bar stuck to the page counts: one stuck inside a scrolling area\n * sits under nothing the page floats.\n *\n * @param sentinel - an empty element at the bar's own place after the rows; while it is below the view, the bar is stuck\n * @param bar - the bar, measured for its height\n * @param canStick - whether the bar may stick at all\n */\nexport function useStickyBottomInset(\n sentinel: Readonly<Ref<HTMLElement | null | undefined>>,\n bar: MaybeComputedElementRef,\n canStick: MaybeRefOrGetter<boolean>,\n): void {\n const id = Symbol('sticky-pagination-bar');\n const belowView = shallowRef<boolean>(false);\n const pageLevel = shallowRef<boolean>(false);\n const { height } = useElementSize(bar, undefined, { box: 'border-box' });\n\n onMounted(() => {\n const element = get(sentinel);\n set(pageLevel, !!element && !findScroller(element));\n });\n\n useIntersectionObserver(sentinel, ([entry]) => {\n if (!entry)\n return;\n // no root bounds (a cross-origin frame) reads as not stuck, which leaves the floating parts where they are\n set(belowView, !entry.isIntersecting && entry.boundingClientRect.top >= (entry.rootBounds?.bottom ?? Number.POSITIVE_INFINITY));\n });\n\n watchEffect(() => {\n if (get(pageLevel) && toValue(canStick) && get(belowView) && get(height) > 0)\n stuckBarHeights.set(id, get(height));\n else\n stuckBarHeights.delete(id);\n publishStickyBottom();\n });\n\n onScopeDispose(() => {\n stuckBarHeights.delete(id);\n publishStickyBottom();\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"page-scroll.js","names":[],"sources":["../../../../src/composables/tables/data-table/page-scroll.ts"],"sourcesContent":["import type { MaybeRefOrGetter, Ref } from 'vue';\nimport type { MaybeElementRef } from '@/types/dom';\n\nexport interface UsePageScrollReturn {\n /** Scroll the table's top into view, if it has scrolled above the visible area. */\n revealTableTop: () => Promise<void>;\n}\n\n/**\n * How much of a table, from its top, must be on screen before its pagination bar sticks: the\n * column header and the first rows. A table just scrolling in from below keeps its bar under the\n * rows, rather than showing the bar on top of the header.\n */\nconst STICK_AFTER = 128;\n\n/**\n * The nearest ancestor that scrolls vertically. `undefined` means the page itself does, through\n * the window or a scrolling `<body>`, which is where a sticky offset such as an app bar applies.\n */\nfunction findScroller(element: HTMLElement): HTMLElement | undefined {\n let node = element.parentElement;\n while (node && node !== document.body && node !== document.documentElement) {\n const { overflowY } = getComputedStyle(node);\n if ((overflowY === 'auto' || overflowY === 'scroll') && node.scrollHeight > node.clientHeight)\n return node;\n node = node.parentElement;\n }\n return undefined;\n}\n\nfunction scrollPageBy(top: number): void {\n const body = document.body;\n if (body.scrollHeight > body.clientHeight && ['auto', 'scroll'].includes(getComputedStyle(body).overflowY))\n body.scrollBy({ top });\n else\n window.scrollBy({ top });\n}\n\n/**\n * After a page change, brings the table's first row back into view so the new page reads from the\n * top. It scrolls only when the table's top sits above the visible area, as it does after paging\n * from the bottom bar of a long page; a table that already shows its top stays put, so paging never\n * makes the view jump.\n */\nexport function usePageScroll(\n wrapper: Readonly<Ref<HTMLElement | null | undefined>>,\n stickyOffset: MaybeRefOrGetter<number | undefined>,\n): UsePageScrollReturn {\n async function revealTableTop(): Promise<void> {\n await nextTick();\n const element = get(wrapper);\n if (!element)\n return;\n\n const scroller = findScroller(element);\n const viewTop = scroller ? scroller.getBoundingClientRect().top : (toValue(stickyOffset) ?? 0);\n const distance = element.getBoundingClientRect().top - viewTop;\n if (distance >= 0)\n return;\n\n if (scroller)\n scroller.scrollBy({ top: distance });\n else\n scrollPageBy(distance);\n }\n\n return { revealTableTop };\n}\n\n/**\n * Whether the pagination bar may stick: true once the top {@link STICK_AFTER}px of the table are\n * inside its scrolling area. It starts true, so a page without IntersectionObserver keeps the bar\n * sticky.\n */\nexport function useStickyPaginationBar(\n wrapper: Readonly<Ref<HTMLElement | null | undefined>>,\n enabled: MaybeRefOrGetter<boolean>,\n): Readonly<Ref<boolean>> {\n const canStick = shallowRef<boolean>(true);\n const root = shallowRef<HTMLElement>();\n\n onMounted(() => {\n const element = get(wrapper);\n if (element)\n set(root, findScroller(element));\n });\n\n useIntersectionObserver(\n () => (toValue(enabled) ? get(wrapper) : undefined),\n ([entry]) => {\n if (!entry)\n return;\n // also true once the table has scrolled past the top, when the bar is out of view anyway\n set(canStick, entry.isIntersecting || entry.boundingClientRect.bottom <= (entry.rootBounds?.top ?? 0));\n },\n { root, rootMargin: `0px 0px -${STICK_AFTER}px 0px` },\n );\n\n return canStick;\n}\n\n/**\n * How far above the bar a bar resting at its own place still counts as covered: a part docked in the\n * bottom corner (rotki's task dock is 82px with its offset) reaches over the bar's top by about this much.\n */\nconst RESTING_CLEARANCE = 48;\n\n/** How much of the view's bottom each pagination bar covers, by table; the page's floating parts clear the most. */\nconst stuckBarHeights = new Map<symbol, number>();\n\nfunction publishStickyBottom(): void {\n if (typeof document === 'undefined')\n return;\n const style = document.documentElement.style;\n if (stuckBarHeights.size === 0)\n style.removeProperty('--rui-sticky-bottom');\n else\n style.setProperty('--rui-sticky-bottom', `${Math.max(...stuckBarHeights.values())}px`);\n}\n\n/**\n * Publishes how much of the view's bottom a stuck pagination bar covers, as `--rui-sticky-bottom` on\n * the document root, so the page's own floating parts (a dock, a FAB) can rise above the bar instead of\n * covering its page buttons. A bar resting at its own place in the bottom band of the view counts too,\n * since it sits in the same corner; it covers from its top down, which is more than its height when it\n * rests above the view's bottom, so the parts keep the same gap above it as above a stuck bar. Only a\n * bar at page level counts: one inside a scrolling area sits under nothing the page floats.\n *\n * @param sentinel - an empty element at the bar's own place after the rows; while it is below the view's bottom band, the bar covers that corner\n * @param bar - the bar, measured for its height\n * @param canStick - whether the bar may stick at all\n */\nexport function useStickyBottomInset(\n sentinel: Readonly<Ref<HTMLElement | null | undefined>>,\n bar: MaybeElementRef,\n canStick: MaybeRefOrGetter<boolean>,\n): void {\n const id = Symbol('sticky-pagination-bar');\n const belowView = shallowRef<boolean>(false);\n const pageLevel = shallowRef<boolean>(false);\n const covered = shallowRef<number>(0);\n const { height } = useElementSize(bar, undefined, { box: 'border-box' });\n const { height: viewHeight } = useWindowSize();\n let frame = 0;\n\n /** From the bar's top to the view's bottom; a stuck bar sits on that bottom, so this is its height. */\n function measureCovered(): void {\n frame = 0;\n const element = unrefElement(bar);\n if (!element)\n return;\n set(covered, Math.max(get(height), Math.ceil(get(viewHeight) - element.getBoundingClientRect().top)));\n }\n\n /** A resting bar moves with the page, so while it is in the band, follow it once per frame. */\n function scheduleMeasure(): void {\n if (get(belowView) && !frame)\n frame = requestAnimationFrame(measureCovered);\n }\n\n useEventListener('scroll', scheduleMeasure, { capture: true, passive: true });\n\n onMounted(() => {\n const element = get(sentinel);\n set(pageLevel, !!element && !findScroller(element));\n });\n\n useIntersectionObserver(sentinel, ([entry]) => {\n if (!entry)\n return;\n // no root bounds (a cross-origin frame) reads as not stuck, which leaves the floating parts where they are\n set(belowView, !entry.isIntersecting && entry.boundingClientRect.top >= (entry.rootBounds?.bottom ?? Number.POSITIVE_INFINITY));\n }, {\n // the view's bottom edge, raised by the band a resting bar covers\n rootMargin: () => `0px 0px -${Math.ceil(get(height)) + RESTING_CLEARANCE}px 0px`,\n });\n\n watch([belowView, height, viewHeight], () => measureCovered());\n\n watchEffect(() => {\n if (get(pageLevel) && toValue(canStick) && get(belowView) && get(height) > 0)\n stuckBarHeights.set(id, Math.max(get(height), get(covered)));\n else\n stuckBarHeights.delete(id);\n publishStickyBottom();\n });\n\n onScopeDispose(() => {\n if (frame)\n cancelAnimationFrame(frame);\n stuckBarHeights.delete(id);\n publishStickyBottom();\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { get, set } from "../../../vendor/@vueuse/shared/dist/index.js";
|
|
1
2
|
import { isRow } from "./types.js";
|
|
2
3
|
import { computed, readonly, ref, shallowRef, watch } from "vue";
|
|
3
|
-
import { get, set } from "@vueuse/shared";
|
|
4
4
|
//#region src/composables/tables/data-table/pagination.ts
|
|
5
5
|
/**
|
|
6
6
|
* Skip data rows until the page start offset, tracking the last group header seen.
|