nuxt-ui-tools 1.2.0 → 1.3.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 (179) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +10 -1
  3. package/dist/runtime/dashboard/components/block/dashboard-block-state.d.vue.ts +2 -0
  4. package/dist/runtime/dashboard/components/block/dashboard-block-state.vue +4 -1
  5. package/dist/runtime/dashboard/components/block/dashboard-block-state.vue.d.ts +2 -0
  6. package/dist/runtime/dashboard/components/block/dashboard-chips.d.vue.ts +20 -0
  7. package/dist/runtime/dashboard/components/block/dashboard-chips.vue +42 -0
  8. package/dist/runtime/dashboard/components/block/dashboard-chips.vue.d.ts +20 -0
  9. package/dist/runtime/dashboard/components/block/dashboard-series-picker.d.vue.ts +8 -0
  10. package/dist/runtime/dashboard/components/block/dashboard-series-picker.vue +29 -0
  11. package/dist/runtime/dashboard/components/block/dashboard-series-picker.vue.d.ts +8 -0
  12. package/dist/runtime/dashboard/components/block/dashboard-skeleton.d.vue.ts +1 -1
  13. package/dist/runtime/dashboard/components/block/dashboard-skeleton.vue.d.ts +1 -1
  14. package/dist/runtime/dashboard/components/block/dashboard-view-scope.d.vue.ts +21 -0
  15. package/dist/runtime/dashboard/components/block/dashboard-view-scope.vue +12 -0
  16. package/dist/runtime/dashboard/components/block/dashboard-view-scope.vue.d.ts +21 -0
  17. package/dist/runtime/dashboard/components/dashboard-alerts.vue +3 -3
  18. package/dist/runtime/dashboard/components/dashboard-bar-chart.d.vue.ts +14 -3
  19. package/dist/runtime/dashboard/components/dashboard-bar-chart.vue +30 -9
  20. package/dist/runtime/dashboard/components/dashboard-bar-chart.vue.d.ts +14 -3
  21. package/dist/runtime/dashboard/components/dashboard-bars.vue +4 -4
  22. package/dist/runtime/dashboard/components/dashboard-card.vue +28 -6
  23. package/dist/runtime/dashboard/components/dashboard-combo-chart.d.vue.ts +6 -1
  24. package/dist/runtime/dashboard/components/dashboard-combo-chart.vue +13 -3
  25. package/dist/runtime/dashboard/components/dashboard-combo-chart.vue.d.ts +6 -1
  26. package/dist/runtime/dashboard/components/dashboard-donut-chart.vue +5 -5
  27. package/dist/runtime/dashboard/components/dashboard-feed.vue +1 -1
  28. package/dist/runtime/dashboard/components/dashboard-filter.d.vue.ts +67 -0
  29. package/dist/runtime/dashboard/components/dashboard-filter.vue +134 -0
  30. package/dist/runtime/dashboard/components/dashboard-filter.vue.d.ts +67 -0
  31. package/dist/runtime/dashboard/components/dashboard-filters.d.vue.ts +49 -0
  32. package/dist/runtime/dashboard/components/dashboard-filters.vue +96 -0
  33. package/dist/runtime/dashboard/components/dashboard-filters.vue.d.ts +49 -0
  34. package/dist/runtime/dashboard/components/dashboard-funnel.vue +5 -5
  35. package/dist/runtime/dashboard/components/dashboard-gauge.vue +3 -3
  36. package/dist/runtime/dashboard/components/dashboard-grid.d.vue.ts +10 -3
  37. package/dist/runtime/dashboard/components/dashboard-grid.vue +24 -9
  38. package/dist/runtime/dashboard/components/dashboard-grid.vue.d.ts +10 -3
  39. package/dist/runtime/dashboard/components/dashboard-line-chart.d.vue.ts +14 -3
  40. package/dist/runtime/dashboard/components/dashboard-line-chart.vue +30 -9
  41. package/dist/runtime/dashboard/components/dashboard-line-chart.vue.d.ts +14 -3
  42. package/dist/runtime/dashboard/components/dashboard-list.d.vue.ts +3 -0
  43. package/dist/runtime/dashboard/components/dashboard-list.vue +18 -6
  44. package/dist/runtime/dashboard/components/dashboard-list.vue.d.ts +3 -0
  45. package/dist/runtime/dashboard/components/dashboard-page.d.vue.ts +53 -0
  46. package/dist/runtime/dashboard/components/dashboard-page.vue +156 -0
  47. package/dist/runtime/dashboard/components/dashboard-page.vue.d.ts +53 -0
  48. package/dist/runtime/dashboard/components/dashboard-paired-bars.vue +4 -4
  49. package/dist/runtime/dashboard/components/dashboard-refresh.d.vue.ts +1 -1
  50. package/dist/runtime/dashboard/components/dashboard-refresh.vue +1 -0
  51. package/dist/runtime/dashboard/components/dashboard-refresh.vue.d.ts +1 -1
  52. package/dist/runtime/dashboard/components/dashboard-stack-bar.vue +4 -4
  53. package/dist/runtime/dashboard/components/dashboard-stat.d.vue.ts +14 -2
  54. package/dist/runtime/dashboard/components/dashboard-stat.vue +28 -10
  55. package/dist/runtime/dashboard/components/dashboard-stat.vue.d.ts +14 -2
  56. package/dist/runtime/dashboard/components/dashboard-stats.vue +3 -3
  57. package/dist/runtime/dashboard/components/dashboard-table.d.vue.ts +1 -1
  58. package/dist/runtime/dashboard/components/dashboard-table.vue +3 -3
  59. package/dist/runtime/dashboard/components/dashboard-table.vue.d.ts +1 -1
  60. package/dist/runtime/dashboard/components/dashboard-tabs.d.vue.ts +1 -1
  61. package/dist/runtime/dashboard/components/dashboard-tabs.vue.d.ts +1 -1
  62. package/dist/runtime/dashboard/components/dashboard-total.d.vue.ts +7 -2
  63. package/dist/runtime/dashboard/components/dashboard-total.vue +10 -2
  64. package/dist/runtime/dashboard/components/dashboard-total.vue.d.ts +7 -2
  65. package/dist/runtime/dashboard/components/dashboard-view-tabs.d.vue.ts +33 -0
  66. package/dist/runtime/dashboard/components/dashboard-view-tabs.vue +66 -0
  67. package/dist/runtime/dashboard/components/dashboard-view-tabs.vue.d.ts +33 -0
  68. package/dist/runtime/dashboard/components/dashboard-widget.vue +1 -1
  69. package/dist/runtime/dashboard/components/filter/dashboard-filter-menu.d.vue.ts +44 -0
  70. package/dist/runtime/dashboard/components/filter/dashboard-filter-menu.vue +220 -0
  71. package/dist/runtime/dashboard/components/filter/dashboard-filter-menu.vue.d.ts +44 -0
  72. package/dist/runtime/dashboard/composables/use-dashboard-api.d.ts +6 -1
  73. package/dist/runtime/dashboard/composables/use-dashboard-api.js +29 -6
  74. package/dist/runtime/dashboard/composables/use-dashboard-block.d.ts +5 -4
  75. package/dist/runtime/dashboard/composables/use-dashboard-block.js +32 -15
  76. package/dist/runtime/dashboard/composables/use-dashboard-chart.d.ts +8 -1
  77. package/dist/runtime/dashboard/composables/use-dashboard-chart.js +16 -4
  78. package/dist/runtime/dashboard/composables/use-dashboard-context.d.ts +50 -0
  79. package/dist/runtime/dashboard/composables/use-dashboard-context.js +56 -0
  80. package/dist/runtime/dashboard/composables/use-dashboard-derived.js +6 -1
  81. package/dist/runtime/dashboard/composables/use-dashboard-filter-control.d.ts +16 -0
  82. package/dist/runtime/dashboard/composables/use-dashboard-filter-control.js +161 -0
  83. package/dist/runtime/dashboard/composables/use-dashboard-filter-scope.d.ts +37 -0
  84. package/dist/runtime/dashboard/composables/use-dashboard-filter-scope.js +107 -0
  85. package/dist/runtime/dashboard/composables/use-dashboard-format.d.ts +46 -6
  86. package/dist/runtime/dashboard/composables/use-dashboard-format.js +51 -8
  87. package/dist/runtime/dashboard/composables/use-dashboard-options.d.ts +11 -4
  88. package/dist/runtime/dashboard/composables/use-dashboard-options.js +33 -47
  89. package/dist/runtime/dashboard/composables/use-dashboard-remote-options.d.ts +8 -5
  90. package/dist/runtime/dashboard/composables/use-dashboard-remote-options.js +59 -70
  91. package/dist/runtime/dashboard/composables/use-dashboard-resource.d.ts +15 -4
  92. package/dist/runtime/dashboard/composables/use-dashboard-resource.js +52 -17
  93. package/dist/runtime/dashboard/composables/use-dashboard-scope.d.ts +10 -7
  94. package/dist/runtime/dashboard/composables/use-dashboard-scope.js +26 -16
  95. package/dist/runtime/dashboard/composables/use-dashboard-series-picker.d.ts +20 -0
  96. package/dist/runtime/dashboard/composables/use-dashboard-series-picker.js +30 -0
  97. package/dist/runtime/dashboard/composables/use-dashboard-time.d.ts +2 -0
  98. package/dist/runtime/dashboard/composables/use-dashboard-time.js +4 -1
  99. package/dist/runtime/dashboard/composables/use-dashboard-ui.d.ts +12 -2
  100. package/dist/runtime/dashboard/composables/use-dashboard-ui.js +3 -0
  101. package/dist/runtime/dashboard/composables/use-dashboard-views.d.ts +15 -3
  102. package/dist/runtime/dashboard/composables/use-dashboard-views.js +33 -10
  103. package/dist/runtime/dashboard/composables/use-dashboard.d.ts +18 -12
  104. package/dist/runtime/dashboard/composables/use-dashboard.js +98 -9
  105. package/dist/runtime/dashboard/index.d.ts +3 -1
  106. package/dist/runtime/dashboard/index.js +3 -1
  107. package/dist/runtime/dashboard/schema/index.d.ts +62 -30
  108. package/dist/runtime/dashboard/schema/index.js +3 -0
  109. package/dist/runtime/dashboard/types/blocks.d.ts +30 -4
  110. package/dist/runtime/dashboard/types/charts.d.ts +2 -2
  111. package/dist/runtime/dashboard/types/controls.d.ts +123 -0
  112. package/dist/runtime/dashboard/types/filters.d.ts +236 -0
  113. package/dist/runtime/dashboard/types/index.d.ts +3 -2
  114. package/dist/runtime/dashboard/types/page.d.ts +17 -0
  115. package/dist/runtime/dashboard/types/page.js +0 -0
  116. package/dist/runtime/dashboard/types/resource.d.ts +66 -28
  117. package/dist/runtime/dashboard/types/runtime.d.ts +39 -8
  118. package/dist/runtime/dashboard/types/schema.d.ts +94 -54
  119. package/dist/runtime/dashboard/types/ui.d.ts +78 -0
  120. package/dist/runtime/dashboard/utils/builders/dashboard-filters.d.ts +142 -0
  121. package/dist/runtime/dashboard/utils/builders/dashboard-filters.js +135 -0
  122. package/dist/runtime/dashboard/utils/charts.d.ts +4 -15
  123. package/dist/runtime/dashboard/utils/charts.js +26 -28
  124. package/dist/runtime/dashboard/utils/{params/codecs.d.ts → codecs.d.ts} +2 -2
  125. package/dist/runtime/dashboard/utils/{params/codecs.js → codecs.js} +1 -1
  126. package/dist/runtime/dashboard/utils/environment.d.ts +36 -0
  127. package/dist/runtime/dashboard/utils/environment.js +30 -0
  128. package/dist/runtime/dashboard/utils/filters.d.ts +44 -0
  129. package/dist/runtime/dashboard/utils/filters.js +62 -0
  130. package/dist/runtime/dashboard/utils/format.d.ts +25 -0
  131. package/dist/runtime/dashboard/utils/format.js +66 -0
  132. package/dist/runtime/dashboard/utils/grid.d.ts +17 -0
  133. package/dist/runtime/dashboard/utils/grid.js +14 -0
  134. package/dist/runtime/dashboard/utils/options.d.ts +3 -1
  135. package/dist/runtime/dashboard/utils/options.js +6 -0
  136. package/dist/runtime/dashboard/utils/remote.d.ts +7 -0
  137. package/dist/runtime/dashboard/utils/remote.js +14 -0
  138. package/dist/runtime/dashboard/utils/resource.d.ts +16 -7
  139. package/dist/runtime/dashboard/utils/resource.js +15 -9
  140. package/dist/runtime/dashboard/utils/schema.d.ts +8 -3
  141. package/dist/runtime/dashboard/utils/schema.js +9 -4
  142. package/dist/runtime/dashboard/utils/state.d.ts +11 -6
  143. package/dist/runtime/dashboard/utils/state.js +10 -5
  144. package/dist/runtime/dashboard/utils/time.d.ts +2 -0
  145. package/dist/runtime/dashboard/utils/time.js +11 -1
  146. package/dist/runtime/dashboard/utils/ui.d.ts +1 -1
  147. package/dist/runtime/dashboard/utils/ui.js +1 -1
  148. package/dist/runtime/form/components/root/form.vue +1 -1
  149. package/dist/runtime/form/fields/card-selection.d.ts +1 -0
  150. package/dist/runtime/form/fields/card-selection.js +1 -0
  151. package/dist/runtime/form/fields/checkbox-card/component.vue +3 -1
  152. package/dist/runtime/form/fields/checkbox-group/component.vue +5 -0
  153. package/dist/runtime/form/fields/radio/component.vue +10 -0
  154. package/dist/runtime/form/fields/radio-card/component.vue +3 -1
  155. package/dist/runtime/form/types/options.d.ts +1 -11
  156. package/dist/runtime/form/utils/focus.js +1 -1
  157. package/dist/runtime/i18n/locales/en.js +29 -0
  158. package/dist/runtime/i18n/locales/fr.js +29 -0
  159. package/dist/runtime/i18n/types.d.ts +41 -0
  160. package/dist/runtime/shared/styles/tokens.css +1 -1
  161. package/dist/runtime/shared/types/query.d.ts +8 -0
  162. package/dist/runtime/table/types/index.d.ts +1 -0
  163. package/dist/runtime/table/types/remote-options.d.ts +64 -0
  164. package/dist/runtime/table/types/remote-options.js +0 -0
  165. package/dist/runtime/table/types/source.d.ts +10 -1
  166. package/dist/runtime/table/utils/builders/table-source.d.ts +2 -6
  167. package/dist/runtime/table/utils/index.d.ts +1 -0
  168. package/dist/runtime/table/utils/index.js +1 -0
  169. package/dist/runtime/table/utils/remote-table-options.d.ts +26 -0
  170. package/dist/runtime/table/utils/remote-table-options.js +88 -0
  171. package/package.json +4 -1
  172. package/dist/runtime/dashboard/composables/use-dashboard-param-scope.d.ts +0 -30
  173. package/dist/runtime/dashboard/composables/use-dashboard-param-scope.js +0 -71
  174. package/dist/runtime/dashboard/types/options.d.ts +0 -46
  175. package/dist/runtime/dashboard/types/params.d.ts +0 -115
  176. package/dist/runtime/dashboard/utils/builders/dashboard-params.d.ts +0 -85
  177. package/dist/runtime/dashboard/utils/builders/dashboard-params.js +0 -87
  178. /package/dist/runtime/dashboard/types/{options.js → controls.js} +0 -0
  179. /package/dist/runtime/dashboard/types/{params.js → filters.js} +0 -0
@@ -9,7 +9,7 @@ declare const __VLS_export: <TValue extends string | number>(__VLS_props: NonNul
9
9
  full?: boolean;
10
10
  } & {
11
11
  /**
12
- * Tab strip for dashboard cards, bound with `v-model`. Bind it to a widget param to switch what a
12
+ * Tab strip for dashboard cards, bound with `v-model`. Bind it to a query filter to switch what a
13
13
  * block fetches (the tab lands in the URL, and the block refreshes in place), or to a local ref to
14
14
  * switch between blocks: render only the active one, so the others' deferred queries stay idle.
15
15
  */
@@ -1,8 +1,13 @@
1
1
  import type { LazyTextValue } from '#ui-tools/shared/types/utils';
2
- import type { DashboardTotalUi } from '../types/index.js';
2
+ import type { DashboardTotalUi, DashboardValueFormat } from '../types/index.js';
3
3
  type __VLS_Props = {
4
4
  label: LazyTextValue;
5
- value: LazyTextValue;
5
+ /**
6
+ * Numbers go through `format` (locale number format by default); text shows as is; `null` /
7
+ * `undefined` show "—".
8
+ */
9
+ value: LazyTextValue | null | undefined;
10
+ format?: DashboardValueFormat;
6
11
  ui?: DashboardTotalUi;
7
12
  };
8
13
  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>;
@@ -1,16 +1,24 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
+ import { isNumber } from "#ui-tools/shared/utils/predicate";
3
4
  import { resolveTextValue } from "#ui-tools/shared/utils/render";
5
+ import { DASHBOARD_MISSING_VALUE, useDashboardFormat } from "../composables/use-dashboard-format";
4
6
  import { useDashboardUi } from "../composables/use-dashboard-ui";
5
7
  import { resolveDashboardClasses } from "../utils/ui";
6
8
  const props = defineProps({
7
9
  label: { type: [String, Number, Function], required: true },
8
- value: { type: [String, Number, Function], required: true },
10
+ value: { type: [String, Number, Function, null], required: true, skipCheck: true },
11
+ format: { type: [String, Function], required: false, skipCheck: true },
9
12
  ui: { type: Object, required: false }
10
13
  });
11
14
  const appUi = useDashboardUi();
15
+ const formats = useDashboardFormat();
12
16
  const label = computed(() => resolveTextValue(props.label));
13
- const value = computed(() => resolveTextValue(props.value));
17
+ const value = computed(() => {
18
+ const resolved = typeof props.value === "function" ? props.value() : props.value;
19
+ if (resolved === null || resolved === void 0) return DASHBOARD_MISSING_VALUE;
20
+ return isNumber(resolved) ? formats.resolve(props.format)(resolved) : resolveTextValue(resolved);
21
+ });
14
22
  const classes = computed(
15
23
  () => resolveDashboardClasses(
16
24
  {
@@ -1,8 +1,13 @@
1
1
  import type { LazyTextValue } from '#ui-tools/shared/types/utils';
2
- import type { DashboardTotalUi } from '../types/index.js';
2
+ import type { DashboardTotalUi, DashboardValueFormat } from '../types/index.js';
3
3
  type __VLS_Props = {
4
4
  label: LazyTextValue;
5
- value: LazyTextValue;
5
+ /**
6
+ * Numbers go through `format` (locale number format by default); text shows as is; `null` /
7
+ * `undefined` show "—".
8
+ */
9
+ value: LazyTextValue | null | undefined;
10
+ format?: DashboardValueFormat;
6
11
  ui?: DashboardTotalUi;
7
12
  };
8
13
  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>;
@@ -0,0 +1,33 @@
1
+ import type { DashboardViewController, DashboardViewTabsUi } from '../types/index.js';
2
+ declare const __VLS_export: <TKey extends string>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
+ dashboard: {
5
+ readonly view: DashboardViewController<TKey>;
6
+ };
7
+ ui?: DashboardViewTabsUi;
8
+ }> & (typeof globalThis extends {
9
+ __VLS_PROPS_FALLBACK: infer P;
10
+ } ? P : {});
11
+ expose: (exposed: {}) => void;
12
+ attrs: any;
13
+ slots: {
14
+ /** Content of one tab. */
15
+ tab?: (props: {
16
+ item: {
17
+ value: TKey;
18
+ label: string;
19
+ };
20
+ active: boolean;
21
+ }) => unknown;
22
+ };
23
+ emit: {};
24
+ }>) => import("vue").VNode & {
25
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
26
+ };
27
+ declare const _default: typeof __VLS_export;
28
+ export default _default;
29
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
30
+ [K in keyof T]: T[K];
31
+ } : {
32
+ [K in keyof T as K]: T[K];
33
+ }) & {};
@@ -0,0 +1,66 @@
1
+ <script setup>
2
+ import { computed, nextTick, onMounted, useTemplateRef, watch } from "vue";
3
+ import { useUiToolsLocale } from "#ui-tools/i18n";
4
+ import { useDashboardUi } from "../composables/use-dashboard-ui";
5
+ import { resolveDashboardClasses } from "../utils/ui";
6
+ const { dashboard, ui } = defineProps({
7
+ dashboard: { type: Object, required: true },
8
+ ui: { type: Object, required: false }
9
+ });
10
+ defineSlots();
11
+ const EDGE = 16;
12
+ const { t } = useUiToolsLocale();
13
+ const appUi = useDashboardUi();
14
+ const strip = useTemplateRef("strip");
15
+ const classes = computed(
16
+ () => resolveDashboardClasses(
17
+ {
18
+ root: "flex min-w-0 gap-0.5 overflow-x-auto border-b border-default [scrollbar-width:none]",
19
+ tab: "relative inline-flex h-10 flex-none items-center px-3 text-sm font-medium whitespace-nowrap text-muted transition-colors outline-none hover:text-default focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset data-[active]:text-highlighted data-[active]:after:absolute data-[active]:after:inset-x-2 data-[active]:after:-bottom-px data-[active]:after:h-0.5 data-[active]:after:rounded-full data-[active]:after:bg-primary"
20
+ },
21
+ appUi.value.viewTabs,
22
+ ui
23
+ )
24
+ );
25
+ function reveal(behavior) {
26
+ const element = strip.value;
27
+ const tab = element?.querySelector('[aria-current="page"]');
28
+ if (!element || !tab) return;
29
+ const bounds = element.getBoundingClientRect();
30
+ const box = tab.getBoundingClientRect();
31
+ const overflow = box.left < bounds.left + EDGE ? box.left - bounds.left - EDGE : Math.max(0, box.right - bounds.right + EDGE);
32
+ if (overflow) element.scrollBy({ behavior, left: overflow });
33
+ }
34
+ onMounted(() => reveal("instant"));
35
+ watch(
36
+ () => dashboard.view.current,
37
+ async () => {
38
+ await nextTick();
39
+ reveal("smooth");
40
+ }
41
+ );
42
+ </script>
43
+
44
+ <template>
45
+ <nav
46
+ v-if="dashboard.view.items.length > 1"
47
+ ref="strip"
48
+ :class="classes.root"
49
+ :aria-label="t('dashboard.filters.views')"
50
+ data-dashboard-view-tabs
51
+ >
52
+ <button
53
+ v-for="item in dashboard.view.items"
54
+ :key="item.value"
55
+ type="button"
56
+ :class="classes.tab"
57
+ :data-active="dashboard.view.current === item.value || void 0"
58
+ :aria-current="dashboard.view.current === item.value ? 'page' : void 0"
59
+ @click="dashboard.view.current = item.value"
60
+ >
61
+ <slot name="tab" :item :active="dashboard.view.current === item.value">
62
+ {{ item.label }}
63
+ </slot>
64
+ </button>
65
+ </nav>
66
+ </template>
@@ -0,0 +1,33 @@
1
+ import type { DashboardViewController, DashboardViewTabsUi } from '../types/index.js';
2
+ declare const __VLS_export: <TKey extends string>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
+ dashboard: {
5
+ readonly view: DashboardViewController<TKey>;
6
+ };
7
+ ui?: DashboardViewTabsUi;
8
+ }> & (typeof globalThis extends {
9
+ __VLS_PROPS_FALLBACK: infer P;
10
+ } ? P : {});
11
+ expose: (exposed: {}) => void;
12
+ attrs: any;
13
+ slots: {
14
+ /** Content of one tab. */
15
+ tab?: (props: {
16
+ item: {
17
+ value: TKey;
18
+ label: string;
19
+ };
20
+ active: boolean;
21
+ }) => unknown;
22
+ };
23
+ emit: {};
24
+ }>) => import("vue").VNode & {
25
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
26
+ };
27
+ declare const _default: typeof __VLS_export;
28
+ export default _default;
29
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
30
+ [K in keyof T]: T[K];
31
+ } : {
32
+ [K in keyof T as K]: T[K];
33
+ }) & {};
@@ -15,12 +15,12 @@ const {
15
15
  title: { type: [String, Number, Function], required: false },
16
16
  subtitle: { type: [String, Number, Function], required: false },
17
17
  size: { type: String, required: false },
18
- rows: { type: String, required: false },
19
18
  card: { type: Boolean, required: false },
20
19
  activation: { type: String, required: false },
21
20
  empty: { type: Object, required: false },
22
21
  menu: { type: [Boolean, Array, Function], required: false },
23
22
  actions: { type: Array, required: false },
23
+ filters: { type: Array, required: false },
24
24
  freshness: { type: Boolean, required: false },
25
25
  ui: { type: Object, required: false },
26
26
  source: { type: Object, required: true },
@@ -0,0 +1,44 @@
1
+ import type { DashboardFilterControl, DashboardFilterUi, DashboardOption, DashboardOptionValue } from '../../types/index.js';
2
+ declare const __VLS_export: <TValue, TItem extends DashboardOptionValue>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
+ filter: DashboardFilterControl<TValue, TItem>;
5
+ classes: Required<DashboardFilterUi>;
6
+ /** Heading above the list. */
7
+ title?: string;
8
+ /** What the menu lists: the options, the presets, or both (presets under the options). */
9
+ list?: "all" | "options" | "presets";
10
+ } & {
11
+ onPicked?: (() => any) | undefined;
12
+ }> & (typeof globalThis extends {
13
+ __VLS_PROPS_FALLBACK: infer P;
14
+ } ? P : {});
15
+ expose: (exposed: {}) => void;
16
+ attrs: any;
17
+ slots: {
18
+ item?: (props: {
19
+ item: DashboardOption<TItem>;
20
+ selected: boolean;
21
+ disabled: boolean;
22
+ filter: DashboardFilterControl<TValue, TItem>;
23
+ }) => unknown;
24
+ header?: (props: {
25
+ filter: DashboardFilterControl<TValue, TItem>;
26
+ }) => unknown;
27
+ footer?: (props: {
28
+ filter: DashboardFilterControl<TValue, TItem>;
29
+ }) => unknown;
30
+ empty?: (props: {
31
+ filter: DashboardFilterControl<TValue, TItem>;
32
+ }) => unknown;
33
+ };
34
+ emit: (evt: "picked") => void;
35
+ }>) => import("vue").VNode & {
36
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
37
+ };
38
+ declare const _default: typeof __VLS_export;
39
+ export default _default;
40
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
41
+ [K in keyof T]: T[K];
42
+ } : {
43
+ [K in keyof T as K]: T[K];
44
+ }) & {};
@@ -0,0 +1,220 @@
1
+ <script setup>
2
+ import UIcon from "@nuxt/ui/components/Icon.vue";
3
+ import { useEventListener } from "@vueuse/core";
4
+ import { computed, nextTick, useTemplateRef, watch } from "vue";
5
+ import { useUiToolsLocale } from "#ui-tools/i18n";
6
+ const PREFETCH_VIEWPORTS = 3;
7
+ const {
8
+ filter,
9
+ classes,
10
+ title,
11
+ list = "all"
12
+ } = defineProps({
13
+ filter: { type: Object, required: true },
14
+ classes: { type: Object, required: true },
15
+ title: { type: String, required: false },
16
+ list: { type: String, required: false }
17
+ });
18
+ const emit = defineEmits(["picked"]);
19
+ defineSlots();
20
+ const { t } = useUiToolsLocale();
21
+ const listbox = useTemplateRef("listbox");
22
+ const showOptions = computed(() => list !== "presets");
23
+ const showPresets = computed(() => list !== "options" && filter.presets.length > 0);
24
+ const searching = computed(() => filter.search.trim() !== "");
25
+ const clearRow = computed(
26
+ () => !filter.multiple && filter.defaultValue === void 0 && !searching.value
27
+ );
28
+ const full = computed(
29
+ () => filter.multiple && filter.max !== void 0 && filter.selected.length >= filter.max
30
+ );
31
+ const grid = computed(
32
+ () => filter.columns && filter.columns > 1 ? { gridTemplateColumns: `repeat(${filter.columns}, minmax(0, 1fr))` } : void 0
33
+ );
34
+ function pick(item) {
35
+ filter.toggle(item.value);
36
+ if (!filter.multiple) emit("picked");
37
+ }
38
+ function clear() {
39
+ filter.reset();
40
+ if (!filter.multiple) emit("picked");
41
+ }
42
+ function apply(preset) {
43
+ preset.apply();
44
+ emit("picked");
45
+ }
46
+ function loadAhead() {
47
+ const element = listbox.value;
48
+ if (!element || element.clientHeight === 0) return;
49
+ if (!filter.hasMore || filter.loadingMore || filter.loading || filter.error) return;
50
+ const remaining = element.scrollHeight - element.scrollTop - element.clientHeight;
51
+ if (remaining <= element.clientHeight * PREFETCH_VIEWPORTS) filter.loadMore();
52
+ }
53
+ useEventListener(listbox, "scroll", loadAhead, { passive: true });
54
+ watch(
55
+ () => [filter.items.length, filter.hasMore, filter.loadingMore],
56
+ async () => {
57
+ await nextTick();
58
+ loadAhead();
59
+ },
60
+ { immediate: true }
61
+ );
62
+ function onKeydown(event) {
63
+ const rows = [
64
+ ...listbox.value?.querySelectorAll("[data-filter-row]:not(:disabled)") ?? []
65
+ ];
66
+ const current = rows.findIndex((row) => row === document.activeElement);
67
+ const target = (/* @__PURE__ */ new Map([
68
+ ["ArrowDown", current + 1],
69
+ ["ArrowUp", current - 1],
70
+ ["End", rows.length - 1],
71
+ ["Home", 0]
72
+ ])).get(event.key);
73
+ if (target === void 0 || rows.length === 0) return;
74
+ event.preventDefault();
75
+ rows[Math.max(0, Math.min(rows.length - 1, target))]?.focus();
76
+ }
77
+ function focusFirstRow(event) {
78
+ if (event.key !== "ArrowDown") return;
79
+ event.preventDefault();
80
+ listbox.value?.querySelector("[data-filter-row]:not(:disabled)")?.focus();
81
+ }
82
+ </script>
83
+
84
+ <template>
85
+ <div v-if="title" :class="classes.title">{{ title }}</div>
86
+ <label v-if="showOptions && filter.searchable" :class="classes.search" data-filter-search>
87
+ <UIcon name="i-lucide-search" class="size-3.5 flex-none text-dimmed" />
88
+ <input
89
+ v-model="filter.search"
90
+ type="search"
91
+ :placeholder="t('dashboard.filters.search')"
92
+ :aria-label="t('dashboard.filters.search')"
93
+ class="min-w-0 flex-1 border-0 bg-transparent text-[13px] outline-none placeholder:text-dimmed"
94
+ autocomplete="off"
95
+ @keydown="focusFirstRow"
96
+ />
97
+ </label>
98
+ <slot name="header" :filter />
99
+ <div
100
+ v-if="showOptions"
101
+ ref="listbox"
102
+ role="listbox"
103
+ :aria-label="filter.label"
104
+ :aria-multiselectable="filter.multiple || void 0"
105
+ :class="[classes.list, grid && 'grid']"
106
+ :style="grid"
107
+ data-filter-list
108
+ @keydown="onKeydown"
109
+ >
110
+ <button
111
+ v-if="clearRow"
112
+ type="button"
113
+ role="option"
114
+ :aria-selected="filter.selected.length === 0"
115
+ :class="classes.item"
116
+ :data-selected="filter.selected.length === 0 || void 0"
117
+ data-filter-row
118
+ data-filter-clear-row
119
+ @click="clear"
120
+ >
121
+ <span class="truncate">{{ filter.placeholder }}</span>
122
+ <UIcon v-if="filter.selected.length === 0" name="i-lucide-check" :class="classes.tick" />
123
+ </button>
124
+ <button
125
+ v-for="item in filter.items"
126
+ :key="String(item.value)"
127
+ type="button"
128
+ role="option"
129
+ :aria-selected="filter.isSelected(item.value)"
130
+ :disabled="item.disabled || full && !filter.isSelected(item.value)"
131
+ :class="classes.item"
132
+ :data-selected="filter.isSelected(item.value) || void 0"
133
+ data-filter-row
134
+ @click="pick(item)"
135
+ >
136
+ <slot
137
+ name="item"
138
+ :item
139
+ :selected="filter.isSelected(item.value)"
140
+ :disabled="Boolean(item.disabled || full && !filter.isSelected(item.value))"
141
+ :filter
142
+ >
143
+ <span
144
+ v-if="filter.multiple"
145
+ :class="classes.check"
146
+ :data-checked="filter.isSelected(item.value) || void 0"
147
+ aria-hidden="true"
148
+ >
149
+ <UIcon name="i-lucide-check" class="size-3" />
150
+ </span>
151
+ <UIcon v-if="item.icon" :name="item.icon" class="size-3.5 flex-none text-muted" />
152
+ <img
153
+ v-else-if="item.avatar?.src"
154
+ :src="item.avatar.src"
155
+ :alt="item.avatar.alt ?? ''"
156
+ :class="classes.avatar"
157
+ />
158
+ <span v-else-if="item.avatar?.text" :class="classes.avatar" aria-hidden="true">
159
+ {{ item.avatar.text }}
160
+ </span>
161
+ <span class="min-w-0 flex-1 truncate">{{ item.label }}</span>
162
+ <span v-if="item.hint" :class="classes.hint">{{ item.hint }}</span>
163
+ <UIcon
164
+ v-if="!filter.multiple && filter.isSelected(item.value)"
165
+ name="i-lucide-check"
166
+ :class="classes.tick"
167
+ />
168
+ </slot>
169
+ </button>
170
+ <div
171
+ v-if="filter.loading || filter.loadingMore"
172
+ :class="classes.note"
173
+ class="col-span-full"
174
+ data-filter-loading
175
+ >
176
+ {{ t("dashboard.filters.loading") }}
177
+ </div>
178
+ <div v-else-if="filter.error" :class="classes.note" class="col-span-full text-error">
179
+ <span class="truncate">{{ t("dashboard.filters.loadError") }}</span>
180
+ <button
181
+ type="button"
182
+ class="ms-2 font-medium underline-offset-2 hover:underline"
183
+ @click="filter.refresh()"
184
+ >
185
+ {{ t("dashboard.filters.retry") }}
186
+ </button>
187
+ </div>
188
+ <div v-else-if="filter.items.length === 0" :class="classes.note" class="col-span-full">
189
+ <slot name="empty" :filter>{{ t("dashboard.filters.empty") }}</slot>
190
+ </div>
191
+ </div>
192
+ <template v-if="showOptions && filter.multiple && filter.selected.length > 0">
193
+ <div :class="classes.separator" role="separator" />
194
+ <button type="button" :class="classes.item" data-filter-clear-selection @click="clear">
195
+ <UIcon name="i-lucide-x" class="size-3.5 flex-none text-muted" />
196
+ <span class="truncate">{{ t("dashboard.filters.clearSelection") }}</span>
197
+ </button>
198
+ </template>
199
+ <template v-if="showPresets">
200
+ <template v-if="showOptions">
201
+ <div :class="classes.separator" role="separator" />
202
+ <div :class="classes.title">{{ t("dashboard.filters.presets") }}</div>
203
+ </template>
204
+ <button
205
+ v-for="preset in filter.presets"
206
+ :key="preset.label"
207
+ type="button"
208
+ :class="classes.item"
209
+ :data-selected="preset.active || void 0"
210
+ data-filter-preset
211
+ @click="apply(preset)"
212
+ >
213
+ <UIcon :name="preset.icon ?? 'i-lucide-layers'" class="size-3.5 flex-none text-muted" />
214
+ <span class="min-w-0 flex-1 truncate">{{ preset.label }}</span>
215
+ <span v-if="preset.hint" :class="classes.hint">{{ preset.hint }}</span>
216
+ <UIcon v-if="preset.active" name="i-lucide-check" :class="classes.tick" />
217
+ </button>
218
+ </template>
219
+ <slot name="footer" :filter />
220
+ </template>
@@ -0,0 +1,44 @@
1
+ import type { DashboardFilterControl, DashboardFilterUi, DashboardOption, DashboardOptionValue } from '../../types/index.js';
2
+ declare const __VLS_export: <TValue, TItem extends DashboardOptionValue>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
+ filter: DashboardFilterControl<TValue, TItem>;
5
+ classes: Required<DashboardFilterUi>;
6
+ /** Heading above the list. */
7
+ title?: string;
8
+ /** What the menu lists: the options, the presets, or both (presets under the options). */
9
+ list?: "all" | "options" | "presets";
10
+ } & {
11
+ onPicked?: (() => any) | undefined;
12
+ }> & (typeof globalThis extends {
13
+ __VLS_PROPS_FALLBACK: infer P;
14
+ } ? P : {});
15
+ expose: (exposed: {}) => void;
16
+ attrs: any;
17
+ slots: {
18
+ item?: (props: {
19
+ item: DashboardOption<TItem>;
20
+ selected: boolean;
21
+ disabled: boolean;
22
+ filter: DashboardFilterControl<TValue, TItem>;
23
+ }) => unknown;
24
+ header?: (props: {
25
+ filter: DashboardFilterControl<TValue, TItem>;
26
+ }) => unknown;
27
+ footer?: (props: {
28
+ filter: DashboardFilterControl<TValue, TItem>;
29
+ }) => unknown;
30
+ empty?: (props: {
31
+ filter: DashboardFilterControl<TValue, TItem>;
32
+ }) => unknown;
33
+ };
34
+ emit: (evt: "picked") => void;
35
+ }>) => import("vue").VNode & {
36
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
37
+ };
38
+ declare const _default: typeof __VLS_export;
39
+ export default _default;
40
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
41
+ [K in keyof T]: T[K];
42
+ } : {
43
+ [K in keyof T as K]: T[K];
44
+ }) & {};
@@ -5,6 +5,8 @@ import type { useDashboardViews } from './use-dashboard-views.js';
5
5
  /**
6
6
  * Facade only: projects the owning scopes into the public dashboard object. Every runtime member is
7
7
  * a getter over a ref owned elsewhere, so templates read plain values and `v-model` writes through.
8
+ *
9
+ * Returns the facade and the view handles by view key.
8
10
  */
9
11
  export declare function useDashboardApi(params: {
10
12
  schema: DashboardSchemaLike;
@@ -12,4 +14,7 @@ export declare function useDashboardApi(params: {
12
14
  autoRefresh: WritableComputedRef<number>;
13
15
  root: ReturnType<typeof useDashboardScope>;
14
16
  views: ReturnType<typeof useDashboardViews> | null;
15
- }): object;
17
+ }): {
18
+ api: import("vue").Raw<Record<string, unknown>>;
19
+ handles: Map<string, object>;
20
+ };
@@ -7,6 +7,7 @@ import {
7
7
  export function useDashboardApi(params) {
8
8
  const { root, views } = params;
9
9
  const api = Object.fromEntries(root.members);
10
+ const handles = /* @__PURE__ */ new Map();
10
11
  for (const view of views?.views ?? []) {
11
12
  const handle = Object.fromEntries(view.scope.members);
12
13
  Object.defineProperties(handle, {
@@ -17,6 +18,9 @@ export function useDashboardApi(params) {
17
18
  get active() {
18
19
  return views?.current.value === view.key;
19
20
  },
21
+ get enabled() {
22
+ return view.enabled.value;
23
+ },
20
24
  key: view.key,
21
25
  get label() {
22
26
  return view.label();
@@ -27,16 +31,20 @@ export function useDashboardApi(params) {
27
31
  })
28
32
  }
29
33
  });
30
- api[view.key] = markRaw(handle);
34
+ const facade = markRaw(handle);
35
+ api[view.key] = facade;
36
+ handles.set(view.key, facade);
31
37
  }
32
38
  const openedScopes = () => [
33
39
  root,
34
40
  ...views?.views.filter((view) => view.opened.value).map((view) => view.scope) ?? []
35
41
  ];
42
+ const currentView = () => views?.views.find((view) => view.key === views.current.value);
36
43
  const visibleScopes = () => {
37
- const current = views?.views.find((view) => view.key === views.current.value);
44
+ const current = currentView();
38
45
  return current ? [root, current.scope] : [root];
39
46
  };
47
+ const visibleFilters = () => visibleScopes().map((scope) => scope.filterScope);
40
48
  Object.defineProperties(api, {
41
49
  ...scopeMembers(root),
42
50
  autoRefresh: {
@@ -46,11 +54,21 @@ export function useDashboardApi(params) {
46
54
  params.autoRefresh.value = Number.isFinite(seconds) ? Math.max(0, Math.round(seconds)) : 0;
47
55
  }
48
56
  },
57
+ filtered: {
58
+ enumerable: true,
59
+ get: () => visibleFilters().some((filters) => filters.changed())
60
+ },
49
61
  refresh: { enumerable: true, value: () => refreshDashboardSources(openedScopes()) },
50
62
  refreshing: {
51
63
  enumerable: true,
52
64
  get: () => openedScopes().some((scope) => scope.refreshing.value)
53
65
  },
66
+ resetFilters: {
67
+ enumerable: true,
68
+ value: () => {
69
+ for (const filters of visibleFilters()) filters.reset();
70
+ }
71
+ },
54
72
  schema: { enumerable: true, value: params.schema },
55
73
  state: {
56
74
  enumerable: true,
@@ -72,19 +90,24 @@ export function useDashboardApi(params) {
72
90
  views.current.value = next;
73
91
  },
74
92
  get items() {
75
- return views.views.map((view) => ({ label: view.label(), value: view.key }));
93
+ return views.views.flatMap(
94
+ (view) => view.enabled.value ? [{ label: view.label(), value: view.key }] : []
95
+ );
76
96
  }
77
97
  })
78
98
  });
79
99
  }
80
- return markRaw(api);
100
+ return { api: markRaw(api), handles };
81
101
  }
82
102
  function scopeMembers(scope) {
103
+ const { filterScope } = scope;
83
104
  return {
84
- options: { enumerable: true, value: scope.paramScope.options },
85
- params: { enumerable: true, value: scope.paramScope.values },
105
+ controls: { enumerable: true, value: filterScope.controls },
106
+ filtered: { enumerable: true, get: () => filterScope.changed() },
107
+ filters: { enumerable: true, value: filterScope.values },
86
108
  refresh: { enumerable: true, value: scope.refresh },
87
109
  refreshing: { enumerable: true, get: () => scope.refreshing.value },
110
+ resetFilters: { enumerable: true, value: () => filterScope.reset() },
88
111
  state: { enumerable: true, get: () => scope.state.value },
89
112
  updatedAt: { enumerable: true, get: () => scope.updatedAt.value }
90
113
  };
@@ -1,8 +1,9 @@
1
1
  import type { ShallowRef } from 'vue';
2
2
  import type { DashboardBlockActivation, DashboardBlockPhase, DashboardSourceLike } from '../types/index.js';
3
3
  /**
4
- * Non-visual block behaviour: reduces the source into one render phase, activates deferred sources
5
- * (on mount or when the block nears the viewport), and resolves the grid placement.
4
+ * Non-visual block behaviour: reduces the source into one render phase, hides the block while its
5
+ * source is `disabled`, activates deferred sources (on mount or when the block nears the viewport),
6
+ * and resolves the grid cell.
6
7
  *
7
8
  * The phase is a branch, not a mask: while a block is `idle` or `loading`, its real values are not
8
9
  * in the DOM at all, so nothing can flash through.
@@ -12,11 +13,11 @@ export declare function useDashboardBlock(params: {
12
13
  empty: () => boolean;
13
14
  activation: () => DashboardBlockActivation | undefined;
14
15
  size: () => string | undefined;
15
- rows: () => string | undefined;
16
16
  root: Readonly<ShallowRef<HTMLElement | null>>;
17
17
  }): {
18
+ fetching: import("vue").ComputedRef<boolean>;
19
+ hidden: import("vue").ComputedRef<boolean>;
18
20
  phase: import("vue").ComputedRef<DashboardBlockPhase>;
19
- refreshing: import("vue").ComputedRef<boolean>;
20
21
  retry: () => void;
21
22
  style: import("vue").ComputedRef<string>;
22
23
  };