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
@@ -1,9 +1,12 @@
1
1
  import { keepPreviousData, useQuery } from "@tanstack/vue-query";
2
- import { computed, onScopeDispose, ref, shallowRef, unref, watch } from "vue";
2
+ import { computed, nextTick, onScopeDispose, ref, shallowRef, unref, watch } from "vue";
3
3
  import { createFormFieldInstance } from "../utils/field-instance.js";
4
4
  import {
5
+ formOptionKey,
6
+ mergeResolvedOptions,
5
7
  normalizeOptionItem,
6
- normalizeOptionItems
8
+ normalizeOptionItems,
9
+ normalizeOptionSelection
7
10
  } from "../utils/options.js";
8
11
  import { isRecord, relativePathSegments } from "../utils/path.js";
9
12
  import { resolveFormText } from "../utils/text.js";
@@ -14,11 +17,15 @@ export function useFieldOptions(params) {
14
17
  const promiseFetching = ref(false);
15
18
  const promiseError = ref(null);
16
19
  const promiseRun = ref(0);
20
+ const refreshRun = ref(0);
21
+ const promiseResolution = shallowRef(null);
17
22
  const creating = ref(false);
18
- const trackedOptionSource = computed(
19
- () => resolveTrackedOptionSource(params.field(), params.callbackParams.value)
20
- );
23
+ const trackedOptionSource = computed(() => {
24
+ void refreshRun.value;
25
+ return resolveTrackedOptionSource(params.field(), params.callbackParams.value);
26
+ });
21
27
  const resolvedSource = computed(() => trackedOptionSource.value.source);
28
+ const optionKeys = computed(() => resolveOptionKeys(params.field()));
22
29
  const contextResources = computed(() => {
23
30
  const ctx = params.callbackParams.value.ctx;
24
31
  if (!isRecord(ctx)) return [];
@@ -80,21 +87,26 @@ export function useFieldOptions(params) {
80
87
  promiseError.value = null;
81
88
  return;
82
89
  }
83
- await resolvePromiseOptions(source);
90
+ const resolution = resolvePromiseOptions(source);
91
+ promiseResolution.value = resolution;
92
+ await resolution;
93
+ if (promiseResolution.value === resolution) promiseResolution.value = null;
84
94
  },
85
95
  { immediate: true }
86
96
  );
87
97
  const sourceItems = computed(() => {
88
98
  if (querySource.value) {
89
99
  const data = unref(optionQuery.data);
90
- return normalizeOptionItems(Array.isArray(data) ? data : []);
100
+ return normalizeOptionItems(Array.isArray(data) ? data : [], optionKeys.value);
91
101
  }
92
- return normalizeOptionItems(promiseOptions.value);
102
+ return normalizeOptionItems(promiseOptions.value, optionKeys.value);
93
103
  });
94
- const items = computed(() => [
95
- ...sourceItems.value,
96
- ...createdOptions.value.map((option) => normalizeOptionItem(option))
97
- ]);
104
+ const items = computed(
105
+ () => mergeResolvedOptions(
106
+ sourceItems.value,
107
+ normalizeOptionItems(createdOptions.value, optionKeys.value)
108
+ )
109
+ );
98
110
  const hasItems = computed(() => items.value.length > 0);
99
111
  const pending = computed(() => {
100
112
  if (contextPending.value && !hasItems.value) return true;
@@ -109,6 +121,7 @@ export function useFieldOptions(params) {
109
121
  const loading = computed(() => pending.value);
110
122
  const error = computed(() => {
111
123
  if (contextError.value !== null) return contextError.value;
124
+ if (trackedOptionSource.value.error !== null) return trackedOptionSource.value.error;
112
125
  if (querySource.value) return unref(optionQuery.error) ?? null;
113
126
  return promiseError.value;
114
127
  });
@@ -148,21 +161,26 @@ export function useFieldOptions(params) {
148
161
  previousOptions: previousOptions ?? []
149
162
  });
150
163
  }
151
- clearInvalidValue(nextOptions);
152
164
  },
153
165
  { immediate: true }
154
166
  );
167
+ watch(items, (nextOptions) => clearInvalidValue(nextOptions), { immediate: true });
155
168
  async function refresh() {
156
- const source = resolvedSource.value;
157
169
  await Promise.all(contextResources.value.map((resource) => resource.refresh()));
158
170
  if (querySource.value) {
159
171
  await optionQuery.refetch();
160
172
  return;
161
173
  }
162
- if (isPromise(source)) await resolvePromiseOptions(source);
174
+ refreshRun.value += 1;
175
+ await nextTick();
176
+ await promiseResolution.value;
163
177
  }
164
178
  function add(option) {
179
+ const normalized = normalizeOptionItem(option, optionKeys.value);
180
+ if (items.value.some((item) => formOptionKey(item.value) === formOptionKey(normalized.value)))
181
+ return normalized;
165
182
  createdOptions.value = [...createdOptions.value, option];
183
+ return normalized;
166
184
  }
167
185
  async function create(label) {
168
186
  const createOption = optionConfig.value?.create;
@@ -176,30 +194,34 @@ export function useFieldOptions(params) {
176
194
  label
177
195
  });
178
196
  if (result === null || typeof result === "undefined") return null;
179
- createdOptions.value = [...createdOptions.value, result];
197
+ const normalized = add(result);
198
+ if (selectCreatedOption.value) selectOption(normalized.value);
180
199
  await params.refreshFieldOptions(
181
200
  resolveRevalidatePaths(params.path(), createOption.revalidateFieldOptions ?? [])
182
201
  );
183
- return normalizeOptionItem(result);
202
+ return normalized;
184
203
  } finally {
185
204
  creating.value = false;
186
205
  }
187
206
  }
207
+ function selectOption(value) {
208
+ if (Object.getOwnPropertyDescriptor(params.field(), "multiple")?.value !== true) {
209
+ params.api.value.value.set(value);
210
+ return;
211
+ }
212
+ const current = params.api.value.value.get();
213
+ const selected = Array.isArray(current) ? current.filter(isOptionValue) : [];
214
+ if (selected.some((item) => formOptionKey(item) === formOptionKey(value))) return;
215
+ params.api.value.value.set([...selected, value]);
216
+ }
188
217
  function clearInvalidValue(options) {
218
+ if (!optionConfig.value) return;
189
219
  if (pending.value) return;
190
220
  if (optionConfig.value?.clearOnInvalid === false) return;
191
221
  const current = params.api.value.value.get();
192
- const validValues = options.map((option) => option.value);
193
- if (Array.isArray(current)) {
194
- const nextValue = current.filter(
195
- (value) => isOptionValue(value) && validValues.includes(value)
196
- );
197
- if (nextValue.length !== current.length) params.api.value.value.set(nextValue);
198
- return;
199
- }
200
- if (current === null || typeof current === "undefined") return;
201
- if (isOptionValue(current) && validValues.includes(current)) return;
202
- params.api.value.value.set(null);
222
+ const nextValue = normalizeOptionSelection(current, options);
223
+ if (!hasSelectionChanged(current, nextValue)) return;
224
+ params.api.value.value.set(nextValue);
203
225
  }
204
226
  async function resolvePromiseOptions(source) {
205
227
  const run = promiseRun.value + 1;
@@ -240,11 +262,28 @@ function resolveTrackedOptionSource(field, params) {
240
262
  ...params,
241
263
  ctx: trackContextAccess(params.ctx, contextKeys)
242
264
  };
265
+ try {
266
+ return {
267
+ source: resolveOptionSource(field, trackedParams),
268
+ contextKeys: [...contextKeys],
269
+ error: null
270
+ };
271
+ } catch (error) {
272
+ return { source: null, contextKeys: [...contextKeys], error };
273
+ }
274
+ }
275
+ function resolveOptionKeys(field) {
276
+ if (!createFormFieldInstance(field).type.isAny(["tree", "tree-select", "cascader"])) return {};
243
277
  return {
244
- source: resolveOptionSource(field, trackedParams),
245
- contextKeys: [...contextKeys]
278
+ value: resolveStringProperty(field, "valueKey"),
279
+ label: resolveStringProperty(field, "labelKey"),
280
+ children: resolveStringProperty(field, "childrenKey")
246
281
  };
247
282
  }
283
+ function resolveStringProperty(value, key) {
284
+ const property = Object.getOwnPropertyDescriptor(value, key)?.value;
285
+ return typeof property === "string" ? property : void 0;
286
+ }
248
287
  function trackContextAccess(ctx, contextKeys) {
249
288
  return new Proxy(ctx, {
250
289
  get(target, property, receiver) {
@@ -259,7 +298,7 @@ function isRuntimeQueryOptions(value) {
259
298
  return Array.isArray(queryKey);
260
299
  }
261
300
  function isPromise(value) {
262
- return isRecord(value) && typeof Object.getOwnPropertyDescriptor(value, "then")?.value === "function";
301
+ return isRecord(value) && typeof value.then === "function";
263
302
  }
264
303
  function isAsyncResource(value) {
265
304
  if (!isRecord(value)) return false;
@@ -280,6 +319,11 @@ function resolveRevalidatePaths(path, revalidatePaths) {
280
319
  return revalidatePath;
281
320
  });
282
321
  }
322
+ function hasSelectionChanged(current, next) {
323
+ if (!Array.isArray(current) || !Array.isArray(next)) return current !== next;
324
+ if (current.length !== next.length) return true;
325
+ return current.some((value, index) => value !== next[index]);
326
+ }
283
327
  function isOptionValue(value) {
284
328
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
285
329
  }
@@ -1,3 +1,6 @@
1
1
  import type { Ref } from 'vue';
2
+ import type { FormObject } from '../types/index.js';
2
3
  export declare function provideFormFieldBare(bare: Readonly<Ref<boolean>>): void;
3
4
  export declare function useFormFieldBare(): Readonly<Ref<boolean, boolean>>;
5
+ export declare function provideFormFieldControlAttrs(attrs: Readonly<Ref<FormObject>>): void;
6
+ export declare function useFormFieldControlAttrs(): Readonly<Ref<import("../../shared/index.js").GenericObject, import("../../shared/index.js").GenericObject>>;
@@ -1,8 +1,17 @@
1
1
  import { inject, provide, shallowRef } from "vue";
2
2
  const formFieldBareKey = Symbol("form-field-bare");
3
+ const formFieldControlAttrsKey = Symbol(
4
+ "form-field-control-attrs"
5
+ );
3
6
  export function provideFormFieldBare(bare) {
4
7
  provide(formFieldBareKey, bare);
5
8
  }
6
9
  export function useFormFieldBare() {
7
10
  return inject(formFieldBareKey, shallowRef(false));
8
11
  }
12
+ export function provideFormFieldControlAttrs(attrs) {
13
+ provide(formFieldControlAttrsKey, attrs);
14
+ }
15
+ export function useFormFieldControlAttrs() {
16
+ return inject(formFieldControlAttrsKey, shallowRef({}));
17
+ }
@@ -2,7 +2,7 @@ import { computed, inject, provide, ref, watch } from "vue";
2
2
  import { createPublicFormApi } from "../utils/api.js";
3
3
  import { resolveFieldDependencies } from "../utils/dependencies.js";
4
4
  import { resolveFormLayoutConfig } from "../utils/layout.js";
5
- import { isRecord } from "../utils/path.js";
5
+ import { isRecord, pathSegments } from "../utils/path.js";
6
6
  import {
7
7
  childParentPath,
8
8
  collectFormFieldPaths,
@@ -92,11 +92,21 @@ export function useFormRuntime(params) {
92
92
  getSchema: () => params.schema.value,
93
93
  getContext: () => context
94
94
  });
95
- state.initialize();
95
+ let hasInitializedInput = typeof params.input?.value !== "undefined";
96
+ state.initialize(params.input?.value);
96
97
  if (params.input)
97
- watch(params.input, (input) => state.syncInput(input, params.syncInput?.value ?? false), {
98
- deep: true
99
- });
98
+ watch(
99
+ params.input,
100
+ (input) => {
101
+ if (!hasInitializedInput && typeof input !== "undefined") {
102
+ hasInitializedInput = true;
103
+ state.initialize(input);
104
+ return;
105
+ }
106
+ state.syncInput(input, params.syncInput?.value ?? false);
107
+ },
108
+ { deep: true }
109
+ );
100
110
  watch(params.schema, (schema) => {
101
111
  currentStepIndex.value = 0;
102
112
  validation.clearError();
@@ -251,6 +261,8 @@ export function useFormRuntime(params) {
251
261
  ),
252
262
  validate,
253
263
  validateCurrentStep,
264
+ setError: (path, message) => validation.setError(pathSegments(path), message),
265
+ clearError: (path) => validation.clearError(path ? pathSegments(path) : void 0),
254
266
  focusRequest: focus.request,
255
267
  registerFieldElement: focus.registerField,
256
268
  focusField: focus.focusField,
@@ -89,6 +89,9 @@ function applyVirtualFields(item, index) {
89
89
  function itemHeading(item, index) {
90
90
  return resolveFormText(props.field.headerTemplate?.(item, index, actionParams(index).deps)) ?? `${itemLabel.value} ${index + 1}`;
91
91
  }
92
+ function selectTab(index) {
93
+ activeIndex.value = index;
94
+ }
92
95
  function resolveAction(condition, index) {
93
96
  if (typeof condition === "boolean") return condition;
94
97
  if (typeof condition !== "function") return true;
@@ -237,7 +240,7 @@ function isFormObject(value) {
237
240
  :color="activeIndex === index ? 'primary' : 'neutral'"
238
241
  :variant="activeIndex === index ? 'solid' : 'ghost'"
239
242
  :class="mergeFormUiClass('shrink-0', formUi.ui.value.arrayList?.ui?.tab)"
240
- @click="activeIndex = index"
243
+ @click="selectTab(index)"
241
244
  >
242
245
  {{ itemHeading(items[index] ?? {}, index) }}
243
246
  </UButton>
@@ -2,12 +2,14 @@
2
2
  import UButton from "@nuxt/ui/components/Button.vue";
3
3
  import UInputMenu from "@nuxt/ui/components/InputMenu.vue";
4
4
  import { computed, ref } from "vue";
5
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
5
6
  import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
6
7
  import { useFieldControl } from "../../composables/use-field-control";
7
8
  const props = defineProps({
8
9
  field: { type: Object, required: true },
9
10
  path: { type: Array, required: true }
10
11
  });
12
+ const { t } = useUiToolsLocale();
11
13
  const { form, controlProps, disabled, handleBlur, options, placeholder } = useFieldControl(
12
14
  () => props.field,
13
15
  () => props.path
@@ -24,26 +26,29 @@ const model = computed({
24
26
  });
25
27
  const items = computed(() => [...options.items.value]);
26
28
  const createItem = computed(
27
- () => options.creatable.value ? props.field.createItem ?? { position: "bottom", when: "empty" } : false
29
+ () => options.creatable.value && props.field.createItem ? props.field.createItem : false
28
30
  );
29
- const showFooterActions = computed(() => options.refreshable.value || options.creatable.value);
31
+ const showExplicitCreate = computed(
32
+ () => options.creatable.value && !props.field.createItem
33
+ );
34
+ const showFooterActions = computed(() => options.refreshable.value || showExplicitCreate.value);
30
35
  const footerActionsClass = computed(
31
- () => options.refreshable.value && options.creatable.value ? "sm:grid-cols-2" : "grid-cols-1"
36
+ () => options.refreshable.value && showExplicitCreate.value ? "sm:grid-cols-2" : "grid-cols-1"
37
+ );
38
+ const createActionLabel = computed(
39
+ () => options.createLabel.value ?? t("form.fields.options.create")
32
40
  );
33
- const createActionLabel = computed(() => options.createLabel.value ?? "Create option");
34
41
  async function handleCreate(label) {
35
- const option = await options.create(label);
36
- if (!option || !options.selectCreatedOption.value) return;
37
- if (props.field.multiple) {
38
- const current = Array.isArray(model.value) ? model.value.filter(isOptionValue) : [];
39
- model.value = [...current, option.value];
40
- return;
41
- }
42
- model.value = option.value;
42
+ await options.create(label);
43
43
  }
44
44
  async function handleCreateAction() {
45
45
  await handleCreate(searchTerm.value);
46
46
  }
47
+ async function handleNativeCreate(label) {
48
+ const normalizedLabel = label.trim();
49
+ if (!normalizedLabel) return;
50
+ await handleCreate(normalizedLabel);
51
+ }
47
52
  function isOptionValue(value) {
48
53
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
49
54
  }
@@ -65,11 +70,13 @@ function isOptionValue(value) {
65
70
  :loading="options.loading.value || options.creating.value"
66
71
  :clear="field.clearable === true"
67
72
  :create-item="createItem"
68
- @create="handleCreate"
73
+ @create="handleNativeCreate"
69
74
  @blur="handleBlur"
70
75
  >
71
76
  <template #create-item-label="{ item }">
72
- {{ options.creating.value ? "Creating..." : `Create "${item}"` }}
77
+ {{
78
+ options.creating.value ? t("form.fields.options.creating") : t("form.fields.options.createNamed", { label: item })
79
+ }}
73
80
  </template>
74
81
 
75
82
  <template #content-bottom>
@@ -88,10 +95,10 @@ function isOptionValue(value) {
88
95
  :loading="options.pending.value || options.fetching.value"
89
96
  @click.stop="options.refresh"
90
97
  >
91
- Refresh
98
+ {{ t("form.fields.options.refresh") }}
92
99
  </UButton>
93
100
  <UButton
94
- v-if="options.creatable.value"
101
+ v-if="showExplicitCreate"
95
102
  block
96
103
  size="xs"
97
104
  variant="ghost"
@@ -1,8 +1,9 @@
1
1
  <script setup>
2
- import UCheckbox from "@nuxt/ui/components/Checkbox.vue";
2
+ import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
3
3
  import { computed } from "vue";
4
4
  import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
5
5
  import { useFieldControl } from "../../composables/use-field-control";
6
+ import { formOptionKey } from "../../utils/options";
6
7
  const props = defineProps({
7
8
  field: { type: Object, required: true },
8
9
  path: { type: Array, required: true }
@@ -11,22 +12,25 @@ const { form, controlProps, disabled, handleBlur, options } = useFieldControl(
11
12
  () => props.field,
12
13
  () => props.path
13
14
  );
15
+ const valueByKey = computed(
16
+ () => new Map(options.items.value.map((item) => [formOptionKey(item.value), item.value]))
17
+ );
14
18
  const model = computed({
15
19
  get: () => {
16
20
  const value = form.getValue(props.path);
17
- return Array.isArray(value) ? value.filter(isOptionValue) : [];
21
+ return Array.isArray(value) ? value.filter(isOptionValue).map(formOptionKey) : [];
18
22
  },
19
- set: (value) => form.setValue(props.path, value)
23
+ set: (value) => form.setValue(
24
+ props.path,
25
+ value.flatMap((key) => {
26
+ const optionValue = valueByKey.value.get(key);
27
+ return typeof optionValue === "undefined" ? [] : [optionValue];
28
+ })
29
+ )
20
30
  });
21
- const items = computed(() => [...options.items.value]);
22
- function toggleOption(value, checked) {
23
- const current = model.value;
24
- if (checked) {
25
- model.value = current.includes(value) ? current : [...current, value];
26
- return;
27
- }
28
- model.value = current.filter((item) => item !== value);
29
- }
31
+ const items = computed(
32
+ () => options.items.value.map((item) => ({ ...item, value: formOptionKey(item.value) }))
33
+ );
30
34
  function isOptionValue(value) {
31
35
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
32
36
  }
@@ -34,23 +38,18 @@ function isOptionValue(value) {
34
38
 
35
39
  <template>
36
40
  <FormFieldShell :field="field" :path="path">
37
- <div
41
+ <UCheckboxGroup
42
+ v-model="model"
38
43
  v-bind="controlProps"
39
- class="grid gap-2"
40
- :class="field.orientation === 'horizontal' ? 'sm:flex sm:flex-wrap' : ''"
41
- >
42
- <UCheckbox
43
- v-for="item in items"
44
- :key="String(item.value)"
45
- :model-value="model.includes(item.value)"
46
- :label="item.label"
47
- :description="item.description"
48
- :disabled="disabled || item.disabled"
49
- variant="card"
50
- :indicator="field.indicator"
51
- @update:model-value="toggleOption(item.value, $event === true)"
52
- @blur="handleBlur"
53
- />
54
- </div>
44
+ :items="items"
45
+ value-key="value"
46
+ label-key="label"
47
+ description-key="description"
48
+ :orientation="field.orientation"
49
+ variant="card"
50
+ :indicator="field.indicator"
51
+ :disabled="disabled"
52
+ @blur="handleBlur"
53
+ />
55
54
  </FormFieldShell>
56
55
  </template>
@@ -1,8 +1,9 @@
1
1
  <script setup>
2
- import UCheckbox from "@nuxt/ui/components/Checkbox.vue";
2
+ import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
3
3
  import { computed } from "vue";
4
4
  import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
5
5
  import { useFieldControl } from "../../composables/use-field-control";
6
+ import { formOptionKey } from "../../utils/options";
6
7
  const props = defineProps({
7
8
  field: { type: Object, required: true },
8
9
  path: { type: Array, required: true }
@@ -11,22 +12,25 @@ const { form, controlProps, disabled, handleBlur, options } = useFieldControl(
11
12
  () => props.field,
12
13
  () => props.path
13
14
  );
15
+ const valueByKey = computed(
16
+ () => new Map(options.items.value.map((item) => [formOptionKey(item.value), item.value]))
17
+ );
14
18
  const model = computed({
15
19
  get: () => {
16
20
  const value = form.getValue(props.path);
17
- return Array.isArray(value) ? value.filter(isOptionValue) : [];
21
+ return Array.isArray(value) ? value.filter(isOptionValue).map(formOptionKey) : [];
18
22
  },
19
- set: (value) => form.setValue(props.path, value)
23
+ set: (value) => form.setValue(
24
+ props.path,
25
+ value.flatMap((key) => {
26
+ const optionValue = valueByKey.value.get(key);
27
+ return typeof optionValue === "undefined" ? [] : [optionValue];
28
+ })
29
+ )
20
30
  });
21
- const items = computed(() => [...options.items.value]);
22
- function toggleOption(value, checked) {
23
- const current = model.value;
24
- if (checked) {
25
- model.value = current.includes(value) ? current : [...current, value];
26
- return;
27
- }
28
- model.value = current.filter((item) => item !== value);
29
- }
31
+ const items = computed(
32
+ () => options.items.value.map((item) => ({ ...item, value: formOptionKey(item.value) }))
33
+ );
30
34
  function isOptionValue(value) {
31
35
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
32
36
  }
@@ -34,23 +38,18 @@ function isOptionValue(value) {
34
38
 
35
39
  <template>
36
40
  <FormFieldShell :field="field" :path="path">
37
- <div
41
+ <UCheckboxGroup
42
+ v-model="model"
38
43
  v-bind="controlProps"
39
- class="grid gap-2"
40
- :class="field.orientation === 'horizontal' ? 'sm:flex sm:flex-wrap' : ''"
41
- >
42
- <UCheckbox
43
- v-for="item in items"
44
- :key="String(item.value)"
45
- :model-value="model.includes(item.value)"
46
- :label="item.label"
47
- :description="item.description"
48
- :disabled="disabled || item.disabled"
49
- :variant="field.variant === 'table' ? 'list' : field.variant ?? 'list'"
50
- :indicator="field.indicator"
51
- @update:model-value="toggleOption(item.value, $event === true)"
52
- @blur="handleBlur"
53
- />
54
- </div>
44
+ :items="items"
45
+ value-key="value"
46
+ label-key="label"
47
+ description-key="description"
48
+ :orientation="field.orientation"
49
+ :variant="field.variant === 'table' ? 'list' : field.variant ?? 'list'"
50
+ :indicator="field.indicator"
51
+ :disabled="disabled"
52
+ @blur="handleBlur"
53
+ />
55
54
  </FormFieldShell>
56
55
  </template>
@@ -4,6 +4,7 @@ import UColorPicker from "@nuxt/ui/components/ColorPicker.vue";
4
4
  import UInput from "@nuxt/ui/components/Input.vue";
5
5
  import UPopover from "@nuxt/ui/components/Popover.vue";
6
6
  import { computed, ref } from "vue";
7
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
7
8
  import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
8
9
  import { useFieldControl } from "../../composables/use-field-control";
9
10
  import { resolveFormText } from "../../utils/text";
@@ -11,6 +12,7 @@ const props = defineProps({
11
12
  field: { type: Object, required: true },
12
13
  path: { type: Array, required: true }
13
14
  });
15
+ const { t } = useUiToolsLocale();
14
16
  const { form, controlProps, disabled } = useFieldControl(
15
17
  () => props.field,
16
18
  () => props.path
@@ -53,7 +55,7 @@ function clearColor() {
53
55
  color="neutral"
54
56
  variant="outline"
55
57
  :disabled="disabled"
56
- aria-label="Open color picker"
58
+ :aria-label="t('form.fields.color.open')"
57
59
  >
58
60
  <span
59
61
  class="size-5 rounded-sm border border-default"
@@ -85,7 +87,7 @@ function clearColor() {
85
87
  variant="ghost"
86
88
  size="xs"
87
89
  :disabled="disabled"
88
- aria-label="Clear color"
90
+ :aria-label="t('form.fields.color.clear')"
89
91
  @mousedown.prevent
90
92
  @click="clearColor"
91
93
  />
@@ -1,13 +1,16 @@
1
1
  <script setup>
2
2
  import UButton from "@nuxt/ui/components/Button.vue";
3
3
  import UInput from "@nuxt/ui/components/Input.vue";
4
- import { computed } from "vue";
4
+ import { computed, useId } from "vue";
5
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
5
6
  import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
6
7
  import { useFieldControl } from "../../composables/use-field-control";
7
8
  const props = defineProps({
8
9
  field: { type: null, required: true },
9
10
  path: { type: Array, required: true }
10
11
  });
12
+ const { t } = useUiToolsLocale();
13
+ const rangeId = useId();
11
14
  const { form, controlProps, disabled, handleBlur, placeholder } = useFieldControl(
12
15
  () => props.field,
13
16
  () => props.path
@@ -66,24 +69,26 @@ async function clear() {
66
69
  <UInput
67
70
  v-model="startModel"
68
71
  v-bind="controlProps"
72
+ :id="`${rangeId}-start`"
69
73
  class="min-w-0 flex-1"
70
74
  :type="inputType"
71
75
  :min="min"
72
76
  :max="max"
73
77
  :disabled="disabled"
74
- aria-label="Start"
78
+ :aria-label="t('form.fields.date.start')"
75
79
  @blur="handleBlur"
76
80
  />
77
81
  <span class="text-sm text-muted" aria-hidden="true">–</span>
78
82
  <UInput
79
83
  v-model="endModel"
80
84
  v-bind="controlProps"
85
+ :id="`${rangeId}-end`"
81
86
  class="min-w-0 flex-1"
82
87
  :type="inputType"
83
88
  :min="startModel || min"
84
89
  :max="max"
85
90
  :disabled="disabled"
86
- aria-label="End"
91
+ :aria-label="t('form.fields.date.end')"
87
92
  @blur="handleBlur"
88
93
  />
89
94
  </template>
@@ -106,7 +111,7 @@ async function clear() {
106
111
  variant="ghost"
107
112
  size="xs"
108
113
  :disabled="disabled"
109
- aria-label="Clear value"
114
+ :aria-label="t('form.fields.date.clear')"
110
115
  @click="clear"
111
116
  />
112
117
  </div>
@@ -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;