nuxt-ui-tools 1.25.0 → 1.26.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/display/components/blocks/display-accordion.vue +4 -1
- package/dist/runtime/display/components/blocks/display-alert.vue +5 -2
- package/dist/runtime/display/components/blocks/display-breakdown.vue +13 -7
- package/dist/runtime/display/components/blocks/display-chart.vue +6 -2
- package/dist/runtime/display/components/blocks/display-code.vue +2 -1
- package/dist/runtime/display/components/blocks/display-date.vue +21 -16
- package/dist/runtime/display/components/blocks/display-diff.vue +19 -7
- package/dist/runtime/display/components/blocks/display-empty.vue +3 -2
- package/dist/runtime/display/components/blocks/display-facts.vue +24 -9
- package/dist/runtime/display/components/blocks/display-heading.vue +10 -3
- package/dist/runtime/display/components/blocks/display-list.vue +10 -4
- package/dist/runtime/display/components/blocks/display-media.vue +19 -9
- package/dist/runtime/display/components/blocks/display-meter.vue +12 -11
- package/dist/runtime/display/components/blocks/display-person.vue +12 -5
- package/dist/runtime/display/components/blocks/display-record.vue +78 -18
- package/dist/runtime/display/components/blocks/display-stat.vue +16 -5
- package/dist/runtime/display/components/blocks/display-table.vue +4 -0
- package/dist/runtime/display/components/blocks/display-text.vue +8 -2
- package/dist/runtime/display/components/blocks/display-timeline.vue +7 -1
- package/dist/runtime/display/components/parts/display-actions.vue +4 -1
- package/dist/runtime/display/components/parts/display-chip.vue +27 -1
- package/dist/runtime/display/components/parts/display-fact-value.vue +2 -1
- package/dist/runtime/display/components/parts/display-record-media.d.vue.ts +2 -0
- package/dist/runtime/display/components/parts/display-record-media.vue +5 -3
- package/dist/runtime/display/components/parts/display-record-media.vue.d.ts +2 -0
- package/dist/runtime/display/directives/reveal-truncated.d.ts +10 -0
- package/dist/runtime/display/directives/reveal-truncated.js +31 -0
- package/dist/runtime/display/types/blocks/diff.d.ts +1 -1
- package/dist/runtime/display/types/blocks/facts.d.ts +3 -1
- package/dist/runtime/display/types/blocks/person.d.ts +3 -1
- package/dist/runtime/display/types/blocks/record.d.ts +4 -2
- package/dist/runtime/display/types/config.d.ts +9 -2
- package/dist/runtime/display/types/shared.d.ts +9 -0
- package/dist/runtime/display/utils/config.d.ts +7 -2
- package/dist/runtime/display/utils/config.js +11 -1
- package/dist/runtime/display/utils/links.d.ts +12 -0
- package/dist/runtime/display/utils/links.js +9 -0
- package/dist/runtime/file-preview/components/shell/file-preview-header.d.vue.ts +2 -0
- package/dist/runtime/file-preview/components/shell/file-preview-header.vue +13 -7
- package/dist/runtime/file-preview/components/shell/file-preview-header.vue.d.ts +2 -0
- package/dist/runtime/file-preview/components/shell/file-preview-shell.vue +6 -0
- package/dist/runtime/file-preview/components/shell/file-preview-stage.d.vue.ts +5 -3
- package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +4 -10
- package/dist/runtime/file-preview/components/shell/file-preview-stage.vue.d.ts +5 -3
- package/dist/runtime/form/components/root/form.vue +1 -0
- package/dist/runtime/form/composables/use-field-options.js +3 -1
- package/dist/runtime/form/composables/use-form-overlay-controller.js +1 -1
- package/dist/runtime/form/composables/use-form-root.d.ts +3 -1
- package/dist/runtime/form/composables/use-form-root.js +41 -15
- package/dist/runtime/form/composables/use-form-runtime.js +14 -4
- package/dist/runtime/form/composables/use-form-validation.d.ts +5 -2
- package/dist/runtime/form/composables/use-form-validation.js +51 -5
- package/dist/runtime/form/fields/custom-component/component.vue +2 -3
- package/dist/runtime/form/fields/date-family/component.vue +7 -6
- package/dist/runtime/form/fields/date-family/utils.d.ts +5 -0
- package/dist/runtime/form/fields/date-family/utils.js +13 -0
- package/dist/runtime/form/fields/number/component.vue +11 -5
- package/dist/runtime/form/fields/phone-number/component.vue +20 -1
- package/dist/runtime/form/types/api.d.ts +9 -2
- package/dist/runtime/form/types/options.d.ts +4 -1
- package/dist/runtime/form/types/runtime.d.ts +10 -0
- package/dist/runtime/form/types/schema.d.ts +5 -1
- package/dist/runtime/form/utils/api.js +1 -0
- package/dist/runtime/i18n/locales/en.js +12 -3
- package/dist/runtime/i18n/locales/fr.js +12 -3
- package/dist/runtime/i18n/types.d.ts +16 -0
- package/dist/runtime/table/components/filters/tags/date-filter-tag.vue +4 -0
- package/dist/runtime/table/types/filters.d.ts +17 -0
- package/dist/runtime/table/utils/filters/options.js +2 -1
- package/dist/runtime/table/utils/filters/preview/option-preview.js +4 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DisplayAction, DisplayBlockBase, DisplayChip, DisplayContent, DisplaySurface, DisplayTone } from '../shared.js';
|
|
1
|
+
import type { DisplayAction, DisplayBlockBase, DisplayChip, DisplayContent, DisplayLinkTarget, DisplaySurface, DisplayTone } from '../shared.js';
|
|
2
2
|
export type DisplayFactsLayout = 'ledger' | 'grid' | 'inline' | 'columns' | 'tiles';
|
|
3
3
|
export type DisplayFactsSlot = 'root' | 'group' | 'groupHeader' | 'groupTitle' | 'groupAside' | 'item' | 'label' | 'hint' | 'value' | 'empty' | 'actions';
|
|
4
4
|
export interface DisplayFact {
|
|
@@ -19,6 +19,8 @@ export interface DisplayFact {
|
|
|
19
19
|
icon?: string;
|
|
20
20
|
/** Makes the value a link. */
|
|
21
21
|
to?: string;
|
|
22
|
+
/** Where `to` opens. Absolute http(s) links open in a new tab by default. */
|
|
23
|
+
target?: DisplayLinkTarget | null;
|
|
22
24
|
/** Draws a placeholder bar instead of the value, such as one figure still loading. */
|
|
23
25
|
loading?: boolean;
|
|
24
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DisplayAvatar, DisplayBlockBase, DisplayChip, DisplayContent, DisplaySize, DisplayText, DisplayTone } from '../shared.js';
|
|
1
|
+
import type { DisplayAvatar, DisplayBlockBase, DisplayChip, DisplayContent, DisplayLinkTarget, DisplaySize, DisplayText, DisplayTone } from '../shared.js';
|
|
2
2
|
export type DisplayPersonSlot = 'root' | 'avatar' | 'body' | 'name' | 'description' | 'chip' | 'group' | 'label' | 'contacts' | 'contact' | 'contactIcon' | 'contactValue';
|
|
3
3
|
export interface DisplayPerson {
|
|
4
4
|
name: DisplayText;
|
|
@@ -14,6 +14,8 @@ export interface DisplayPersonContact {
|
|
|
14
14
|
value: string;
|
|
15
15
|
/** Makes the value a link, such as `mailto:` or `tel:`. */
|
|
16
16
|
href?: string;
|
|
17
|
+
/** Where `href` opens. Absolute http(s) links open in a new tab by default. */
|
|
18
|
+
target?: DisplayLinkTarget | null;
|
|
17
19
|
/** Adds a copy button. Defaults to `true`. */
|
|
18
20
|
copy?: boolean;
|
|
19
21
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DisplayAvatar, DisplayBlockBase, DisplayChip, DisplayContent, DisplayRevealable, DisplaySurface, DisplayTone } from '../shared.js';
|
|
2
|
-
export type DisplayRecordSlot = 'root' | 'media' | 'icon' | 'body' | 'reference' | 'title' | 'meta' | 'description' | 'badges' | 'trailing' | 'trailingValue' | 'trailingCaption' | 'header' | 'list' | 'item' | 'more' | 'empty';
|
|
1
|
+
import type { DisplayAvatar, DisplayBlockBase, DisplayChip, DisplayContent, DisplayLinkTarget, DisplayRevealable, DisplaySurface, DisplayTone } from '../shared.js';
|
|
2
|
+
export type DisplayRecordSlot = 'root' | 'media' | 'avatar' | 'icon' | 'body' | 'reference' | 'title' | 'meta' | 'description' | 'badges' | 'trailing' | 'trailingValue' | 'trailingCaption' | 'header' | 'list' | 'item' | 'more' | 'empty';
|
|
3
3
|
export interface DisplayRecordItem {
|
|
4
4
|
/** Small reference over the title, such as an identifier. */
|
|
5
5
|
reference?: DisplayContent;
|
|
@@ -25,6 +25,8 @@ export interface DisplayRecordItem {
|
|
|
25
25
|
chip?: DisplayChip;
|
|
26
26
|
/** Makes the record a link. */
|
|
27
27
|
to?: string;
|
|
28
|
+
/** Where `to` opens. Absolute http(s) links open in a new tab by default. */
|
|
29
|
+
target?: DisplayLinkTarget | null;
|
|
28
30
|
onClick?: () => void;
|
|
29
31
|
}
|
|
30
32
|
export interface DisplayRecordProps extends DisplayBlockBase<DisplayRecordSlot>, Partial<DisplayRecordItem>, DisplayRevealable {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DisplayKind, DisplayPropsMap, DisplayUiMap } from './registry.js';
|
|
2
|
-
import type { DisplayBuiltinTone, DisplayToneRegistry } from './shared.js';
|
|
2
|
+
import type { DisplayBuiltinTone, DisplayChipUi, DisplayToneRegistry } from './shared.js';
|
|
3
3
|
/**
|
|
4
4
|
* Colors of a tone, as CSS colors. Only `color` is needed for a new tone: the ink defaults to it,
|
|
5
5
|
* the soft background and the line mix it into the page background.
|
|
@@ -49,5 +49,12 @@ export interface DisplayUiConfig {
|
|
|
49
49
|
presets?: DisplayPresets;
|
|
50
50
|
/** Extra tones, and color changes to the built-in ones. Register new names in `DisplayToneRegistry`. */
|
|
51
51
|
tones?: Partial<DisplayToneDefinitions>;
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Slot classes per kind, plus `chip`: the `UBadge` slots of every chip the blocks draw (facts
|
|
54
|
+
* chips, record badges, list chips…), so chips match the app's badge style. Tones still color
|
|
55
|
+
* them: leave colors out of these classes.
|
|
56
|
+
*/
|
|
57
|
+
ui?: Partial<DisplayUiMap> & {
|
|
58
|
+
chip?: DisplayChipUi;
|
|
59
|
+
};
|
|
53
60
|
}
|
|
@@ -38,6 +38,11 @@ export type DisplayContent = LazyRenderableValue;
|
|
|
38
38
|
* when the link is used (opened, previewed or downloaded), with the button showing its progress.
|
|
39
39
|
*/
|
|
40
40
|
export type DisplayUrl = string | (() => MaybePromise<string>);
|
|
41
|
+
/**
|
|
42
|
+
* Where a block link opens. Absolute http(s) links default to `_blank`, others to the same tab;
|
|
43
|
+
* `null` keeps an absolute link in the same tab.
|
|
44
|
+
*/
|
|
45
|
+
export type DisplayLinkTarget = '_blank' | '_self' | '_parent' | '_top' | (string & {});
|
|
41
46
|
/** Date accepted by date-aware blocks: a `Date`, an ISO string, or epoch milliseconds. */
|
|
42
47
|
export type DisplayDate = Date | string | number;
|
|
43
48
|
/** Button rendered by a block, with every `UButton` prop. */
|
|
@@ -49,6 +54,10 @@ export interface DisplayChip extends Omit<BadgeProps, 'label'> {
|
|
|
49
54
|
label: DisplayText;
|
|
50
55
|
tone?: DisplayTone;
|
|
51
56
|
}
|
|
57
|
+
/** The `UBadge` slots of a chip. */
|
|
58
|
+
export type DisplayChipSlot = 'base' | 'label' | 'leadingIcon' | 'leadingAvatar' | 'leadingAvatarSize' | 'trailingIcon';
|
|
59
|
+
/** Classes per `UBadge` slot of every chip, from `appConfig.nuxtUiTools.display.ui.chip`. */
|
|
60
|
+
export type DisplayChipUi = DisplayUiSlots<DisplayChipSlot>;
|
|
52
61
|
/** Avatar with every `UAvatar` prop. Without `src`, the initials of `alt` show. */
|
|
53
62
|
export type DisplayAvatar = AvatarProps;
|
|
54
63
|
/** Slot classes of a block: every key is optional and merged over the defaults. */
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { AppConfig } from 'nuxt/schema';
|
|
2
|
-
import type { DisplayKind, DisplayPropsMap, DisplayUiConfig } from '../types/index.js';
|
|
2
|
+
import type { DisplayKind, DisplayPropsMap, DisplayUiConfig, DisplayUiSlots } from '../types/index.js';
|
|
3
3
|
/** `appConfig.nuxtUiTools.display`, or an empty config. */
|
|
4
4
|
export declare function resolveAppDisplayConfig(config: Pick<AppConfig, 'nuxtUiTools'>): DisplayUiConfig;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The props of a block over the defaults of its preset; an unknown preset changes nothing. Slot
|
|
7
|
+
* classes merge per slot, the block's over the preset's, like the other props.
|
|
8
|
+
*/
|
|
6
9
|
export declare function applyDisplayPreset<TKind extends DisplayKind>(kind: TKind, props: DisplayPropsMap[TKind], preset: string | undefined, config: DisplayUiConfig): DisplayPropsMap[TKind];
|
|
10
|
+
/** Two slot class maps merged per slot with tailwind-merge, `over` winning conflicts. */
|
|
11
|
+
export declare function mergeDisplaySlots(base: DisplayUiSlots<string> | undefined, over: DisplayUiSlots<string> | undefined): DisplayUiSlots<string> | undefined;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { twMerge } from "tailwind-merge";
|
|
1
2
|
export function resolveAppDisplayConfig(config) {
|
|
2
3
|
return config.nuxtUiTools?.display ?? {};
|
|
3
4
|
}
|
|
4
5
|
export function applyDisplayPreset(kind, props, preset, config) {
|
|
5
6
|
const defaults = preset === void 0 ? void 0 : config.presets?.[kind]?.[preset];
|
|
6
|
-
|
|
7
|
+
if (!defaults) return props;
|
|
8
|
+
const merged = { ...defaults, ...props };
|
|
9
|
+
const ui = mergeDisplaySlots(defaults.ui, props.ui);
|
|
10
|
+
return ui ? { ...merged, ui } : merged;
|
|
11
|
+
}
|
|
12
|
+
export function mergeDisplaySlots(base, over) {
|
|
13
|
+
if (!base || !over) return base ?? over;
|
|
14
|
+
const merged = { ...base };
|
|
15
|
+
for (const [slot, classes] of Object.entries(over)) merged[slot] = twMerge(base[slot], classes);
|
|
16
|
+
return merged;
|
|
7
17
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DisplayLinkTarget } from '../types/index.js';
|
|
2
|
+
/** Whether a link leaves the app: an absolute http(s) address (route objects never do). */
|
|
3
|
+
export declare function isExternalLink<TTo>(to: TTo): to is TTo & string;
|
|
4
|
+
/**
|
|
5
|
+
* Attributes of a block link: an absolute http(s) address opens in a new tab unless `target` says
|
|
6
|
+
* otherwise, and a new tab never gets access to the app (`noopener noreferrer`).
|
|
7
|
+
*/
|
|
8
|
+
export declare function displayLinkAttrs(to: string, target?: DisplayLinkTarget | null): {
|
|
9
|
+
rel: string | undefined;
|
|
10
|
+
target: DisplayLinkTarget | undefined;
|
|
11
|
+
to: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { isString } from "../../shared/utils/predicate.js";
|
|
2
|
+
const ABSOLUTE = /^https?:\/\//iu;
|
|
3
|
+
export function isExternalLink(to) {
|
|
4
|
+
return isString(to) && ABSOLUTE.test(to);
|
|
5
|
+
}
|
|
6
|
+
export function displayLinkAttrs(to, target) {
|
|
7
|
+
const resolved = target === void 0 ? isExternalLink(to) ? "_blank" : void 0 : target ?? void 0;
|
|
8
|
+
return { rel: resolved === "_blank" ? "noopener noreferrer" : void 0, target: resolved, to };
|
|
9
|
+
}
|
|
@@ -16,12 +16,13 @@ const props = defineProps({
|
|
|
16
16
|
narrow: { type: Boolean, required: true },
|
|
17
17
|
canExpand: { type: Boolean, required: true },
|
|
18
18
|
expanded: { type: Boolean, required: true },
|
|
19
|
-
details: { type: Boolean, required: true }
|
|
19
|
+
details: { type: Boolean, required: true },
|
|
20
|
+
unavailable: { type: Boolean, required: true }
|
|
20
21
|
});
|
|
21
22
|
const emit = defineEmits(["previous", "next", "close", "expand", "details", "download", "open", "action"]);
|
|
22
23
|
const { t } = useUiToolsLocale();
|
|
23
24
|
const name = computed(() => splitFilePreviewName(props.item.name || t("filePreview.untitled")));
|
|
24
|
-
const downloadable = computed(() => props.item.file.download !== false);
|
|
25
|
+
const downloadable = computed(() => !props.unavailable && props.item.file.download !== false);
|
|
25
26
|
const actions = computed(() => props.item.file.actions ?? []);
|
|
26
27
|
const menu = computed(() => [
|
|
27
28
|
[
|
|
@@ -33,11 +34,13 @@ const menu = computed(() => [
|
|
|
33
34
|
onSelect: () => emit("download")
|
|
34
35
|
}
|
|
35
36
|
] : [],
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
...props.unavailable ? [] : [
|
|
38
|
+
{
|
|
39
|
+
icon: "i-lucide-external-link",
|
|
40
|
+
label: t("filePreview.openInNewTab"),
|
|
41
|
+
onSelect: () => emit("open")
|
|
42
|
+
}
|
|
43
|
+
]
|
|
41
44
|
],
|
|
42
45
|
actions.value.map((action) => ({
|
|
43
46
|
icon: action.icon,
|
|
@@ -124,11 +127,14 @@ const menu = computed(() => [
|
|
|
124
127
|
v-if="downloadable"
|
|
125
128
|
icon="i-lucide-download"
|
|
126
129
|
:label="t('filePreview.download')"
|
|
130
|
+
data-file-preview-header-download=""
|
|
127
131
|
@click="emit('download')"
|
|
128
132
|
/>
|
|
129
133
|
<FilePreviewButton
|
|
134
|
+
v-if="!unavailable"
|
|
130
135
|
icon="i-lucide-external-link"
|
|
131
136
|
:label="t('filePreview.openInNewTab')"
|
|
137
|
+
data-file-preview-open-tab=""
|
|
132
138
|
@click="emit('open')"
|
|
133
139
|
/>
|
|
134
140
|
<span class="mx-1 h-5 w-px shrink-0 bg-(--ui-border)" aria-hidden="true" />
|
|
@@ -46,6 +46,7 @@ const stage = useTemplateRef("stage");
|
|
|
46
46
|
const strip = useTemplateRef("strip");
|
|
47
47
|
const showDetails = ref(false);
|
|
48
48
|
const reported = shallowRef([]);
|
|
49
|
+
const failure = shallowRef(null);
|
|
49
50
|
const files = computed(() => props.instance.files.value);
|
|
50
51
|
const index = computed(() => props.instance.index.value);
|
|
51
52
|
const loop = computed(() => props.instance.options.loop === true);
|
|
@@ -59,6 +60,9 @@ const itemRenderer = computed(
|
|
|
59
60
|
);
|
|
60
61
|
const kindLabel = computed(() => filePreviewKindLabel(itemRenderer.value, t));
|
|
61
62
|
const source = useFilePreviewSource(() => display.value);
|
|
63
|
+
const unavailable = computed(
|
|
64
|
+
() => source.status.value === "error" || failure.value?.reason === "source"
|
|
65
|
+
);
|
|
62
66
|
const many = computed(() => files.value.length > 1);
|
|
63
67
|
const hasPrevious = computed(() => loop.value || index.value > 0);
|
|
64
68
|
const hasNext = computed(() => loop.value || index.value < files.value.length - 1);
|
|
@@ -194,6 +198,7 @@ async function runAction(action) {
|
|
|
194
198
|
:can-expand="canExpand"
|
|
195
199
|
:expanded="expanded"
|
|
196
200
|
:details="showDetails"
|
|
201
|
+
:unavailable="unavailable"
|
|
197
202
|
@previous="instance.handle.previous"
|
|
198
203
|
@next="instance.handle.next"
|
|
199
204
|
@close="emit('close')"
|
|
@@ -227,6 +232,7 @@ async function runAction(action) {
|
|
|
227
232
|
:kind-label="kindLabel"
|
|
228
233
|
:arrows="arrows"
|
|
229
234
|
@ready="reported = $event"
|
|
235
|
+
@failure="failure = $event"
|
|
230
236
|
@previous="instance.handle.previous"
|
|
231
237
|
@next="instance.handle.next"
|
|
232
238
|
@download="download"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { useFilePreviewSource } from '../../composables/use-file-preview-source.js';
|
|
2
|
-
import type { FilePreviewContainer, FilePreviewDetail, FilePreviewItem, FilePreviewRendererDefinition } from '../../types/index.js';
|
|
2
|
+
import type { FilePreviewContainer, FilePreviewDetail, FilePreviewItem, FilePreviewRendererDefinition, FilePreviewRendererError } from '../../types/index.js';
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
item: FilePreviewItem;
|
|
5
5
|
display: FilePreviewItem;
|
|
@@ -12,20 +12,22 @@ type __VLS_Props = {
|
|
|
12
12
|
next: boolean;
|
|
13
13
|
} | null;
|
|
14
14
|
};
|
|
15
|
-
declare var
|
|
15
|
+
declare var __VLS_57: {};
|
|
16
16
|
type __VLS_Slots = {} & {
|
|
17
|
-
default?: (props: typeof
|
|
17
|
+
default?: (props: typeof __VLS_57) => any;
|
|
18
18
|
};
|
|
19
19
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
20
|
previous: () => any;
|
|
21
21
|
ready: (details: readonly FilePreviewDetail[]) => any;
|
|
22
22
|
download: () => any;
|
|
23
23
|
next: () => any;
|
|
24
|
+
failure: (error: FilePreviewRendererError | null) => any;
|
|
24
25
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
26
|
onPrevious?: (() => any) | undefined;
|
|
26
27
|
onReady?: ((details: readonly FilePreviewDetail[]) => any) | undefined;
|
|
27
28
|
onDownload?: (() => any) | undefined;
|
|
28
29
|
onNext?: (() => any) | undefined;
|
|
30
|
+
onFailure?: ((error: FilePreviewRendererError | null) => any) | undefined;
|
|
29
31
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
32
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
31
33
|
declare const _default: typeof __VLS_export;
|
|
@@ -15,7 +15,7 @@ const props = defineProps({
|
|
|
15
15
|
kindLabel: { type: String, required: true },
|
|
16
16
|
arrows: { type: [Object, null], required: true }
|
|
17
17
|
});
|
|
18
|
-
const emit = defineEmits(["ready", "previous", "next", "download"]);
|
|
18
|
+
const emit = defineEmits(["ready", "failure", "previous", "next", "download"]);
|
|
19
19
|
const { t } = useUiToolsLocale();
|
|
20
20
|
const failure = shallowRef(null);
|
|
21
21
|
const ready = ref(false);
|
|
@@ -28,6 +28,7 @@ const downloadable = computed(() => props.item.file.download !== false);
|
|
|
28
28
|
const extension = computed(() => (props.display.extension ?? props.kindLabel).toUpperCase());
|
|
29
29
|
function restart() {
|
|
30
30
|
failure.value = null;
|
|
31
|
+
emit("failure", null);
|
|
31
32
|
ready.value = false;
|
|
32
33
|
slow.value = false;
|
|
33
34
|
delay.start();
|
|
@@ -41,6 +42,7 @@ function handleReady(details) {
|
|
|
41
42
|
function handleError(error) {
|
|
42
43
|
failure.value = error;
|
|
43
44
|
delay.stop();
|
|
45
|
+
emit("failure", error);
|
|
44
46
|
}
|
|
45
47
|
function retry() {
|
|
46
48
|
attempt.value += 1;
|
|
@@ -101,14 +103,6 @@ function retry() {
|
|
|
101
103
|
data-file-preview-retry=""
|
|
102
104
|
@click="retry"
|
|
103
105
|
/>
|
|
104
|
-
<UButton
|
|
105
|
-
v-if="downloadable"
|
|
106
|
-
icon="i-lucide-download"
|
|
107
|
-
:label="t('filePreview.download')"
|
|
108
|
-
color="neutral"
|
|
109
|
-
variant="outline"
|
|
110
|
-
@click="emit('download')"
|
|
111
|
-
/>
|
|
112
106
|
</FilePreviewMessage>
|
|
113
107
|
|
|
114
108
|
<FilePreviewMessage
|
|
@@ -121,7 +115,7 @@ function retry() {
|
|
|
121
115
|
:detail="failure.message"
|
|
122
116
|
>
|
|
123
117
|
<UButton
|
|
124
|
-
v-if="downloadable"
|
|
118
|
+
v-if="downloadable && failure.reason !== 'source'"
|
|
125
119
|
icon="i-lucide-download"
|
|
126
120
|
:label="t('filePreview.download')"
|
|
127
121
|
@click="emit('download')"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { useFilePreviewSource } from '../../composables/use-file-preview-source.js';
|
|
2
|
-
import type { FilePreviewContainer, FilePreviewDetail, FilePreviewItem, FilePreviewRendererDefinition } from '../../types/index.js';
|
|
2
|
+
import type { FilePreviewContainer, FilePreviewDetail, FilePreviewItem, FilePreviewRendererDefinition, FilePreviewRendererError } from '../../types/index.js';
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
item: FilePreviewItem;
|
|
5
5
|
display: FilePreviewItem;
|
|
@@ -12,20 +12,22 @@ type __VLS_Props = {
|
|
|
12
12
|
next: boolean;
|
|
13
13
|
} | null;
|
|
14
14
|
};
|
|
15
|
-
declare var
|
|
15
|
+
declare var __VLS_57: {};
|
|
16
16
|
type __VLS_Slots = {} & {
|
|
17
|
-
default?: (props: typeof
|
|
17
|
+
default?: (props: typeof __VLS_57) => any;
|
|
18
18
|
};
|
|
19
19
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
20
|
previous: () => any;
|
|
21
21
|
ready: (details: readonly FilePreviewDetail[]) => any;
|
|
22
22
|
download: () => any;
|
|
23
23
|
next: () => any;
|
|
24
|
+
failure: (error: FilePreviewRendererError | null) => any;
|
|
24
25
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
26
|
onPrevious?: (() => any) | undefined;
|
|
26
27
|
onReady?: ((details: readonly FilePreviewDetail[]) => any) | undefined;
|
|
27
28
|
onDownload?: (() => any) | undefined;
|
|
28
29
|
onNext?: (() => any) | undefined;
|
|
30
|
+
onFailure?: ((error: FilePreviewRendererError | null) => any) | undefined;
|
|
29
31
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
32
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
31
33
|
declare const _default: typeof __VLS_export;
|
|
@@ -49,6 +49,7 @@ const {
|
|
|
49
49
|
onCancelled: (value) => emit("cancel", value),
|
|
50
50
|
onSubmitted: (value, result) => emit("submit", value, result),
|
|
51
51
|
schema: () => props.schema,
|
|
52
|
+
shell: () => props.shell,
|
|
52
53
|
syncInput: () => props.syncInput,
|
|
53
54
|
ui: () => props.ui,
|
|
54
55
|
validate: () => props.validate
|
|
@@ -198,7 +198,9 @@ export function useFieldOptions(params) {
|
|
|
198
198
|
const createLabel = computed(
|
|
199
199
|
() => resolveFormText(optionConfig.value?.create?.label)
|
|
200
200
|
);
|
|
201
|
-
const disableOnLoading = computed(
|
|
201
|
+
const disableOnLoading = computed(
|
|
202
|
+
() => !remoteConfig.value && optionConfig.value?.disableOnLoading !== false
|
|
203
|
+
);
|
|
202
204
|
const selectCreatedOption = computed(
|
|
203
205
|
() => optionConfig.value?.create?.selectOnCreation !== false
|
|
204
206
|
);
|
|
@@ -55,7 +55,7 @@ export function useFormOverlayController(instance) {
|
|
|
55
55
|
open.value = true;
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
void runtime.close();
|
|
59
59
|
}
|
|
60
60
|
function requestComplete(formData, submitData) {
|
|
61
61
|
closeWithResolution({ formData, submitData, type: "complete" });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FormObject, FormRendererController, FormRuntime, FormSubmitHandlerResult, FormUiConfig, FormValidationMode, FormValue } from '../types/index.js';
|
|
1
|
+
import type { FormObject, FormRenderShell, 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;
|
|
@@ -9,6 +9,8 @@ export interface UseFormRootParams {
|
|
|
9
9
|
syncInput: () => boolean | readonly string[] | undefined;
|
|
10
10
|
validate: () => FormValidationMode | undefined;
|
|
11
11
|
ui: () => FormUiConfig | undefined;
|
|
12
|
+
/** Shell the form renders in. Overlay shells ask before closing a modified form by default. */
|
|
13
|
+
shell?: () => FormRenderShell | undefined;
|
|
12
14
|
onSubmitted: (value: FormObject, result: FormSubmitHandlerResult<FormValue>) => void;
|
|
13
15
|
onCancelled: (value: FormObject) => void;
|
|
14
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useAppConfig } from "nuxt/app";
|
|
2
|
-
import { computed, nextTick, onBeforeUnmount, onMounted, watch } from "vue";
|
|
2
|
+
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, watch } from "vue";
|
|
3
3
|
import { useRouter } from "vue-router";
|
|
4
4
|
import { useUiToolsLocale } from "../../i18n/use-locale.js";
|
|
5
5
|
import {
|
|
@@ -19,6 +19,7 @@ import { provideFormRuntime, useFormRuntime } from "./use-form-runtime.js";
|
|
|
19
19
|
import { provideFormUi } from "./use-form-ui.js";
|
|
20
20
|
export function useFormRoot(params) {
|
|
21
21
|
const router = useRouter();
|
|
22
|
+
const appContext = getCurrentInstance()?.appContext;
|
|
22
23
|
const { t } = useUiToolsLocale();
|
|
23
24
|
const appConfig = useAppConfig();
|
|
24
25
|
const schema = computed(() => params.form()?.schema.value ?? params.schema() ?? {});
|
|
@@ -41,6 +42,7 @@ export function useFormRoot(params) {
|
|
|
41
42
|
onBeforeUnmount(() => runtime.bindCancel(null));
|
|
42
43
|
const confirm = useFormConfirm();
|
|
43
44
|
let leaveConfirmed = false;
|
|
45
|
+
let pendingLeave = null;
|
|
44
46
|
watch(
|
|
45
47
|
() => runtime.dirtyPaths.value,
|
|
46
48
|
() => {
|
|
@@ -122,30 +124,51 @@ export function useFormRoot(params) {
|
|
|
122
124
|
}
|
|
123
125
|
async function submit() {
|
|
124
126
|
const controller = params.form();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
params.onSubmitted(runtime.output.value, result);
|
|
127
|
+
try {
|
|
128
|
+
const result = controller ? await controller.submitHandler() : await runtime.submitHandler();
|
|
129
|
+
if (result.success) params.onSubmitted(runtime.output.value, result);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
reportSubmitError(error);
|
|
128
132
|
}
|
|
129
133
|
}
|
|
134
|
+
function reportSubmitError(error) {
|
|
135
|
+
const handler = appContext?.config.errorHandler;
|
|
136
|
+
if (handler) handler(error, null, "nuxt-ui-tools form submit");
|
|
137
|
+
else
|
|
138
|
+
queueMicrotask(() => {
|
|
139
|
+
throw error;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
130
142
|
async function cancel() {
|
|
131
|
-
if (!leaveConfirmed &&
|
|
143
|
+
if (!leaveConfirmed && shouldConfirmDirtyClose() && !await confirmLeave()) {
|
|
132
144
|
return;
|
|
133
145
|
}
|
|
134
146
|
params.onCancelled(runtime.output.value);
|
|
135
147
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
message: getDirtyNavigationMessage()
|
|
140
|
-
});
|
|
141
|
-
return leaveConfirmed;
|
|
148
|
+
function confirmLeave() {
|
|
149
|
+
pendingLeave ??= askLeave();
|
|
150
|
+
return pendingLeave;
|
|
142
151
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
async function askLeave() {
|
|
153
|
+
try {
|
|
154
|
+
leaveConfirmed = await confirm({
|
|
155
|
+
kind: "unsaved-changes",
|
|
156
|
+
message: getDirtyNavigationMessage()
|
|
157
|
+
});
|
|
158
|
+
return leaveConfirmed;
|
|
159
|
+
} finally {
|
|
160
|
+
pendingLeave = null;
|
|
146
161
|
}
|
|
162
|
+
}
|
|
163
|
+
function shouldConfirmDirtyNavigation() {
|
|
164
|
+
return confirmsDirtyChanges(getSchemaDirtyNavigation(schema.value));
|
|
165
|
+
}
|
|
166
|
+
function shouldConfirmDirtyClose() {
|
|
147
167
|
const config = getSchemaDirtyNavigation(schema.value);
|
|
148
|
-
|
|
168
|
+
return confirmsDirtyChanges(isUndefined(config) ? isOverlayShell(params.shell?.()) : config);
|
|
169
|
+
}
|
|
170
|
+
function confirmsDirtyChanges(config) {
|
|
171
|
+
if (!runtime.isDirty.value || !config) {
|
|
149
172
|
return false;
|
|
150
173
|
}
|
|
151
174
|
if (!isRecord(config)) {
|
|
@@ -186,6 +209,9 @@ export function useFormRoot(params) {
|
|
|
186
209
|
validationMode
|
|
187
210
|
};
|
|
188
211
|
}
|
|
212
|
+
function isOverlayShell(shell) {
|
|
213
|
+
return shell === "modal" || shell === "drawer" || shell === "fullscreen";
|
|
214
|
+
}
|
|
189
215
|
function isSameDocument(to, from) {
|
|
190
216
|
return to.fullPath.split("#")[0] === from.fullPath.split("#")[0];
|
|
191
217
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { computed, inject, nextTick, provide, ref, shallowRef, watch } from "vue";
|
|
2
2
|
import { useUiToolsLocale } from "../../i18n/use-locale.js";
|
|
3
|
+
import { dateFamilyCalendarType, formatDateFamilyBound } from "../fields/date-family/utils.js";
|
|
3
4
|
import { createPublicFormApi } from "../utils/api.js";
|
|
4
5
|
import { resolveFieldDependencies } from "../utils/dependencies.js";
|
|
5
6
|
import { resolveFormLayoutConfig } from "../utils/layout.js";
|
|
@@ -45,7 +46,7 @@ export function useFormRuntime(params) {
|
|
|
45
46
|
const navigationActionPending = ref(null);
|
|
46
47
|
const effects = createEffectLifecycle();
|
|
47
48
|
const cancelHandler = shallowRef(null);
|
|
48
|
-
const { t } = useUiToolsLocale();
|
|
49
|
+
const { code, t } = useUiToolsLocale();
|
|
49
50
|
setContext(getSchemaContext(params.schema.value));
|
|
50
51
|
function createFormNamespace(path, read) {
|
|
51
52
|
const parentPath = path.slice(0, -1);
|
|
@@ -66,7 +67,7 @@ export function useFormRuntime(params) {
|
|
|
66
67
|
state: () => read.state(),
|
|
67
68
|
submit: () => runtime.submit(),
|
|
68
69
|
validate: () => runtime.validate(),
|
|
69
|
-
close: () =>
|
|
70
|
+
close: () => runtime.close()
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
function createApiFactory(read) {
|
|
@@ -111,8 +112,15 @@ export function useFormRuntime(params) {
|
|
|
111
112
|
const validation = useFormValidation({
|
|
112
113
|
apiFactory,
|
|
113
114
|
context,
|
|
114
|
-
getDateMaxMessage: (max) => t(
|
|
115
|
-
|
|
115
|
+
getDateMaxMessage: (max, type) => t(`form.validation.${dateFamilyCalendarType(type)}Max`, {
|
|
116
|
+
max: formatDateFamilyBound(max, code.value)
|
|
117
|
+
}),
|
|
118
|
+
getDateMinMessage: (min, type) => t(`form.validation.${dateFamilyCalendarType(type)}Min`, {
|
|
119
|
+
min: formatDateFamilyBound(min, code.value)
|
|
120
|
+
}),
|
|
121
|
+
getNumberBoundMessage: (edge, bound) => t(`form.validation.number${edge === "min" ? "Min" : "Max"}`, {
|
|
122
|
+
bound: new Intl.NumberFormat(code.value).format(bound)
|
|
123
|
+
}),
|
|
116
124
|
getRequiredMessage: () => t("form.validation.required"),
|
|
117
125
|
getUniqueMessage: () => t("form.fields.array.unique"),
|
|
118
126
|
getValidationMode: () => params.validationMode?.value ?? true,
|
|
@@ -432,6 +440,7 @@ export function useFormRuntime(params) {
|
|
|
432
440
|
},
|
|
433
441
|
schema: params.schema,
|
|
434
442
|
setError: (path, message, options) => validation.setError(pathSegments(path), message, options),
|
|
443
|
+
setInputError: (path, message) => validation.setInputError(pathSegments(path), message),
|
|
435
444
|
setValue: state.setValue,
|
|
436
445
|
settleEffects: settleEffectRounds,
|
|
437
446
|
shouldRender: (field, path) => shouldRenderField(
|
|
@@ -454,6 +463,7 @@ export function useFormRuntime(params) {
|
|
|
454
463
|
bindCancel: (cancel) => {
|
|
455
464
|
cancelHandler.value = cancel;
|
|
456
465
|
},
|
|
466
|
+
close: () => cancelHandler.value?.() ?? Promise.resolve(),
|
|
457
467
|
trackEffect: effects.track,
|
|
458
468
|
validate,
|
|
459
469
|
validateCurrentStep
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FormDateFamilyType } from '../fields/date-family/utils.js';
|
|
1
2
|
import type { FormValue, FormErrorOptions, FormField, FormObject, FormRuntimeContext, FormValidationError, FormValidationMode } from '../types/index.js';
|
|
2
3
|
import type { FormFieldApiFactory } from './use-form-state.js';
|
|
3
4
|
/**
|
|
@@ -14,8 +15,9 @@ export declare function useFormValidation(params: {
|
|
|
14
15
|
getValidationMode: () => FormValidationMode;
|
|
15
16
|
getRequiredMessage?: () => string;
|
|
16
17
|
getUniqueMessage?: () => string;
|
|
17
|
-
getDateMinMessage?: (bound: string) => string;
|
|
18
|
-
getDateMaxMessage?: (bound: string) => string;
|
|
18
|
+
getDateMinMessage?: (bound: string, type: FormDateFamilyType) => string;
|
|
19
|
+
getDateMaxMessage?: (bound: string, type: FormDateFamilyType) => string;
|
|
20
|
+
getNumberBoundMessage?: (edge: 'min' | 'max', bound: number) => string;
|
|
19
21
|
}): {
|
|
20
22
|
clearError: (path?: readonly string[]) => void;
|
|
21
23
|
clearValidationState: () => void;
|
|
@@ -25,6 +27,7 @@ export declare function useFormValidation(params: {
|
|
|
25
27
|
isTouched: (path: readonly string[]) => boolean;
|
|
26
28
|
markTouched: (path: readonly string[]) => void;
|
|
27
29
|
setError: (path: readonly string[], message: string, options?: FormErrorOptions) => void;
|
|
30
|
+
setInputError: (path: readonly string[], message: string | null) => void;
|
|
28
31
|
validate: () => Promise<boolean>;
|
|
29
32
|
validateFields: (fields: readonly FormField[], parentPath: readonly string[]) => Promise<boolean>;
|
|
30
33
|
validationErrors: import("vue").ComputedRef<readonly FormValidationError[]>;
|