nuxt-ui-tools 0.1.0 → 0.1.2

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 (186) hide show
  1. package/dist/module.d.mts +4 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +30 -15
  4. package/dist/runtime/form/components/actions/FormActions.d.vue.ts +0 -2
  5. package/dist/runtime/form/components/actions/FormActions.vue +5 -8
  6. package/dist/runtime/form/components/actions/FormActions.vue.d.ts +0 -2
  7. package/dist/runtime/form/components/layout/DrawerLayout.vue +5 -1
  8. package/dist/runtime/form/components/layout/ModalLayout.vue +5 -1
  9. package/dist/runtime/form/components/renderer/FormCompositeControl.d.vue.ts +15 -0
  10. package/dist/runtime/form/components/renderer/FormCompositeControl.vue +13 -0
  11. package/dist/runtime/form/components/renderer/FormCompositeControl.vue.d.ts +15 -0
  12. package/dist/runtime/form/components/renderer/FormFieldRenderer.d.vue.ts +1 -0
  13. package/dist/runtime/form/components/renderer/FormFieldRenderer.vue +19 -3
  14. package/dist/runtime/form/components/renderer/FormFieldRenderer.vue.d.ts +1 -0
  15. package/dist/runtime/form/components/renderer/FormFieldShell.d.vue.ts +4 -4
  16. package/dist/runtime/form/components/renderer/FormFieldShell.vue +15 -1
  17. package/dist/runtime/form/components/renderer/FormFieldShell.vue.d.ts +4 -4
  18. package/dist/runtime/form/components/root/Form.d.vue.ts +2 -2
  19. package/dist/runtime/form/components/root/Form.vue +5 -3
  20. package/dist/runtime/form/components/root/Form.vue.d.ts +2 -2
  21. package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
  22. package/dist/runtime/form/composables/use-field-control.js +4 -0
  23. package/dist/runtime/form/composables/use-field-options.d.ts +1 -1
  24. package/dist/runtime/form/composables/use-field-options.js +75 -31
  25. package/dist/runtime/form/composables/use-form-field-chrome.d.ts +3 -0
  26. package/dist/runtime/form/composables/use-form-field-chrome.js +9 -0
  27. package/dist/runtime/form/composables/use-form-runtime.js +17 -5
  28. package/dist/runtime/form/fields/array-list/component.vue +4 -1
  29. package/dist/runtime/form/fields/auto-complete/component.vue +23 -16
  30. package/dist/runtime/form/fields/checkbox-card/component.vue +28 -29
  31. package/dist/runtime/form/fields/checkbox-group/component.vue +28 -29
  32. package/dist/runtime/form/fields/color-picker/component.vue +4 -2
  33. package/dist/runtime/form/fields/date-family/component.vue +9 -4
  34. package/dist/runtime/form/fields/hierarchy/SelectionCheckbox.d.vue.ts +13 -0
  35. package/dist/runtime/form/fields/hierarchy/SelectionCheckbox.vue +24 -0
  36. package/dist/runtime/form/fields/hierarchy/SelectionCheckbox.vue.d.ts +13 -0
  37. package/dist/runtime/form/fields/hierarchy/SelectionRadio.d.vue.ts +14 -0
  38. package/dist/runtime/form/fields/hierarchy/SelectionRadio.vue +27 -0
  39. package/dist/runtime/form/fields/hierarchy/SelectionRadio.vue.d.ts +14 -0
  40. package/dist/runtime/form/fields/hierarchy/component.vue +257 -142
  41. package/dist/runtime/form/fields/hierarchy/utils.d.ts +9 -0
  42. package/dist/runtime/form/fields/hierarchy/utils.js +32 -0
  43. package/dist/runtime/form/fields/input-group/component.vue +7 -30
  44. package/dist/runtime/form/fields/matrix/component.vue +9 -3
  45. package/dist/runtime/form/fields/phone-number/component.vue +7 -2
  46. package/dist/runtime/form/fields/rating/component.vue +25 -2
  47. package/dist/runtime/form/fields/select/component.vue +29 -21
  48. package/dist/runtime/form/fields/switch-group/component.vue +8 -3
  49. package/dist/runtime/form/fields/time/component.vue +7 -5
  50. package/dist/runtime/form/fields/upload/component.vue +5 -3
  51. package/dist/runtime/form/types/api.d.ts +9 -3
  52. package/dist/runtime/form/types/field-base.d.ts +3 -1
  53. package/dist/runtime/form/types/runtime.d.ts +2 -0
  54. package/dist/runtime/form/types/ui.d.ts +1 -0
  55. package/dist/runtime/form/utils/api.js +3 -0
  56. package/dist/runtime/form/utils/options.d.ts +11 -2
  57. package/dist/runtime/form/utils/options.js +57 -9
  58. package/dist/runtime/form/utils/state.js +10 -2
  59. package/dist/runtime/i18n/locales/en.js +38 -0
  60. package/dist/runtime/i18n/locales/fr.js +38 -0
  61. package/dist/runtime/i18n/types.d.ts +38 -0
  62. package/dist/runtime/i18n/use-locale.d.ts +2 -2
  63. package/dist/runtime/i18n/use-locale.js +3 -3
  64. package/dist/runtime/spreadsheet/components/layout/SpreadsheetImportFooter.d.vue.ts +2 -2
  65. package/dist/runtime/spreadsheet/components/layout/SpreadsheetImportFooter.vue.d.ts +2 -2
  66. package/dist/runtime/spreadsheet/components/steps/review/SpreadsheetReviewInspection.d.vue.ts +2 -2
  67. package/dist/runtime/spreadsheet/components/steps/review/SpreadsheetReviewInspection.vue.d.ts +2 -2
  68. package/dist/runtime/spreadsheet/composables/use-spreadsheet-internals.d.ts +15 -15
  69. package/dist/runtime/spreadsheet/composables/use-spreadsheet-resolutions.d.ts +2 -2
  70. package/dist/runtime/spreadsheet/schema/normalize.d.ts +1 -1
  71. package/dist/runtime/spreadsheet/utils/references/sources.d.ts +1 -1
  72. package/dist/runtime/table/components/DataList.d.vue.ts +2 -0
  73. package/dist/runtime/table/components/DataList.vue.d.ts +2 -0
  74. package/dist/runtime/table/components/actions/RowActions.d.vue.ts +4 -2
  75. package/dist/runtime/table/components/actions/RowActions.vue +2 -0
  76. package/dist/runtime/table/components/actions/RowActions.vue.d.ts +4 -2
  77. package/dist/runtime/table/components/data-list/DataListAddFilter.d.vue.ts +5 -3
  78. package/dist/runtime/table/components/data-list/DataListAddFilter.vue +6 -1
  79. package/dist/runtime/table/components/data-list/DataListAddFilter.vue.d.ts +5 -3
  80. package/dist/runtime/table/components/data-list/DataListContent.d.vue.ts +24 -17
  81. package/dist/runtime/table/components/data-list/DataListContent.vue +70 -17
  82. package/dist/runtime/table/components/data-list/DataListContent.vue.d.ts +24 -17
  83. package/dist/runtime/table/components/data-list/DataListDefault.d.vue.ts +2 -0
  84. package/dist/runtime/table/components/data-list/DataListDefault.vue.d.ts +2 -0
  85. package/dist/runtime/table/components/data-list/DataListFilterTags.d.vue.ts +2 -0
  86. package/dist/runtime/table/components/data-list/DataListFilterTags.vue.d.ts +2 -0
  87. package/dist/runtime/table/components/data-list/DataListInfiniteLoader.d.vue.ts +1 -1
  88. package/dist/runtime/table/components/data-list/DataListInfiniteLoader.vue.d.ts +1 -1
  89. package/dist/runtime/table/components/data-list/DataListLayoutSwitch.vue +17 -11
  90. package/dist/runtime/table/components/data-list/DataListPagination.d.vue.ts +1 -1
  91. package/dist/runtime/table/components/data-list/DataListPagination.vue.d.ts +1 -1
  92. package/dist/runtime/table/components/data-list/DataListRoot.vue +2 -46
  93. package/dist/runtime/table/components/data-list/DataListSortMenu.d.vue.ts +1 -1
  94. package/dist/runtime/table/components/data-list/DataListSortMenu.vue +6 -0
  95. package/dist/runtime/table/components/data-list/DataListSortMenu.vue.d.ts +1 -1
  96. package/dist/runtime/table/components/filters/shared/DynamicFilterPicker.d.vue.ts +5 -0
  97. package/dist/runtime/table/components/filters/shared/DynamicFilterPicker.vue +182 -59
  98. package/dist/runtime/table/components/filters/shared/DynamicFilterPicker.vue.d.ts +5 -0
  99. package/dist/runtime/table/components/filters/shared/FilterMatchModeButton.d.vue.ts +3 -1
  100. package/dist/runtime/table/components/filters/shared/FilterMatchModeButton.vue +29 -5
  101. package/dist/runtime/table/components/filters/shared/FilterMatchModeButton.vue.d.ts +3 -1
  102. package/dist/runtime/table/components/filters/shared/FilterMatchModePanel.d.vue.ts +17 -0
  103. package/dist/runtime/table/components/filters/shared/FilterMatchModePanel.vue +47 -0
  104. package/dist/runtime/table/components/filters/shared/FilterMatchModePanel.vue.d.ts +17 -0
  105. package/dist/runtime/table/components/filters/shared/FilterOptionMultipleList.d.vue.ts +3 -1
  106. package/dist/runtime/table/components/filters/shared/FilterOptionMultipleList.vue +12 -3
  107. package/dist/runtime/table/components/filters/shared/FilterOptionMultipleList.vue.d.ts +3 -1
  108. package/dist/runtime/table/components/filters/shared/FilterOptionPickerContent.d.vue.ts +3 -0
  109. package/dist/runtime/table/components/filters/shared/FilterOptionPickerContent.vue +11 -1
  110. package/dist/runtime/table/components/filters/shared/FilterOptionPickerContent.vue.d.ts +3 -0
  111. package/dist/runtime/table/components/filters/shared/FilterOptionRow.d.vue.ts +3 -0
  112. package/dist/runtime/table/components/filters/shared/FilterOptionRow.vue +48 -9
  113. package/dist/runtime/table/components/filters/shared/FilterOptionRow.vue.d.ts +3 -0
  114. package/dist/runtime/table/components/filters/shared/FilterOptionSingleList.d.vue.ts +3 -0
  115. package/dist/runtime/table/components/filters/shared/FilterOptionSingleList.vue +35 -9
  116. package/dist/runtime/table/components/filters/shared/FilterOptionSingleList.vue.d.ts +3 -0
  117. package/dist/runtime/table/components/filters/shared/FilterOptionTreeContent.d.vue.ts +3 -0
  118. package/dist/runtime/table/components/filters/shared/FilterOptionTreeContent.vue +77 -20
  119. package/dist/runtime/table/components/filters/shared/FilterOptionTreeContent.vue.d.ts +3 -0
  120. package/dist/runtime/table/components/filters/shared/FilterPopoverShell.d.vue.ts +27 -0
  121. package/dist/runtime/table/components/filters/shared/FilterPopoverShell.vue +36 -0
  122. package/dist/runtime/table/components/filters/shared/FilterPopoverShell.vue.d.ts +27 -0
  123. package/dist/runtime/table/components/filters/shared/FilterSearchablePanel.d.vue.ts +3 -0
  124. package/dist/runtime/table/components/filters/shared/FilterSearchablePanel.vue +40 -10
  125. package/dist/runtime/table/components/filters/shared/FilterSearchablePanel.vue.d.ts +3 -0
  126. package/dist/runtime/table/components/filters/shared/FilterStageTransition.d.vue.ts +23 -0
  127. package/dist/runtime/table/components/filters/shared/FilterStageTransition.vue +21 -0
  128. package/dist/runtime/table/components/filters/shared/FilterStageTransition.vue.d.ts +23 -0
  129. package/dist/runtime/table/components/filters/shared/FilterTriggerTag.d.vue.ts +3 -2
  130. package/dist/runtime/table/components/filters/shared/FilterTriggerTag.vue +26 -25
  131. package/dist/runtime/table/components/filters/shared/FilterTriggerTag.vue.d.ts +3 -2
  132. package/dist/runtime/table/components/filters/tags/BooleanFilterTag.d.vue.ts +1 -1
  133. package/dist/runtime/table/components/filters/tags/BooleanFilterTag.vue +75 -22
  134. package/dist/runtime/table/components/filters/tags/BooleanFilterTag.vue.d.ts +1 -1
  135. package/dist/runtime/table/components/filters/tags/DateFilterTag.d.vue.ts +3 -1
  136. package/dist/runtime/table/components/filters/tags/DateFilterTag.vue +254 -160
  137. package/dist/runtime/table/components/filters/tags/DateFilterTag.vue.d.ts +3 -1
  138. package/dist/runtime/table/components/filters/tags/FilterTagsBar.d.vue.ts +7 -6
  139. package/dist/runtime/table/components/filters/tags/FilterTagsBar.vue +12 -27
  140. package/dist/runtime/table/components/filters/tags/FilterTagsBar.vue.d.ts +7 -6
  141. package/dist/runtime/table/components/filters/tags/NumberFilterTag.d.vue.ts +3 -2
  142. package/dist/runtime/table/components/filters/tags/NumberFilterTag.vue +192 -106
  143. package/dist/runtime/table/components/filters/tags/NumberFilterTag.vue.d.ts +3 -2
  144. package/dist/runtime/table/components/filters/tags/OptionFilterTag.d.vue.ts +3 -2
  145. package/dist/runtime/table/components/filters/tags/OptionFilterTag.vue +110 -48
  146. package/dist/runtime/table/components/filters/tags/OptionFilterTag.vue.d.ts +3 -2
  147. package/dist/runtime/table/components/filters/tags/TextFilterTag.d.vue.ts +3 -2
  148. package/dist/runtime/table/components/filters/tags/TextFilterTag.vue +120 -57
  149. package/dist/runtime/table/components/filters/tags/TextFilterTag.vue.d.ts +3 -2
  150. package/dist/runtime/table/components/grid/GridRenderer.vue +1 -1
  151. package/dist/runtime/table/components/layout/TableFooter.d.vue.ts +1 -1
  152. package/dist/runtime/table/components/layout/TableFooter.vue +1 -0
  153. package/dist/runtime/table/components/layout/TableFooter.vue.d.ts +1 -1
  154. package/dist/runtime/table/components/table/TableCellEllipsis.d.ts +1 -1
  155. package/dist/runtime/table/components/table/TableRenderer.vue +24 -12
  156. package/dist/runtime/table/composables/use-filter-tag-session.d.ts +3 -4
  157. package/dist/runtime/table/composables/use-filter-tag-session.js +12 -32
  158. package/dist/runtime/table/composables/use-query-state.d.ts +5 -5
  159. package/dist/runtime/table/composables/use-query-state.js +7 -32
  160. package/dist/runtime/table/composables/use-table-api.js +2 -2
  161. package/dist/runtime/table/composables/use-table-columns.d.ts +4 -14
  162. package/dist/runtime/table/composables/use-table-filter-presentation.d.ts +0 -3
  163. package/dist/runtime/table/composables/use-table-filter-presentation.js +0 -9
  164. package/dist/runtime/table/composables/use-table-filters.js +18 -7
  165. package/dist/runtime/table/composables/use-table-internals.d.ts +24 -63
  166. package/dist/runtime/table/composables/use-table-pagination.d.ts +2 -2
  167. package/dist/runtime/table/composables/use-table-state.d.ts +2 -2
  168. package/dist/runtime/table/types/actions.d.ts +1 -1
  169. package/dist/runtime/table/types/data-list.d.ts +41 -8
  170. package/dist/runtime/table/types/filters.d.ts +4 -0
  171. package/dist/runtime/table/utils/actions.d.ts +1 -1
  172. package/dist/runtime/table/utils/columns/menu.d.ts +2 -11
  173. package/dist/runtime/table/utils/columns/render.d.ts +2 -9
  174. package/dist/runtime/table/utils/columns/render.js +39 -30
  175. package/dist/runtime/table/utils/columns/state.d.ts +1 -1
  176. package/dist/runtime/table/utils/data-list-ui.d.ts +3 -1
  177. package/dist/runtime/table/utils/data-list-ui.js +45 -0
  178. package/dist/runtime/table/utils/filters/editor-size.d.ts +11 -0
  179. package/dist/runtime/table/utils/filters/editor-size.js +45 -0
  180. package/dist/runtime/table/utils/filters/index.d.ts +1 -1
  181. package/dist/runtime/table/utils/filters/index.js +1 -1
  182. package/dist/runtime/table/utils/query-state.d.ts +18 -1
  183. package/dist/runtime/table/utils/query-state.js +109 -0
  184. package/package.json +4 -2
  185. package/dist/runtime/table/utils/filters/operators.d.ts +0 -4
  186. package/dist/runtime/table/utils/filters/operators.js +0 -6
@@ -0,0 +1,24 @@
1
+ <script setup>
2
+ import UCheckbox from "@nuxt/ui/components/Checkbox.vue";
3
+ import { formFieldInjectionKey, inputIdInjectionKey } from "@nuxt/ui/composables/useFormField";
4
+ import { provide, ref } from "vue";
5
+ defineProps({
6
+ modelValue: { type: [Boolean, String], required: true },
7
+ label: { type: String, required: true },
8
+ disabled: { type: Boolean, required: false },
9
+ controlClass: { type: String, required: false }
10
+ });
11
+ const emit = defineEmits(["update:modelValue"]);
12
+ provide(formFieldInjectionKey, void 0);
13
+ provide(inputIdInjectionKey, ref(void 0));
14
+ </script>
15
+
16
+ <template>
17
+ <UCheckbox
18
+ :model-value="modelValue"
19
+ :aria-label="label"
20
+ :disabled="disabled"
21
+ :ui="{ root: controlClass }"
22
+ @update:model-value="emit('update:modelValue', $event)"
23
+ />
24
+ </template>
@@ -0,0 +1,13 @@
1
+ type __VLS_Props = {
2
+ modelValue: boolean | 'indeterminate';
3
+ label: string;
4
+ disabled?: boolean;
5
+ controlClass?: string;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ "update:modelValue": (value: boolean | "indeterminate") => any;
9
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
+ "onUpdate:modelValue"?: ((value: boolean | "indeterminate") => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ value: string;
3
+ label: string;
4
+ selected: boolean;
5
+ disabled?: boolean;
6
+ controlClass?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ select: () => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onSelect?: (() => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -0,0 +1,27 @@
1
+ <script setup>
2
+ import URadioGroup from "@nuxt/ui/components/RadioGroup.vue";
3
+ import { formFieldInjectionKey, inputIdInjectionKey } from "@nuxt/ui/composables/useFormField";
4
+ import { computed, provide, ref } from "vue";
5
+ const props = defineProps({
6
+ value: { type: String, required: true },
7
+ label: { type: String, required: true },
8
+ selected: { type: Boolean, required: true },
9
+ disabled: { type: Boolean, required: false },
10
+ controlClass: { type: String, required: false }
11
+ });
12
+ const emit = defineEmits(["select"]);
13
+ const items = computed(() => [{ value: props.value, label: props.label }]);
14
+ provide(formFieldInjectionKey, void 0);
15
+ provide(inputIdInjectionKey, ref(void 0));
16
+ </script>
17
+
18
+ <template>
19
+ <URadioGroup
20
+ :model-value="selected ? value : void 0"
21
+ :items="items"
22
+ :aria-label="label"
23
+ :disabled="disabled"
24
+ :ui="{ root: controlClass, label: 'sr-only' }"
25
+ @update:model-value="emit('select')"
26
+ />
27
+ </template>
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ value: string;
3
+ label: string;
4
+ selected: boolean;
5
+ disabled?: boolean;
6
+ controlClass?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ select: () => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onSelect?: (() => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -6,14 +6,22 @@ import UPopover from "@nuxt/ui/components/Popover.vue";
6
6
  import USelectMenu from "@nuxt/ui/components/SelectMenu.vue";
7
7
  import UTree from "@nuxt/ui/components/Tree.vue";
8
8
  import { computed, ref, watch } from "vue";
9
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
10
+ import FormCompositeControl from "../../components/renderer/FormCompositeControl.vue";
9
11
  import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
10
12
  import { useFieldControl } from "../../composables/use-field-control";
11
13
  import { useFormUi } from "../../composables/use-form-ui";
14
+ import { formOptionKey } from "../../utils/options";
15
+ import { resolveFormText } from "../../utils/text";
12
16
  import { mergeFormUiClass } from "../../utils/ui";
17
+ import SelectionCheckbox from "./SelectionCheckbox.vue";
18
+ import SelectionRadio from "./SelectionRadio.vue";
19
+ import { resolveHierarchySelection } from "./utils";
13
20
  const props = defineProps({
14
21
  field: { type: null, required: true },
15
22
  path: { type: Array, required: true }
16
23
  });
24
+ const { t } = useUiToolsLocale();
17
25
  const { form, controlProps, controlSize, disabled, handleBlur, options, placeholder } = useFieldControl(
18
26
  () => props.field,
19
27
  () => props.path
@@ -36,6 +44,7 @@ const treeSelectionControl = computed(() => {
36
44
  });
37
45
  const treeSelectOpen = ref(false);
38
46
  const treeSearch = ref("");
47
+ const expandedKeys = ref([]);
39
48
  const separator = computed(
40
49
  () => props.field.type === "cascader" ? props.field.separator ?? " / " : " / "
41
50
  );
@@ -76,9 +85,14 @@ const treeSelectLabel = computed(() => {
76
85
  if (!selected) return placeholder.value;
77
86
  return props.field.type === "tree-select" && props.field.showPath ? selected.pathLabel : String(selected.label);
78
87
  });
88
+ const fieldLabel = computed(() => resolveFormText(props.field.label));
79
89
  watch(treeSelectOpen, (open) => {
80
90
  if (!open) treeSearch.value = "";
81
91
  });
92
+ watch(treeSearch, (query) => {
93
+ if (!query.trim()) return;
94
+ expandedKeys.value = flattenTreeItems(visibleTreeItems.value).filter((item) => item.children?.length).map((item) => treeKey(item));
95
+ });
82
96
  function flattenOptions(items, labels = [], depth = 0) {
83
97
  return items.flatMap((item) => {
84
98
  const path = [...labels, item.label];
@@ -102,9 +116,13 @@ function toTreeItems(items, labels = []) {
102
116
  ...item,
103
117
  label: treeItemLabel(item),
104
118
  pathLabel: [...labels, item.label].join(separator.value),
105
- children: item.children ? toTreeItems(item.children, [...labels, item.label]) : void 0
119
+ children: item.children ? toTreeItems(item.children, [...labels, item.label]) : void 0,
120
+ ...treeSelectionControl.value === "none" ? {} : { onSelect: preventTreeSelection }
106
121
  }));
107
122
  }
123
+ function preventTreeSelection(event) {
124
+ event.preventDefault();
125
+ }
108
126
  function treeItemLabel(item) {
109
127
  if (props.field.type !== "tree-select" || !props.field.showChildrenCount) return item.label;
110
128
  const count = item.children?.length ?? 0;
@@ -125,9 +143,16 @@ function flattenTreeItems(items) {
125
143
  }
126
144
  function updateTreeModel(value) {
127
145
  if (Array.isArray(value)) {
146
+ const selected = /* @__PURE__ */ new Map();
147
+ for (const item of value) selected.set(formOptionKey(item.value), item.value);
128
148
  form.setValue(
129
149
  props.path,
130
- value.map((item) => item.value)
150
+ resolveHierarchySelection({
151
+ next: [...selected.values()],
152
+ items: options.items.value,
153
+ propagate: treePropagateSelect(props.field) === true,
154
+ bubble: treeBubbleSelect(props.field) === true
155
+ })
131
156
  );
132
157
  return;
133
158
  }
@@ -139,7 +164,35 @@ function clearTreeSelection() {
139
164
  treeSelectOpen.value = false;
140
165
  }
141
166
  function treeKey(item) {
142
- return String(item.value);
167
+ return formOptionKey(item.value);
168
+ }
169
+ function toggleTreeItem(item) {
170
+ const current = Array.isArray(model.value) ? model.value : [];
171
+ const selected = new Map(current.map((value) => [formOptionKey(value), value]));
172
+ const key = formOptionKey(item.value);
173
+ if (selected.has(key)) selected.delete(key);
174
+ else selected.set(key, item.value);
175
+ form.setValue(
176
+ props.path,
177
+ resolveHierarchySelection({
178
+ next: [...selected.values()],
179
+ intent: item.value,
180
+ items: options.items.value,
181
+ propagate: treePropagateSelect(props.field) === true,
182
+ bubble: treeBubbleSelect(props.field) === true
183
+ })
184
+ );
185
+ }
186
+ function selectRadioTreeItem(item) {
187
+ form.setValue(props.path, item.value);
188
+ if (isTreeSelect.value) treeSelectOpen.value = false;
189
+ }
190
+ function selectTreeRow(item) {
191
+ if (treeSelectionControl.value === "checkbox") {
192
+ toggleTreeItem(item);
193
+ return;
194
+ }
195
+ if (treeSelectionControl.value === "radio") selectRadioTreeItem(item);
143
196
  }
144
197
  function hasShowPath(field) {
145
198
  return field.type === "tree-select" && field.showPath === true;
@@ -154,12 +207,6 @@ function treeSelectionBehavior(field) {
154
207
  if (field.type !== "tree" && field.type !== "tree-select") return void 0;
155
208
  return field.selectionBehavior ?? (treeMultiple.value ? "toggle" : "replace");
156
209
  }
157
- function treeSelectionIcon(selected, indeterminate) {
158
- if (treeSelectionControl.value === "radio")
159
- return selected ? "i-lucide-circle-dot" : "i-lucide-circle";
160
- if (indeterminate) return "i-lucide-square-minus";
161
- return selected ? "i-lucide-square-check-big" : "i-lucide-square";
162
- }
163
210
  function treeVirtualize(field) {
164
211
  return field.type === "tree" ? field.virtualize : void 0;
165
212
  }
@@ -170,21 +217,24 @@ function isOptionValue(value) {
170
217
 
171
218
  <template>
172
219
  <FormFieldShell :field="field" :path="path">
173
- <UTree
174
- v-if="isTree"
175
- v-bind="controlProps"
176
- :model-value="treeModel"
177
- :items="treeItems"
178
- :get-key="treeKey"
179
- :multiple="treeMultiple"
180
- :selection-behavior="treeSelectionBehavior(field)"
181
- :propagate-select="treePropagateSelect(field)"
182
- :bubble-select="treeBubbleSelect(field)"
183
- :virtualize="treeVirtualize(field)"
184
- :disabled="disabled"
185
- :size="controlSize"
186
- :class="mergeFormUiClass('w-full py-1', formUi.ui.value.tree?.ui?.root)"
187
- :ui="{
220
+ <FormCompositeControl v-slot="{ attrs }">
221
+ <UTree
222
+ v-if="isTree"
223
+ v-bind="{ ...controlProps, ...attrs }"
224
+ :aria-label="fieldLabel"
225
+ :model-value="treeModel"
226
+ v-model:expanded="expandedKeys"
227
+ :items="treeItems"
228
+ :get-key="treeKey"
229
+ :multiple="treeMultiple"
230
+ :selection-behavior="treeSelectionBehavior(field)"
231
+ :propagate-select="treePropagateSelect(field)"
232
+ :bubble-select="treeBubbleSelect(field)"
233
+ :virtualize="treeVirtualize(field)"
234
+ :disabled="disabled"
235
+ :size="controlSize"
236
+ :class="mergeFormUiClass('w-full py-1', formUi.ui.value.tree?.ui?.root)"
237
+ :ui="{
188
238
  item: formUi.ui.value.tree?.ui?.item,
189
239
  itemWithChildren: formUi.ui.value.tree?.ui?.itemWithChildren,
190
240
  listWithChildren: formUi.ui.value.tree?.ui?.listWithChildren,
@@ -194,86 +244,122 @@ function isOptionValue(value) {
194
244
  linkTrailing: formUi.ui.value.tree?.ui?.linkTrailing,
195
245
  linkTrailingIcon: formUi.ui.value.tree?.ui?.linkTrailingIcon
196
246
  }"
197
- @update:model-value="updateTreeModel"
198
- @blur="handleBlur"
199
- >
200
- <template v-if="treeSelectionControl !== 'none'" #item-leading="{ selected, indeterminate }">
201
- <UIcon
202
- :name="treeSelectionIcon(selected, indeterminate)"
203
- aria-hidden="true"
204
- :class="
205
- mergeFormUiClass(
206
- 'size-4 shrink-0 transition-colors',
207
- selected || indeterminate ? 'text-primary' : 'text-muted',
208
- formUi.ui.value.tree?.ui?.selectionControl
209
- )
210
- "
211
- />
212
- </template>
213
- </UTree>
214
- <UPopover
215
- v-else-if="isTreeSelect"
216
- v-model:open="treeSelectOpen"
217
- :content="{ align: 'start', sideOffset: 6 }"
218
- :ui="{
247
+ @update:model-value="updateTreeModel"
248
+ @blur="handleBlur"
249
+ >
250
+ <template
251
+ v-if="treeSelectionControl !== 'none'"
252
+ #item-wrapper="{ item, selected, expanded, indeterminate, handleToggle, ui }"
253
+ >
254
+ <div
255
+ :class="ui.link({ selected, disabled: disabled || item.disabled })"
256
+ :data-selected="selected ? '' : void 0"
257
+ @click="selectTreeRow(item)"
258
+ >
259
+ <SelectionCheckbox
260
+ v-if="treeSelectionControl === 'checkbox'"
261
+ :model-value="indeterminate ? 'indeterminate' : selected"
262
+ :label="String(item.label)"
263
+ :disabled="disabled || item.disabled"
264
+ :control-class="formUi.ui.value.tree?.ui?.selectionControl"
265
+ @click.stop
266
+ @update:model-value="toggleTreeItem(item)"
267
+ />
268
+ <SelectionRadio
269
+ v-else
270
+ :value="treeKey(item)"
271
+ :label="String(item.label)"
272
+ :selected="selected"
273
+ :disabled="disabled || item.disabled"
274
+ :control-class="formUi.ui.value.tree?.ui?.selectionControl"
275
+ @click.stop
276
+ @select="selectRadioTreeItem(item)"
277
+ />
278
+ <span :class="ui.linkLabel()">{{ item.label }}</span>
279
+ <button
280
+ v-if="item.children?.length"
281
+ type="button"
282
+ :aria-label="String(item.label)"
283
+ class="ms-auto inline-flex rounded-sm p-0.5 focus-visible:outline-2 focus-visible:outline-primary"
284
+ @click.stop="handleToggle"
285
+ >
286
+ <UIcon
287
+ name="i-lucide-chevron-down"
288
+ aria-hidden="true"
289
+ :class="[ui.linkTrailingIcon(), expanded ? 'rotate-180' : '']"
290
+ />
291
+ </button>
292
+ </div>
293
+ </template>
294
+ </UTree>
295
+ <UPopover
296
+ v-else-if="isTreeSelect"
297
+ v-model:open="treeSelectOpen"
298
+ :content="{ align: 'start', sideOffset: 6 }"
299
+ :ui="{
219
300
  content: mergeFormUiClass(
220
301
  'w-(--reka-popper-anchor-width) min-w-72 p-0',
221
302
  formUi.ui.value.treeSelect?.ui?.content
222
303
  )
223
304
  }"
224
- >
225
- <UButton
226
- type="button"
227
- color="neutral"
228
- variant="outline"
229
- :size="controlSize"
230
- :disabled="disabled"
231
- :loading="options.loading.value"
232
- :trailing-icon="treeSelectOpen ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
233
- :class="
305
+ >
306
+ <UButton
307
+ v-bind="attrs"
308
+ type="button"
309
+ color="neutral"
310
+ variant="outline"
311
+ :size="controlSize"
312
+ :disabled="disabled"
313
+ :loading="options.loading.value"
314
+ :trailing-icon="treeSelectOpen ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
315
+ :class="
234
316
  mergeFormUiClass(
235
317
  'w-full justify-between font-normal',
236
318
  formUi.ui.value.treeSelect?.ui?.trigger
237
319
  )
238
320
  "
239
- :ui="{
321
+ :ui="{
240
322
  label: mergeFormUiClass(
241
323
  'min-w-0 flex-1 truncate text-left',
242
324
  formUi.ui.value.treeSelect?.ui?.triggerLabel
243
325
  )
244
326
  }"
245
- @blur="handleBlur"
246
- >
247
- {{ treeSelectLabel }}
248
- </UButton>
327
+ @blur="handleBlur"
328
+ >
329
+ {{ treeSelectLabel }}
330
+ </UButton>
249
331
 
250
- <template #content>
251
- <div :class="mergeFormUiClass('grid min-w-0', formUi.ui.value.treeSelect?.ui?.root)">
252
- <div v-if="field.searchable !== false" class="border-b border-default p-2">
253
- <UInput
254
- v-model="treeSearch"
255
- icon="i-lucide-search"
332
+ <template #content>
333
+ <div :class="mergeFormUiClass('grid min-w-0', formUi.ui.value.treeSelect?.ui?.root)">
334
+ <div v-if="field.searchable !== false" class="border-b border-default p-2">
335
+ <UInput
336
+ v-model="treeSearch"
337
+ icon="i-lucide-search"
338
+ :size="controlSize"
339
+ :placeholder="t('form.fields.hierarchy.search')"
340
+ :class="mergeFormUiClass('w-full', formUi.ui.value.treeSelect?.ui?.search)"
341
+ autofocus
342
+ />
343
+ </div>
344
+ <UTree
345
+ v-if="visibleTreeItems.length"
346
+ :model-value="treeModel"
347
+ v-model:expanded="expandedKeys"
348
+ :items="visibleTreeItems"
349
+ :get-key="treeKey"
350
+ :multiple="treeMultiple"
351
+ :selection-behavior="treeSelectionBehavior(field)"
352
+ :propagate-select="treeSearch ? false : treePropagateSelect(field)"
353
+ :bubble-select="treeSearch ? false : treeBubbleSelect(field)"
354
+ :disabled="disabled"
256
355
  :size="controlSize"
257
- placeholder="Search"
258
- :class="mergeFormUiClass('w-full', formUi.ui.value.treeSelect?.ui?.search)"
259
- autofocus
260
- />
261
- </div>
262
- <UTree
263
- v-if="visibleTreeItems.length"
264
- :model-value="treeModel"
265
- :items="visibleTreeItems"
266
- :get-key="treeKey"
267
- :multiple="treeMultiple"
268
- :selection-behavior="treeSelectionBehavior(field)"
269
- :propagate-select="treePropagateSelect(field)"
270
- :bubble-select="treeBubbleSelect(field)"
271
- :disabled="disabled"
272
- :size="controlSize"
273
- :class="
274
- mergeFormUiClass('max-h-72 overflow-y-auto p-2', formUi.ui.value.treeSelect?.ui?.tree)
356
+ :class="
357
+ mergeFormUiClass(
358
+ 'max-h-72 overflow-y-auto p-2',
359
+ formUi.ui.value.treeSelect?.ui?.tree
360
+ )
275
361
  "
276
- :ui="{
362
+ :ui="{
277
363
  item: formUi.ui.value.tree?.ui?.item,
278
364
  itemWithChildren: formUi.ui.value.tree?.ui?.itemWithChildren,
279
365
  listWithChildren: formUi.ui.value.tree?.ui?.listWithChildren,
@@ -283,68 +369,97 @@ function isOptionValue(value) {
283
369
  linkTrailing: formUi.ui.value.tree?.ui?.linkTrailing,
284
370
  linkTrailingIcon: formUi.ui.value.tree?.ui?.linkTrailingIcon
285
371
  }"
286
- @update:model-value="updateTreeModel"
287
- >
288
- <template
289
- v-if="treeSelectionControl !== 'none'"
290
- #item-leading="{ selected, indeterminate }"
372
+ @update:model-value="updateTreeModel"
291
373
  >
292
- <UIcon
293
- :name="treeSelectionIcon(selected, indeterminate)"
294
- aria-hidden="true"
295
- :class="
296
- mergeFormUiClass(
297
- 'size-4 shrink-0 transition-colors',
298
- selected || indeterminate ? 'text-primary' : 'text-muted',
299
- formUi.ui.value.tree?.ui?.selectionControl
300
- )
301
- "
302
- />
303
- </template>
304
- </UTree>
305
- <p
306
- v-else
307
- :class="
374
+ <template
375
+ v-if="treeSelectionControl !== 'none'"
376
+ #item-wrapper="{ item, selected, expanded, indeterminate, handleToggle, ui }"
377
+ >
378
+ <div
379
+ :class="ui.link({ selected, disabled: disabled || item.disabled })"
380
+ :data-selected="selected ? '' : void 0"
381
+ @click="selectTreeRow(item)"
382
+ >
383
+ <SelectionCheckbox
384
+ v-if="treeSelectionControl === 'checkbox'"
385
+ :model-value="indeterminate ? 'indeterminate' : selected"
386
+ :label="String(item.label)"
387
+ :disabled="disabled || item.disabled"
388
+ :control-class="formUi.ui.value.tree?.ui?.selectionControl"
389
+ @click.stop
390
+ @update:model-value="toggleTreeItem(item)"
391
+ />
392
+ <SelectionRadio
393
+ v-else
394
+ :value="treeKey(item)"
395
+ :label="String(item.label)"
396
+ :selected="selected"
397
+ :disabled="disabled || item.disabled"
398
+ :control-class="formUi.ui.value.tree?.ui?.selectionControl"
399
+ @click.stop
400
+ @select="selectRadioTreeItem(item)"
401
+ />
402
+ <span :class="ui.linkLabel()">{{ item.label }}</span>
403
+ <button
404
+ v-if="item.children?.length"
405
+ type="button"
406
+ :aria-label="String(item.label)"
407
+ class="ms-auto inline-flex rounded-sm p-0.5 focus-visible:outline-2 focus-visible:outline-primary"
408
+ @click.stop="handleToggle"
409
+ >
410
+ <UIcon
411
+ name="i-lucide-chevron-down"
412
+ aria-hidden="true"
413
+ :class="[ui.linkTrailingIcon(), expanded ? 'rotate-180' : '']"
414
+ />
415
+ </button>
416
+ </div>
417
+ </template>
418
+ </UTree>
419
+ <p
420
+ v-else
421
+ :class="
308
422
  mergeFormUiClass(
309
423
  'px-3 py-8 text-center text-sm text-muted',
310
424
  formUi.ui.value.treeSelect?.ui?.empty
311
425
  )
312
426
  "
313
- >
314
- No matching options
315
- </p>
316
- <div
317
- v-if="field.clearable && selectedTreeItems.length"
318
- class="flex justify-end border-t border-default p-2"
319
- >
320
- <UButton
321
- type="button"
322
- color="neutral"
323
- variant="ghost"
324
- size="xs"
325
- icon="i-lucide-x"
326
- label="Clear selection"
327
- @click="clearTreeSelection"
328
- />
427
+ >
428
+ {{ t("form.fields.hierarchy.empty") }}
429
+ </p>
430
+ <div
431
+ v-if="field.clearable && selectedTreeItems.length"
432
+ class="flex justify-end border-t border-default p-2"
433
+ >
434
+ <UButton
435
+ type="button"
436
+ color="neutral"
437
+ variant="ghost"
438
+ size="xs"
439
+ icon="i-lucide-x"
440
+ :label="t('form.fields.hierarchy.clear')"
441
+ @click="clearTreeSelection"
442
+ />
443
+ </div>
329
444
  </div>
330
- </div>
331
- </template>
332
- </UPopover>
333
- <USelectMenu
334
- v-else
335
- v-model="model"
336
- v-bind="controlProps"
337
- class="w-full"
338
- :items="selectItems"
339
- value-key="value"
340
- :label-key="field.type === 'cascader' || hasShowPath(field) ? 'pathLabel' : 'label'"
341
- :multiple="field.multiple"
342
- :search-input="field.searchable ?? true"
343
- :clear="field.clearable === true"
344
- :placeholder="placeholder"
345
- :disabled="disabled"
346
- :loading="options.loading.value"
347
- @blur="handleBlur"
348
- />
445
+ </template>
446
+ </UPopover>
447
+ <USelectMenu
448
+ v-else
449
+ v-model="model"
450
+ v-bind="controlProps"
451
+ class="w-full"
452
+ :items="selectItems"
453
+ value-key="value"
454
+ :label-key="field.type === 'cascader' || hasShowPath(field) ? 'pathLabel' : 'label'"
455
+ :multiple="field.multiple"
456
+ :search-input="field.searchable ?? true"
457
+ :clear="field.clearable === true"
458
+ :placeholder="placeholder"
459
+ :disabled="disabled"
460
+ :loading="options.loading.value"
461
+ @blur="handleBlur"
462
+ />
463
+ </FormCompositeControl>
349
464
  </FormFieldShell>
350
465
  </template>
@@ -0,0 +1,9 @@
1
+ import type { FormOptionValue } from '../../types/index.js';
2
+ import { type ResolvedFormOption } from '../../utils/options.js';
3
+ export declare function resolveHierarchySelection(params: {
4
+ next: readonly FormOptionValue[];
5
+ intent?: FormOptionValue;
6
+ items: readonly ResolvedFormOption[];
7
+ propagate: boolean;
8
+ bubble: boolean;
9
+ }): FormOptionValue[];
@@ -0,0 +1,32 @@
1
+ import { formOptionKey } from "../../utils/options.js";
2
+ export function resolveHierarchySelection(params) {
3
+ const values = /* @__PURE__ */ new Map();
4
+ for (const value of params.next) values.set(formOptionKey(value), value);
5
+ const intent = params.intent ? findOption(params.items, params.intent) : void 0;
6
+ if (params.propagate && intent) {
7
+ const selecting = values.has(formOptionKey(intent.value));
8
+ for (const descendant of flattenOptions(intent.children ?? [])) {
9
+ const key = formOptionKey(descendant.value);
10
+ if (selecting) values.set(key, descendant.value);
11
+ else values.delete(key);
12
+ }
13
+ }
14
+ if (params.bubble)
15
+ for (const item of [...flattenOptions(params.items)].reverse()) {
16
+ if (!item.children?.length) continue;
17
+ const key = formOptionKey(item.value);
18
+ const allChildrenSelected = item.children.every(
19
+ (child) => values.has(formOptionKey(child.value))
20
+ );
21
+ if (allChildrenSelected) values.set(key, item.value);
22
+ else values.delete(key);
23
+ }
24
+ return [...values.values()];
25
+ }
26
+ function findOption(items, value) {
27
+ const key = formOptionKey(value);
28
+ return flattenOptions(items).find((item) => formOptionKey(item.value) === key);
29
+ }
30
+ function flattenOptions(items) {
31
+ return items.flatMap((item) => [item, ...flattenOptions(item.children ?? [])]);
32
+ }