nuxt-ui-tools 1.1.0 → 1.2.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 (44) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/dashboard/components/block/dashboard-skeleton.vue +6 -3
  3. package/dist/runtime/dashboard/components/dashboard-feed.vue +7 -6
  4. package/dist/runtime/dashboard/composables/use-dashboard-remote-options.js +7 -7
  5. package/dist/runtime/dashboard/utils/options.d.ts +0 -7
  6. package/dist/runtime/dashboard/utils/options.js +0 -5
  7. package/dist/runtime/i18n/locales/en.js +3 -1
  8. package/dist/runtime/i18n/locales/fr.js +3 -1
  9. package/dist/runtime/i18n/types.d.ts +3 -0
  10. package/dist/runtime/shared/styles/scrollbars.css +1 -1
  11. package/dist/runtime/shared/utils/remote-options.d.ts +10 -0
  12. package/dist/runtime/shared/utils/remote-options.js +6 -0
  13. package/dist/runtime/table/components/filters/shared/filter-option-list-end.d.vue.ts +11 -0
  14. package/dist/runtime/table/components/filters/shared/filter-option-list-end.vue +76 -0
  15. package/dist/runtime/table/components/filters/shared/filter-option-list-end.vue.d.ts +11 -0
  16. package/dist/runtime/table/components/filters/shared/filter-option-loading-list.d.vue.ts +3 -0
  17. package/dist/runtime/table/components/filters/shared/filter-option-loading-list.vue +2 -1
  18. package/dist/runtime/table/components/filters/shared/filter-option-loading-list.vue.d.ts +3 -0
  19. package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +1 -1
  20. package/dist/runtime/table/components/filters/shared/filter-option-picker-content.vue +13 -0
  21. package/dist/runtime/table/components/filters/shared/filter-option-single-list.d.vue.ts +2 -0
  22. package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +1 -1
  23. package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue.d.ts +2 -0
  24. package/dist/runtime/table/components/filters/shared/filter-option-tree-content.d.vue.ts +3 -0
  25. package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue +2 -2
  26. package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue.d.ts +3 -0
  27. package/dist/runtime/table/components/filters/shared/filter-searchable-panel.vue +1 -0
  28. package/dist/runtime/table/composables/use-option-filter-editor-state.d.ts +13 -1
  29. package/dist/runtime/table/composables/use-option-filter-editor-state.js +6 -2
  30. package/dist/runtime/table/composables/use-table-filter-options.d.ts +11 -1
  31. package/dist/runtime/table/composables/use-table-filter-options.js +128 -17
  32. package/dist/runtime/table/composables/use-table-filter-selected-options.d.ts +20 -0
  33. package/dist/runtime/table/composables/use-table-filter-selected-options.js +99 -0
  34. package/dist/runtime/table/composables/use-table-filters.d.ts +5 -0
  35. package/dist/runtime/table/composables/use-table-filters.js +20 -1
  36. package/dist/runtime/table/composables/use-table-internals.d.ts +15 -0
  37. package/dist/runtime/table/composables/use-table-remote-filter-options.d.ts +42 -0
  38. package/dist/runtime/table/composables/use-table-remote-filter-options.js +102 -0
  39. package/dist/runtime/table/types/filters.d.ts +42 -0
  40. package/dist/runtime/table/types/source.d.ts +5 -0
  41. package/dist/runtime/table/utils/filters/options.d.ts +2 -0
  42. package/dist/runtime/table/utils/filters/options.js +1 -1
  43. package/dist/runtime/table/utils/prefetch.js +21 -1
  44. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "configKey": "nuxtUiTools",
6
6
  "docs": "https://ui.nuxt.com/docs/getting-started/installation/nuxt",
7
7
  "name": "nuxt-ui-tools",
8
- "version": "1.1.0",
8
+ "version": "1.2.0",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
@@ -73,11 +73,14 @@ const funnelWidths = computed(
73
73
  </div>
74
74
 
75
75
  <!-- Feed: a rail of markers, two lines per event, and a time -->
76
- <div v-else-if="kind === 'feed'" class="flex flex-col">
77
- <div v-for="(width, index) in widths" :key="index" class="flex gap-3 pb-3.5">
76
+ <div v-else-if="kind === 'feed'" class="-my-2 flex flex-col">
77
+ <div v-for="(width, index) in widths" :key="index" class="flex gap-3 py-2">
78
78
  <div class="flex w-7 shrink-0 flex-col items-center">
79
79
  <div class="nut-dash-ghost size-7 rounded-full" />
80
- <div v-if="index < widths.length - 1" class="mt-1 w-px flex-1 bg-[var(--nut-dash-grid)]" />
80
+ <div
81
+ v-if="index < widths.length - 1"
82
+ class="mt-1 -mb-4 w-px flex-1 bg-[var(--nut-dash-grid)]"
83
+ />
81
84
  </div>
82
85
  <div class="flex min-w-0 flex-1 flex-col gap-1.5 pt-1">
83
86
  <div class="flex items-center gap-3">
@@ -71,7 +71,7 @@ const classes = computed(
71
71
  {
72
72
  description: "mt-0.5 block text-xs text-muted",
73
73
  group: "pt-1 pb-2.5 text-[11px] font-semibold tracking-wide text-dimmed uppercase",
74
- item: "flex min-w-0 gap-3",
74
+ item: "flex min-w-0 gap-3 py-2",
75
75
  label: "min-w-0 flex-1 text-[13px] text-default",
76
76
  marker: "relative z-[1] grid size-7 shrink-0 place-items-center overflow-hidden rounded-full",
77
77
  time: "shrink-0 text-[11.5px] text-dimmed tabular-nums"
@@ -158,11 +158,12 @@ function tabulate() {
158
158
 
159
159
  <template #default="{ expanded }">
160
160
  <div class="flex flex-col">
161
- <section v-for="group in groups(expanded)" :key="group.key">
161
+ <section v-for="group in groups(expanded)" :key="group.key" class="not-first:mt-3.5">
162
162
  <h3 v-if="group.time !== void 0" :class="classes.group">
163
163
  {{ clock.day(group.time) }}
164
164
  </h3>
165
- <ol class="flex flex-col">
165
+ <!-- Rows pad evenly so the hover box frames them; the list cancels the outer padding -->
166
+ <ol class="-my-2 flex flex-col">
166
167
  <li
167
168
  v-for="(entry, position) in group.entries"
168
169
  :key="entry.key"
@@ -173,7 +174,7 @@ function tabulate() {
173
174
  entry.selected && DASHBOARD_SELECTED_ROW
174
175
  ]"
175
176
  >
176
- <!-- Rail: the marker, then a line down to the next event of the group -->
177
+ <!-- Rail: the marker, then a line through the row padding to the next marker -->
177
178
  <div class="relative flex w-7 shrink-0 flex-col items-center">
178
179
  <span v-if="entry.avatar" :class="classes.marker" class="bg-elevated">
179
180
  <img :src="entry.avatar" alt="" class="size-full object-cover" />
@@ -194,10 +195,10 @@ function tabulate() {
194
195
  <span
195
196
  v-if="position < group.entries.length - 1"
196
197
  aria-hidden="true"
197
- class="w-px flex-1 bg-[var(--nut-dash-grid)]"
198
+ class="-mb-4 w-px flex-1 bg-[var(--nut-dash-grid)]"
198
199
  />
199
200
  </div>
200
- <div class="flex min-w-0 flex-1 items-baseline gap-3 pt-[5px] pb-3.5">
201
+ <div class="flex min-w-0 flex-1 items-baseline gap-3 pt-[5px]">
201
202
  <div :class="classes.label">
202
203
  {{ entry.label }}
203
204
  <small v-if="entry.description" :class="classes.description">
@@ -1,12 +1,12 @@
1
1
  import { useInfiniteQuery, useQuery } from "@tanstack/vue-query";
2
2
  import { refDebounced } from "@vueuse/core";
3
3
  import { computed, markRaw, shallowRef } from "vue";
4
- import { DASHBOARD_REMOTE_OPTIONS_DEFAULTS } from "../constants/query.js";
5
4
  import {
6
- mergeDashboardOptions,
7
- resolveDashboardOptionValues,
8
- resolveDashboardRemoteNextPage
9
- } from "../utils/options.js";
5
+ REMOTE_OPTIONS_FIRST_PAGE,
6
+ resolveRemoteOptionsNextPage
7
+ } from "../../shared/utils/remote-options.js";
8
+ import { DASHBOARD_REMOTE_OPTIONS_DEFAULTS } from "../constants/query.js";
9
+ import { mergeDashboardOptions, resolveDashboardOptionValues } from "../utils/options.js";
10
10
  export function useDashboardRemoteOptions(params) {
11
11
  const { config } = params;
12
12
  const queryKey = config.queryKey ?? params.queryKey;
@@ -21,8 +21,8 @@ export function useDashboardRemoteOptions(params) {
21
21
  const pages = useInfiniteQuery(
22
22
  computed(() => ({
23
23
  enabled: (open.value || searchInput.value !== "") && (search.value.length === 0 || search.value.length >= minLength),
24
- getNextPageParam: (lastPage, allPages) => resolveDashboardRemoteNextPage(lastPage, allPages.length),
25
- initialPageParam: { cursor: null, index: 1 },
24
+ getNextPageParam: (lastPage, allPages) => resolveRemoteOptionsNextPage(lastPage, allPages.length),
25
+ initialPageParam: REMOTE_OPTIONS_FIRST_PAGE,
26
26
  queryFn: ({ pageParam }) => config.load({ page: { ...pageParam, size: pageSize }, search: search.value }),
27
27
  queryKey: [...queryKey, "options", search.value],
28
28
  staleTime: DASHBOARD_REMOTE_OPTIONS_DEFAULTS.staleTime
@@ -1,12 +1,5 @@
1
- import type { RemoteOptionsResult } from '../../shared/types/remote-options.js';
2
1
  import type { DashboardOption } from '../types/index.js';
3
- export interface DashboardRemotePageParam {
4
- index: number;
5
- cursor: string | null;
6
- }
7
2
  /** Normalizes a single or multiple param value to its string keys, in value order. */
8
3
  export declare function resolveDashboardOptionValues(value: unknown): string[];
9
- /** Next page of a page- or cursor-paginated remote source, or `undefined` at the end. */
10
- export declare function resolveDashboardRemoteNextPage(page: RemoteOptionsResult<DashboardOption<string>>, loadedPages: number): DashboardRemotePageParam | undefined;
11
4
  /** Unique options by value, keeping the first occurrence. */
12
5
  export declare function mergeDashboardOptions(...lists: readonly (readonly DashboardOption[])[]): DashboardOption[];
@@ -3,11 +3,6 @@ export function resolveDashboardOptionValues(value) {
3
3
  if (Array.isArray(value)) return value.filter((entry) => !isNullish(entry)).map(String);
4
4
  return isNullish(value) ? [] : [String(value)];
5
5
  }
6
- export function resolveDashboardRemoteNextPage(page, loadedPages) {
7
- if ("nextCursor" in page)
8
- return page.nextCursor ? { cursor: page.nextCursor, index: loadedPages + 1 } : void 0;
9
- return page.hasMore ? { cursor: null, index: loadedPages + 1 } : void 0;
10
- }
11
6
  export function mergeDashboardOptions(...lists) {
12
7
  const seen = /* @__PURE__ */ new Set();
13
8
  const merged = [];
@@ -352,7 +352,9 @@ export default defineUiToolsLocale({
352
352
  lte: "at most"
353
353
  },
354
354
  options: {
355
- empty: "No matching options."
355
+ empty: "No matching options.",
356
+ loadError: "Options could not be loaded.",
357
+ retry: "Retry"
356
358
  },
357
359
  panel: {
358
360
  apply: "Apply",
@@ -352,7 +352,9 @@ export default defineUiToolsLocale({
352
352
  lte: "au plus"
353
353
  },
354
354
  options: {
355
- empty: "Aucune option correspondante."
355
+ empty: "Aucune option correspondante.",
356
+ loadError: "Impossible de charger les options.",
357
+ retry: "R\xE9essayer"
356
358
  },
357
359
  panel: {
358
360
  apply: "Appliquer",
@@ -78,6 +78,9 @@ export interface UiToolsTableMessages {
78
78
  };
79
79
  options: {
80
80
  empty: string;
81
+ /** Remote option pages failed to load. */
82
+ loadError: string;
83
+ retry: string;
81
84
  };
82
85
  booleans: {
83
86
  true: string;
@@ -1 +1 @@
1
- :root{--nut-scrollbar-size:11px;--nut-scrollbar-gap:3px;--nut-scrollbar-thumb:color-mix(in srgb,var(--ui-text,#1f1d1a) 18%,transparent);--nut-scrollbar-thumb-hover:color-mix(in srgb,var(--ui-text,#1f1d1a) 32%,transparent);--nut-scrollbar-thumb-active:color-mix(in srgb,var(--ui-text,#1f1d1a) 44%,transparent)}@supports not selector(::-webkit-scrollbar){*{scrollbar-color:transparent transparent;scrollbar-width:thin;transition:scrollbar-color .2s ease}:hover{scrollbar-color:var(--nut-scrollbar-thumb) transparent}}@supports selector(::-webkit-scrollbar){::-webkit-scrollbar{background:transparent;height:var(--nut-scrollbar-size);width:var(--nut-scrollbar-size)}::-webkit-scrollbar-corner,::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:transparent;border:var(--nut-scrollbar-gap) solid transparent;border-radius:999px}:hover::-webkit-scrollbar-thumb{background-color:var(--nut-scrollbar-thumb)}::-webkit-scrollbar-thumb:hover{background-color:var(--nut-scrollbar-thumb-hover)}::-webkit-scrollbar-thumb:active{background-color:var(--nut-scrollbar-thumb-active)}}
1
+ :root{--nut-scrollbar-size:11px;--nut-scrollbar-gap:3px;--nut-scrollbar-thumb:color-mix(in srgb,var(--ui-text,#1f1d1a) 18%,transparent);--nut-scrollbar-thumb-hover:color-mix(in srgb,var(--ui-text,#1f1d1a) 32%,transparent);--nut-scrollbar-thumb-active:color-mix(in srgb,var(--ui-text,#1f1d1a) 44%,transparent)}@supports (scrollbar-width:thin){*{scrollbar-color:transparent transparent;scrollbar-width:thin;transition:scrollbar-color .2s ease}:hover{scrollbar-color:var(--nut-scrollbar-thumb) transparent}}@supports not (scrollbar-width:thin){::-webkit-scrollbar{background:transparent;height:var(--nut-scrollbar-size);width:var(--nut-scrollbar-size)}::-webkit-scrollbar-corner,::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:transparent;border:var(--nut-scrollbar-gap) solid transparent;border-radius:999px}:hover::-webkit-scrollbar-thumb{background-color:var(--nut-scrollbar-thumb)}::-webkit-scrollbar-thumb:hover{background-color:var(--nut-scrollbar-thumb-hover)}::-webkit-scrollbar-thumb:active{background-color:var(--nut-scrollbar-thumb-active)}}
@@ -0,0 +1,10 @@
1
+ import type { RemoteOptionsResult } from '../types/remote-options.js';
2
+ /** Position of a page in a remote option source: its 1-based index, and its cursor when paged by cursor. */
3
+ export interface RemoteOptionsPageParam {
4
+ index: number;
5
+ cursor: string | null;
6
+ }
7
+ /** First page of any remote option source. */
8
+ export declare const REMOTE_OPTIONS_FIRST_PAGE: RemoteOptionsPageParam;
9
+ /** Next page of a page- or cursor-paginated remote option source, or `undefined` at the end. */
10
+ export declare function resolveRemoteOptionsNextPage<TOption>(page: RemoteOptionsResult<TOption>, loadedPages: number): RemoteOptionsPageParam | undefined;
@@ -0,0 +1,6 @@
1
+ export const REMOTE_OPTIONS_FIRST_PAGE = { cursor: null, index: 1 };
2
+ export function resolveRemoteOptionsNextPage(page, loadedPages) {
3
+ if ("nextCursor" in page)
4
+ return page.nextCursor ? { cursor: page.nextCursor, index: loadedPages + 1 } : void 0;
5
+ return page.hasMore ? { cursor: null, index: loadedPages + 1 } : void 0;
6
+ }
@@ -0,0 +1,11 @@
1
+ import type { useTableFilterOptions } from '../../../composables/use-table-filter-options.js';
2
+ import type { DataListControlSize, DataListFilterEditorUi } from '../../../types/index.js';
3
+ type __VLS_Props = {
4
+ remote: ReturnType<typeof useTableFilterOptions>['remote'];
5
+ indicator: 'checkbox' | 'radio';
6
+ size?: DataListControlSize;
7
+ ui?: DataListFilterEditorUi;
8
+ };
9
+ 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>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,76 @@
1
+ <script setup>
2
+ import { useEventListener } from "@vueuse/core";
3
+ import { nextTick, shallowRef, watch } from "vue";
4
+ import { useUiToolsLocale } from "#ui-tools/i18n";
5
+ import FilterOptionLoadingList from "./filter-option-loading-list.vue";
6
+ const PREFETCH_VIEWPORTS = 3;
7
+ const props = defineProps({
8
+ remote: { type: null, required: true },
9
+ indicator: { type: String, required: true },
10
+ size: { type: String, required: false },
11
+ ui: { type: Object, required: false }
12
+ });
13
+ const { t } = useUiToolsLocale();
14
+ const sentinel = shallowRef(null);
15
+ const viewport = shallowRef(null);
16
+ watch(sentinel, (element) => {
17
+ viewport.value = findScrollContainer(element);
18
+ });
19
+ useEventListener(viewport, "scroll", loadAhead, { passive: true });
20
+ watch(
21
+ () => [props.remote.hasMore.value, props.remote.loadingMore.value, viewport.value],
22
+ async () => {
23
+ await nextTick();
24
+ loadAhead();
25
+ },
26
+ { immediate: true }
27
+ );
28
+ function loadAhead() {
29
+ const element = viewport.value;
30
+ const { failed, hasMore, loadingMore, prefetchDistance } = props.remote;
31
+ if (!element || element.clientHeight === 0) return;
32
+ if (!hasMore.value || loadingMore.value || failed.value) return;
33
+ const distance = prefetchDistance.value;
34
+ const threshold = distance === "viewport" ? element.clientHeight * PREFETCH_VIEWPORTS : Math.max(0, distance);
35
+ if (element.scrollHeight - element.scrollTop - element.clientHeight <= threshold) {
36
+ props.remote.loadMore();
37
+ }
38
+ }
39
+ function findScrollContainer(element) {
40
+ const marked = element?.closest("[data-filter-option-scroll]");
41
+ if (marked instanceof HTMLElement) return marked;
42
+ for (let current = element?.parentElement; current; current = current.parentElement) {
43
+ if (/(auto|scroll)/.test(getComputedStyle(current).overflowY)) return current;
44
+ }
45
+ return null;
46
+ }
47
+ </script>
48
+
49
+ <template>
50
+ <div data-filter-option-list-end>
51
+ <FilterOptionLoadingList
52
+ v-if="remote.loadingMore.value"
53
+ :count="3"
54
+ :indicator="indicator"
55
+ :size="size"
56
+ :ui="ui"
57
+ data-filter-option-loading-more
58
+ />
59
+ <div
60
+ v-else-if="remote.failed.value"
61
+ class="flex items-center justify-between gap-2 px-2 py-1.5 text-xs text-error"
62
+ data-filter-option-error
63
+ >
64
+ <span class="truncate">{{ t("table.filters.options.loadError") }}</span>
65
+ <button
66
+ type="button"
67
+ class="shrink-0 font-medium underline-offset-2 hover:underline focus-visible:underline focus-visible:outline-none"
68
+ data-filter-option-retry
69
+ @click="remote.retry()"
70
+ >
71
+ {{ t("table.filters.options.retry") }}
72
+ </button>
73
+ </div>
74
+ <span ref="sentinel" aria-hidden="true" class="block h-px w-full" />
75
+ </div>
76
+ </template>
@@ -0,0 +1,11 @@
1
+ import type { useTableFilterOptions } from '../../../composables/use-table-filter-options.js';
2
+ import type { DataListControlSize, DataListFilterEditorUi } from '../../../types/index.js';
3
+ type __VLS_Props = {
4
+ remote: ReturnType<typeof useTableFilterOptions>['remote'];
5
+ indicator: 'checkbox' | 'radio';
6
+ size?: DataListControlSize;
7
+ ui?: DataListFilterEditorUi;
8
+ };
9
+ 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>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -2,9 +2,12 @@ import type { DataListControlSize, DataListFilterEditorUi } from '../../../types
2
2
  type __VLS_Props = {
3
3
  size?: DataListControlSize;
4
4
  indicator?: 'checkbox' | 'radio';
5
+ /** Placeholder rows. */
6
+ count?: number;
5
7
  ui?: DataListFilterEditorUi;
6
8
  };
7
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
10
+ count: number;
8
11
  indicator: "checkbox" | "radio";
9
12
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
13
  declare const _default: typeof __VLS_export;
@@ -6,6 +6,7 @@ import { mergeDataListUiClass, resolveFilterEditorSizeClasses } from "../../../u
6
6
  const props = defineProps({
7
7
  size: { type: String, required: false },
8
8
  indicator: { type: String, required: false, default: "radio" },
9
+ count: { type: Number, required: false, default: 5 },
9
10
  ui: { type: Object, required: false }
10
11
  });
11
12
  const dataListUi = useDataListUi();
@@ -18,7 +19,7 @@ const sizeClasses = computed(() => resolveFilterEditorSizeClasses(resolvedSize.v
18
19
  <template>
19
20
  <div :class="mergeDataListUiClass('grid gap-0.5', void 0, ui?.list)">
20
21
  <div
21
- v-for="index in 5"
22
+ v-for="index in props.count"
22
23
  :key="index"
23
24
  :class="
24
25
  mergeDataListUiClass(
@@ -2,9 +2,12 @@ import type { DataListControlSize, DataListFilterEditorUi } from '../../../types
2
2
  type __VLS_Props = {
3
3
  size?: DataListControlSize;
4
4
  indicator?: 'checkbox' | 'radio';
5
+ /** Placeholder rows. */
6
+ count?: number;
5
7
  ui?: DataListFilterEditorUi;
6
8
  };
7
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
10
+ count: number;
8
11
  indicator: "checkbox" | "radio";
9
12
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
13
  declare const _default: typeof __VLS_export;
@@ -100,7 +100,7 @@ const geometry = computed(() => resolveDataListControlGeometry(size.value));
100
100
  </button>
101
101
 
102
102
  <USkeleton
103
- v-if="props.showCounts && props.countLoading"
103
+ v-if="props.showCounts && (props.countLoading || entry.count == null)"
104
104
  :class="[sizeClasses.skeletonCount, 'shrink-0 rounded-full']"
105
105
  />
106
106
  <span
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { isNullish } from "../../../../shared/utils/predicate";
3
+ import FilterOptionListEnd from "./filter-option-list-end.vue";
3
4
  import FilterOptionLoadingList from "./filter-option-loading-list.vue";
4
5
  import FilterOptionMultipleList from "./filter-option-multiple-list.vue";
5
6
  import FilterOptionSingleList from "./filter-option-single-list.vue";
@@ -105,5 +106,17 @@ function handleSelect(options) {
105
106
  :ui="props.ui"
106
107
  />
107
108
  </template>
109
+
110
+ <FilterOptionListEnd
111
+ v-if="
112
+ props.state.optionSource.remote.enabled.value && !props.state.optionSource.isLoading.value
113
+ "
114
+ :remote="props.state.optionSource.remote"
115
+ :indicator="
116
+ props.state.filterUi.value.selection.mode === 'multiple' || props.state.filterUi.value.presentation === 'tree' ? 'checkbox' : 'radio'
117
+ "
118
+ :size="props.size"
119
+ :ui="props.ui"
120
+ />
108
121
  </FilterSearchablePanel>
109
122
  </template>
@@ -4,6 +4,8 @@ type __VLS_Props = {
4
4
  label: string;
5
5
  value: string;
6
6
  count?: number;
7
+ /** Counts are shown but this option's is not known yet. */
8
+ countPending?: boolean;
7
9
  icon?: string;
8
10
  truncate?: boolean;
9
11
  }[];
@@ -71,7 +71,7 @@ const modelValue = defineModel({ type: null, ...{
71
71
  {{ item.label }}
72
72
  </span>
73
73
  <USkeleton
74
- v-if="countLoading"
74
+ v-if="countLoading || item.countPending"
75
75
  :class="[sizeClasses.skeletonCount, 'shrink-0 rounded-full']"
76
76
  />
77
77
  <span v-else-if="item.count != null" class="shrink-0 text-muted">
@@ -4,6 +4,8 @@ type __VLS_Props = {
4
4
  label: string;
5
5
  value: string;
6
6
  count?: number;
7
+ /** Counts are shown but this option's is not known yet. */
8
+ countPending?: boolean;
7
9
  icon?: string;
8
10
  truncate?: boolean;
9
11
  }[];
@@ -4,6 +4,8 @@ interface TreeEntry {
4
4
  label: string;
5
5
  value?: string | number | boolean;
6
6
  count?: number;
7
+ /** Counts are shown but this option's is not known yet. */
8
+ countPending?: boolean;
7
9
  icon?: string;
8
10
  depth: number;
9
11
  expandable: boolean;
@@ -21,6 +23,7 @@ type __VLS_Props = {
21
23
  label: string;
22
24
  value: string;
23
25
  count?: number;
26
+ countPending?: boolean;
24
27
  icon?: string;
25
28
  truncate?: boolean;
26
29
  depth: number;
@@ -138,7 +138,7 @@ function activateTreeEntry(entry) {
138
138
  </button>
139
139
 
140
140
  <USkeleton
141
- v-if="countLoading"
141
+ v-if="countLoading || entry.countPending"
142
142
  :class="[sizeClasses.skeletonCount, 'shrink-0 rounded-full']"
143
143
  />
144
144
  <span
@@ -210,7 +210,7 @@ function activateTreeEntry(entry) {
210
210
  {{ item.label }}
211
211
  </span>
212
212
  <USkeleton
213
- v-if="countLoading"
213
+ v-if="countLoading || item.countPending"
214
214
  :class="[sizeClasses.skeletonCount, 'shrink-0 rounded-full']"
215
215
  />
216
216
  <span v-else-if="item.count != null" class="shrink-0 text-muted">
@@ -4,6 +4,8 @@ interface TreeEntry {
4
4
  label: string;
5
5
  value?: string | number | boolean;
6
6
  count?: number;
7
+ /** Counts are shown but this option's is not known yet. */
8
+ countPending?: boolean;
7
9
  icon?: string;
8
10
  depth: number;
9
11
  expandable: boolean;
@@ -21,6 +23,7 @@ type __VLS_Props = {
21
23
  label: string;
22
24
  value: string;
23
25
  count?: number;
26
+ countPending?: boolean;
24
27
  icon?: string;
25
28
  truncate?: boolean;
26
29
  depth: number;
@@ -59,6 +59,7 @@ const sizeClasses = computed(() => resolveFilterEditorSizeClasses(size));
59
59
  </div>
60
60
 
61
61
  <UScrollArea
62
+ data-filter-option-scroll
62
63
  type="hover"
63
64
  :class="
64
65
  mergeDataListUiClass(
@@ -30,6 +30,7 @@ export declare function useOptionFilterEditorState(options: UseOptionFilterEdito
30
30
  flatRadioItems: ComputedRef<{
31
31
  color: string | undefined;
32
32
  count: number | undefined;
33
+ countPending: boolean;
33
34
  icon: string | undefined;
34
35
  label: string;
35
36
  truncate: boolean;
@@ -47,7 +48,16 @@ export declare function useOptionFilterEditorState(options: UseOptionFilterEdito
47
48
  isError: ComputedRef<boolean>;
48
49
  isLoading: ComputedRef<boolean>;
49
50
  isStaleLoading: ComputedRef<boolean>;
50
- refresh: () => Promise<[import("@tanstack/vue-query").QueryObserverResult<TableFilterOptionEntry[] | import("../types/index.js").TableFilterOptionQueryResult, Error>, import("@tanstack/vue-query").QueryObserverResult<import("../types/index.js").TableFacetExecutionResult<string, unknown>, Error>]>;
51
+ refresh: () => Promise<[import("@tanstack/vue-query").QueryObserverResult<import("@tanstack/vue-query").InfiniteData<import("../../shared/index.js").RemoteOptionsPage<TableFilterOptionEntry> | import("../../shared/index.js").RemoteCursorOptionsPage<TableFilterOptionEntry>, import("../../shared/utils/remote-options.js").RemoteOptionsPageParam>, Error> | import("@tanstack/vue-query").QueryObserverResult<TableFilterOptionEntry[] | import("../types/index.js").TableFilterOptionQueryResult, Error>, import("@tanstack/vue-query").QueryObserverResult<import("../types/index.js").TableFacetExecutionResult<string, unknown>, Error>]>;
52
+ remote: {
53
+ enabled: ComputedRef<boolean>;
54
+ failed: ComputedRef<boolean>;
55
+ hasMore: ComputedRef<boolean>;
56
+ loadMore: () => void;
57
+ loadingMore: ComputedRef<boolean>;
58
+ prefetchDistance: ComputedRef<number | "viewport">;
59
+ retry: () => Promise<import("@tanstack/vue-query").QueryObserverResult<import("@tanstack/vue-query").InfiniteData<import("../../shared/index.js").RemoteOptionsPage<TableFilterOptionEntry> | import("../../shared/index.js").RemoteCursorOptionsPage<TableFilterOptionEntry>, import("../../shared/utils/remote-options.js").RemoteOptionsPageParam>, Error>>;
60
+ };
51
61
  searchExpandedIds: ComputedRef<string[]>;
52
62
  showCounts: ComputedRef<boolean>;
53
63
  sourceEntries: ComputedRef<(TableResolvedFilterOptionEntry<import("../types/index.js").TableFilterPrimitiveValue> & {
@@ -68,6 +78,7 @@ export declare function useOptionFilterEditorState(options: UseOptionFilterEdito
68
78
  treeRadioItems: ComputedRef<{
69
79
  color: string | undefined;
70
80
  count: number | undefined;
81
+ countPending: boolean;
71
82
  depth: number;
72
83
  disabled: boolean;
73
84
  expandable: boolean;
@@ -82,6 +93,7 @@ export declare function useOptionFilterEditorState(options: UseOptionFilterEdito
82
93
  treeRadioValue: import("vue").WritableComputedRef<string | undefined, string | undefined>;
83
94
  triggerSummary: ComputedRef<string>;
84
95
  visibleTreeEntries: ComputedRef<{
96
+ countPending: boolean;
85
97
  expanded: boolean;
86
98
  indeterminate: boolean;
87
99
  id: string;
@@ -88,6 +88,7 @@ export function useOptionFilterEditorState(options) {
88
88
  if (!entry.branchSelectable) {
89
89
  return {
90
90
  ...entry,
91
+ countPending: optionSource.showCounts.value && isNullish(entry.count),
91
92
  expanded: effectiveExpandedIds.value.has(entry.id),
92
93
  indeterminate: false
93
94
  };
@@ -102,6 +103,7 @@ export function useOptionFilterEditorState(options) {
102
103
  ).length;
103
104
  return {
104
105
  ...entry,
106
+ countPending: optionSource.showCounts.value && isNullish(entry.count),
105
107
  expanded: effectiveExpandedIds.value.has(entry.id),
106
108
  indeterminate: selectedCount > 0 && selectedCount < descendantValues.length,
107
109
  selected: descendantValues.length > 0 && selectedCount === descendantValues.length
@@ -111,7 +113,8 @@ export function useOptionFilterEditorState(options) {
111
113
  const flatRadioItems = computed(
112
114
  () => displayEntries.value.map((entry) => ({
113
115
  color: entry.color,
114
- count: optionSource.showCounts.value ? entry.count ?? 0 : void 0,
116
+ count: optionSource.showCounts.value ? entry.count : void 0,
117
+ countPending: optionSource.showCounts.value && isNullish(entry.count),
115
118
  icon: entry.icon,
116
119
  label: entry.label,
117
120
  truncate: filterUi.value.row.truncate,
@@ -134,7 +137,8 @@ export function useOptionFilterEditorState(options) {
134
137
  const treeRadioItems = computed(
135
138
  () => visibleTreeEntries.value.map((entry) => ({
136
139
  color: entry.color,
137
- count: optionSource.showCounts.value ? entry.count ?? 0 : void 0,
140
+ count: optionSource.showCounts.value ? entry.count : void 0,
141
+ countPending: optionSource.showCounts.value && isNullish(entry.count),
138
142
  depth: entry.depth,
139
143
  disabled: !entry.selectable,
140
144
  expandable: entry.expandable,
@@ -22,7 +22,17 @@ export declare function useTableFilterOptions(options: UseTableFilterOptionsPara
22
22
  isError: ComputedRef<boolean>;
23
23
  isLoading: ComputedRef<boolean>;
24
24
  isStaleLoading: ComputedRef<boolean>;
25
- refresh: () => Promise<[import("@tanstack/vue-query").QueryObserverResult<TableFilterOptionEntry[] | TableFilterOptionQueryResult, Error>, import("@tanstack/vue-query").QueryObserverResult<TableFacetExecutionResult<string, unknown>, Error>]>;
25
+ refresh: () => Promise<[import("@tanstack/vue-query").QueryObserverResult<import("@tanstack/vue-query").InfiniteData<import("../../shared/index.js").RemoteOptionsPage<TableFilterOptionEntry> | import("../../shared/index.js").RemoteCursorOptionsPage<TableFilterOptionEntry>, import("../../shared/utils/remote-options.js").RemoteOptionsPageParam>, Error> | import("@tanstack/vue-query").QueryObserverResult<TableFilterOptionEntry[] | TableFilterOptionQueryResult, Error>, import("@tanstack/vue-query").QueryObserverResult<TableFacetExecutionResult<string, unknown>, Error>]>;
26
+ /** Paging of `source.remote`: the editor list loads the next page ahead of the scroll. */
27
+ remote: {
28
+ enabled: ComputedRef<boolean>;
29
+ failed: ComputedRef<boolean>;
30
+ hasMore: ComputedRef<boolean>;
31
+ loadMore: () => void;
32
+ loadingMore: ComputedRef<boolean>;
33
+ prefetchDistance: ComputedRef<number | "viewport">;
34
+ retry: () => Promise<import("@tanstack/vue-query").QueryObserverResult<import("@tanstack/vue-query").InfiniteData<import("../../shared/index.js").RemoteOptionsPage<TableFilterOptionEntry> | import("../../shared/index.js").RemoteCursorOptionsPage<TableFilterOptionEntry>, import("../../shared/utils/remote-options.js").RemoteOptionsPageParam>, Error>>;
35
+ };
26
36
  searchExpandedIds: ComputedRef<string[]>;
27
37
  showCounts: ComputedRef<boolean>;
28
38
  sourceEntries: ComputedRef<(TableResolvedFilterOptionEntry<import("../types/index.js").TableFilterPrimitiveValue> & {