nuxt-ui-tools 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/form/components/provider/form-overlay-host.vue +3 -0
- package/dist/runtime/form/components/root/form.d.vue.ts +2 -1
- package/dist/runtime/form/components/root/form.vue +2 -0
- package/dist/runtime/form/components/root/form.vue.d.ts +2 -1
- package/dist/runtime/form/composables/use-form-overlay-controller.d.ts +1 -0
- package/dist/runtime/form/composables/use-form-overlay-controller.js +10 -0
- package/dist/runtime/form/composables/use-form-root.d.ts +4 -2
- package/dist/runtime/form/composables/use-form-root.js +13 -10
- package/dist/runtime/form/fields/array-collapse/component.vue +2 -1
- package/dist/runtime/form/fields/array-list/component.vue +72 -63
- package/dist/runtime/form/fields/array-list/use-array-items.d.ts +1 -0
- package/dist/runtime/form/fields/array-list/use-array-items.js +4 -0
- package/dist/runtime/form/fields/array-table/component.vue +2 -1
- package/dist/runtime/form/types/api.d.ts +10 -2
- package/dist/runtime/i18n/locales/en.js +1 -0
- package/dist/runtime/i18n/locales/fr.js +1 -0
- package/dist/runtime/i18n/types.d.ts +1 -0
- package/dist/runtime/shared/types/query.d.ts +14 -4
- package/dist/runtime/table/composables/use-table-actions.d.ts +1 -1
- package/dist/runtime/table/composables/use-table-actions.js +11 -3
- package/dist/runtime/table/composables/use-table-internals.d.ts +3 -3
- package/dist/runtime/table/types/actions.d.ts +8 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -49,6 +49,7 @@ function resolveSchemaUi(schema) {
|
|
|
49
49
|
>
|
|
50
50
|
<FormRoot
|
|
51
51
|
:form="overlay.form"
|
|
52
|
+
:runtime="overlay.runtime"
|
|
52
53
|
shell="drawer"
|
|
53
54
|
:show-close-button="drawerConfig?.showCloseButton !== false"
|
|
54
55
|
:ui="formUi"
|
|
@@ -70,6 +71,7 @@ function resolveSchemaUi(schema) {
|
|
|
70
71
|
>
|
|
71
72
|
<FormRoot
|
|
72
73
|
:form="overlay.form"
|
|
74
|
+
:runtime="overlay.runtime"
|
|
73
75
|
shell="fullscreen"
|
|
74
76
|
:show-close-button="fullscreenConfig?.showCloseButton !== false"
|
|
75
77
|
:ui="formUi"
|
|
@@ -91,6 +93,7 @@ function resolveSchemaUi(schema) {
|
|
|
91
93
|
>
|
|
92
94
|
<FormRoot
|
|
93
95
|
:form="overlay.form"
|
|
96
|
+
:runtime="overlay.runtime"
|
|
94
97
|
shell="modal"
|
|
95
98
|
:show-close-button="modalConfig?.showCloseButton !== false"
|
|
96
99
|
:ui="formUi"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { FormValue, FormObject, FormRendererController, FormRenderShell, FormUiConfig, FormValidationMode } from '../../types/index.js';
|
|
1
|
+
import type { FormValue, FormObject, FormRendererController, FormRuntime, FormRenderShell, FormUiConfig, FormValidationMode } from '../../types/index.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
form?: FormRendererController;
|
|
4
|
+
runtime?: FormRuntime;
|
|
4
5
|
schema?: FormValue;
|
|
5
6
|
input?: FormObject;
|
|
6
7
|
shell?: FormRenderShell;
|
|
@@ -20,6 +20,7 @@ import FormFieldRenderer from "../renderer/form-field-renderer.vue";
|
|
|
20
20
|
import FormDirectionalTransition from "../utils/form-directional-transition.vue";
|
|
21
21
|
const props = defineProps({
|
|
22
22
|
form: { type: Object, required: false },
|
|
23
|
+
runtime: { type: Object, required: false },
|
|
23
24
|
schema: { type: null, required: false },
|
|
24
25
|
input: { type: null, required: false },
|
|
25
26
|
shell: { type: String, required: false },
|
|
@@ -42,6 +43,7 @@ const {
|
|
|
42
43
|
submit
|
|
43
44
|
} = useFormRoot({
|
|
44
45
|
form: () => props.form,
|
|
46
|
+
runtime: () => props.runtime,
|
|
45
47
|
input: () => props.input,
|
|
46
48
|
onCancelled: (value) => emit("cancel", value),
|
|
47
49
|
onSubmitted: (value, result) => emit("submit", value, result),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { FormValue, FormObject, FormRendererController, FormRenderShell, FormUiConfig, FormValidationMode } from '../../types/index.js';
|
|
1
|
+
import type { FormValue, FormObject, FormRendererController, FormRuntime, FormRenderShell, FormUiConfig, FormValidationMode } from '../../types/index.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
form?: FormRendererController;
|
|
4
|
+
runtime?: FormRuntime;
|
|
4
5
|
schema?: FormValue;
|
|
5
6
|
input?: FormObject;
|
|
6
7
|
shell?: FormRenderShell;
|
|
@@ -8,5 +8,6 @@ export declare function useFormOverlayController(instance: FormApiRuntimeInstanc
|
|
|
8
8
|
handleSubmitted: (formData: FormObject, result: FormSubmitHandlerResult<FormValue>) => void;
|
|
9
9
|
open: import("vue").Ref<boolean, boolean>;
|
|
10
10
|
resolveAfterClose: () => void;
|
|
11
|
+
runtime: import("../types/index.js").FormRuntime;
|
|
11
12
|
title: import("vue").ComputedRef<string | undefined>;
|
|
12
13
|
};
|
|
@@ -3,6 +3,7 @@ import { getFormOverlayDescription, getFormOverlayTitle } from "../utils/overlay
|
|
|
3
3
|
import { isRecord } from "../utils/path.js";
|
|
4
4
|
import { useForm } from "./use-form.js";
|
|
5
5
|
import { useFormApi } from "./use-form-api.js";
|
|
6
|
+
import { useFormRuntime } from "./use-form-runtime.js";
|
|
6
7
|
export function useFormOverlayController(instance) {
|
|
7
8
|
const formApi = useFormApi();
|
|
8
9
|
const form = useForm({
|
|
@@ -17,6 +18,13 @@ export function useFormOverlayController(instance) {
|
|
|
17
18
|
() => getFormOverlayDescription(instance.schema) ?? getFormOverlayTitle(instance.schema) ?? "Form"
|
|
18
19
|
);
|
|
19
20
|
const dismissible = computed(() => !form.isSubmitting.value);
|
|
21
|
+
const runtime = useFormRuntime({
|
|
22
|
+
input: form.input,
|
|
23
|
+
schema: form.schema,
|
|
24
|
+
syncInput: form.syncInput,
|
|
25
|
+
validationMode: form.validationMode
|
|
26
|
+
});
|
|
27
|
+
form.bind(runtime);
|
|
20
28
|
formApi.setController(instance.id, form);
|
|
21
29
|
const runtimeControls = {
|
|
22
30
|
close: requestCancel,
|
|
@@ -24,6 +32,7 @@ export function useFormOverlayController(instance) {
|
|
|
24
32
|
};
|
|
25
33
|
formApi.setRuntimeControls(instance.id, runtimeControls);
|
|
26
34
|
onBeforeUnmount(() => {
|
|
35
|
+
form.unbind(runtime);
|
|
27
36
|
formApi.removeController(instance.id, form);
|
|
28
37
|
formApi.removeRuntimeControls(instance.id, runtimeControls);
|
|
29
38
|
});
|
|
@@ -85,6 +94,7 @@ export function useFormOverlayController(instance) {
|
|
|
85
94
|
handleSubmitted,
|
|
86
95
|
open,
|
|
87
96
|
resolveAfterClose,
|
|
97
|
+
runtime,
|
|
88
98
|
title
|
|
89
99
|
};
|
|
90
100
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { FormObject, FormRendererController, FormSubmitHandlerResult, FormUiConfig, FormValidationMode, FormValue } from '../types/index.js';
|
|
1
|
+
import type { FormObject, FormRendererController, FormRuntime, FormSubmitHandlerResult, FormUiConfig, FormValidationMode, FormValue } from '../types/index.js';
|
|
2
2
|
export interface UseFormRootParams {
|
|
3
3
|
/** Controller from `useForm`. Its schema, input, and policies win over the direct ones. */
|
|
4
4
|
form: () => FormRendererController | undefined;
|
|
5
|
+
/** Runtime owned by a parent. The root renders it and neither creates, binds nor disposes it. */
|
|
6
|
+
runtime?: () => FormRuntime | undefined;
|
|
5
7
|
schema: () => FormValue;
|
|
6
8
|
input: () => FormObject | undefined;
|
|
7
9
|
syncInput: () => boolean | readonly string[] | undefined;
|
|
@@ -27,7 +29,7 @@ export declare function useFormRoot(params: UseFormRootParams): {
|
|
|
27
29
|
};
|
|
28
30
|
input: import("vue").ComputedRef<import("../../shared/index.js").GenericObject | undefined>;
|
|
29
31
|
refreshContext: () => Promise<void>;
|
|
30
|
-
runtime:
|
|
32
|
+
runtime: FormRuntime;
|
|
31
33
|
schema: import("vue").ComputedRef<{}>;
|
|
32
34
|
submit: () => Promise<void>;
|
|
33
35
|
validationMode: import("vue").ComputedRef<FormValidationMode>;
|
|
@@ -34,17 +34,20 @@ export function useFormRoot(params) {
|
|
|
34
34
|
const validationMode = computed(
|
|
35
35
|
() => params.form()?.validationMode.value ?? params.validate() ?? getSchemaValidationMode(schema.value)
|
|
36
36
|
);
|
|
37
|
-
const
|
|
37
|
+
const ownedRuntime = params.runtime?.();
|
|
38
|
+
const runtime = ownedRuntime ?? useFormRuntime({ input, schema, syncInput, validationMode });
|
|
38
39
|
provideFormRuntime(runtime);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
previousController
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
if (!ownedRuntime) {
|
|
41
|
+
watch(
|
|
42
|
+
params.form,
|
|
43
|
+
(controller, previousController) => {
|
|
44
|
+
previousController?.unbind(runtime);
|
|
45
|
+
controller?.bind(runtime);
|
|
46
|
+
},
|
|
47
|
+
{ immediate: true }
|
|
48
|
+
);
|
|
49
|
+
onBeforeUnmount(() => params.form()?.unbind(runtime));
|
|
50
|
+
}
|
|
48
51
|
onMounted(async () => {
|
|
49
52
|
await nextTick();
|
|
50
53
|
const target = getSchemaAutoFocus(schema.value);
|
|
@@ -41,6 +41,7 @@ const {
|
|
|
41
41
|
itemPath,
|
|
42
42
|
itemRenderKey,
|
|
43
43
|
items,
|
|
44
|
+
removeItemLabel,
|
|
44
45
|
runCustomAction,
|
|
45
46
|
t,
|
|
46
47
|
title,
|
|
@@ -229,7 +230,7 @@ function bodyId(index) {
|
|
|
229
230
|
variant="ghost"
|
|
230
231
|
size="xs"
|
|
231
232
|
:class="ui?.action"
|
|
232
|
-
:aria-label="
|
|
233
|
+
:aria-label="removeItemLabel(item, index)"
|
|
233
234
|
@click="removeItem(index)"
|
|
234
235
|
/>
|
|
235
236
|
<UButton
|
|
@@ -43,6 +43,7 @@ const {
|
|
|
43
43
|
itemPath,
|
|
44
44
|
itemRenderKey,
|
|
45
45
|
items,
|
|
46
|
+
removeItemLabel,
|
|
46
47
|
runCustomAction,
|
|
47
48
|
t,
|
|
48
49
|
title,
|
|
@@ -230,7 +231,7 @@ function updateDraggedItems(value) {
|
|
|
230
231
|
variant="ghost"
|
|
231
232
|
size="xs"
|
|
232
233
|
:class="ui?.action"
|
|
233
|
-
:aria-label="
|
|
234
|
+
:aria-label="removeItemLabel(item, index)"
|
|
234
235
|
@click.stop="removeItem(index)"
|
|
235
236
|
/>
|
|
236
237
|
</div>
|
|
@@ -291,76 +292,84 @@ function updateDraggedItems(value) {
|
|
|
291
292
|
handle=".array-drag-handle"
|
|
292
293
|
:animation="150"
|
|
293
294
|
>
|
|
294
|
-
<
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
<TransitionGroup
|
|
296
|
+
enter-active-class="transition duration-200 ease-out motion-reduce:transition-none"
|
|
297
|
+
enter-from-class="translate-y-1 opacity-0"
|
|
298
|
+
leave-active-class="transition duration-150 ease-in motion-reduce:transition-none"
|
|
299
|
+
leave-to-class="scale-[0.98] opacity-0"
|
|
300
|
+
>
|
|
301
|
+
<div
|
|
302
|
+
v-for="(item, index) in items"
|
|
303
|
+
:key="itemRenderKey(item, index)"
|
|
304
|
+
:class="[
|
|
298
305
|
mergeFormUiClass('grid rounded-lg border border-default bg-default', ui?.item),
|
|
299
306
|
fieldProps.compact ? 'gap-3 p-3' : 'gap-4 p-4'
|
|
300
307
|
]"
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
{{ itemHeading(items[index] ?? {}, index) }}
|
|
308
|
-
</span>
|
|
309
|
-
<div :class="mergeFormUiClass('flex items-center gap-1', ui?.itemActions)">
|
|
310
|
-
<UButton
|
|
311
|
-
v-if="isDraggable"
|
|
312
|
-
icon="i-lucide-grip-vertical"
|
|
313
|
-
color="neutral"
|
|
314
|
-
variant="ghost"
|
|
315
|
-
size="xs"
|
|
316
|
-
:class="mergeFormUiClass('array-drag-handle cursor-grab', ui?.action)"
|
|
317
|
-
:aria-label="t('form.fields.array.dragItem')"
|
|
318
|
-
/>
|
|
319
|
-
<UButton
|
|
320
|
-
v-if="canDelete(index)"
|
|
321
|
-
icon="i-lucide-trash-2"
|
|
322
|
-
color="neutral"
|
|
323
|
-
variant="ghost"
|
|
324
|
-
size="xs"
|
|
325
|
-
:class="ui?.action"
|
|
326
|
-
:aria-label="t('form.fields.array.removeItem')"
|
|
327
|
-
@click="removeItem(index)"
|
|
328
|
-
/>
|
|
329
|
-
<UButton
|
|
330
|
-
v-for="(action, actionIndex) in field.actions?.custom ?? []"
|
|
331
|
-
v-show="customActionVisible(index, actionIndex)"
|
|
332
|
-
:key="actionIndex"
|
|
333
|
-
:icon="action.icon"
|
|
334
|
-
color="neutral"
|
|
335
|
-
variant="ghost"
|
|
336
|
-
size="xs"
|
|
337
|
-
:class="ui?.action"
|
|
338
|
-
@click="runCustomAction(index, actionIndex)"
|
|
308
|
+
:data-form-array-item="index"
|
|
309
|
+
>
|
|
310
|
+
<div :class="mergeFormUiClass('flex items-center justify-between gap-3', ui?.itemHeader)">
|
|
311
|
+
<span
|
|
312
|
+
:class="mergeFormUiClass('text-sm font-medium text-highlighted', ui?.itemTitle)"
|
|
313
|
+
:data-form-array-heading="index"
|
|
339
314
|
>
|
|
340
|
-
{{
|
|
341
|
-
</
|
|
315
|
+
{{ itemHeading(items[index] ?? {}, index) }}
|
|
316
|
+
</span>
|
|
317
|
+
<div :class="mergeFormUiClass('flex items-center gap-1', ui?.itemActions)">
|
|
318
|
+
<UButton
|
|
319
|
+
v-if="isDraggable"
|
|
320
|
+
icon="i-lucide-grip-vertical"
|
|
321
|
+
color="neutral"
|
|
322
|
+
variant="ghost"
|
|
323
|
+
size="xs"
|
|
324
|
+
:class="mergeFormUiClass('array-drag-handle cursor-grab', ui?.action)"
|
|
325
|
+
:aria-label="t('form.fields.array.dragItem')"
|
|
326
|
+
/>
|
|
327
|
+
<UButton
|
|
328
|
+
v-if="canDelete(index)"
|
|
329
|
+
icon="i-lucide-trash-2"
|
|
330
|
+
color="neutral"
|
|
331
|
+
variant="ghost"
|
|
332
|
+
size="xs"
|
|
333
|
+
:class="ui?.action"
|
|
334
|
+
:aria-label="removeItemLabel(items[index] ?? {}, index)"
|
|
335
|
+
@click="removeItem(index)"
|
|
336
|
+
/>
|
|
337
|
+
<UButton
|
|
338
|
+
v-for="(action, actionIndex) in field.actions?.custom ?? []"
|
|
339
|
+
v-show="customActionVisible(index, actionIndex)"
|
|
340
|
+
:key="actionIndex"
|
|
341
|
+
:icon="action.icon"
|
|
342
|
+
color="neutral"
|
|
343
|
+
variant="ghost"
|
|
344
|
+
size="xs"
|
|
345
|
+
:class="ui?.action"
|
|
346
|
+
@click="runCustomAction(index, actionIndex)"
|
|
347
|
+
>
|
|
348
|
+
{{ resolveFormText(action.label) }}
|
|
349
|
+
</UButton>
|
|
350
|
+
</div>
|
|
342
351
|
</div>
|
|
343
|
-
</div>
|
|
344
|
-
|
|
345
|
-
<USelect
|
|
346
|
-
v-if="field.type === 'array-variant'"
|
|
347
|
-
:model-value="variantValue(items[index])"
|
|
348
|
-
:items="variantItems"
|
|
349
|
-
value-key="value"
|
|
350
|
-
:size="formUi.controlSize.value"
|
|
351
|
-
:class="mergeFormUiClass('w-full max-w-xs', ui?.variant)"
|
|
352
|
-
@update:model-value="updateVariant(index, $event)"
|
|
353
|
-
/>
|
|
354
352
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
:
|
|
359
|
-
|
|
360
|
-
:
|
|
353
|
+
<USelect
|
|
354
|
+
v-if="field.type === 'array-variant'"
|
|
355
|
+
:model-value="variantValue(items[index])"
|
|
356
|
+
:items="variantItems"
|
|
357
|
+
value-key="value"
|
|
358
|
+
:size="formUi.controlSize.value"
|
|
359
|
+
:class="mergeFormUiClass('w-full max-w-xs', ui?.variant)"
|
|
360
|
+
@update:model-value="updateVariant(index, $event)"
|
|
361
361
|
/>
|
|
362
|
+
|
|
363
|
+
<div :class="mergeFormUiClass('grid', ui?.fields)" :style="containerLayout.style.value">
|
|
364
|
+
<FormFieldRenderer
|
|
365
|
+
v-for="child in fieldsForItem(items[index] ?? {})"
|
|
366
|
+
:key="child.key"
|
|
367
|
+
:field="child"
|
|
368
|
+
:parent-path="itemPath(index)"
|
|
369
|
+
/>
|
|
370
|
+
</div>
|
|
362
371
|
</div>
|
|
363
|
-
</
|
|
372
|
+
</TransitionGroup>
|
|
364
373
|
</component>
|
|
365
374
|
|
|
366
375
|
<UButton
|
|
@@ -42,6 +42,7 @@ export declare function useFormArrayItems(field: () => FormArrayItemsField, path
|
|
|
42
42
|
itemRenderKey: (item: FormObject, index: number) => string;
|
|
43
43
|
items: import("vue").ComputedRef<readonly import("../../../shared/index.js").GenericObject[]>;
|
|
44
44
|
removeItem: (index: number) => boolean;
|
|
45
|
+
removeItemLabel: (item: FormObject, index: number) => string;
|
|
45
46
|
resolveAction: (action: FormArrayAction | undefined, index: number) => (import("./types.js").FormArrayActionCondition<{}, {}> | undefined) & boolean;
|
|
46
47
|
runCustomAction: (index: number, actionIndex: number) => Promise<void>;
|
|
47
48
|
t: import("../../../i18n/index.js").UiToolsTranslator;
|
|
@@ -106,6 +106,9 @@ export function useFormArrayItems(field, path) {
|
|
|
106
106
|
function itemHeading(item, index) {
|
|
107
107
|
return resolveFormText(field().headerTemplate?.(item, index, actionParams(index).deps)) ?? `${itemLabel.value} ${index + 1}`;
|
|
108
108
|
}
|
|
109
|
+
function removeItemLabel(item, index) {
|
|
110
|
+
return t("form.fields.array.removeNamedItem", { item: itemHeading(item, index) });
|
|
111
|
+
}
|
|
109
112
|
function resolveAction(action, index) {
|
|
110
113
|
const condition = isArrayActionConfig(action) ? action.condition : action;
|
|
111
114
|
if (isBoolean(condition)) {
|
|
@@ -239,6 +242,7 @@ export function useFormArrayItems(field, path) {
|
|
|
239
242
|
itemRenderKey,
|
|
240
243
|
items,
|
|
241
244
|
removeItem,
|
|
245
|
+
removeItemLabel,
|
|
242
246
|
resolveAction,
|
|
243
247
|
runCustomAction,
|
|
244
248
|
t,
|
|
@@ -41,6 +41,7 @@ const {
|
|
|
41
41
|
itemRenderKey,
|
|
42
42
|
items,
|
|
43
43
|
removeItem,
|
|
44
|
+
removeItemLabel,
|
|
44
45
|
runCustomAction,
|
|
45
46
|
t,
|
|
46
47
|
title,
|
|
@@ -208,7 +209,7 @@ function fieldLabel(field) {
|
|
|
208
209
|
variant="ghost"
|
|
209
210
|
size="xs"
|
|
210
211
|
:class="ui?.action"
|
|
211
|
-
:aria-label="
|
|
212
|
+
:aria-label="removeItemLabel(item, index)"
|
|
212
213
|
@click="removeItem(index)"
|
|
213
214
|
/>
|
|
214
215
|
<UButton
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DecrementDepth } from '../../shared/types/utils.js';
|
|
2
2
|
import type { FormValue } from './/index.js';
|
|
3
3
|
import type { FormContextData } from './context.js';
|
|
4
4
|
import type { FormMaybePromise, FormObject } from './utils.js';
|
|
@@ -170,10 +170,17 @@ export interface FormFieldApi<TValue = FormValue, TOption = FormValue, TContext
|
|
|
170
170
|
/** Focuses the mounted control for this field when it has a focusable input. */
|
|
171
171
|
focus: () => Promise<boolean>;
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Dot path into submitted output. Array items are addressed by index (`contacts.0.email`); the
|
|
175
|
+
* unindexed form (`contacts.email`) stays accepted.
|
|
176
|
+
*/
|
|
177
|
+
type FormOutputPath<T, MaxDepth extends number = 10> = [MaxDepth] extends [0] ? never : T extends readonly (infer TItem)[] ? `${number}` | `${number}.${FormOutputPath<TItem, DecrementDepth<MaxDepth>>}` | FormOutputPath<TItem, DecrementDepth<MaxDepth>> : T extends object ? {
|
|
178
|
+
[K in keyof T & string]: `${K}` | `${K}.${FormOutputPath<T[K], DecrementDepth<MaxDepth>>}`;
|
|
179
|
+
}[keyof T & string] : never;
|
|
173
180
|
/**
|
|
174
181
|
* Public form API available from form-level callbacks.
|
|
175
182
|
*/
|
|
176
|
-
export type FormFieldPath<TOutput = FormObject> = TOutput extends FormObject ? FormObject extends TOutput ? string :
|
|
183
|
+
export type FormFieldPath<TOutput = FormObject> = TOutput extends FormObject ? FormObject extends TOutput ? string : FormOutputPath<TOutput> : string;
|
|
177
184
|
export interface FormApi<TOutput = FormObject> {
|
|
178
185
|
/** Reads an internal form value by raw path. */
|
|
179
186
|
get: (path: string) => FormValue;
|
|
@@ -212,3 +219,4 @@ export interface FormSubmitHandlerResult<TSubmitData = FormValue> {
|
|
|
212
219
|
success: boolean;
|
|
213
220
|
data?: TSubmitData;
|
|
214
221
|
}
|
|
222
|
+
export {};
|
|
@@ -229,6 +229,7 @@ export default defineUiToolsLocale({
|
|
|
229
229
|
expand: "D\xE9plier",
|
|
230
230
|
item: "\xC9l\xE9ment",
|
|
231
231
|
removeItem: "Supprimer l\u2019\xE9l\xE9ment",
|
|
232
|
+
removeNamedItem: "Supprimer {item}",
|
|
232
233
|
unique: "Cette valeur est d\xE9j\xE0 dans la liste"
|
|
233
234
|
},
|
|
234
235
|
color: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { QueryFunction, QueryKey,
|
|
1
|
+
import type { DataTag, QueryFunction, QueryKey, UseQueryOptions } from '@tanstack/vue-query';
|
|
2
|
+
import type { UnwrapRef } from 'vue';
|
|
2
3
|
/**
|
|
3
4
|
* TanStack query definition accepted by runtime domains that own the `useQuery` call themselves
|
|
4
5
|
* (table sources, dashboard queries): an explicit `queryKey`, an optional `queryFn`, and any other
|
|
@@ -16,7 +17,16 @@ export interface QueryFnDefinition<TData = unknown> {
|
|
|
16
17
|
queryKey: QueryKey;
|
|
17
18
|
queryFn: QueryFunction<TData, QueryKey, string | null>;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
+
type FunctionResult<TFunction> = TFunction extends (...args: never[]) => infer TResult ? TResult : never;
|
|
21
|
+
type QueryFnResult<TQuery> = TQuery extends {
|
|
22
|
+
queryFn?: infer TQueryFn;
|
|
23
|
+
} ? Awaited<FunctionResult<UnwrapRef<Exclude<TQueryFn, undefined>>>> : never;
|
|
24
|
+
/**
|
|
25
|
+
* Data returned by a query definition. A tagged `queryKey` (TanStack `queryOptions`, generated
|
|
26
|
+
* clients such as Tuyau) gives the data even when the options are ref-wrapped; otherwise the
|
|
27
|
+
* `queryFn` result is used, unwrapping a ref and ignoring `skipToken`.
|
|
28
|
+
*/
|
|
20
29
|
export type QueryFunctionResult<TQuery> = TQuery extends {
|
|
21
|
-
|
|
22
|
-
} ?
|
|
30
|
+
queryKey: DataTag<QueryKey, infer TTagged, infer _TError>;
|
|
31
|
+
} ? TTagged : QueryFnResult<TQuery>;
|
|
32
|
+
export {};
|
|
@@ -21,7 +21,7 @@ export declare function useTableActions(options: UseTableActionsParams): {
|
|
|
21
21
|
actions: ComputedRef<TableActionController[]>;
|
|
22
22
|
bulkActions: ComputedRef<TableActionController[]>;
|
|
23
23
|
definitions: ComputedRef<TableActionDefinition[]>;
|
|
24
|
-
execute: (definition: TableActionDefinition) => Promise<
|
|
24
|
+
execute: (definition: TableActionDefinition) => Promise<boolean>;
|
|
25
25
|
runningKeys: import("vue").Ref<string[], string[]>;
|
|
26
26
|
toolbarActions: ComputedRef<TableActionController[]>;
|
|
27
27
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { computed, ref } from "vue";
|
|
2
|
+
import { isRecord } from "../../shared/utils/path.js";
|
|
2
3
|
import { isFunction } from "../../shared/utils/predicate.js";
|
|
3
4
|
import { resolveTableActionDefinitions } from "../utils/actions.js";
|
|
4
5
|
export function useTableActions(options) {
|
|
@@ -46,10 +47,10 @@ export function useTableActions(options) {
|
|
|
46
47
|
async function execute(definition) {
|
|
47
48
|
const state = resolveState(definition);
|
|
48
49
|
if (!state.visible || state.disabled || state.loading || state.running) {
|
|
49
|
-
return;
|
|
50
|
+
return false;
|
|
50
51
|
}
|
|
51
52
|
if (!definition.action) {
|
|
52
|
-
return;
|
|
53
|
+
return false;
|
|
53
54
|
}
|
|
54
55
|
const actionContext = context.value;
|
|
55
56
|
runningKeys.value = [...runningKeys.value, definition.key];
|
|
@@ -58,10 +59,14 @@ export function useTableActions(options) {
|
|
|
58
59
|
selectionApi.clear();
|
|
59
60
|
}
|
|
60
61
|
try {
|
|
61
|
-
await definition.action(actionContext);
|
|
62
|
+
const result = await definition.action(actionContext);
|
|
63
|
+
if (isFailedActionResult(result)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
62
66
|
if (selectionClear === "success") {
|
|
63
67
|
selectionApi.clear();
|
|
64
68
|
}
|
|
69
|
+
return true;
|
|
65
70
|
} finally {
|
|
66
71
|
runningKeys.value = runningKeys.value.filter((key) => key !== definition.key);
|
|
67
72
|
}
|
|
@@ -111,6 +116,9 @@ function resolveFlag(value, context) {
|
|
|
111
116
|
function isBooleanResolver(value) {
|
|
112
117
|
return isFunction(value);
|
|
113
118
|
}
|
|
119
|
+
function isFailedActionResult(result) {
|
|
120
|
+
return result === false || isRecord(result) && result.success === false;
|
|
121
|
+
}
|
|
114
122
|
function toPlainRecord(value) {
|
|
115
123
|
return Object.fromEntries(Object.entries(value));
|
|
116
124
|
}
|
|
@@ -22,7 +22,7 @@ declare function createTableInternals<TSchema>(options: {
|
|
|
22
22
|
[x: string]: unknown;
|
|
23
23
|
}, {
|
|
24
24
|
[x: string]: unknown;
|
|
25
|
-
}>) => Promise<
|
|
25
|
+
}>) => Promise<boolean>;
|
|
26
26
|
runningKeys: import("vue").Ref<string[], string[]>;
|
|
27
27
|
toolbarActions: import("vue").ComputedRef<import("./use-table-actions.js").TableActionController[]>;
|
|
28
28
|
};
|
|
@@ -618,7 +618,7 @@ declare function useProvideTableInternals<TSchema>(options: {
|
|
|
618
618
|
[x: string]: unknown;
|
|
619
619
|
}, {
|
|
620
620
|
[x: string]: unknown;
|
|
621
|
-
}>) => Promise<
|
|
621
|
+
}>) => Promise<boolean>;
|
|
622
622
|
runningKeys: import("vue").Ref<string[], string[]>;
|
|
623
623
|
toolbarActions: import("vue").ComputedRef<import("./use-table-actions.js").TableActionController[]>;
|
|
624
624
|
};
|
|
@@ -1211,7 +1211,7 @@ declare function useTableInternals(): {
|
|
|
1211
1211
|
[x: string]: unknown;
|
|
1212
1212
|
}, {
|
|
1213
1213
|
[x: string]: unknown;
|
|
1214
|
-
}>) => Promise<
|
|
1214
|
+
}>) => Promise<boolean>;
|
|
1215
1215
|
runningKeys: import("vue").Ref<string[], string[]>;
|
|
1216
1216
|
toolbarActions: import("vue").ComputedRef<import("./use-table-actions.js").TableActionController[]>;
|
|
1217
1217
|
};
|
|
@@ -18,10 +18,16 @@ export interface TableToolbarAction<TRow extends GenericObject = GenericObject,
|
|
|
18
18
|
condition?: boolean | ((context: TContext) => boolean);
|
|
19
19
|
disabled?: boolean | ((context: TableActionContext<TRow, TContext, TPageContext>) => boolean);
|
|
20
20
|
loading?: boolean | ((context: TableActionContext<TRow, TContext, TPageContext>) => boolean);
|
|
21
|
+
/**
|
|
22
|
+
* Runs the action. Returning `false` or `{ success: false }` marks the run as failed without
|
|
23
|
+
* throwing, like a form submit handler: the selection is kept and `execute()` resolves `false`.
|
|
24
|
+
* A rejection also keeps the selection and propagates to the caller.
|
|
25
|
+
*/
|
|
21
26
|
action?: (context: TableActionContext<TRow, TContext, TPageContext>) => MaybePromise<unknown>;
|
|
22
27
|
}
|
|
23
28
|
export interface TableBulkAction<TRow extends GenericObject = GenericObject, TContext extends GenericObject = GenericObject, TPageContext extends GenericObject = GenericObject> extends TableToolbarAction<TRow, TContext, TPageContext> {
|
|
24
29
|
requiresSelection?: boolean;
|
|
30
|
+
/** When the selection clears: before the action runs, after it succeeds, or never. */
|
|
25
31
|
selectionClear?: 'trigger' | 'success' | 'never';
|
|
26
32
|
}
|
|
27
33
|
/** Runtime state exposed for a configured toolbar or bulk action. */
|
|
@@ -42,7 +48,8 @@ export interface TableActionSlotProps<TRow extends GenericObject = GenericObject
|
|
|
42
48
|
state: TableActionState;
|
|
43
49
|
running: boolean;
|
|
44
50
|
selection: TableApi['selection'];
|
|
45
|
-
|
|
51
|
+
/** Runs the action and resolves `true` when it ran and succeeded. */
|
|
52
|
+
execute: () => Promise<boolean>;
|
|
46
53
|
}
|
|
47
54
|
export interface TableRowActionContext<TRow extends GenericObject = GenericObject, TContext extends GenericObject = GenericObject, TPageContext extends GenericObject = GenericObject> extends TableRowRenderParams<TRow, TContext, TPageContext> {
|
|
48
55
|
layout: TableLayout;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Nuxt module and runtime toolkit for UI-heavy Nuxt applications",
|
|
5
5
|
"homepage": "https://github.com/ChronicStone/nuxt-ui-tools#readme",
|
|
6
6
|
"bugs": "https://github.com/ChronicStone/nuxt-ui-tools/issues",
|