nuxt-ui-tools 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/form/components/page/form-page-header.d.vue.ts +3 -1
- package/dist/runtime/form/components/page/form-page-header.vue +44 -34
- package/dist/runtime/form/components/page/form-page-header.vue.d.ts +3 -1
- package/dist/runtime/form/components/page/form-page.vue +2 -1
- package/dist/runtime/form/components/utils/form-option-choice-label.d.vue.ts +16 -0
- package/dist/runtime/form/components/utils/form-option-choice-label.vue +37 -0
- package/dist/runtime/form/components/utils/form-option-choice-label.vue.d.ts +16 -0
- package/dist/runtime/form/components/utils/form-option-display-value.d.vue.ts +14 -0
- package/dist/runtime/form/components/utils/form-option-display-value.vue +20 -0
- package/dist/runtime/form/components/utils/form-option-display-value.vue.d.ts +14 -0
- package/dist/runtime/form/components/utils/form-option-item-label.d.vue.ts +9 -2
- package/dist/runtime/form/components/utils/form-option-item-label.vue +20 -13
- package/dist/runtime/form/components/utils/form-option-item-label.vue.d.ts +9 -2
- package/dist/runtime/form/components/utils/form-option-leading.d.vue.ts +23 -0
- package/dist/runtime/form/components/utils/form-option-leading.vue +41 -0
- package/dist/runtime/form/components/utils/form-option-leading.vue.d.ts +23 -0
- package/dist/runtime/form/components/utils/form-option-render.d.ts +11 -0
- package/dist/runtime/form/components/utils/form-option-render.js +5 -0
- package/dist/runtime/form/components/utils/form-option-suffix.d.vue.ts +8 -0
- package/dist/runtime/form/components/utils/form-option-suffix.vue +15 -0
- package/dist/runtime/form/components/utils/form-option-suffix.vue.d.ts +8 -0
- package/dist/runtime/form/components/utils/form-option-value-overlay.d.vue.ts +30 -0
- package/dist/runtime/form/components/utils/form-option-value-overlay.vue +44 -0
- package/dist/runtime/form/components/utils/form-option-value-overlay.vue.d.ts +30 -0
- package/dist/runtime/form/composables/use-choice-group-items.d.ts +27 -0
- package/dist/runtime/form/composables/use-choice-group-items.js +24 -0
- package/dist/runtime/form/composables/use-field-control.d.ts +5 -5
- package/dist/runtime/form/composables/use-form-layout.js +4 -3
- package/dist/runtime/form/composables/use-option-menu.d.ts +20 -0
- package/dist/runtime/form/composables/use-option-menu.js +41 -0
- package/dist/runtime/form/fields/auto-complete/component.vue +92 -51
- package/dist/runtime/form/fields/auto-complete/config.d.ts +1 -0
- package/dist/runtime/form/fields/auto-complete/config.js +1 -1
- package/dist/runtime/form/fields/auto-complete/types.d.ts +2 -2
- package/dist/runtime/form/fields/checkbox-card/component.vue +3 -3
- package/dist/runtime/form/fields/checkbox-card/types.d.ts +3 -3
- package/dist/runtime/form/fields/checkbox-group/component.vue +18 -4
- package/dist/runtime/form/fields/checkbox-group/types.d.ts +2 -2
- package/dist/runtime/form/fields/choice-card/types.d.ts +3 -22
- package/dist/runtime/form/fields/choice-card/utils.d.ts +0 -17
- package/dist/runtime/form/fields/choice-card/utils.js +0 -11
- package/dist/runtime/form/fields/index.d.ts +2 -0
- package/dist/runtime/form/fields/radio/component.vue +19 -2
- package/dist/runtime/form/fields/radio/types.d.ts +2 -2
- package/dist/runtime/form/fields/radio-card/component.vue +3 -3
- package/dist/runtime/form/fields/radio-card/types.d.ts +3 -3
- package/dist/runtime/form/fields/select/component.vue +56 -58
- package/dist/runtime/form/fields/select/config.d.ts +1 -0
- package/dist/runtime/form/fields/select/config.js +1 -1
- package/dist/runtime/form/fields/select/types.d.ts +2 -2
- package/dist/runtime/form/types/layout.d.ts +6 -0
- package/dist/runtime/form/types/options.d.ts +137 -0
- package/dist/runtime/form/types/ui.d.ts +5 -0
- package/dist/runtime/form/utils/options.d.ts +37 -13
- package/dist/runtime/form/utils/options.js +71 -0
- package/dist/runtime/form/utils/page-surface.d.ts +5 -0
- package/dist/runtime/form/utils/page-surface.js +1 -0
- package/dist/runtime/spreadsheet/components/internal/spreadsheet-import-column-row.d.vue.ts +2 -2
- package/dist/runtime/spreadsheet/components/internal/spreadsheet-import-column-row.vue.d.ts +2 -2
- package/dist/runtime/table/components/filters/tags/boolean-filter-tag.d.vue.ts +2 -2
- package/dist/runtime/table/components/filters/tags/boolean-filter-tag.vue.d.ts +2 -2
- package/dist/runtime/table/components/filters/tags/date-filter-tag.d.vue.ts +2 -2
- package/dist/runtime/table/components/filters/tags/date-filter-tag.vue.d.ts +2 -2
- package/dist/runtime/table/components/filters/tags/number-filter-tag.d.vue.ts +2 -2
- package/dist/runtime/table/components/filters/tags/number-filter-tag.vue.d.ts +2 -2
- package/dist/runtime/table/components/filters/tags/option-filter-tag.d.vue.ts +2 -2
- package/dist/runtime/table/components/filters/tags/option-filter-tag.vue.d.ts +2 -2
- package/dist/runtime/table/components/filters/tags/text-filter-tag.d.vue.ts +2 -2
- package/dist/runtime/table/components/filters/tags/text-filter-tag.vue.d.ts +2 -2
- package/dist/runtime/table/types/remote-options.d.ts +4 -1
- package/dist/runtime/table/utils/data-list-ui.d.ts +1 -1
- package/dist/runtime/table/utils/remote-table-options.js +1 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* badge while a form with `controls.dirtyCheck` is modified, and the page actions. It stays
|
|
4
4
|
* pinned while the page scrolls on wide layouts, and sections scroll to just below it. The badge
|
|
5
5
|
* keeps its place while the form is unmodified, hidden, and does not grow its line, so the first
|
|
6
|
-
* edit neither shifts the page nor resizes the pinned header.
|
|
6
|
+
* edit neither shifts the page nor resizes the pinned header. The header spans the page, but on a
|
|
7
|
+
* page without a navigation its row takes the centered column of the body, so both share their
|
|
8
|
+
* edges.
|
|
7
9
|
*/
|
|
8
10
|
type __VLS_Slots = {
|
|
9
11
|
/** Before the heading, e.g. an avatar of the record. */
|
|
@@ -6,6 +6,7 @@ import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
|
6
6
|
import { useFormPageContext } from "../../composables/use-form-page";
|
|
7
7
|
import { useFormUi } from "../../composables/use-form-ui";
|
|
8
8
|
import { getFormHeader } from "../../utils/overlay";
|
|
9
|
+
import { FORM_PAGE_COLUMN } from "../../utils/page-surface";
|
|
9
10
|
import { isRecord } from "../../utils/path";
|
|
10
11
|
import { resolveFormBoundaryText } from "../../utils/text";
|
|
11
12
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
@@ -20,6 +21,12 @@ const eyebrow = computed(() => headerText(header.value, "eyebrow"));
|
|
|
20
21
|
const title = computed(() => headerText(header.value, "title"));
|
|
21
22
|
const description = computed(() => headerText(header.value, "description"));
|
|
22
23
|
const unsaved = computed(() => page.dirtyCheck.value && page.root.runtime.isDirty.value);
|
|
24
|
+
const ROW = "flex flex-col gap-3 @3xl/form-page:flex-row @3xl/form-page:items-center @3xl/form-page:justify-between @3xl/form-page:gap-5";
|
|
25
|
+
const PADDING_X = "px-4 @3xl/form-page:px-7";
|
|
26
|
+
const headerClass = computed(() => page.navigationVisible.value ? `${ROW} ${PADDING_X}` : "");
|
|
27
|
+
const rowClass = computed(
|
|
28
|
+
() => page.navigationVisible.value ? "contents" : `${ROW} ${PADDING_X} ${FORM_PAGE_COLUMN}`
|
|
29
|
+
);
|
|
23
30
|
const element = useTemplateRef("element");
|
|
24
31
|
useResizeObserver(element, () => {
|
|
25
32
|
const node = element.value;
|
|
@@ -38,62 +45,65 @@ function headerText(config, key) {
|
|
|
38
45
|
ref="element"
|
|
39
46
|
:class="
|
|
40
47
|
mergeFormUiClass(
|
|
41
|
-
'z-10
|
|
48
|
+
'z-10 border-b border-default bg-default py-4 @3xl/form-page:sticky @3xl/form-page:top-0 @3xl/form-page:pt-[18px] @3xl/form-page:pb-4',
|
|
49
|
+
headerClass,
|
|
42
50
|
pageUi?.header
|
|
43
51
|
)
|
|
44
52
|
"
|
|
45
53
|
data-form-page-header
|
|
46
54
|
>
|
|
47
|
-
<div :class="mergeFormUiClass(
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
<div :class="mergeFormUiClass(rowClass, pageUi?.headerRow)" data-form-page-header-row>
|
|
56
|
+
<div :class="mergeFormUiClass('flex min-w-0 items-center gap-3.5', pageUi?.headerContent)">
|
|
57
|
+
<slot name="leading" />
|
|
58
|
+
<div :class="mergeFormUiClass('grid min-w-0 gap-1.5', pageUi?.heading)">
|
|
59
|
+
<p
|
|
60
|
+
v-if="eyebrow || $slots.eyebrow"
|
|
61
|
+
:class="
|
|
53
62
|
mergeFormUiClass(
|
|
54
63
|
'text-[11px] font-semibold tracking-[0.08em] text-dimmed uppercase',
|
|
55
64
|
pageUi?.eyebrow
|
|
56
65
|
)
|
|
57
66
|
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
>
|
|
68
|
+
<slot name="eyebrow">{{ eyebrow }}</slot>
|
|
69
|
+
</p>
|
|
70
|
+
<h1
|
|
71
|
+
v-if="title || $slots.title"
|
|
72
|
+
:class="
|
|
64
73
|
mergeFormUiClass(
|
|
65
74
|
'm-0 truncate text-[22px] leading-[1.1] font-semibold tracking-[-0.025em] text-highlighted @3xl/form-page:text-[28px]',
|
|
66
75
|
pageUi?.title
|
|
67
76
|
)
|
|
68
77
|
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
>
|
|
79
|
+
<slot name="title">{{ title }}</slot>
|
|
80
|
+
</h1>
|
|
81
|
+
<div
|
|
82
|
+
v-if="description || page.dirtyCheck.value || $slots.description"
|
|
83
|
+
:class="
|
|
75
84
|
mergeFormUiClass(
|
|
76
85
|
'flex flex-wrap items-center gap-2.5 text-[13.5px] text-muted',
|
|
77
86
|
pageUi?.meta
|
|
78
87
|
)
|
|
79
88
|
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
>
|
|
90
|
+
<slot name="description">
|
|
91
|
+
<span v-if="description">{{ description }}</span>
|
|
92
|
+
</slot>
|
|
93
|
+
<UBadge
|
|
94
|
+
v-if="page.dirtyCheck.value"
|
|
95
|
+
color="warning"
|
|
96
|
+
variant="soft"
|
|
97
|
+
:label="t('form.page.unsavedChanges')"
|
|
98
|
+
:class="mergeFormUiClass(unsaved ? '-my-1' : '-my-1 invisible', pageUi?.unsaved)"
|
|
99
|
+
:data-form-page-unsaved="unsaved ? '' : void 0"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
92
102
|
</div>
|
|
93
103
|
</div>
|
|
104
|
+
<slot name="actions">
|
|
105
|
+
<FormPageActions />
|
|
106
|
+
</slot>
|
|
94
107
|
</div>
|
|
95
|
-
<slot name="actions">
|
|
96
|
-
<FormPageActions />
|
|
97
|
-
</slot>
|
|
98
108
|
</header>
|
|
99
109
|
</template>
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* badge while a form with `controls.dirtyCheck` is modified, and the page actions. It stays
|
|
4
4
|
* pinned while the page scrolls on wide layouts, and sections scroll to just below it. The badge
|
|
5
5
|
* keeps its place while the form is unmodified, hidden, and does not grow its line, so the first
|
|
6
|
-
* edit neither shifts the page nor resizes the pinned header.
|
|
6
|
+
* edit neither shifts the page nor resizes the pinned header. The header spans the page, but on a
|
|
7
|
+
* page without a navigation its row takes the centered column of the body, so both share their
|
|
8
|
+
* edges.
|
|
7
9
|
*/
|
|
8
10
|
type __VLS_Slots = {
|
|
9
11
|
/** Before the heading, e.g. an avatar of the record. */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed, useTemplateRef } from "vue";
|
|
3
3
|
import { provideFormPage, useFormPage } from "../../composables/use-form-page";
|
|
4
4
|
import { useFormRoot } from "../../composables/use-form-root";
|
|
5
|
+
import { FORM_PAGE_COLUMN } from "../../utils/page-surface";
|
|
5
6
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
6
7
|
import FormPageHeader from "./form-page-header.vue";
|
|
7
8
|
import FormPageNavigation from "./form-page-navigation.vue";
|
|
@@ -32,7 +33,7 @@ const page = provideFormPage(useFormPage({ element: () => element.value, hash: (
|
|
|
32
33
|
const pageUi = computed(() => root.formUi.ui.value.page?.ui);
|
|
33
34
|
const BODY_UI = {
|
|
34
35
|
beside: "grid gap-4 px-4 pt-4 pb-16 @3xl/form-page:grid-cols-[200px_minmax(0,1fr)] @3xl/form-page:items-start @3xl/form-page:gap-7 @3xl/form-page:px-7 @3xl/form-page:pt-6 @3xl/form-page:pb-20 @5xl/form-page:grid-cols-[230px_minmax(0,760px)]",
|
|
35
|
-
single:
|
|
36
|
+
single: `${FORM_PAGE_COLUMN} grid gap-4 px-4 pt-4 pb-16 @3xl/form-page:gap-7 @3xl/form-page:px-7 @3xl/form-page:pt-6 @3xl/form-page:pb-20`
|
|
36
37
|
};
|
|
37
38
|
</script>
|
|
38
39
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Label of an option in a radio or checkbox list: the leading visual, the label, and the suffix at
|
|
5
|
+
* the end of the row. The description stays with the Nuxt UI group, under the row. An option with a
|
|
6
|
+
* `renderOption` function shows what it returns instead, and a plain option shows its label as it
|
|
7
|
+
* always did.
|
|
8
|
+
*/
|
|
9
|
+
type __VLS_Props = {
|
|
10
|
+
option: ResolvedFormOption;
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
render?: (context: FormOptionRenderContext) => VNodeChild;
|
|
13
|
+
};
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { resolveOptionLeading } from "../../utils/options";
|
|
3
|
+
import FormOptionLeading from "./form-option-leading.vue";
|
|
4
|
+
import FormOptionRender from "./form-option-render";
|
|
5
|
+
import FormOptionSuffix from "./form-option-suffix.vue";
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
option: { type: Object, required: true },
|
|
8
|
+
selected: { type: Boolean, required: false },
|
|
9
|
+
render: { type: Function, required: false }
|
|
10
|
+
});
|
|
11
|
+
function content() {
|
|
12
|
+
const { item } = props.option;
|
|
13
|
+
if (item === void 0) return null;
|
|
14
|
+
return props.render?.({
|
|
15
|
+
disabled: props.option.disabled === true,
|
|
16
|
+
item,
|
|
17
|
+
option: props.option,
|
|
18
|
+
selected: props.selected === true
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<span v-if="render" class="block whitespace-normal" data-form-option-custom>
|
|
25
|
+
<FormOptionRender :content="content" />
|
|
26
|
+
</span>
|
|
27
|
+
<span
|
|
28
|
+
v-else-if="resolveOptionLeading(option) || option.suffix"
|
|
29
|
+
class="flex min-w-0 items-center gap-2"
|
|
30
|
+
data-form-option-row
|
|
31
|
+
>
|
|
32
|
+
<FormOptionLeading :option="option" />
|
|
33
|
+
<span class="min-w-0 flex-1 break-words">{{ option.label }}</span>
|
|
34
|
+
<FormOptionSuffix :option="option" class="ps-2 font-normal" />
|
|
35
|
+
</span>
|
|
36
|
+
<template v-else>{{ option.label }}</template>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Label of an option in a radio or checkbox list: the leading visual, the label, and the suffix at
|
|
5
|
+
* the end of the row. The description stays with the Nuxt UI group, under the row. An option with a
|
|
6
|
+
* `renderOption` function shows what it returns instead, and a plain option shows its label as it
|
|
7
|
+
* always did.
|
|
8
|
+
*/
|
|
9
|
+
type __VLS_Props = {
|
|
10
|
+
option: ResolvedFormOption;
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
render?: (context: FormOptionRenderContext) => VNodeChild;
|
|
13
|
+
};
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionValueRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The chosen option as the closed control of a select or auto-complete reads: what `renderValue`
|
|
5
|
+
* returns, or the label followed by the dimmed suffix. The leading visual belongs to the control's
|
|
6
|
+
* own leading slot.
|
|
7
|
+
*/
|
|
8
|
+
type __VLS_Props = {
|
|
9
|
+
option: ResolvedFormOption;
|
|
10
|
+
render?: (context: FormOptionValueRenderContext) => VNodeChild;
|
|
11
|
+
};
|
|
12
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import FormOptionRender from "./form-option-render";
|
|
3
|
+
import FormOptionSuffix from "./form-option-suffix.vue";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
option: { type: Object, required: true },
|
|
6
|
+
render: { type: Function, required: false }
|
|
7
|
+
});
|
|
8
|
+
function content() {
|
|
9
|
+
const { item } = props.option;
|
|
10
|
+
return item === void 0 ? null : props.render?.({ item, option: props.option });
|
|
11
|
+
}
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<FormOptionRender v-if="render" :content="content" />
|
|
16
|
+
<template v-else>
|
|
17
|
+
{{ option.label }}
|
|
18
|
+
<FormOptionSuffix :option="option" class="ms-1.5" />
|
|
19
|
+
</template>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionValueRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The chosen option as the closed control of a select or auto-complete reads: what `renderValue`
|
|
5
|
+
* returns, or the label followed by the dimmed suffix. The leading visual belongs to the control's
|
|
6
|
+
* own leading slot.
|
|
7
|
+
*/
|
|
8
|
+
type __VLS_Props = {
|
|
9
|
+
option: ResolvedFormOption;
|
|
10
|
+
render?: (context: FormOptionValueRenderContext) => VNodeChild;
|
|
11
|
+
};
|
|
12
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Label of an option in a menu. The load-more row keeps its own loader, an option with a
|
|
5
|
+
* `renderOption` function shows what it returns, and any other option shows its label.
|
|
6
|
+
*/
|
|
2
7
|
type __VLS_Props = {
|
|
3
|
-
|
|
8
|
+
option: ResolvedFormOption;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
render?: (context: FormOptionRenderContext) => VNodeChild;
|
|
4
11
|
};
|
|
5
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
6
13
|
declare const _default: typeof __VLS_export;
|
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
import UIcon from "@nuxt/ui/components/Icon.vue";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
import { isLoadMoreOption } from "../../utils/options";
|
|
5
|
-
import
|
|
6
|
-
import { isNumber, isString } from "../../utils/predicate";
|
|
5
|
+
import FormOptionRender from "./form-option-render";
|
|
7
6
|
const props = defineProps({
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const label = computed(() => {
|
|
12
|
-
if (isRecord(props.item)) {
|
|
13
|
-
const value = props.item.label;
|
|
14
|
-
return isString(value) || isNumber(value) ? String(value) : "";
|
|
15
|
-
}
|
|
16
|
-
return isString(props.item) || isNumber(props.item) ? String(props.item) : "";
|
|
7
|
+
option: { type: Object, required: true },
|
|
8
|
+
selected: { type: Boolean, required: false },
|
|
9
|
+
render: { type: Function, required: false }
|
|
17
10
|
});
|
|
11
|
+
const loadMore = computed(() => isLoadMoreOption(props.option));
|
|
12
|
+
function content() {
|
|
13
|
+
const { item } = props.option;
|
|
14
|
+
if (item === void 0) return null;
|
|
15
|
+
return props.render?.({
|
|
16
|
+
disabled: props.option.disabled === true,
|
|
17
|
+
item,
|
|
18
|
+
option: props.option,
|
|
19
|
+
selected: props.selected === true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
18
22
|
</script>
|
|
19
23
|
|
|
20
24
|
<template>
|
|
@@ -24,7 +28,10 @@ const label = computed(() => {
|
|
|
24
28
|
data-form-option-loading=""
|
|
25
29
|
>
|
|
26
30
|
<UIcon name="i-lucide-loader-circle" class="size-3.5 animate-spin" aria-hidden="true" />
|
|
27
|
-
{{ label }}
|
|
31
|
+
{{ option.label }}
|
|
32
|
+
</span>
|
|
33
|
+
<span v-else-if="render" class="block whitespace-normal" data-form-option-custom>
|
|
34
|
+
<FormOptionRender :content="content" />
|
|
28
35
|
</span>
|
|
29
|
-
<template v-else>{{ label }}</template>
|
|
36
|
+
<template v-else>{{ option.label }}</template>
|
|
30
37
|
</template>
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Label of an option in a menu. The load-more row keeps its own loader, an option with a
|
|
5
|
+
* `renderOption` function shows what it returns, and any other option shows its label.
|
|
6
|
+
*/
|
|
2
7
|
type __VLS_Props = {
|
|
3
|
-
|
|
8
|
+
option: ResolvedFormOption;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
render?: (context: FormOptionRenderContext) => VNodeChild;
|
|
4
11
|
};
|
|
5
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
6
13
|
declare const _default: typeof __VLS_export;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ResolvedFormOption } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one visual an option draws before its label: its icon, else its avatar, else its chip, the
|
|
4
|
+
* same order as Nuxt UI items. Draws nothing for an option that declares none, or for no option:
|
|
5
|
+
* a menu fills its leading slot with this component even when a `renderOption` function owns the
|
|
6
|
+
* content, because an empty slot would bring back the Nuxt UI icon.
|
|
7
|
+
*/
|
|
8
|
+
type __VLS_Props = {
|
|
9
|
+
option?: ResolvedFormOption;
|
|
10
|
+
/** Classes of the icon, so a control can match its own leading icon. */
|
|
11
|
+
iconClass?: string;
|
|
12
|
+
/** Nuxt UI avatar size; one the avatar does not know falls back to `xs`. */
|
|
13
|
+
avatarSize?: string;
|
|
14
|
+
/** Classes of the box the chip dot is centered in. */
|
|
15
|
+
chipClass?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
18
|
+
iconClass: string;
|
|
19
|
+
avatarSize: string;
|
|
20
|
+
chipClass: string;
|
|
21
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
|
+
declare const _default: typeof __VLS_export;
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import UAvatar from "@nuxt/ui/components/Avatar.vue";
|
|
3
|
+
import UChip from "@nuxt/ui/components/Chip.vue";
|
|
4
|
+
import UIcon from "@nuxt/ui/components/Icon.vue";
|
|
5
|
+
import { computed } from "vue";
|
|
6
|
+
const AVATAR_SIZES = ["3xs", "2xs", "xs", "sm", "md", "lg", "xl", "2xl", "3xl"];
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
option: { type: Object, required: false },
|
|
9
|
+
iconClass: { type: String, required: false, default: "size-4 text-muted" },
|
|
10
|
+
avatarSize: { type: String, required: false, default: "xs" },
|
|
11
|
+
chipClass: { type: String, required: false, default: "size-4" }
|
|
12
|
+
});
|
|
13
|
+
const size = computed(() => AVATAR_SIZES.find((entry) => entry === props.avatarSize) ?? "xs");
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<UIcon
|
|
18
|
+
v-if="option?.icon"
|
|
19
|
+
:name="option.icon"
|
|
20
|
+
:class="['shrink-0', iconClass]"
|
|
21
|
+
aria-hidden="true"
|
|
22
|
+
data-form-option-leading="icon"
|
|
23
|
+
/>
|
|
24
|
+
<UAvatar
|
|
25
|
+
v-else-if="option?.avatar"
|
|
26
|
+
:src="option.avatar.src"
|
|
27
|
+
:alt="option.avatar.alt ?? option.label"
|
|
28
|
+
:text="option.avatar.text"
|
|
29
|
+
:size="size"
|
|
30
|
+
class="shrink-0"
|
|
31
|
+
data-form-option-leading="avatar"
|
|
32
|
+
/>
|
|
33
|
+
<span
|
|
34
|
+
v-else-if="option?.chip"
|
|
35
|
+
:class="['grid shrink-0 place-items-center', chipClass]"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
data-form-option-leading="chip"
|
|
38
|
+
>
|
|
39
|
+
<UChip standalone inset size="md" :color="option.chip.color" />
|
|
40
|
+
</span>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ResolvedFormOption } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one visual an option draws before its label: its icon, else its avatar, else its chip, the
|
|
4
|
+
* same order as Nuxt UI items. Draws nothing for an option that declares none, or for no option:
|
|
5
|
+
* a menu fills its leading slot with this component even when a `renderOption` function owns the
|
|
6
|
+
* content, because an empty slot would bring back the Nuxt UI icon.
|
|
7
|
+
*/
|
|
8
|
+
type __VLS_Props = {
|
|
9
|
+
option?: ResolvedFormOption;
|
|
10
|
+
/** Classes of the icon, so a control can match its own leading icon. */
|
|
11
|
+
iconClass?: string;
|
|
12
|
+
/** Nuxt UI avatar size; one the avatar does not know falls back to `xs`. */
|
|
13
|
+
avatarSize?: string;
|
|
14
|
+
/** Classes of the box the chip dot is centered in. */
|
|
15
|
+
chipClass?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
18
|
+
iconClass: string;
|
|
19
|
+
avatarSize: string;
|
|
20
|
+
chipClass: string;
|
|
21
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
|
+
declare const _default: typeof __VLS_export;
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Renders what a `renderOption` or `renderValue` function returned. The function is called inside
|
|
4
|
+
* this component's render, so the reactive state it reads re-renders only this option.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("vue").DefineSetupFnComponent<{
|
|
7
|
+
content: () => VNodeChild;
|
|
8
|
+
}, {}, {}, {
|
|
9
|
+
content: () => VNodeChild;
|
|
10
|
+
} & {}, import("vue").PublicProps>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ResolvedFormOption } from '../../types/index.js';
|
|
2
|
+
/** The secondary text at the end of an option, dimmed, with numbers that line up in a column. */
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
option: ResolvedFormOption;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
option: { type: Object, required: true }
|
|
4
|
+
});
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<span
|
|
9
|
+
v-if="option.suffix"
|
|
10
|
+
class="shrink-0 whitespace-nowrap text-dimmed tabular-nums"
|
|
11
|
+
data-form-option-suffix
|
|
12
|
+
>
|
|
13
|
+
{{ option.suffix }}
|
|
14
|
+
</span>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ResolvedFormOption } from '../../types/index.js';
|
|
2
|
+
/** The secondary text at the end of an option, dimmed, with numbers that line up in a column. */
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
option: ResolvedFormOption;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionValueRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Draws the chosen option of an auto-complete over the text of its input, while the field rests.
|
|
5
|
+
* The input holds plain text, so a `renderValue` result or a suffix after the label can only sit
|
|
6
|
+
* on top of it: the input keeps its own text (made transparent by the field while `active`), its
|
|
7
|
+
* selection and its focus, and the overlay copies the input's padding and type so the value starts
|
|
8
|
+
* where the text would. It ignores the pointer, and is hidden from assistive technology because
|
|
9
|
+
* the input already announces the label.
|
|
10
|
+
*/
|
|
11
|
+
type __VLS_Props = {
|
|
12
|
+
/** The chosen option, or none. */
|
|
13
|
+
option?: ResolvedFormOption;
|
|
14
|
+
render?: (context: FormOptionValueRenderContext) => VNodeChild;
|
|
15
|
+
/** True while the overlay stands in for the text of the input. */
|
|
16
|
+
active: boolean;
|
|
17
|
+
};
|
|
18
|
+
declare var __VLS_1: {};
|
|
19
|
+
type __VLS_Slots = {} & {
|
|
20
|
+
default?: (props: typeof __VLS_1) => any;
|
|
21
|
+
};
|
|
22
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
26
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
27
|
+
new (): {
|
|
28
|
+
$slots: S;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { nextTick, onMounted, ref, useTemplateRef, watch } from "vue";
|
|
3
|
+
import FormOptionDisplayValue from "./form-option-display-value.vue";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
option: { type: Object, required: false },
|
|
6
|
+
render: { type: Function, required: false },
|
|
7
|
+
active: { type: Boolean, required: true }
|
|
8
|
+
});
|
|
9
|
+
const root = useTemplateRef("root");
|
|
10
|
+
const inset = ref({});
|
|
11
|
+
function measure() {
|
|
12
|
+
const input = root.value?.querySelector("input");
|
|
13
|
+
if (!input) return;
|
|
14
|
+
const style = getComputedStyle(input);
|
|
15
|
+
inset.value = {
|
|
16
|
+
fontSize: style.fontSize,
|
|
17
|
+
insetInlineEnd: style.paddingInlineEnd,
|
|
18
|
+
insetInlineStart: style.paddingInlineStart,
|
|
19
|
+
lineHeight: style.lineHeight
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
onMounted(measure);
|
|
23
|
+
watch(
|
|
24
|
+
() => [props.option?.value, props.active],
|
|
25
|
+
() => nextTick(measure)
|
|
26
|
+
);
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div ref="root" class="relative w-full min-w-0">
|
|
31
|
+
<slot />
|
|
32
|
+
<div
|
|
33
|
+
v-if="active && option"
|
|
34
|
+
class="pointer-events-none absolute inset-y-0 flex min-w-0 items-center overflow-hidden whitespace-nowrap text-highlighted"
|
|
35
|
+
:style="inset"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
data-form-option-value-overlay
|
|
38
|
+
>
|
|
39
|
+
<span class="min-w-0 truncate">
|
|
40
|
+
<FormOptionDisplayValue :option="option" :render="render" />
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionValueRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Draws the chosen option of an auto-complete over the text of its input, while the field rests.
|
|
5
|
+
* The input holds plain text, so a `renderValue` result or a suffix after the label can only sit
|
|
6
|
+
* on top of it: the input keeps its own text (made transparent by the field while `active`), its
|
|
7
|
+
* selection and its focus, and the overlay copies the input's padding and type so the value starts
|
|
8
|
+
* where the text would. It ignores the pointer, and is hidden from assistive technology because
|
|
9
|
+
* the input already announces the label.
|
|
10
|
+
*/
|
|
11
|
+
type __VLS_Props = {
|
|
12
|
+
/** The chosen option, or none. */
|
|
13
|
+
option?: ResolvedFormOption;
|
|
14
|
+
render?: (context: FormOptionValueRenderContext) => VNodeChild;
|
|
15
|
+
/** True while the overlay stands in for the text of the input. */
|
|
16
|
+
active: boolean;
|
|
17
|
+
};
|
|
18
|
+
declare var __VLS_1: {};
|
|
19
|
+
type __VLS_Slots = {} & {
|
|
20
|
+
default?: (props: typeof __VLS_1) => any;
|
|
21
|
+
};
|
|
22
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
26
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
27
|
+
new (): {
|
|
28
|
+
$slots: S;
|
|
29
|
+
};
|
|
30
|
+
};
|