nuxt-ui-tools 1.17.1 → 1.19.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 (178) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +1 -0
  3. package/dist/runtime/form/components/page/form-page-header.d.vue.ts +3 -1
  4. package/dist/runtime/form/components/page/form-page-header.vue +44 -34
  5. package/dist/runtime/form/components/page/form-page-header.vue.d.ts +3 -1
  6. package/dist/runtime/form/components/page/form-page-navigation.d.vue.ts +5 -3
  7. package/dist/runtime/form/components/page/form-page-navigation.vue +6 -3
  8. package/dist/runtime/form/components/page/form-page-navigation.vue.d.ts +5 -3
  9. package/dist/runtime/form/components/page/form-page-section.vue +6 -5
  10. package/dist/runtime/form/components/page/form-page-sections.d.vue.ts +4 -3
  11. package/dist/runtime/form/components/page/form-page-sections.vue +7 -2
  12. package/dist/runtime/form/components/page/form-page-sections.vue.d.ts +4 -3
  13. package/dist/runtime/form/components/page/form-page.d.vue.ts +2 -0
  14. package/dist/runtime/form/components/page/form-page.vue +11 -2
  15. package/dist/runtime/form/components/page/form-page.vue.d.ts +2 -0
  16. package/dist/runtime/form/components/renderer/form-field-renderer.vue +6 -4
  17. package/dist/runtime/form/components/renderer/form-field-shell.d.vue.ts +4 -4
  18. package/dist/runtime/form/components/renderer/form-field-shell.vue +28 -4
  19. package/dist/runtime/form/components/renderer/form-field-shell.vue.d.ts +4 -4
  20. package/dist/runtime/form/components/root/form.vue +2 -2
  21. package/dist/runtime/form/components/utils/form-option-choice-label.d.vue.ts +16 -0
  22. package/dist/runtime/form/components/utils/form-option-choice-label.vue +37 -0
  23. package/dist/runtime/form/components/utils/form-option-choice-label.vue.d.ts +16 -0
  24. package/dist/runtime/form/components/utils/form-option-display-value.d.vue.ts +14 -0
  25. package/dist/runtime/form/components/utils/form-option-display-value.vue +20 -0
  26. package/dist/runtime/form/components/utils/form-option-display-value.vue.d.ts +14 -0
  27. package/dist/runtime/form/components/utils/form-option-item-label.d.vue.ts +9 -2
  28. package/dist/runtime/form/components/utils/form-option-item-label.vue +20 -13
  29. package/dist/runtime/form/components/utils/form-option-item-label.vue.d.ts +9 -2
  30. package/dist/runtime/form/components/utils/form-option-leading.d.vue.ts +23 -0
  31. package/dist/runtime/form/components/utils/form-option-leading.vue +41 -0
  32. package/dist/runtime/form/components/utils/form-option-leading.vue.d.ts +23 -0
  33. package/dist/runtime/form/components/utils/form-option-render.d.ts +11 -0
  34. package/dist/runtime/form/components/utils/form-option-render.js +5 -0
  35. package/dist/runtime/form/components/utils/form-option-suffix.d.vue.ts +8 -0
  36. package/dist/runtime/form/components/utils/form-option-suffix.vue +15 -0
  37. package/dist/runtime/form/components/utils/form-option-suffix.vue.d.ts +8 -0
  38. package/dist/runtime/form/components/utils/form-option-value-overlay.d.vue.ts +30 -0
  39. package/dist/runtime/form/components/utils/form-option-value-overlay.vue +44 -0
  40. package/dist/runtime/form/components/utils/form-option-value-overlay.vue.d.ts +30 -0
  41. package/dist/runtime/form/composables/use-choice-group-items.d.ts +27 -0
  42. package/dist/runtime/form/composables/use-choice-group-items.js +24 -0
  43. package/dist/runtime/form/composables/use-field-control.d.ts +6 -6
  44. package/dist/runtime/form/composables/use-field-control.js +9 -21
  45. package/dist/runtime/form/composables/use-field-options.js +8 -4
  46. package/dist/runtime/form/composables/use-form-layout.js +4 -3
  47. package/dist/runtime/form/composables/use-form-page.d.ts +11 -1
  48. package/dist/runtime/form/composables/use-form-page.js +12 -6
  49. package/dist/runtime/form/composables/use-form-runtime.js +1 -1
  50. package/dist/runtime/form/composables/use-option-menu.d.ts +20 -0
  51. package/dist/runtime/form/composables/use-option-menu.js +41 -0
  52. package/dist/runtime/form/fields/array-primitive/types.d.ts +1 -1
  53. package/dist/runtime/form/fields/auto-complete/component.vue +92 -51
  54. package/dist/runtime/form/fields/auto-complete/config.d.ts +1 -0
  55. package/dist/runtime/form/fields/auto-complete/config.js +1 -1
  56. package/dist/runtime/form/fields/auto-complete/types.d.ts +2 -2
  57. package/dist/runtime/form/fields/checkbox-card/component.vue +55 -29
  58. package/dist/runtime/form/fields/checkbox-card/config.d.ts +1 -0
  59. package/dist/runtime/form/fields/checkbox-card/config.js +1 -1
  60. package/dist/runtime/form/fields/checkbox-card/types.d.ts +3 -3
  61. package/dist/runtime/form/fields/checkbox-group/component.vue +18 -4
  62. package/dist/runtime/form/fields/checkbox-group/types.d.ts +2 -2
  63. package/dist/runtime/form/fields/choice-card/choice-card-label.d.vue.ts +7 -0
  64. package/dist/runtime/form/fields/choice-card/choice-card-label.vue +27 -1
  65. package/dist/runtime/form/fields/choice-card/choice-card-label.vue.d.ts +7 -0
  66. package/dist/runtime/form/fields/choice-card/types.d.ts +35 -1
  67. package/dist/runtime/form/fields/choice-card/use-choice-card.d.ts +7 -3
  68. package/dist/runtime/form/fields/choice-card/use-choice-card.js +37 -15
  69. package/dist/runtime/form/fields/choice-card/utils.d.ts +10 -0
  70. package/dist/runtime/form/fields/choice-card/utils.js +6 -0
  71. package/dist/runtime/form/fields/hierarchy/component.vue +3 -2
  72. package/dist/runtime/form/fields/index.d.ts +4 -0
  73. package/dist/runtime/form/fields/radio/component.vue +19 -2
  74. package/dist/runtime/form/fields/radio/types.d.ts +2 -2
  75. package/dist/runtime/form/fields/radio-card/component.vue +58 -28
  76. package/dist/runtime/form/fields/radio-card/config.d.ts +1 -0
  77. package/dist/runtime/form/fields/radio-card/config.js +1 -1
  78. package/dist/runtime/form/fields/radio-card/types.d.ts +3 -3
  79. package/dist/runtime/form/fields/select/component.vue +56 -58
  80. package/dist/runtime/form/fields/select/config.d.ts +1 -0
  81. package/dist/runtime/form/fields/select/config.js +1 -1
  82. package/dist/runtime/form/fields/select/types.d.ts +2 -2
  83. package/dist/runtime/form/types/field-base.d.ts +6 -0
  84. package/dist/runtime/form/types/field-kind.d.ts +3 -1
  85. package/dist/runtime/form/types/field.d.ts +1 -0
  86. package/dist/runtime/form/types/layout.d.ts +6 -0
  87. package/dist/runtime/form/types/options.d.ts +137 -0
  88. package/dist/runtime/form/types/page.d.ts +20 -8
  89. package/dist/runtime/form/types/ui.d.ts +29 -4
  90. package/dist/runtime/form/utils/disabled.d.ts +11 -0
  91. package/dist/runtime/form/utils/disabled.js +11 -0
  92. package/dist/runtime/form/utils/field-instance.js +3 -0
  93. package/dist/runtime/form/utils/options.d.ts +56 -11
  94. package/dist/runtime/form/utils/options.js +86 -0
  95. package/dist/runtime/form/utils/page-surface.d.ts +26 -0
  96. package/dist/runtime/form/utils/page-surface.js +28 -0
  97. package/dist/runtime/form/utils/page.d.ts +21 -1
  98. package/dist/runtime/form/utils/page.js +24 -2
  99. package/dist/runtime/i18n/locales/en.js +28 -8
  100. package/dist/runtime/i18n/locales/fr.js +28 -8
  101. package/dist/runtime/i18n/types.d.ts +46 -7
  102. package/dist/runtime/spreadsheet/components/internal/spreadsheet-import-column-row.d.vue.ts +2 -2
  103. package/dist/runtime/spreadsheet/components/internal/spreadsheet-import-column-row.vue.d.ts +2 -2
  104. package/dist/runtime/table/components/actions/row-actions.d.vue.ts +2 -0
  105. package/dist/runtime/table/components/actions/row-actions.vue +8 -3
  106. package/dist/runtime/table/components/actions/row-actions.vue.d.ts +2 -0
  107. package/dist/runtime/table/components/data-list/data-list-content.vue +7 -1
  108. package/dist/runtime/table/components/data-list/data-list-default.d.vue.ts +16 -16
  109. package/dist/runtime/table/components/data-list/data-list-default.vue +3 -0
  110. package/dist/runtime/table/components/data-list/data-list-default.vue.d.ts +16 -16
  111. package/dist/runtime/table/components/data-list/data-list-layout-switch.vue +7 -9
  112. package/dist/runtime/table/components/data-list/data-list-root.vue +5 -6
  113. package/dist/runtime/table/components/data-list/data-list-sort-menu.vue +3 -1
  114. package/dist/runtime/table/components/data-list/data-list-table.d.vue.ts +8 -1
  115. package/dist/runtime/table/components/data-list/data-list-table.vue +18 -1
  116. package/dist/runtime/table/components/data-list/data-list-table.vue.d.ts +8 -1
  117. package/dist/runtime/table/components/data-list/data-list-view-toggle.d.vue.ts +25 -0
  118. package/dist/runtime/table/components/data-list/data-list-view-toggle.vue +57 -0
  119. package/dist/runtime/table/components/data-list/data-list-view-toggle.vue.d.ts +25 -0
  120. package/dist/runtime/table/components/data-list/sort-menu-panel.vue +1 -1
  121. package/dist/runtime/table/components/filters/tags/boolean-filter-tag.d.vue.ts +2 -2
  122. package/dist/runtime/table/components/filters/tags/boolean-filter-tag.vue.d.ts +2 -2
  123. package/dist/runtime/table/components/filters/tags/date-filter-tag.d.vue.ts +2 -2
  124. package/dist/runtime/table/components/filters/tags/date-filter-tag.vue.d.ts +2 -2
  125. package/dist/runtime/table/components/filters/tags/number-filter-tag.d.vue.ts +2 -2
  126. package/dist/runtime/table/components/filters/tags/number-filter-tag.vue.d.ts +2 -2
  127. package/dist/runtime/table/components/filters/tags/option-filter-tag.d.vue.ts +2 -2
  128. package/dist/runtime/table/components/filters/tags/option-filter-tag.vue.d.ts +2 -2
  129. package/dist/runtime/table/components/filters/tags/text-filter-tag.d.vue.ts +2 -2
  130. package/dist/runtime/table/components/filters/tags/text-filter-tag.vue.d.ts +2 -2
  131. package/dist/runtime/table/components/layout/table-footer.d.vue.ts +6 -4
  132. package/dist/runtime/table/components/layout/table-footer.vue +80 -3
  133. package/dist/runtime/table/components/layout/table-footer.vue.d.ts +6 -4
  134. package/dist/runtime/table/components/summary/summary-content.d.ts +13 -0
  135. package/dist/runtime/table/components/summary/summary-content.js +5 -0
  136. package/dist/runtime/table/components/summary/summary-renderer.d.vue.ts +31 -0
  137. package/dist/runtime/table/components/summary/summary-renderer.vue +282 -0
  138. package/dist/runtime/table/components/summary/summary-renderer.vue.d.ts +31 -0
  139. package/dist/runtime/table/components/summary/summary-row.d.vue.ts +19 -0
  140. package/dist/runtime/table/components/summary/summary-row.vue +102 -0
  141. package/dist/runtime/table/components/summary/summary-row.vue.d.ts +19 -0
  142. package/dist/runtime/table/components/table/table-empty-state.vue +39 -9
  143. package/dist/runtime/table/components/table/table-renderer.vue +1 -1
  144. package/dist/runtime/table/components/table/table-row-actions-control.vue +10 -6
  145. package/dist/runtime/table/components/table/table-selection-control.d.vue.ts +5 -1
  146. package/dist/runtime/table/components/table/table-selection-control.vue +17 -4
  147. package/dist/runtime/table/components/table/table-selection-control.vue.d.ts +5 -1
  148. package/dist/runtime/table/components/table/table-tree-control.vue +2 -1
  149. package/dist/runtime/table/components/table/table-tree-header.vue +0 -1
  150. package/dist/runtime/table/composables/use-table-api.d.ts +2 -0
  151. package/dist/runtime/table/composables/use-table-api.js +8 -0
  152. package/dist/runtime/table/composables/use-table-columns.d.ts +2 -0
  153. package/dist/runtime/table/composables/use-table-controls.d.ts +6 -0
  154. package/dist/runtime/table/composables/use-table-controls.js +6 -3
  155. package/dist/runtime/table/composables/use-table-internals.d.ts +33 -0
  156. package/dist/runtime/table/composables/use-table-internals.js +9 -0
  157. package/dist/runtime/table/composables/use-table-row-labels.d.ts +16 -0
  158. package/dist/runtime/table/composables/use-table-row-labels.js +22 -0
  159. package/dist/runtime/table/composables/use-table-row-summary.d.ts +18 -0
  160. package/dist/runtime/table/composables/use-table-row-summary.js +41 -0
  161. package/dist/runtime/table/constants/mobile.d.ts +79 -0
  162. package/dist/runtime/table/constants/mobile.js +17 -0
  163. package/dist/runtime/table/constants/summary.d.ts +67 -0
  164. package/dist/runtime/table/constants/summary.js +31 -0
  165. package/dist/runtime/table/types/data-list.d.ts +58 -8
  166. package/dist/runtime/table/types/layout.d.ts +73 -0
  167. package/dist/runtime/table/types/remote-options.d.ts +4 -1
  168. package/dist/runtime/table/types/schema.d.ts +9 -1
  169. package/dist/runtime/table/types/table-api.d.ts +12 -0
  170. package/dist/runtime/table/utils/columns/defs.js +3 -1
  171. package/dist/runtime/table/utils/columns/render.d.ts +2 -0
  172. package/dist/runtime/table/utils/columns/render.js +2 -2
  173. package/dist/runtime/table/utils/data-list-ui.d.ts +1 -1
  174. package/dist/runtime/table/utils/data-list-ui.js +3 -1
  175. package/dist/runtime/table/utils/mobile-defaults.d.ts +7 -0
  176. package/dist/runtime/table/utils/mobile-defaults.js +25 -0
  177. package/dist/runtime/table/utils/remote-table-options.js +1 -1
  178. package/package.json +1 -1
@@ -2,7 +2,8 @@ import type { FormPageSectionState } from '../types/index.js';
2
2
  import type { useFormRoot } from './use-form-root.js';
3
3
  /**
4
4
  * State shared by a form page and its parts: the visible sections and where each stands, the
5
- * section in view, the navigation summary, and the actions a part can take on a section.
5
+ * section in view, the navigation summary, whether the navigation shows, the surface the
6
+ * sections are drawn with, and the actions a part can take on a section.
6
7
  */
7
8
  export declare function useFormPage(params: {
8
9
  root: ReturnType<typeof useFormRoot>;
@@ -19,6 +20,7 @@ export declare function useFormPage(params: {
19
20
  }[]>;
20
21
  modified: import("vue").ComputedRef<number>;
21
22
  navigationTitle: import("vue").ComputedRef<string | undefined>;
23
+ navigationVisible: import("vue").ComputedRef<boolean>;
22
24
  register: (key: string, element: HTMLElement) => () => void;
23
25
  remaining: import("vue").ComputedRef<number>;
24
26
  resetSection: (key: string) => Promise<void>;
@@ -42,6 +44,8 @@ export declare function useFormPage(params: {
42
44
  scrollTo: (key: string, options?: import("../types/index.js").FormPageScrollOptions) => boolean;
43
45
  sections: import("vue").ComputedRef<readonly FormPageSectionState[]>;
44
46
  stickyOffset: import("vue").Ref<number, number>;
47
+ surface: import("vue").ComputedRef<import("../types/index.js").FormPageSurface>;
48
+ surfaceClasses: import("vue").ComputedRef<import("../utils/page-surface.js").FormPageSurfaceClasses>;
45
49
  };
46
50
  export declare function provideFormPage(page: ReturnType<typeof useFormPage>): {
47
51
  active: import("vue").Ref<string | undefined, string | undefined>;
@@ -53,6 +57,7 @@ export declare function provideFormPage(page: ReturnType<typeof useFormPage>): {
53
57
  }[]>;
54
58
  modified: import("vue").ComputedRef<number>;
55
59
  navigationTitle: import("vue").ComputedRef<string | undefined>;
60
+ navigationVisible: import("vue").ComputedRef<boolean>;
56
61
  register: (key: string, element: HTMLElement) => () => void;
57
62
  remaining: import("vue").ComputedRef<number>;
58
63
  resetSection: (key: string) => Promise<void>;
@@ -76,6 +81,8 @@ export declare function provideFormPage(page: ReturnType<typeof useFormPage>): {
76
81
  scrollTo: (key: string, options?: import("../types/index.js").FormPageScrollOptions) => boolean;
77
82
  sections: import("vue").ComputedRef<readonly FormPageSectionState[]>;
78
83
  stickyOffset: import("vue").Ref<number, number>;
84
+ surface: import("vue").ComputedRef<import("../types/index.js").FormPageSurface>;
85
+ surfaceClasses: import("vue").ComputedRef<import("../utils/page-surface.js").FormPageSurfaceClasses>;
79
86
  };
80
87
  /** The page a part renders in. Parts only work inside `FormPage`. */
81
88
  export declare function useFormPageContext(): {
@@ -88,6 +95,7 @@ export declare function useFormPageContext(): {
88
95
  }[]>;
89
96
  modified: import("vue").ComputedRef<number>;
90
97
  navigationTitle: import("vue").ComputedRef<string | undefined>;
98
+ navigationVisible: import("vue").ComputedRef<boolean>;
91
99
  register: (key: string, element: HTMLElement) => () => void;
92
100
  remaining: import("vue").ComputedRef<number>;
93
101
  resetSection: (key: string) => Promise<void>;
@@ -111,4 +119,6 @@ export declare function useFormPageContext(): {
111
119
  scrollTo: (key: string, options?: import("../types/index.js").FormPageScrollOptions) => boolean;
112
120
  sections: import("vue").ComputedRef<readonly FormPageSectionState[]>;
113
121
  stickyOffset: import("vue").Ref<number, number>;
122
+ surface: import("vue").ComputedRef<import("../types/index.js").FormPageSurface>;
123
+ surfaceClasses: import("vue").ComputedRef<import("../utils/page-surface.js").FormPageSurfaceClasses>;
114
124
  };
@@ -1,11 +1,14 @@
1
1
  import { computed, inject, provide, ref } from "vue";
2
2
  import { getSchemaDirtyCheck } from "../utils/controls.js";
3
3
  import {
4
+ getFormPageNavigationMinSections,
4
5
  getFormPageNavigationTitle,
5
6
  getFormPageSections,
7
+ isFormPageSectionBlocking,
6
8
  isFormPageSectionVisible,
7
9
  resolveFormPageSectionState
8
10
  } from "../utils/page.js";
11
+ import { getFormPageSurfaceClasses, resolveFormPageSurface } from "../utils/page-surface.js";
9
12
  import { cloneFormValue } from "../utils/path.js";
10
13
  import { useFormPageScroll } from "./use-form-page-scroll.js";
11
14
  export function useFormPage(params) {
@@ -36,11 +39,11 @@ export function useFormPage(params) {
36
39
  );
37
40
  const dirtyCheck = computed(() => getSchemaDirtyCheck(root.schema.value));
38
41
  const navigationTitle = computed(() => getFormPageNavigationTitle(root.schema.value));
39
- const remaining = computed(
40
- () => sections.value.filter(
41
- (section) => section.optional ? section.missing > 0 || section.status === "invalid" : section.status !== "complete"
42
- ).length
43
- );
42
+ const navigationMinSections = computed(() => getFormPageNavigationMinSections(root.schema.value));
43
+ const navigationVisible = computed(() => sections.value.length >= navigationMinSections.value);
44
+ const surface = computed(() => resolveFormPageSurface(root.formUi.ui.value.page?.surface));
45
+ const surfaceClasses = computed(() => getFormPageSurfaceClasses(surface.value));
46
+ const remaining = computed(() => sections.value.filter(isFormPageSectionBlocking).length);
44
47
  const modified = computed(() => sections.value.filter((section) => section.dirty).length);
45
48
  const stickyOffset = ref(0);
46
49
  const scroll = useFormPageScroll({ hash: params.hash, root: params.element, sections });
@@ -62,13 +65,16 @@ export function useFormPage(params) {
62
65
  items,
63
66
  modified,
64
67
  navigationTitle,
68
+ navigationVisible,
65
69
  register: scroll.register,
66
70
  remaining,
67
71
  resetSection,
68
72
  root,
69
73
  scrollTo: scroll.scrollTo,
70
74
  sections,
71
- stickyOffset
75
+ stickyOffset,
76
+ surface,
77
+ surfaceClasses
72
78
  };
73
79
  }
74
80
  const formPageKey = Symbol("nuxt-ui-tools-form-page");
@@ -221,7 +221,7 @@ export function useFormRuntime(params) {
221
221
  active: currentStepIndex.value === index,
222
222
  index,
223
223
  key: step.key ?? String(index + 1),
224
- label: isFunction(step.title) ? String(step.title()) : String(step.title ?? step.key ?? `Step ${index + 1}`),
224
+ label: isFunction(step.title) ? String(step.title()) : String(step.title ?? step.key ?? t("form.steps.fallback", { number: index + 1 })),
225
225
  root: step.root
226
226
  }))
227
227
  );
@@ -0,0 +1,20 @@
1
+ import type { FormAutoCompleteField, FormOptionRuntimeState, FormOptionValue, FormSelectField, ResolvedFormOption } from '../types/index.js';
2
+ /**
3
+ * What the select and the auto-complete share around the options they list in a Nuxt UI menu: the
4
+ * menu items (grouped when the options carry headings), which option is selected, and the option
5
+ * the closed control shows with its leading visual and its custom value.
6
+ */
7
+ export declare function useOptionMenu(params: {
8
+ field: () => FormSelectField | FormAutoCompleteField;
9
+ model: () => FormOptionValue | readonly FormOptionValue[] | null | undefined;
10
+ multiple: () => boolean;
11
+ options: FormOptionRuntimeState;
12
+ loadMoreLabel: () => string;
13
+ }): {
14
+ customValueOption: import("vue").ComputedRef<ResolvedFormOption | undefined>;
15
+ isSelected: (option: ResolvedFormOption) => boolean;
16
+ items: import("vue").ComputedRef<import("../utils/options.js").OptionMenuItem[] | import("../utils/options.js").OptionMenuItem[][]>;
17
+ leadingOption: import("vue").ComputedRef<ResolvedFormOption | undefined>;
18
+ renderOption: import("vue").ComputedRef<((context: import("../types/index.js").FormOptionRenderContext<import("../types/index.js").FormOptionItem<FormOptionValue>>) => import("vue").VNodeChild) | undefined>;
19
+ renderValue: import("vue").ComputedRef<((context: import("../types/index.js").FormOptionValueRenderContext<import("../types/index.js").FormOptionItem<FormOptionValue>>) => import("vue").VNodeChild) | undefined>;
20
+ };
@@ -0,0 +1,41 @@
1
+ import { computed } from "vue";
2
+ import { formOptionKey, resolveOptionLeading, toMenuItems } from "../utils/options.js";
3
+ export function useOptionMenu(params) {
4
+ const renderOption = computed(() => params.field().renderOption);
5
+ const renderValue = computed(() => params.field().renderValue);
6
+ const items = computed(
7
+ () => toMenuItems(params.options.items.value, {
8
+ label: params.loadMoreLabel(),
9
+ show: params.options.remote.value && (params.options.hasMore.value || params.options.loadingMore.value)
10
+ })
11
+ );
12
+ const selectedKeys = computed(() => {
13
+ const model = params.model();
14
+ const values = Array.isArray(model) ? model : [model];
15
+ return new Set(
16
+ values.filter((value) => value !== null && value !== void 0).map(formOptionKey)
17
+ );
18
+ });
19
+ const valueOption = computed(
20
+ () => params.multiple() ? void 0 : params.options.selectedItems.value[0]
21
+ );
22
+ const leadingOption = computed(() => {
23
+ const option = valueOption.value;
24
+ return option && !renderValue.value && resolveOptionLeading(option) ? option : void 0;
25
+ });
26
+ const customValueOption = computed(() => {
27
+ const option = valueOption.value;
28
+ return option && (renderValue.value || option.suffix) ? option : void 0;
29
+ });
30
+ function isSelected(option) {
31
+ return selectedKeys.value.has(formOptionKey(option.value));
32
+ }
33
+ return {
34
+ customValueOption,
35
+ isSelected,
36
+ items,
37
+ leadingOption,
38
+ renderOption,
39
+ renderValue
40
+ };
41
+ }
@@ -5,7 +5,7 @@ import type { FormStatefulFieldBase } from '../../types/field-base.js';
5
5
  import type { ResolveFormFieldValue } from '../../types/field-output.js';
6
6
  import type { FormObject, FormRenderable, FormText } from '../../types/utils.js';
7
7
  type ContainerFieldType = 'array-list' | 'array-table' | 'array-tabs' | 'array-variant' | 'array-collapse' | 'array-primitive' | 'object' | 'group' | 'input-group' | 'card' | 'column' | 'matrix' | 'info' | 'divider' | 'section' | 'button' | 'hidden';
8
- type ItemFieldChrome = 'key' | 'label' | 'description' | 'hint' | 'help' | 'labelExtra' | 'layout';
8
+ type ItemFieldChrome = 'key' | 'label' | 'description' | 'hint' | 'help' | 'disabledReason' | 'labelExtra' | 'layout';
9
9
  type DistributiveOmit<TValue, TKey extends PropertyKey> = TValue extends unknown ? Omit<TValue, TKey> : never;
10
10
  export type FormArrayPrimitiveItemField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> = DistributiveOmit<Exclude<FormField<TContext, TDeps>, {
11
11
  type: ContainerFieldType;
@@ -4,9 +4,12 @@ import { computed, ref, watch } from "vue";
4
4
  import { useUiToolsLocale } from "../../../i18n/use-locale";
5
5
  import FormFieldShell from "../../components/renderer/form-field-shell.vue";
6
6
  import FormOptionItemLabel from "../../components/utils/form-option-item-label.vue";
7
+ import FormOptionLeading from "../../components/utils/form-option-leading.vue";
7
8
  import FormOptionMenuFooter from "../../components/utils/form-option-menu-footer.vue";
9
+ import FormOptionSuffix from "../../components/utils/form-option-suffix.vue";
10
+ import FormOptionValueOverlay from "../../components/utils/form-option-value-overlay.vue";
8
11
  import { useFieldControl } from "../../composables/use-field-control";
9
- import { appendLoadMoreOption } from "../../utils/options";
12
+ import { useOptionMenu } from "../../composables/use-option-menu";
10
13
  import { isBoolean, isNumber, isString } from "../../utils/predicate";
11
14
  import { mergeFormUiClass } from "../../utils/ui";
12
15
  const props = defineProps({
@@ -30,6 +33,7 @@ const {
30
33
  { omit: ["createItem", "clearable"] }
31
34
  );
32
35
  const searchTerm = ref("");
36
+ const open = ref(false);
33
37
  const model = computed({
34
38
  get: () => {
35
39
  const value = form.getValue(props.path);
@@ -43,16 +47,27 @@ const model = computed({
43
47
  },
44
48
  set: (value) => form.setValue(props.path, value)
45
49
  });
46
- const items = computed(() => [
47
- ...appendLoadMoreOption(
48
- options.items.value,
49
- options.remote.value && (options.hasMore.value || options.loadingMore.value),
50
- t("form.fields.options.loadingMore")
51
- )
52
- ]);
50
+ const menu = useOptionMenu({
51
+ field: () => props.field,
52
+ loadMoreLabel: () => t("form.fields.options.loadingMore"),
53
+ model: () => model.value,
54
+ multiple: () => fieldProps.value.multiple === true,
55
+ options
56
+ });
57
+ const overlayActive = computed(
58
+ () => menu.customValueOption.value !== void 0 && !open.value && searchTerm.value === ""
59
+ );
53
60
  const controlUi = computed(() => ({
54
61
  ...controlProps.value.ui,
55
- content: mergeFormUiClass(controlProps.value.ui?.content, interactionOwnerClass.value)
62
+ base: mergeFormUiClass(
63
+ controlProps.value.ui?.base,
64
+ overlayActive.value ? "text-transparent" : void 0
65
+ ),
66
+ content: mergeFormUiClass(controlProps.value.ui?.content, interactionOwnerClass.value),
67
+ itemDescription: mergeFormUiClass(
68
+ menu.renderOption.value ? "hidden" : "text-[12.5px]",
69
+ controlProps.value.ui?.itemDescription
70
+ )
56
71
  }));
57
72
  const createItem = computed(
58
73
  () => options.creatable.value && fieldProps.value.createItem ? fieldProps.value.createItem : false
@@ -68,10 +83,9 @@ watch(searchTerm, (term) => {
68
83
  options.setSearch(term);
69
84
  }
70
85
  });
71
- function handleOpen(open) {
72
- if (open) {
73
- options.activate();
74
- }
86
+ function handleOpen(next) {
87
+ open.value = next;
88
+ if (next) options.activate();
75
89
  }
76
90
  async function handleCreate(label) {
77
91
  await options.create(label);
@@ -93,47 +107,74 @@ function isOptionValue(value) {
93
107
 
94
108
  <template>
95
109
  <FormFieldShell :field="field" :path="path">
96
- <UInputMenu
97
- v-model="model"
98
- v-model:search-term="searchTerm"
99
- v-bind="controlProps"
100
- class="w-full"
101
- value-key="value"
102
- label-key="label"
103
- :items="items"
104
- :multiple="fieldProps.multiple"
105
- :placeholder="placeholder"
106
- :disabled="disabled"
107
- :loading="
110
+ <FormOptionValueOverlay
111
+ :option="menu.customValueOption.value"
112
+ :render="menu.renderValue.value"
113
+ :active="overlayActive"
114
+ >
115
+ <UInputMenu
116
+ v-model="model"
117
+ v-model:search-term="searchTerm"
118
+ v-bind="controlProps"
119
+ class="w-full"
120
+ value-key="value"
121
+ label-key="label"
122
+ :items="menu.items.value"
123
+ :multiple="fieldProps.multiple"
124
+ :placeholder="placeholder"
125
+ :disabled="disabled"
126
+ :loading="
108
127
  validationPending || options.loading.value || options.fetching.value || options.creating.value
109
128
  "
110
- :clear="fieldProps.clearable === true"
111
- :ignore-filter="options.remote.value"
112
- :create-item="createItem"
113
- :ui="controlUi"
114
- @update:open="handleOpen"
115
- @create="handleNativeCreate"
116
- @blur="handleBlur"
117
- >
118
- <template #create-item-label="{ item }">
119
- {{
120
- options.creating.value ? t("form.fields.options.creating") : t("form.fields.options.createNamed", { label: item })
121
- }}
122
- </template>
129
+ :clear="fieldProps.clearable === true"
130
+ :ignore-filter="options.remote.value"
131
+ :create-item="createItem"
132
+ :ui="controlUi"
133
+ @update:open="handleOpen"
134
+ @create="handleNativeCreate"
135
+ @blur="handleBlur"
136
+ >
137
+ <template #create-item-label="{ item }">
138
+ {{
139
+ options.creating.value ? t("form.fields.options.creating") : t("form.fields.options.createNamed", { label: item })
140
+ }}
141
+ </template>
142
+
143
+ <template v-if="menu.leadingOption.value" #leading="{ ui }">
144
+ <FormOptionLeading
145
+ :option="menu.leadingOption.value"
146
+ :icon-class="ui.leadingIcon()"
147
+ :avatar-size="ui.itemLeadingAvatarSize()"
148
+ chip-class="size-5"
149
+ />
150
+ </template>
151
+
152
+ <template #item-leading="{ item }">
153
+ <FormOptionLeading :option="menu.renderOption.value ? void 0 : item" />
154
+ </template>
155
+
156
+ <template #item-label="{ item }">
157
+ <FormOptionItemLabel
158
+ :option="item"
159
+ :selected="menu.isSelected(item)"
160
+ :render="menu.renderOption.value"
161
+ />
162
+ </template>
123
163
 
124
- <template #item-label="{ item }">
125
- <FormOptionItemLabel :item="item" />
126
- </template>
164
+ <template #item-trailing="{ item }">
165
+ <FormOptionSuffix v-if="!menu.renderOption.value" :option="item" />
166
+ </template>
127
167
 
128
- <template #content-bottom>
129
- <FormOptionMenuFooter
130
- :options="options"
131
- :disabled="disabled"
132
- :show-create="showExplicitCreate"
133
- :create-label="createActionLabel"
134
- @create="handleCreateAction"
135
- />
136
- </template>
137
- </UInputMenu>
168
+ <template #content-bottom>
169
+ <FormOptionMenuFooter
170
+ :options="options"
171
+ :disabled="disabled"
172
+ :show-create="showExplicitCreate"
173
+ :create-label="createActionLabel"
174
+ @create="handleCreateAction"
175
+ />
176
+ </template>
177
+ </UInputMenu>
178
+ </FormOptionValueOverlay>
138
179
  </FormFieldShell>
139
180
  </template>
@@ -4,6 +4,7 @@ export declare const autoCompleteFieldKind: {
4
4
  };
5
5
  readonly options: {
6
6
  readonly enabled: true;
7
+ readonly groups: true;
7
8
  };
8
9
  readonly state: "stateful";
9
10
  readonly transform: true;
@@ -1,7 +1,7 @@
1
1
  import { defineFormFieldKind } from "../../utils/field-kind.js";
2
2
  export const autoCompleteFieldKind = defineFormFieldKind({
3
3
  layout: { item: true },
4
- options: { enabled: true },
4
+ options: { enabled: true, groups: true },
5
5
  state: "stateful",
6
6
  transform: true,
7
7
  type: "auto-complete",
@@ -1,13 +1,13 @@
1
1
  import type { FormStatefulFieldBase } from '../../types/field-base.js';
2
2
  import type { FieldOptionValue, FieldProps, FallbackNever, NullableValue } from '../../types/field-output-utils.js';
3
- import type { FormAnyOptionConfig, FormOptionItem, FormOptionValue } from '../../types/options.js';
3
+ import type { FormAnyOptionConfig, FormOptionGrouping, FormOptionItem, FormOptionValue, FormOptionValueRenderers } from '../../types/options.js';
4
4
  import type { FormSelectCreateItem } from '../select/types.js';
5
5
  export interface FormAutoCompleteProps {
6
6
  multiple?: boolean;
7
7
  clearable?: boolean;
8
8
  createItem?: FormSelectCreateItem;
9
9
  }
10
- export interface FormAutoCompleteField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'auto-complete', TValue | readonly TValue[] | null, TContext, TDeps, FormAutoCompleteProps> {
10
+ export interface FormAutoCompleteField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'auto-complete', TValue | readonly TValue[] | null, TContext, TDeps, FormAutoCompleteProps>, FormOptionGrouping, FormOptionValueRenderers<TOption> {
11
11
  options: FormAnyOptionConfig<TOption, TContext, TDeps, TValue | readonly TValue[] | null>;
12
12
  }
13
13
  type AutoCompleteFieldValue<TField> = FieldProps<TField> extends {
@@ -3,10 +3,11 @@ import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
3
3
  import { computed } from "vue";
4
4
  import FormFieldShell from "../../components/renderer/form-field-shell.vue";
5
5
  import { useFieldControl } from "../../composables/use-field-control";
6
- import { formOptionKey } from "../../utils/options";
6
+ import { formOptionKey, groupOptions } from "../../utils/options";
7
7
  import { isBoolean, isNumber, isString, isUndefined } from "../../utils/predicate";
8
8
  import ChoiceCardLabel from "../choice-card/choice-card-label.vue";
9
9
  import { CHOICE_CARD_PROPS, useChoiceCard } from "../choice-card/use-choice-card";
10
+ import { choiceCardGroupAttrs } from "../choice-card/utils";
10
11
  const props = defineProps({
11
12
  field: { type: Object, required: true },
12
13
  path: { type: Array, required: true }
@@ -16,7 +17,11 @@ const { fieldProps, form, controlProps, disabled, handleBlur, options } = useFie
16
17
  () => props.path,
17
18
  { omit: CHOICE_CARD_PROPS }
18
19
  );
19
- const cards = useChoiceCard({ props: () => fieldProps.value, ui: () => controlProps.value.ui });
20
+ const cards = useChoiceCard({
21
+ kind: "checkbox",
22
+ props: () => fieldProps.value,
23
+ ui: () => controlProps.value.ui
24
+ });
20
25
  const optionByKey = computed(
21
26
  () => new Map(options.items.value.map((item) => [formOptionKey(item.value), item]))
22
27
  );
@@ -36,9 +41,20 @@ const model = computed({
36
41
  const items = computed(
37
42
  () => options.items.value.map(({ icon: _icon, ...item }) => ({
38
43
  ...item,
44
+ description: cards.compact.value ? void 0 : item.description,
39
45
  value: formOptionKey(item.value)
40
46
  }))
41
47
  );
48
+ const groups = computed(() => groupOptions(items.value));
49
+ function selectionOf(group) {
50
+ return model.value.filter((key) => group.items.some((item) => item.value === key));
51
+ }
52
+ function update(group, keys) {
53
+ const kept = model.value.filter(
54
+ (key) => keys.includes(key) || !group.items.some((item) => item.value === key)
55
+ );
56
+ model.value = [...kept, ...keys.filter((key) => !kept.includes(key))];
57
+ }
42
58
  function isOptionValue(value) {
43
59
  return isString(value) || isNumber(value) || isBoolean(value);
44
60
  }
@@ -46,32 +62,42 @@ function isOptionValue(value) {
46
62
 
47
63
  <template>
48
64
  <FormFieldShell :field="field" :path="path">
49
- <UCheckboxGroup
50
- v-model="model"
51
- v-bind="controlProps"
52
- :items="items"
53
- value-key="value"
54
- label-key="label"
55
- description-key="description"
56
- variant="card"
57
- :orientation="cards.orientation.value"
58
- :indicator="cards.indicator.value"
59
- :ui="cards.ui.value"
60
- :style="cards.style.value"
61
- :disabled="disabled"
62
- @blur="handleBlur"
63
- >
64
- <template #label="{ item }">
65
- <ChoiceCardLabel
66
- :label="optionByKey.get(item.value)?.label ?? item.label"
67
- :icon="optionByKey.get(item.value)?.icon"
68
- :value="optionByKey.get(item.value)?.value"
69
- :tile="cards.tile.value"
70
- :mark="cards.mark.value"
71
- :selected="model.includes(item.value)"
72
- :ui="cards.ui.value"
73
- />
74
- </template>
75
- </UCheckboxGroup>
65
+ <div :class="cards.groupsClass.value">
66
+ <UCheckboxGroup
67
+ v-for="(group, index) in groups"
68
+ :key="group.key"
69
+ :model-value="selectionOf(group)"
70
+ v-bind="choiceCardGroupAttrs(controlProps, index)"
71
+ :items="group.items"
72
+ value-key="value"
73
+ label-key="label"
74
+ description-key="description"
75
+ variant="card"
76
+ :legend="group.heading"
77
+ :orientation="cards.orientation.value"
78
+ :indicator="cards.indicator.value"
79
+ :ui="cards.ui.value"
80
+ :style="cards.style.value"
81
+ :disabled="disabled"
82
+ @update:model-value="update(group, $event)"
83
+ @blur="handleBlur"
84
+ >
85
+ <template #label="{ item }">
86
+ <ChoiceCardLabel
87
+ :label="optionByKey.get(item.value)?.label ?? item.label"
88
+ :description="
89
+ cards.compact.value ? optionByKey.get(item.value)?.description : void 0
90
+ "
91
+ :icon="optionByKey.get(item.value)?.icon"
92
+ :value="optionByKey.get(item.value)?.value"
93
+ :tile="cards.tile.value"
94
+ :compact="cards.compact.value"
95
+ :mark="cards.mark.value"
96
+ :selected="model.includes(item.value)"
97
+ :ui="cards.ui.value"
98
+ />
99
+ </template>
100
+ </UCheckboxGroup>
101
+ </div>
76
102
  </FormFieldShell>
77
103
  </template>
@@ -4,6 +4,7 @@ export declare const checkboxCardFieldKind: {
4
4
  };
5
5
  readonly options: {
6
6
  readonly enabled: true;
7
+ readonly groups: true;
7
8
  };
8
9
  readonly state: "stateful";
9
10
  readonly transform: true;
@@ -1,7 +1,7 @@
1
1
  import { defineFormFieldKind } from "../../utils/field-kind.js";
2
2
  export const checkboxCardFieldKind = defineFormFieldKind({
3
3
  layout: { item: true },
4
- options: { enabled: true },
4
+ options: { enabled: true, groups: true },
5
5
  state: "stateful",
6
6
  transform: true,
7
7
  type: "checkbox-card",
@@ -1,8 +1,8 @@
1
1
  import type { FormStatefulFieldBase } from '../../types/field-base.js';
2
2
  import type { FieldOptionValue, NullableValue } from '../../types/field-output-utils.js';
3
- import type { FormOptionConfig, FormOptionItem, FormOptionValue, FormOptionsSource } from '../../types/options.js';
4
- import type { FormChoiceCardProps } from '../choice-card/types.js';
5
- export interface FormCheckboxCardField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-card', readonly TValue[] | null, TContext, TDeps, FormChoiceCardProps> {
3
+ import type { FormOptionConfig, FormOptionGrouping, FormOptionValue, FormOptionsSource } from '../../types/options.js';
4
+ import type { FormChoiceCardOptionItem, FormChoiceCardProps } from '../choice-card/types.js';
5
+ export interface FormCheckboxCardField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormChoiceCardOptionItem<TValue> = FormChoiceCardOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-card', readonly TValue[] | null, TContext, TDeps, FormChoiceCardProps>, FormOptionGrouping {
6
6
  options: FormOptionConfig<TOption, TContext, TDeps, readonly TValue[] | null> | FormOptionsSource<TOption, TContext, TDeps, readonly TValue[] | null>;
7
7
  }
8
8
  export type CheckboxCardFieldOutput<TField> = readonly FieldOptionValue<TField>[] | NullableValue;
@@ -2,6 +2,8 @@
2
2
  import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
3
3
  import { computed } from "vue";
4
4
  import FormFieldShell from "../../components/renderer/form-field-shell.vue";
5
+ import FormOptionChoiceLabel from "../../components/utils/form-option-choice-label.vue";
6
+ import { useChoiceGroupItems } from "../../composables/use-choice-group-items";
5
7
  import { useFieldControl } from "../../composables/use-field-control";
6
8
  import { formOptionKey } from "../../utils/options";
7
9
  import { isBoolean, isNumber, isString, isUndefined } from "../../utils/predicate";
@@ -31,9 +33,13 @@ const model = computed({
31
33
  })
32
34
  )
33
35
  });
34
- const items = computed(
35
- () => options.items.value.map((item) => ({ ...item, value: formOptionKey(item.value) }))
36
- );
36
+ const renderOption = computed(() => props.field.renderOption);
37
+ const { items, optionOf } = useChoiceGroupItems({
38
+ custom: () => renderOption.value !== void 0,
39
+ options,
40
+ ui: () => controlProps.value.ui,
41
+ valueOf: (option) => formOptionKey(option.value)
42
+ });
37
43
  const groupUi = computed(() => ({
38
44
  ...controlProps.value.ui,
39
45
  fieldset: mergeFormUiClass(
@@ -65,6 +71,14 @@ function isOptionValue(value) {
65
71
  :indicator="fieldProps.indicator"
66
72
  :disabled="disabled"
67
73
  @blur="handleBlur"
68
- />
74
+ >
75
+ <template #label="{ item }">
76
+ <FormOptionChoiceLabel
77
+ :option="optionOf(item.value)"
78
+ :selected="model.includes(item.value)"
79
+ :render="renderOption"
80
+ />
81
+ </template>
82
+ </UCheckboxGroup>
69
83
  </FormFieldShell>
70
84
  </template>
@@ -1,6 +1,6 @@
1
1
  import type { FormStatefulFieldBase } from '../../types/field-base.js';
2
2
  import type { FieldOptionValue, NullableValue } from '../../types/field-output-utils.js';
3
- import type { FormOptionConfig, FormOptionItem, FormOptionValue, FormOptionsSource } from '../../types/options.js';
3
+ import type { FormOptionConfig, FormOptionItem, FormOptionRenderers, FormOptionValue, FormOptionsSource } from '../../types/options.js';
4
4
  export interface FormCheckboxGroupProps {
5
5
  /**
6
6
  * `list` (the default) lines up bare checkboxes, `card` frames each option on its own, and
@@ -10,7 +10,7 @@ export interface FormCheckboxGroupProps {
10
10
  orientation?: 'horizontal' | 'vertical';
11
11
  indicator?: 'start' | 'end' | 'hidden';
12
12
  }
13
- export interface FormCheckboxGroupField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-group', readonly TValue[] | null, TContext, TDeps, FormCheckboxGroupProps> {
13
+ export interface FormCheckboxGroupField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-group', readonly TValue[] | null, TContext, TDeps, FormCheckboxGroupProps>, FormOptionRenderers<TOption> {
14
14
  options: FormOptionConfig<TOption, TContext, TDeps, readonly TValue[] | null> | FormOptionsSource<TOption, TContext, TDeps, readonly TValue[] | null>;
15
15
  }
16
16
  export type CheckboxGroupFieldOutput<TField> = readonly FieldOptionValue<TField>[] | NullableValue;