nuxt-ui-tools 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +3 -1
  3. package/dist/runtime/dashboard/composables/use-dashboard-remote-options.js +8 -3
  4. package/dist/runtime/dashboard/types/filters.d.ts +7 -1
  5. package/dist/runtime/dashboard/utils/builders/dashboard-filters.js +19 -2
  6. package/dist/runtime/form/composables/use-field-options.js +19 -0
  7. package/dist/runtime/form/composables/use-remote-field-options.js +21 -2
  8. package/dist/runtime/form/types/options.d.ts +22 -2
  9. package/dist/runtime/i18n/locales/en.js +23 -2
  10. package/dist/runtime/i18n/locales/fr.js +23 -2
  11. package/dist/runtime/i18n/types.d.ts +21 -0
  12. package/dist/runtime/shared/index.d.ts +1 -0
  13. package/dist/runtime/shared/index.js +1 -0
  14. package/dist/runtime/shared/types/query.d.ts +5 -1
  15. package/dist/runtime/shared/types/remote-options.d.ts +56 -0
  16. package/dist/runtime/shared/utils/define-remote-options.d.ts +23 -0
  17. package/dist/runtime/shared/utils/define-remote-options.js +24 -0
  18. package/dist/runtime/shared/utils/remote-options-query.d.ts +6 -0
  19. package/dist/runtime/shared/utils/remote-options-query.js +18 -0
  20. package/dist/runtime/table/components/data-list/data-list-content.vue +7 -1
  21. package/dist/runtime/table/components/data-list/data-list-error-state.d.vue.ts +18 -1
  22. package/dist/runtime/table/components/data-list/data-list-error-state.vue +65 -41
  23. package/dist/runtime/table/components/data-list/data-list-error-state.vue.d.ts +18 -1
  24. package/dist/runtime/table/components/data-list/data-list-filter-tags.d.vue.ts +1 -1
  25. package/dist/runtime/table/components/data-list/data-list-filter-tags.vue +1 -1
  26. package/dist/runtime/table/components/data-list/data-list-filter-tags.vue.d.ts +1 -1
  27. package/dist/runtime/table/components/data-list/sort-menu-panel.vue +33 -25
  28. package/dist/runtime/table/components/drawers/column-panel.vue +19 -6
  29. package/dist/runtime/table/components/filters/panel/filter-panel.vue +14 -2
  30. package/dist/runtime/table/components/filters/shared/dynamic-filter-picker.vue +1 -1
  31. package/dist/runtime/table/components/filters/shared/filter-editor-header.d.vue.ts +16 -0
  32. package/dist/runtime/table/components/filters/shared/filter-editor-header.vue +46 -0
  33. package/dist/runtime/table/components/filters/shared/filter-editor-header.vue.d.ts +16 -0
  34. package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.d.vue.ts +3 -1
  35. package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +6 -5
  36. package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue.d.ts +3 -1
  37. package/dist/runtime/table/components/filters/shared/filter-option-picker-content.vue +4 -1
  38. package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +4 -4
  39. package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue +12 -8
  40. package/dist/runtime/table/components/filters/shared/filter-popover-shell.vue +10 -1
  41. package/dist/runtime/table/components/filters/shared/filter-searchable-panel.vue +14 -6
  42. package/dist/runtime/table/components/filters/shared/filter-trigger-tag.vue +12 -2
  43. package/dist/runtime/table/components/filters/tags/boolean-filter-tag.d.vue.ts +6 -1
  44. package/dist/runtime/table/components/filters/tags/boolean-filter-tag.vue +19 -5
  45. package/dist/runtime/table/components/filters/tags/boolean-filter-tag.vue.d.ts +6 -1
  46. package/dist/runtime/table/components/filters/tags/date-filter-tag.d.vue.ts +6 -1
  47. package/dist/runtime/table/components/filters/tags/date-filter-tag.vue +65 -29
  48. package/dist/runtime/table/components/filters/tags/date-filter-tag.vue.d.ts +6 -1
  49. package/dist/runtime/table/components/filters/tags/number-filter-tag.d.vue.ts +6 -1
  50. package/dist/runtime/table/components/filters/tags/number-filter-tag.vue +66 -18
  51. package/dist/runtime/table/components/filters/tags/number-filter-tag.vue.d.ts +6 -1
  52. package/dist/runtime/table/components/filters/tags/option-filter-tag.d.vue.ts +2 -2
  53. package/dist/runtime/table/components/filters/tags/option-filter-tag.vue +10 -34
  54. package/dist/runtime/table/components/filters/tags/option-filter-tag.vue.d.ts +2 -2
  55. package/dist/runtime/table/components/filters/tags/text-filter-tag.d.vue.ts +6 -1
  56. package/dist/runtime/table/components/filters/tags/text-filter-tag.vue +39 -14
  57. package/dist/runtime/table/components/filters/tags/text-filter-tag.vue.d.ts +6 -1
  58. package/dist/runtime/table/components/grid/grid-renderer.d.vue.ts +8 -2
  59. package/dist/runtime/table/components/grid/grid-renderer.vue +47 -115
  60. package/dist/runtime/table/components/grid/grid-renderer.vue.d.ts +8 -2
  61. package/dist/runtime/table/components/layout/progress-line.d.vue.ts +9 -0
  62. package/dist/runtime/table/components/layout/progress-line.vue +43 -0
  63. package/dist/runtime/table/components/layout/progress-line.vue.d.ts +9 -0
  64. package/dist/runtime/table/components/table/table-empty-state.d.vue.ts +1 -1
  65. package/dist/runtime/table/components/table/table-empty-state.vue +1 -1
  66. package/dist/runtime/table/components/table/table-empty-state.vue.d.ts +1 -1
  67. package/dist/runtime/table/components/table/table-layout.d.ts +57 -0
  68. package/dist/runtime/table/components/table/table-layout.js +23 -0
  69. package/dist/runtime/table/components/table/table-renderer.d.vue.ts +8 -16
  70. package/dist/runtime/table/components/table/table-renderer.vue +180 -274
  71. package/dist/runtime/table/components/table/table-renderer.vue.d.ts +8 -16
  72. package/dist/runtime/table/components/table/table-row.d.vue.ts +19 -0
  73. package/dist/runtime/table/components/table/table-row.vue +51 -0
  74. package/dist/runtime/table/components/table/table-row.vue.d.ts +19 -0
  75. package/dist/runtime/table/components/table/table-skeleton-cell.d.ts +9 -0
  76. package/dist/runtime/table/components/table/table-skeleton-cell.js +104 -0
  77. package/dist/runtime/table/components/utils/search-query-input.vue +27 -1
  78. package/dist/runtime/table/composables/use-deferred-commit.d.ts +20 -0
  79. package/dist/runtime/table/composables/use-deferred-commit.js +31 -0
  80. package/dist/runtime/table/composables/use-table-data.d.ts +5 -1
  81. package/dist/runtime/table/composables/use-table-data.js +20 -2
  82. package/dist/runtime/table/composables/use-table-filter-selected-options.js +16 -1
  83. package/dist/runtime/table/composables/use-table-summaries.js +10 -1
  84. package/dist/runtime/table/types/columns.d.ts +15 -1
  85. package/dist/runtime/table/types/data-list.d.ts +1 -0
  86. package/dist/runtime/table/types/filters.d.ts +7 -0
  87. package/dist/runtime/table/types/remote-options.d.ts +3 -16
  88. package/dist/runtime/table/utils/columns/defs.js +1 -1
  89. package/dist/runtime/table/utils/filters/date.d.ts +6 -0
  90. package/dist/runtime/table/utils/filters/date.js +26 -2
  91. package/dist/runtime/table/utils/filters/index.d.ts +1 -0
  92. package/dist/runtime/table/utils/filters/index.js +1 -0
  93. package/dist/runtime/table/utils/filters/number.d.ts +5 -0
  94. package/dist/runtime/table/utils/filters/number.js +11 -0
  95. package/dist/runtime/table/utils/filters/ui.js +5 -5
  96. package/dist/runtime/table/utils/remote-table-options.js +37 -37
  97. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { DataListControlSize, DataListTableUi } from '../../types/index.js';
2
+ import type { TableColumnSlot } from './table-layout.js';
2
3
  type __VLS_Props = {
3
4
  gutter?: number;
4
5
  height?: string;
@@ -7,17 +8,8 @@ type __VLS_Props = {
7
8
  ui?: DataListTableUi;
8
9
  };
9
10
  declare function refresh(): void;
10
- type ColumnSlot = {
11
- kind: 'column';
12
- columnId: string;
13
- key: string;
14
- } | {
15
- kind: 'spacer';
16
- colSpan: number;
17
- key: string;
18
- };
19
- declare var __VLS_34: {
20
- columnSlots: ColumnSlot[];
11
+ declare var __VLS_28: {
12
+ columnSlots: TableColumnSlot[];
21
13
  leafColumns: import("@tanstack/table-core").Column<{
22
14
  columnPinningFeature: import("@tanstack/table-core").TableFeature;
23
15
  columnResizingFeature: import("@tanstack/table-core").TableFeature;
@@ -26,16 +18,16 @@ declare var __VLS_34: {
26
18
  coreRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
27
19
  columnMeta: import("../../utils/index.js").DataListColumnMeta;
28
20
  }, object, unknown>[];
29
- }, __VLS_36: {
21
+ }, __VLS_30: {
30
22
  error: {};
31
23
  retry: typeof refresh;
32
- }, __VLS_45: {};
24
+ }, __VLS_39: {};
33
25
  type __VLS_Slots = {} & {
34
- footer?: (props: typeof __VLS_34) => any;
26
+ footer?: (props: typeof __VLS_28) => any;
35
27
  } & {
36
- error?: (props: typeof __VLS_36) => any;
28
+ error?: (props: typeof __VLS_30) => any;
37
29
  } & {
38
- empty?: (props: typeof __VLS_45) => any;
30
+ empty?: (props: typeof __VLS_39) => any;
39
31
  };
40
32
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
41
33
  resetColumnSizing: () => void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * One body row. As a component it is patched by key and re-renders only when its own props change,
3
+ * so rows that stay on screen while the virtual window slides keep their cells untouched.
4
+ */
5
+ import type { HTMLAttributes } from 'vue';
6
+ import type { GenericObject } from '../../types/index.js';
7
+ import type { tableRowCells } from './table-layout.js';
8
+ type __VLS_Props = {
9
+ rowId: string;
10
+ original: GenericObject;
11
+ index: number;
12
+ cells: ReturnType<typeof tableRowCells>;
13
+ selected: boolean;
14
+ appended: boolean;
15
+ rowClass?: HTMLAttributes['class'];
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,51 @@
1
+ <script setup>
2
+ import TableCell from "./table-cell";
3
+ defineProps({
4
+ rowId: { type: String, required: true },
5
+ original: { type: Object, required: true },
6
+ index: { type: Number, required: true },
7
+ cells: { type: null, required: true },
8
+ selected: { type: Boolean, required: true },
9
+ appended: { type: Boolean, required: true },
10
+ rowClass: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true }
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <tr
16
+ class="nut-dl-row group/row"
17
+ :class="[
18
+ selected ? 'nut-dl-row--selected' : '',
19
+ appended ? 'nut-dl-row--appended' : '',
20
+ rowClass
21
+ ]"
22
+ :data-index="index"
23
+ :data-row-id="rowId"
24
+ >
25
+ <template v-for="slot in cells" :key="slot.key">
26
+ <td v-if="slot.kind === 'fill'" class="nut-dl-table__fill p-0" aria-hidden="true" />
27
+ <td
28
+ v-else-if="slot.kind === 'spacer'"
29
+ :colspan="slot.colSpan"
30
+ class="nut-dl-table__spacer p-0"
31
+ aria-hidden="true"
32
+ />
33
+ <td
34
+ v-else-if="slot.layout"
35
+ class="nut-dl-td h-[var(--nut-dl-row-h)] py-0 align-middle"
36
+ :class="slot.layout.class"
37
+ :data-col="slot.columnId"
38
+ :style="slot.layout.style"
39
+ >
40
+ <div class="nut-dl-td__inner min-w-0" :class="slot.layout.innerClass">
41
+ <TableCell
42
+ v-if="slot.layout.render"
43
+ :index="index"
44
+ :render="slot.layout.render"
45
+ :row="original"
46
+ />
47
+ </div>
48
+ </td>
49
+ </template>
50
+ </tr>
51
+ </template>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * One body row. As a component it is patched by key and re-renders only when its own props change,
3
+ * so rows that stay on screen while the virtual window slides keep their cells untouched.
4
+ */
5
+ import type { HTMLAttributes } from 'vue';
6
+ import type { GenericObject } from '../../types/index.js';
7
+ import type { tableRowCells } from './table-layout.js';
8
+ type __VLS_Props = {
9
+ rowId: string;
10
+ original: GenericObject;
11
+ index: number;
12
+ cells: ReturnType<typeof tableRowCells>;
13
+ selected: boolean;
14
+ appended: boolean;
15
+ rowClass?: HTMLAttributes['class'];
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import type { FunctionalComponent } from 'vue';
2
+ import type { TableColumnAlign, TableColumnSkeleton } from '../../types/index.js';
3
+ interface TableSkeletonCellProps {
4
+ skeleton?: TableColumnSkeleton;
5
+ align?: TableColumnAlign;
6
+ seed: number;
7
+ }
8
+ declare const TableSkeletonCell: FunctionalComponent<TableSkeletonCellProps>;
9
+ export default TableSkeletonCell;
@@ -0,0 +1,104 @@
1
+ import { h } from "vue";
2
+ import { isNumber } from "../../../shared/utils/predicate.js";
3
+ const SPREAD = [0.6, 0.15, 0.9, 0.4, 0.75, 0.05, 0.5, 0.3];
4
+ const TEXT_RANGE = [0.38, 0.78];
5
+ const NUMBER_RANGE = [0.28, 0.44];
6
+ const CAPTION_SHARE = 0.62;
7
+ const GAPS = {
8
+ badge: "gap-1",
9
+ dot: "gap-2",
10
+ icon: "gap-1.5"
11
+ };
12
+ function share(options) {
13
+ if (isNumber(options.width)) return options.width;
14
+ const [min, max] = options.width ?? TEXT_RANGE;
15
+ return min + (max - min) * SPREAD[options.seed % SPREAD.length];
16
+ }
17
+ function bar(options) {
18
+ return h("span", {
19
+ class: [
20
+ "nut-dl-skeleton block max-w-full rounded-full",
21
+ options.caption ? "h-[0.55em]" : "h-[0.66em]"
22
+ ],
23
+ style: { width: `${Math.round(options.width * 100)}%` }
24
+ });
25
+ }
26
+ function lines(options) {
27
+ const width = share({ seed: options.seed, width: options.config.width ?? options.range });
28
+ return h(
29
+ "span",
30
+ {
31
+ class: [
32
+ "flex min-w-0 flex-1 flex-col gap-[0.5em]",
33
+ options.align === "right" ? "items-end" : "",
34
+ options.align === "center" ? "items-center" : ""
35
+ ]
36
+ },
37
+ options.lines === 2 ? [bar({ width }), bar({ caption: true, width: width * CAPTION_SHARE })] : [bar({ width })]
38
+ );
39
+ }
40
+ const SHAPES = {
41
+ avatar: (options) => [
42
+ h("span", {
43
+ class: [
44
+ "nut-dl-skeleton size-7 shrink-0",
45
+ options.config.avatar === "circle" ? "rounded-full" : "rounded-md"
46
+ ]
47
+ }),
48
+ lines({ ...options, lines: options.config.lines ?? 2 })
49
+ ],
50
+ badge: (options) => Array.from(
51
+ { length: options.config.count ?? 1 },
52
+ (_, index) => h("span", {
53
+ class: "nut-dl-skeleton block h-[1.55em] shrink-0 rounded-md",
54
+ style: {
55
+ width: `${(3.6 + share({ seed: options.seed + index, width: [0, 1.6] })).toFixed(2)}em`
56
+ }
57
+ })
58
+ ),
59
+ check: () => h("span", { class: "nut-dl-skeleton block size-4 shrink-0 rounded-[4px]" }),
60
+ icon: (options) => [
61
+ h("span", {
62
+ class: [
63
+ "nut-dl-skeleton size-[18px] shrink-0",
64
+ options.config.avatar === "circle" ? "rounded-full" : "rounded-[5px]"
65
+ ]
66
+ }),
67
+ lines({ ...options, lines: options.config.lines ?? 1 })
68
+ ],
69
+ dot: (options) => [
70
+ h("span", { class: "nut-dl-skeleton size-[7px] shrink-0 rounded-full" }),
71
+ lines({ ...options, lines: options.config.lines ?? 1, range: [0.3, 0.6] })
72
+ ],
73
+ number: (options) => lines({ ...options, align: "right", lines: options.config.lines ?? 1, range: NUMBER_RANGE }),
74
+ progress: () => [
75
+ h("span", { class: "nut-dl-skeleton block h-1.5 min-w-0 flex-1 rounded-full" }),
76
+ h("span", { class: "nut-dl-skeleton block h-[0.66em] w-[2.4em] shrink-0 rounded-full" })
77
+ ],
78
+ text: (options) => lines({ ...options, lines: options.config.lines ?? 1 })
79
+ };
80
+ function resolve(skeleton) {
81
+ if (!skeleton) return { kind: "text" };
82
+ return typeof skeleton === "string" ? { kind: skeleton } : skeleton;
83
+ }
84
+ const TableSkeletonCell = (props) => {
85
+ const config = resolve(props.skeleton);
86
+ if (config.kind === "none") return null;
87
+ const align = config.kind === "number" ? "right" : props.align;
88
+ return h(
89
+ "span",
90
+ {
91
+ class: [
92
+ "nut-dl-skeleton-cell flex w-full min-w-0 items-center",
93
+ GAPS[config.kind] ?? "gap-2.5",
94
+ align === "right" ? "justify-end" : "",
95
+ align === "center" ? "justify-center" : ""
96
+ ],
97
+ "data-skeleton": config.kind
98
+ },
99
+ SHAPES[config.kind]({ align, config, seed: props.seed })
100
+ );
101
+ };
102
+ TableSkeletonCell.props = ["skeleton", "align", "seed"];
103
+ TableSkeletonCell.displayName = "DataListTableSkeletonCell";
104
+ export default TableSkeletonCell;
@@ -1,6 +1,8 @@
1
1
  <script setup>
2
+ import UButton from "@nuxt/ui/components/Button.vue";
2
3
  import UInput from "@nuxt/ui/components/Input.vue";
3
4
  import { computed, ref, watch } from "vue";
5
+ import { useUiToolsLocale } from "#ui-tools/i18n";
4
6
  const props = defineProps({
5
7
  placeholder: { type: String, required: true },
6
8
  loading: { type: Boolean, required: false },
@@ -9,7 +11,9 @@ const props = defineProps({
9
11
  inputProps: { type: Object, required: false }
10
12
  });
11
13
  const model = defineModel({ type: String, ...{ required: true } });
14
+ const { t } = useUiToolsLocale();
12
15
  const localValue = ref(model.value);
16
+ const input = ref(null);
13
17
  watch(
14
18
  model,
15
19
  (value) => {
@@ -26,16 +30,23 @@ const inputAttrs = computed(() => ({
26
30
  variant: "outline",
27
31
  ...props.inputProps
28
32
  }));
33
+ const clearable = computed(() => localValue.value.length > 0);
29
34
  function commitValue() {
30
35
  if (localValue.value === model.value) {
31
36
  return;
32
37
  }
33
38
  model.value = localValue.value;
34
39
  }
40
+ function clear() {
41
+ localValue.value = "";
42
+ model.value = "";
43
+ input.value?.inputRef?.focus();
44
+ }
35
45
  </script>
36
46
 
37
47
  <template>
38
48
  <UInput
49
+ ref="input"
39
50
  v-bind="inputAttrs"
40
51
  :model-value="localValue"
41
52
  :size="props.size"
@@ -46,7 +57,22 @@ function commitValue() {
46
57
  @update:model-value="localValue = String($event ?? '')"
47
58
  @blur="commitValue"
48
59
  @keydown.enter.prevent="commitValue"
49
- />
60
+ @keydown.escape="clearable && clear()"
61
+ >
62
+ <template v-if="clearable" #trailing>
63
+ <UButton
64
+ color="neutral"
65
+ variant="link"
66
+ size="xs"
67
+ icon="i-lucide-circle-x"
68
+ :aria-label="t('table.controls.clearSearch')"
69
+ :class="ui?.clear"
70
+ class="nut-dl-search__clear -mr-1 text-dimmed hover:text-default"
71
+ @mousedown.prevent
72
+ @click="clear"
73
+ />
74
+ </template>
75
+ </UInput>
50
76
  </template>
51
77
 
52
78
  <style>
@@ -0,0 +1,20 @@
1
+ /** Delay between the last edit of a typed filter value and its commit to the query. */
2
+ export declare const FILTER_COMMIT_DELAY = 350;
3
+ export interface UseDeferredCommitParams {
4
+ commit: () => void;
5
+ delay?: number;
6
+ /** Commits a pending value when the owner unmounts, for editors embedded in a picker or sheet. */
7
+ flushOnDispose?: boolean;
8
+ }
9
+ /**
10
+ * Commits a typed value after a short pause, on demand (`flush`), or not at all (`cancel`).
11
+ * Editors schedule a commit on every edit, flush it when they close, and cancel it once the value
12
+ * is committed by another path (Enter, clear). Embedded editors unmount with their picker or sheet
13
+ * instead of closing, so they flush on dispose.
14
+ */
15
+ export declare function useDeferredCommit(params: UseDeferredCommitParams): {
16
+ cancel: () => void;
17
+ flush: () => void;
18
+ pending: import("vue").Ref<boolean, boolean>;
19
+ schedule: () => void;
20
+ };
@@ -0,0 +1,31 @@
1
+ import { onScopeDispose, ref } from "vue";
2
+ export const FILTER_COMMIT_DELAY = 350;
3
+ export function useDeferredCommit(params) {
4
+ const pending = ref(false);
5
+ let timer;
6
+ function cancel() {
7
+ if (timer !== void 0) {
8
+ clearTimeout(timer);
9
+ timer = void 0;
10
+ }
11
+ pending.value = false;
12
+ }
13
+ function schedule() {
14
+ cancel();
15
+ pending.value = true;
16
+ timer = setTimeout(() => {
17
+ timer = void 0;
18
+ pending.value = false;
19
+ params.commit();
20
+ }, params.delay ?? FILTER_COMMIT_DELAY);
21
+ }
22
+ function flush() {
23
+ if (!pending.value) {
24
+ return;
25
+ }
26
+ cancel();
27
+ params.commit();
28
+ }
29
+ onScopeDispose(() => params.flushOnDispose ? flush() : cancel());
30
+ return { cancel, flush, pending, schedule };
31
+ }
@@ -1,5 +1,5 @@
1
1
  import { useInfiniteQuery, useQueries, useQuery } from '@tanstack/vue-query';
2
- import type { ComputedRef } from 'vue';
2
+ import type { ComputedRef, Ref } from 'vue';
3
3
  import type { GenericObject, TableExternalState, TableFacetExecutionResult, TableRemoteSourceRequest, TableSchemaView, TableSourceRequestContext, TableRefreshResult, TableRuntimeRecord } from '../types/index.js';
4
4
  import type { UseTableStartupReturn } from './use-table-startup.js';
5
5
  import type { useTableState } from './use-table-state.js';
@@ -36,6 +36,8 @@ export interface UseTableDataReturn {
36
36
  isFetching: boolean;
37
37
  isRefreshing: boolean;
38
38
  isRevalidating: boolean;
39
+ /** Rows of the previous request stay on screen while a new page, sort, filter or search loads. */
40
+ isReplacing: boolean;
39
41
  isContextPending: boolean;
40
42
  isContextFetching: boolean;
41
43
  isDataPending: boolean;
@@ -45,6 +47,8 @@ export interface UseTableDataReturn {
45
47
  }>;
46
48
  refreshContext: () => Promise<TableRefreshResult[]>;
47
49
  refreshData: () => (...args: Parameters<ReturnType<typeof useQuery>['refetch']>) => Promise<TableRefreshResult>;
50
+ /** Counts refresh requests, including ones issued while a request is already running. */
51
+ refreshes: Readonly<Ref<number>>;
48
52
  refreshPageContext: () => Promise<TableRefreshResult[]>;
49
53
  updateRows: (rows: GenericObject[]) => void;
50
54
  }
@@ -1,5 +1,5 @@
1
1
  import { keepPreviousData, useInfiniteQuery, useQueries, useQuery } from "@tanstack/vue-query";
2
- import { computed, shallowRef, watch } from "vue";
2
+ import { computed, readonly, ref, shallowRef, watch } from "vue";
3
3
  import { isArray, isFunction, isNumber, isObject, isString } from "../../shared/utils/predicate.js";
4
4
  import { QUERY_DEFAULTS } from "../constants/query-state.js";
5
5
  import {
@@ -72,9 +72,20 @@ export function useTableData(params) {
72
72
  const usesEmbeddedRemoteFacets = computed(() => remoteSource.value?.facets === true);
73
73
  const facetsContextKey = computed(() => JSON.stringify(facetsBaseContext.value));
74
74
  const lastResolvedEmbeddedFacetsKey = shallowRef(null);
75
+ const facetsCarrierKey = shallowRef(null);
76
+ const dataRequestKey = computed(
77
+ () => JSON.stringify({
78
+ ...facetsBaseContext.value,
79
+ pagination: requestPagination.value,
80
+ sorting: requestSorting.value
81
+ })
82
+ );
83
+ const requestsEmbeddedFacets = computed(
84
+ () => usesEmbeddedRemoteFacets.value && globalFacetDescriptors.value.length > 0 && (facetsContextKey.value !== lastResolvedEmbeddedFacetsKey.value || dataRequestKey.value === facetsCarrierKey.value)
85
+ );
75
86
  const requestContext = computed(() => ({
76
87
  context: contextData.value,
77
- facets: usesEmbeddedRemoteFacets.value && globalFacetDescriptors.value.length > 0 && facetsContextKey.value !== lastResolvedEmbeddedFacetsKey.value ? globalFacetDescriptors.value : void 0,
88
+ facets: requestsEmbeddedFacets.value ? globalFacetDescriptors.value : void 0,
78
89
  filters: requestFilters.value,
79
90
  pagination: requestPagination.value,
80
91
  search: requestSearch.value,
@@ -329,6 +340,7 @@ export function useTableData(params) {
329
340
  isPageContextPending: false,
330
341
  isPending: true,
331
342
  isRefreshing: false,
343
+ isReplacing: false,
332
344
  isRevalidating: false,
333
345
  phase: params.startup.phase.value
334
346
  };
@@ -348,6 +360,7 @@ export function useTableData(params) {
348
360
  isPageContextPending: isPageContextPending.value,
349
361
  isPending: isContextPending.value || !isActiveDataSuccess.value && isDataPending || !initialized.value && isPageContextPending.value,
350
362
  isRefreshing,
363
+ isReplacing: !isCursorPagination.value && query.isPlaceholderData.value && isDataFetching && rawData.value.rows.length > 0,
351
364
  isRevalidating: isActiveDataRefetching.value && rawData.value.rows.length > 0,
352
365
  phase: params.startup.phase.value
353
366
  };
@@ -369,6 +382,7 @@ export function useTableData(params) {
369
382
  }
370
383
  embeddedFacetsState.value = embeddedFacets;
371
384
  lastResolvedEmbeddedFacetsKey.value = facetsContextKey.value;
385
+ facetsCarrierKey.value = dataRequestKey.value;
372
386
  },
373
387
  { immediate: true }
374
388
  );
@@ -385,6 +399,7 @@ export function useTableData(params) {
385
399
  }
386
400
  embeddedFacetsState.value = embeddedFacets;
387
401
  lastResolvedEmbeddedFacetsKey.value = facetsContextKey.value;
402
+ facetsCarrierKey.value = dataRequestKey.value;
388
403
  return;
389
404
  }
390
405
  },
@@ -397,8 +412,10 @@ export function useTableData(params) {
397
412
  params.startup.start();
398
413
  return Promise.all(contextResults.value.map((item) => item.refetch()));
399
414
  }
415
+ const refreshes = ref(0);
400
416
  function refreshData() {
401
417
  return (...args) => {
418
+ refreshes.value += 1;
402
419
  params.startup.start();
403
420
  if (isCursorPagination.value) {
404
421
  cursorRowOverrides.value = /* @__PURE__ */ new Map();
@@ -453,6 +470,7 @@ export function useTableData(params) {
453
470
  rawData,
454
471
  refreshContext,
455
472
  refreshData,
473
+ refreshes: readonly(refreshes),
456
474
  refreshPageContext,
457
475
  requestContext,
458
476
  sourceRequest,
@@ -1,4 +1,4 @@
1
- import { useQuery, useQueryClient } from "@tanstack/vue-query";
1
+ import { hashKey, useQuery, useQueryClient } from "@tanstack/vue-query";
2
2
  import { computed, shallowRef, watch } from "vue";
3
3
  import { isBoolean, isNullish, isNumber, isString } from "../../shared/utils/predicate.js";
4
4
  import { QUERY_DEFAULTS } from "../constants/query-state.js";
@@ -11,6 +11,21 @@ export function useTableFilterSelectedOptions(params) {
11
11
  return remote ? [{ key: definition.key, remote }] : [];
12
12
  })
13
13
  );
14
+ const selectedScopes = computed(
15
+ () => remoteDefinitions.value.map(({ key, remote }) => ({
16
+ identity: remote.selectedQueryKeyFor ? hashKey(remote.selectedQueryKeyFor({ values: [] })) : null,
17
+ key
18
+ }))
19
+ );
20
+ watch(selectedScopes, (current, previous) => {
21
+ const earlier = new Map(previous.map((entry) => [entry.key, entry.identity]));
22
+ const next = new Map(known.value);
23
+ for (const entry of current) {
24
+ if (earlier.has(entry.key) && earlier.get(entry.key) !== entry.identity)
25
+ next.delete(entry.key);
26
+ }
27
+ if (next.size !== known.value.size) known.value = next;
28
+ });
14
29
  function selectedValues(key) {
15
30
  const rule = params.rules.value.find((entry) => entry.key === key);
16
31
  const values = Array.isArray(rule?.value) ? rule.value : isNullish(rule?.value) ? [] : [rule.value];
@@ -7,7 +7,16 @@ export function useTableSummaries(params) {
7
7
  const scopes = computed(
8
8
  () => config.value?.scopes ?? (params.selection.selectionEnabled.value ? ["filtered", "page", "selection"] : ["filtered", "page"])
9
9
  );
10
- const scope = ref(config.value?.scope ?? "filtered");
10
+ const defaultScope = computed(() => config.value?.scope ?? "filtered");
11
+ const scope = ref(defaultScope.value);
12
+ watch(
13
+ () => params.selection.selectedRows.value.length > 0,
14
+ (selected) => {
15
+ if (scopes.value.includes("selection")) {
16
+ scope.value = selected ? "selection" : defaultScope.value;
17
+ }
18
+ }
19
+ );
11
20
  const rows = computed(() => {
12
21
  if (scope.value === "selection") {
13
22
  return params.selection.selectedRows.value;
@@ -1,5 +1,19 @@
1
1
  import type { GenericObject, RenderableType, TableColumnAlign, TableColumnPinned, TableKnownFieldPath, TableFieldValue, TableRowRenderParams, TableSortKey, TableTextValue, TableRuntimeRecord } from './utils.js';
2
- export type TableColumnSkeleton = 'text' | 'avatar' | 'dot' | 'check' | 'badge' | 'number' | 'none';
2
+ /** Placeholder shape drawn in a column while its first page loads. */
3
+ export type TableColumnSkeletonKind = 'text' | 'avatar' | 'icon' | 'dot' | 'check' | 'badge' | 'number' | 'progress' | 'none';
4
+ /** Tunes a placeholder shape so it mirrors the content the column renders. */
5
+ export interface TableColumnSkeletonConfig {
6
+ kind: TableColumnSkeletonKind;
7
+ /** Adds a shorter caption line under the main one. Avatars default to two lines. */
8
+ lines?: 1 | 2;
9
+ /** Draws the avatar or icon as a circle instead of a rounded square. */
10
+ avatar?: 'circle' | 'square';
11
+ /** Share of the cell taken by the main line: a fixed fraction, or a `[min, max]` range varied per row. */
12
+ width?: number | readonly [number, number];
13
+ /** Number of badges drawn side by side. */
14
+ count?: number;
15
+ }
16
+ export type TableColumnSkeleton = TableColumnSkeletonKind | TableColumnSkeletonConfig;
3
17
  export type TableSummaryKind = 'sum' | 'avg' | 'count' | 'min' | 'max';
4
18
  export type TableSummaryScope = 'page' | 'filtered' | 'selection';
5
19
  export type TableSummaryValue = string | number | boolean | Date | null | undefined;
@@ -40,6 +40,7 @@ export interface DataListSearchUi {
40
40
  [slot: string]: DataListUiClass | undefined;
41
41
  root?: DataListUiClass;
42
42
  base?: DataListUiClass;
43
+ clear?: DataListUiClass;
43
44
  leading?: DataListUiClass;
44
45
  leadingIcon?: DataListUiClass;
45
46
  leadingAvatar?: DataListUiClass;
@@ -1,3 +1,4 @@
1
+ import type { QueryKey } from '@tanstack/vue-query';
1
2
  import type { RemoteOptionsPageRequest, RemoteOptionsPagination, RemoteOptionsResult, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
2
3
  import type { TableFilterOperator, TableQueryStateFilterRule, TableQueryStateFilterValue } from './query-state.js';
3
4
  import type { TableFacetExecutionResult, TableFacetRequestDescriptor, TableFacetsContext, TableQueryDefinition } from './source.js';
@@ -566,6 +567,12 @@ export interface TableFilterRemoteOptions<TValue = TableFilterPrimitiveValue, TP
566
567
  resolveSelected?: {
567
568
  bivarianceHack(request: TableFilterRemoteSelectedRequest<TValue>): TableQueryDefinition<readonly TableOptionEntryForPresentation<TValue, TPresentation>[]>;
568
569
  }['bivarianceHack'];
570
+ /** Identity of a selected lookup for reusable loaders with changing external scope. */
571
+ selectedQueryKeyFor?: {
572
+ bivarianceHack(request: {
573
+ values: readonly TValue[];
574
+ }): QueryKey;
575
+ }['bivarianceHack'];
569
576
  /**
570
577
  * Page size, and how far before the list end the next page loads. Defaults to
571
578
  * `{ type: 'page', size: 25 }` with the next page requested three viewport heights ahead.
@@ -1,6 +1,5 @@
1
1
  import type { QueryKey } from '@tanstack/vue-query';
2
- import type { QueryFnDefinition } from '../../shared/types/query.js';
3
- import type { RemoteOptionsPageRequest, RemoteOptionsPagination, RemoteOptionsResult, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
2
+ import type { RemoteOptionsLoader, RemoteOptionsPagination, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
4
3
  import type { TableResolvedFilterGroup } from './filters.js';
5
4
  import type { TableCursorPageResult, TableOffsetPageResult, TableRemoteSourceRequest, TableSourceExecutionResult, TableSourceQueryResult, TableSourceRow } from './source.js';
6
5
  import type { GenericObject, TableKnownFieldPath, TableSortingRule, TableSortKey } from './utils.js';
@@ -47,18 +46,6 @@ export interface RemoteTableOptionsConfig<TRow extends GenericObject, TOption ex
47
46
  }
48
47
  /**
49
48
  * Remote option source built by `remoteTableOptions()`. It fits dashboard `p.remote()`, table
50
- * option filters (`source.remote`), and form remote options (`source: options.load`).
49
+ * option filters (`source.remote`), and form remote options (`loader: options`).
51
50
  */
52
- export interface RemoteTableOptions<TOption> {
53
- /** Query definition of one page of options for a search term. */
54
- load: (request: {
55
- search: string;
56
- page: RemoteOptionsPageRequest;
57
- }) => QueryFnDefinition<RemoteOptionsResult<TOption>>;
58
- /** Query definition of the options of selected values. */
59
- resolveSelected: (request: {
60
- values: readonly (string | number | boolean)[];
61
- }) => QueryFnDefinition<readonly TOption[]>;
62
- pagination: RemoteOptionsPagination;
63
- search?: RemoteOptionsSearch;
64
- }
51
+ export type RemoteTableOptions<TOption> = RemoteOptionsLoader<TOption>;
@@ -87,7 +87,7 @@ export function createColumnDefs(options) {
87
87
  sortableKey: runtimeColumn.sortableKey
88
88
  },
89
89
  minSize: min,
90
- size: Math.max(authored ?? DEFAULT_COLUMN_SIZE, Math.min(floor, 260), min)
90
+ size: Math.max(authored ?? Math.max(DEFAULT_COLUMN_SIZE, Math.min(floor, 260)), min)
91
91
  });
92
92
  }
93
93
  return defs;
@@ -16,13 +16,19 @@ export declare function resolveDateFilterScalarPresets(options: {
16
16
  definition: TableDateFilterDefinition;
17
17
  operator: TableScalarDateFilterOperator;
18
18
  now?: Date;
19
+ translate?: DatePresetTranslator;
19
20
  }): ResolvedTableDateFilterScalarPreset[];
20
21
  export declare function resolveDateFilterRangePresets(options: {
21
22
  definition: TableDateFilterDefinition;
22
23
  now?: Date;
24
+ translate?: DatePresetTranslator;
23
25
  }): ResolvedTableDateFilterRangePreset[];
24
26
  export declare function resolveDateFilterRangeCalendarPanels(options: {
25
27
  definition: TableDateFilterDefinition;
26
28
  mobile: boolean;
27
29
  }): 1 | 2;
28
30
  export declare function resolveDateFilterOperatorDescription(operator: TableDateFilterOperator | undefined): string;
31
+ type DefaultDatePresetKey = 'today' | 'yesterday' | 'sevenDaysAgo' | 'startOfMonth' | 'startOfYear' | 'last7Days' | 'last30Days' | 'thisMonth' | 'lastMonth' | 'yearToDate';
32
+ /** Translates a default preset label; authored presets keep their own text. */
33
+ export type DatePresetTranslator = (key: DefaultDatePresetKey) => string;
34
+ export {};