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.
- package/dist/module.json +1 -1
- package/dist/runtime/dashboard/components/block/dashboard-skeleton.vue +6 -3
- package/dist/runtime/dashboard/components/dashboard-feed.vue +7 -6
- package/dist/runtime/dashboard/composables/use-dashboard-remote-options.js +7 -7
- package/dist/runtime/dashboard/utils/options.d.ts +0 -7
- package/dist/runtime/dashboard/utils/options.js +0 -5
- package/dist/runtime/i18n/locales/en.js +3 -1
- package/dist/runtime/i18n/locales/fr.js +3 -1
- package/dist/runtime/i18n/types.d.ts +3 -0
- package/dist/runtime/shared/styles/scrollbars.css +1 -1
- package/dist/runtime/shared/utils/remote-options.d.ts +10 -0
- package/dist/runtime/shared/utils/remote-options.js +6 -0
- package/dist/runtime/table/components/filters/shared/filter-option-list-end.d.vue.ts +11 -0
- package/dist/runtime/table/components/filters/shared/filter-option-list-end.vue +76 -0
- package/dist/runtime/table/components/filters/shared/filter-option-list-end.vue.d.ts +11 -0
- package/dist/runtime/table/components/filters/shared/filter-option-loading-list.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-option-loading-list.vue +2 -1
- package/dist/runtime/table/components/filters/shared/filter-option-loading-list.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +1 -1
- package/dist/runtime/table/components/filters/shared/filter-option-picker-content.vue +13 -0
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.d.vue.ts +2 -0
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +1 -1
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue.d.ts +2 -0
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-searchable-panel.vue +1 -0
- package/dist/runtime/table/composables/use-option-filter-editor-state.d.ts +13 -1
- package/dist/runtime/table/composables/use-option-filter-editor-state.js +6 -2
- package/dist/runtime/table/composables/use-table-filter-options.d.ts +11 -1
- package/dist/runtime/table/composables/use-table-filter-options.js +128 -17
- package/dist/runtime/table/composables/use-table-filter-selected-options.d.ts +20 -0
- package/dist/runtime/table/composables/use-table-filter-selected-options.js +99 -0
- package/dist/runtime/table/composables/use-table-filters.d.ts +5 -0
- package/dist/runtime/table/composables/use-table-filters.js +20 -1
- package/dist/runtime/table/composables/use-table-internals.d.ts +15 -0
- package/dist/runtime/table/composables/use-table-remote-filter-options.d.ts +42 -0
- package/dist/runtime/table/composables/use-table-remote-filter-options.js +102 -0
- package/dist/runtime/table/types/filters.d.ts +42 -0
- package/dist/runtime/table/types/source.d.ts +5 -0
- package/dist/runtime/table/utils/filters/options.d.ts +2 -0
- package/dist/runtime/table/utils/filters/options.js +1 -1
- package/dist/runtime/table/utils/prefetch.js +21 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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]
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "../
|
|
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) =>
|
|
25
|
-
initialPageParam:
|
|
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",
|
|
@@ -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
|
|
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
|
|
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>
|
|
@@ -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 @@ 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;
|
|
@@ -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
|
|
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
|
|
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> & {
|