nuxt-ui-tools 1.17.1 → 1.18.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/module.mjs +1 -0
- package/dist/runtime/form/components/page/form-page-navigation.d.vue.ts +5 -3
- package/dist/runtime/form/components/page/form-page-navigation.vue +6 -3
- package/dist/runtime/form/components/page/form-page-navigation.vue.d.ts +5 -3
- package/dist/runtime/form/components/page/form-page-section.vue +6 -5
- package/dist/runtime/form/components/page/form-page-sections.d.vue.ts +4 -3
- package/dist/runtime/form/components/page/form-page-sections.vue +7 -2
- package/dist/runtime/form/components/page/form-page-sections.vue.d.ts +4 -3
- package/dist/runtime/form/components/page/form-page.d.vue.ts +2 -0
- package/dist/runtime/form/components/page/form-page.vue +10 -2
- package/dist/runtime/form/components/page/form-page.vue.d.ts +2 -0
- package/dist/runtime/form/components/renderer/form-field-renderer.vue +6 -4
- package/dist/runtime/form/components/renderer/form-field-shell.d.vue.ts +4 -4
- package/dist/runtime/form/components/renderer/form-field-shell.vue +28 -4
- package/dist/runtime/form/components/renderer/form-field-shell.vue.d.ts +4 -4
- package/dist/runtime/form/components/root/form.vue +2 -2
- package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
- package/dist/runtime/form/composables/use-field-control.js +9 -21
- package/dist/runtime/form/composables/use-field-options.js +8 -4
- package/dist/runtime/form/composables/use-form-page.d.ts +11 -1
- package/dist/runtime/form/composables/use-form-page.js +12 -6
- package/dist/runtime/form/composables/use-form-runtime.js +1 -1
- package/dist/runtime/form/fields/array-primitive/types.d.ts +1 -1
- package/dist/runtime/form/fields/checkbox-card/component.vue +54 -28
- package/dist/runtime/form/fields/checkbox-card/config.d.ts +1 -0
- package/dist/runtime/form/fields/checkbox-card/config.js +1 -1
- package/dist/runtime/form/fields/checkbox-card/types.d.ts +3 -3
- package/dist/runtime/form/fields/choice-card/choice-card-label.d.vue.ts +7 -0
- package/dist/runtime/form/fields/choice-card/choice-card-label.vue +27 -1
- package/dist/runtime/form/fields/choice-card/choice-card-label.vue.d.ts +7 -0
- package/dist/runtime/form/fields/choice-card/types.d.ts +54 -1
- package/dist/runtime/form/fields/choice-card/use-choice-card.d.ts +7 -3
- package/dist/runtime/form/fields/choice-card/use-choice-card.js +37 -15
- package/dist/runtime/form/fields/choice-card/utils.d.ts +27 -0
- package/dist/runtime/form/fields/choice-card/utils.js +17 -0
- package/dist/runtime/form/fields/hierarchy/component.vue +3 -2
- package/dist/runtime/form/fields/index.d.ts +2 -0
- package/dist/runtime/form/fields/radio-card/component.vue +58 -28
- package/dist/runtime/form/fields/radio-card/config.d.ts +1 -0
- package/dist/runtime/form/fields/radio-card/config.js +1 -1
- package/dist/runtime/form/fields/radio-card/types.d.ts +3 -3
- package/dist/runtime/form/types/field-base.d.ts +6 -0
- package/dist/runtime/form/types/field-kind.d.ts +3 -1
- package/dist/runtime/form/types/field.d.ts +1 -0
- package/dist/runtime/form/types/page.d.ts +20 -8
- package/dist/runtime/form/types/ui.d.ts +24 -4
- package/dist/runtime/form/utils/disabled.d.ts +11 -0
- package/dist/runtime/form/utils/disabled.js +11 -0
- package/dist/runtime/form/utils/field-instance.js +3 -0
- package/dist/runtime/form/utils/options.d.ts +22 -1
- package/dist/runtime/form/utils/options.js +15 -0
- package/dist/runtime/form/utils/page-surface.d.ts +21 -0
- package/dist/runtime/form/utils/page-surface.js +27 -0
- package/dist/runtime/form/utils/page.d.ts +21 -1
- package/dist/runtime/form/utils/page.js +24 -2
- package/dist/runtime/i18n/locales/en.js +28 -8
- package/dist/runtime/i18n/locales/fr.js +28 -8
- package/dist/runtime/i18n/types.d.ts +46 -7
- package/dist/runtime/table/components/actions/row-actions.d.vue.ts +2 -0
- package/dist/runtime/table/components/actions/row-actions.vue +8 -3
- package/dist/runtime/table/components/actions/row-actions.vue.d.ts +2 -0
- package/dist/runtime/table/components/data-list/data-list-content.vue +7 -1
- package/dist/runtime/table/components/data-list/data-list-default.d.vue.ts +16 -16
- package/dist/runtime/table/components/data-list/data-list-default.vue +3 -0
- package/dist/runtime/table/components/data-list/data-list-default.vue.d.ts +16 -16
- package/dist/runtime/table/components/data-list/data-list-layout-switch.vue +7 -9
- package/dist/runtime/table/components/data-list/data-list-root.vue +5 -6
- package/dist/runtime/table/components/data-list/data-list-sort-menu.vue +3 -1
- package/dist/runtime/table/components/data-list/data-list-table.d.vue.ts +8 -1
- package/dist/runtime/table/components/data-list/data-list-table.vue +18 -1
- package/dist/runtime/table/components/data-list/data-list-table.vue.d.ts +8 -1
- package/dist/runtime/table/components/data-list/data-list-view-toggle.d.vue.ts +25 -0
- package/dist/runtime/table/components/data-list/data-list-view-toggle.vue +57 -0
- package/dist/runtime/table/components/data-list/data-list-view-toggle.vue.d.ts +25 -0
- package/dist/runtime/table/components/data-list/sort-menu-panel.vue +1 -1
- package/dist/runtime/table/components/layout/table-footer.d.vue.ts +6 -4
- package/dist/runtime/table/components/layout/table-footer.vue +80 -3
- package/dist/runtime/table/components/layout/table-footer.vue.d.ts +6 -4
- package/dist/runtime/table/components/summary/summary-content.d.ts +13 -0
- package/dist/runtime/table/components/summary/summary-content.js +5 -0
- package/dist/runtime/table/components/summary/summary-renderer.d.vue.ts +31 -0
- package/dist/runtime/table/components/summary/summary-renderer.vue +282 -0
- package/dist/runtime/table/components/summary/summary-renderer.vue.d.ts +31 -0
- package/dist/runtime/table/components/summary/summary-row.d.vue.ts +19 -0
- package/dist/runtime/table/components/summary/summary-row.vue +102 -0
- package/dist/runtime/table/components/summary/summary-row.vue.d.ts +19 -0
- package/dist/runtime/table/components/table/table-empty-state.vue +39 -9
- package/dist/runtime/table/components/table/table-renderer.vue +1 -1
- package/dist/runtime/table/components/table/table-row-actions-control.vue +10 -6
- package/dist/runtime/table/components/table/table-selection-control.d.vue.ts +5 -1
- package/dist/runtime/table/components/table/table-selection-control.vue +17 -4
- package/dist/runtime/table/components/table/table-selection-control.vue.d.ts +5 -1
- package/dist/runtime/table/components/table/table-tree-control.vue +2 -1
- package/dist/runtime/table/components/table/table-tree-header.vue +0 -1
- package/dist/runtime/table/composables/use-table-api.d.ts +2 -0
- package/dist/runtime/table/composables/use-table-api.js +8 -0
- package/dist/runtime/table/composables/use-table-columns.d.ts +2 -0
- package/dist/runtime/table/composables/use-table-controls.d.ts +6 -0
- package/dist/runtime/table/composables/use-table-controls.js +6 -3
- package/dist/runtime/table/composables/use-table-internals.d.ts +33 -0
- package/dist/runtime/table/composables/use-table-internals.js +9 -0
- package/dist/runtime/table/composables/use-table-row-labels.d.ts +16 -0
- package/dist/runtime/table/composables/use-table-row-labels.js +22 -0
- package/dist/runtime/table/composables/use-table-row-summary.d.ts +18 -0
- package/dist/runtime/table/composables/use-table-row-summary.js +41 -0
- package/dist/runtime/table/constants/mobile.d.ts +79 -0
- package/dist/runtime/table/constants/mobile.js +17 -0
- package/dist/runtime/table/constants/summary.d.ts +67 -0
- package/dist/runtime/table/constants/summary.js +31 -0
- package/dist/runtime/table/types/data-list.d.ts +58 -8
- package/dist/runtime/table/types/layout.d.ts +73 -0
- package/dist/runtime/table/types/schema.d.ts +9 -1
- package/dist/runtime/table/types/table-api.d.ts +12 -0
- package/dist/runtime/table/utils/columns/defs.js +3 -1
- package/dist/runtime/table/utils/columns/render.d.ts +2 -0
- package/dist/runtime/table/utils/columns/render.js +2 -2
- package/dist/runtime/table/utils/data-list-ui.js +3 -1
- package/dist/runtime/table/utils/mobile-defaults.d.ts +7 -0
- package/dist/runtime/table/utils/mobile-defaults.js +25 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import type { FormStatefulFieldBase } from '../../types/field-base.js';
|
|
|
5
5
|
import type { ResolveFormFieldValue } from '../../types/field-output.js';
|
|
6
6
|
import type { FormObject, FormRenderable, FormText } from '../../types/utils.js';
|
|
7
7
|
type ContainerFieldType = 'array-list' | 'array-table' | 'array-tabs' | 'array-variant' | 'array-collapse' | 'array-primitive' | 'object' | 'group' | 'input-group' | 'card' | 'column' | 'matrix' | 'info' | 'divider' | 'section' | 'button' | 'hidden';
|
|
8
|
-
type ItemFieldChrome = 'key' | 'label' | 'description' | 'hint' | 'help' | 'labelExtra' | 'layout';
|
|
8
|
+
type ItemFieldChrome = 'key' | 'label' | 'description' | 'hint' | 'help' | 'disabledReason' | 'labelExtra' | 'layout';
|
|
9
9
|
type DistributiveOmit<TValue, TKey extends PropertyKey> = TValue extends unknown ? Omit<TValue, TKey> : never;
|
|
10
10
|
export type FormArrayPrimitiveItemField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> = DistributiveOmit<Exclude<FormField<TContext, TDeps>, {
|
|
11
11
|
type: ContainerFieldType;
|
|
@@ -7,6 +7,7 @@ import { formOptionKey } from "../../utils/options";
|
|
|
7
7
|
import { isBoolean, isNumber, isString, isUndefined } from "../../utils/predicate";
|
|
8
8
|
import ChoiceCardLabel from "../choice-card/choice-card-label.vue";
|
|
9
9
|
import { CHOICE_CARD_PROPS, useChoiceCard } from "../choice-card/use-choice-card";
|
|
10
|
+
import { choiceCardGroupAttrs, groupChoiceCardItems } from "../choice-card/utils";
|
|
10
11
|
const props = defineProps({
|
|
11
12
|
field: { type: Object, required: true },
|
|
12
13
|
path: { type: Array, required: true }
|
|
@@ -16,7 +17,11 @@ const { fieldProps, form, controlProps, disabled, handleBlur, options } = useFie
|
|
|
16
17
|
() => props.path,
|
|
17
18
|
{ omit: CHOICE_CARD_PROPS }
|
|
18
19
|
);
|
|
19
|
-
const cards = useChoiceCard({
|
|
20
|
+
const cards = useChoiceCard({
|
|
21
|
+
kind: "checkbox",
|
|
22
|
+
props: () => fieldProps.value,
|
|
23
|
+
ui: () => controlProps.value.ui
|
|
24
|
+
});
|
|
20
25
|
const optionByKey = computed(
|
|
21
26
|
() => new Map(options.items.value.map((item) => [formOptionKey(item.value), item]))
|
|
22
27
|
);
|
|
@@ -36,9 +41,20 @@ const model = computed({
|
|
|
36
41
|
const items = computed(
|
|
37
42
|
() => options.items.value.map(({ icon: _icon, ...item }) => ({
|
|
38
43
|
...item,
|
|
44
|
+
description: cards.compact.value ? void 0 : item.description,
|
|
39
45
|
value: formOptionKey(item.value)
|
|
40
46
|
}))
|
|
41
47
|
);
|
|
48
|
+
const groups = computed(() => groupChoiceCardItems(items.value));
|
|
49
|
+
function selectionOf(group) {
|
|
50
|
+
return model.value.filter((key) => group.items.some((item) => item.value === key));
|
|
51
|
+
}
|
|
52
|
+
function update(group, keys) {
|
|
53
|
+
const kept = model.value.filter(
|
|
54
|
+
(key) => keys.includes(key) || !group.items.some((item) => item.value === key)
|
|
55
|
+
);
|
|
56
|
+
model.value = [...kept, ...keys.filter((key) => !kept.includes(key))];
|
|
57
|
+
}
|
|
42
58
|
function isOptionValue(value) {
|
|
43
59
|
return isString(value) || isNumber(value) || isBoolean(value);
|
|
44
60
|
}
|
|
@@ -46,32 +62,42 @@ function isOptionValue(value) {
|
|
|
46
62
|
|
|
47
63
|
<template>
|
|
48
64
|
<FormFieldShell :field="field" :path="path">
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
<div :class="cards.groupsClass.value">
|
|
66
|
+
<UCheckboxGroup
|
|
67
|
+
v-for="(group, index) in groups"
|
|
68
|
+
:key="group.key"
|
|
69
|
+
:model-value="selectionOf(group)"
|
|
70
|
+
v-bind="choiceCardGroupAttrs(controlProps, index)"
|
|
71
|
+
:items="group.items"
|
|
72
|
+
value-key="value"
|
|
73
|
+
label-key="label"
|
|
74
|
+
description-key="description"
|
|
75
|
+
variant="card"
|
|
76
|
+
:legend="group.heading"
|
|
77
|
+
:orientation="cards.orientation.value"
|
|
78
|
+
:indicator="cards.indicator.value"
|
|
79
|
+
:ui="cards.ui.value"
|
|
80
|
+
:style="cards.style.value"
|
|
81
|
+
:disabled="disabled"
|
|
82
|
+
@update:model-value="update(group, $event)"
|
|
83
|
+
@blur="handleBlur"
|
|
84
|
+
>
|
|
85
|
+
<template #label="{ item }">
|
|
86
|
+
<ChoiceCardLabel
|
|
87
|
+
:label="optionByKey.get(item.value)?.label ?? item.label"
|
|
88
|
+
:description="
|
|
89
|
+
cards.compact.value ? optionByKey.get(item.value)?.description : void 0
|
|
90
|
+
"
|
|
91
|
+
:icon="optionByKey.get(item.value)?.icon"
|
|
92
|
+
:value="optionByKey.get(item.value)?.value"
|
|
93
|
+
:tile="cards.tile.value"
|
|
94
|
+
:compact="cards.compact.value"
|
|
95
|
+
:mark="cards.mark.value"
|
|
96
|
+
:selected="model.includes(item.value)"
|
|
97
|
+
:ui="cards.ui.value"
|
|
98
|
+
/>
|
|
99
|
+
</template>
|
|
100
|
+
</UCheckboxGroup>
|
|
101
|
+
</div>
|
|
76
102
|
</FormFieldShell>
|
|
77
103
|
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineFormFieldKind } from "../../utils/field-kind.js";
|
|
2
2
|
export const checkboxCardFieldKind = defineFormFieldKind({
|
|
3
3
|
layout: { item: true },
|
|
4
|
-
options: { enabled: true },
|
|
4
|
+
options: { enabled: true, groups: true },
|
|
5
5
|
state: "stateful",
|
|
6
6
|
transform: true,
|
|
7
7
|
type: "checkbox-card",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { FormStatefulFieldBase } from '../../types/field-base.js';
|
|
2
2
|
import type { FieldOptionValue, NullableValue } from '../../types/field-output-utils.js';
|
|
3
|
-
import type { FormOptionConfig,
|
|
4
|
-
import type { FormChoiceCardProps } from '../choice-card/types.js';
|
|
5
|
-
export interface FormCheckboxCardField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends
|
|
3
|
+
import type { FormOptionConfig, FormOptionValue, FormOptionsSource } from '../../types/options.js';
|
|
4
|
+
import type { FormChoiceCardGrouping, FormChoiceCardOptionItem, FormChoiceCardProps } from '../choice-card/types.js';
|
|
5
|
+
export interface FormCheckboxCardField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormChoiceCardOptionItem<TValue> = FormChoiceCardOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-card', readonly TValue[] | null, TContext, TDeps, FormChoiceCardProps>, FormChoiceCardGrouping {
|
|
6
6
|
options: FormOptionConfig<TOption, TContext, TDeps, readonly TValue[] | null> | FormOptionsSource<TOption, TContext, TDeps, readonly TValue[] | null>;
|
|
7
7
|
}
|
|
8
8
|
export type CheckboxCardFieldOutput<TField> = readonly FieldOptionValue<TField>[] | NullableValue;
|
|
@@ -7,12 +7,19 @@ import type { ChoiceCardMark } from './types.js';
|
|
|
7
7
|
* semantics. Styled through `ui.tile`, `ui.tileIcon`, `ui.optionIcon`, `ui.check`,
|
|
8
8
|
* `ui.checkIcon`, `ui.switch`, and `ui.switchThumb`; the tile and the switch carry the option
|
|
9
9
|
* value as `data-value` for per-option styling.
|
|
10
|
+
*
|
|
11
|
+
* A `compact` card lays itself out as one row inside the grid its label becomes: the icon (or a
|
|
12
|
+
* smaller tile) beside the title, over a one-line description. Both lines truncate and keep the
|
|
13
|
+
* full text in their tooltip, and the marks center on the row. The card passes the description
|
|
14
|
+
* here because the Nuxt UI group would draw it below the icon instead.
|
|
10
15
|
*/
|
|
11
16
|
type __VLS_Props = {
|
|
12
17
|
label?: string;
|
|
18
|
+
description?: string;
|
|
13
19
|
icon?: string;
|
|
14
20
|
value?: string | number | boolean;
|
|
15
21
|
tile: boolean;
|
|
22
|
+
compact?: boolean;
|
|
16
23
|
mark: ChoiceCardMark;
|
|
17
24
|
selected: boolean;
|
|
18
25
|
ui?: FormControlUi;
|
|
@@ -3,13 +3,17 @@ import UIcon from "@nuxt/ui/components/Icon.vue";
|
|
|
3
3
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
4
4
|
defineProps({
|
|
5
5
|
label: { type: String, required: false },
|
|
6
|
+
description: { type: String, required: false },
|
|
6
7
|
icon: { type: String, required: false },
|
|
7
8
|
value: { type: [String, Number, Boolean], required: false },
|
|
8
9
|
tile: { type: Boolean, required: true },
|
|
10
|
+
compact: { type: Boolean, required: false },
|
|
9
11
|
mark: { type: String, required: true },
|
|
10
12
|
selected: { type: Boolean, required: true },
|
|
11
13
|
ui: { type: Object, required: false }
|
|
12
14
|
});
|
|
15
|
+
const COMPACT_MARK = "end-2.5 top-1/2 -translate-y-1/2";
|
|
16
|
+
const COMPACT_TILE = "col-start-1 row-span-2 me-2.5 size-7 rounded-md";
|
|
13
17
|
</script>
|
|
14
18
|
|
|
15
19
|
<template>
|
|
@@ -20,6 +24,7 @@ defineProps({
|
|
|
20
24
|
:class="
|
|
21
25
|
mergeFormUiClass(
|
|
22
26
|
'grid size-8 place-items-center rounded-lg bg-elevated text-muted transition-colors data-[selected]:bg-primary/10 data-[selected]:text-primary',
|
|
27
|
+
compact ? COMPACT_TILE : void 0,
|
|
23
28
|
ui?.tile
|
|
24
29
|
)
|
|
25
30
|
"
|
|
@@ -27,7 +32,26 @@ defineProps({
|
|
|
27
32
|
>
|
|
28
33
|
<UIcon :name="icon" :class="mergeFormUiClass('size-4', ui?.tileIcon)" aria-hidden="true" />
|
|
29
34
|
</span>
|
|
30
|
-
<
|
|
35
|
+
<template v-if="compact">
|
|
36
|
+
<UIcon
|
|
37
|
+
v-if="icon && !tile"
|
|
38
|
+
:name="icon"
|
|
39
|
+
:class="
|
|
40
|
+
mergeFormUiClass('col-start-1 row-span-2 me-2.5 size-4 shrink-0 text-muted', ui?.optionIcon)
|
|
41
|
+
"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
/>
|
|
44
|
+
<span :title="label" class="col-start-2 block truncate" data-choice-title>{{ label }}</span>
|
|
45
|
+
<span
|
|
46
|
+
v-if="description"
|
|
47
|
+
:title="description"
|
|
48
|
+
class="col-start-2 block truncate text-xs font-normal text-muted"
|
|
49
|
+
data-choice-description
|
|
50
|
+
>
|
|
51
|
+
{{ description }}
|
|
52
|
+
</span>
|
|
53
|
+
</template>
|
|
54
|
+
<span v-else-if="!tile && icon" class="inline-flex items-center gap-2">
|
|
31
55
|
<UIcon
|
|
32
56
|
:name="icon"
|
|
33
57
|
:class="mergeFormUiClass('size-4 shrink-0', ui?.optionIcon)"
|
|
@@ -42,6 +66,7 @@ defineProps({
|
|
|
42
66
|
:class="
|
|
43
67
|
mergeFormUiClass(
|
|
44
68
|
'absolute end-3 top-3 grid size-4 place-items-center rounded-[4px] bg-primary text-inverted',
|
|
69
|
+
compact ? COMPACT_MARK : void 0,
|
|
45
70
|
ui?.check
|
|
46
71
|
)
|
|
47
72
|
"
|
|
@@ -57,6 +82,7 @@ defineProps({
|
|
|
57
82
|
:class="
|
|
58
83
|
mergeFormUiClass(
|
|
59
84
|
'group/switch absolute end-3 top-3.5 inline-flex h-[18px] w-8 shrink-0 items-center rounded-full bg-accented p-0.5 transition-colors duration-150 data-[state=checked]:bg-primary',
|
|
85
|
+
compact ? COMPACT_MARK : void 0,
|
|
60
86
|
ui?.switch
|
|
61
87
|
)
|
|
62
88
|
"
|
|
@@ -7,12 +7,19 @@ import type { ChoiceCardMark } from './types.js';
|
|
|
7
7
|
* semantics. Styled through `ui.tile`, `ui.tileIcon`, `ui.optionIcon`, `ui.check`,
|
|
8
8
|
* `ui.checkIcon`, `ui.switch`, and `ui.switchThumb`; the tile and the switch carry the option
|
|
9
9
|
* value as `data-value` for per-option styling.
|
|
10
|
+
*
|
|
11
|
+
* A `compact` card lays itself out as one row inside the grid its label becomes: the icon (or a
|
|
12
|
+
* smaller tile) beside the title, over a one-line description. Both lines truncate and keep the
|
|
13
|
+
* full text in their tooltip, and the marks center on the row. The card passes the description
|
|
14
|
+
* here because the Nuxt UI group would draw it below the icon instead.
|
|
10
15
|
*/
|
|
11
16
|
type __VLS_Props = {
|
|
12
17
|
label?: string;
|
|
18
|
+
description?: string;
|
|
13
19
|
icon?: string;
|
|
14
20
|
value?: string | number | boolean;
|
|
15
21
|
tile: boolean;
|
|
22
|
+
compact?: boolean;
|
|
16
23
|
mark: ChoiceCardMark;
|
|
17
24
|
selected: boolean;
|
|
18
25
|
ui?: FormControlUi;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { FormOption, FormOptionValue } from '../../types/options.js';
|
|
2
|
+
import type { FormText } from '../../types/utils.js';
|
|
3
|
+
/** Size of the cards: `compact` packs long option lists into smaller, single-row cards. */
|
|
4
|
+
export type FormChoiceCardDensity = 'default' | 'compact';
|
|
1
5
|
/**
|
|
2
6
|
* Presentation of the radio and checkbox cards.
|
|
3
7
|
*
|
|
@@ -6,6 +10,9 @@
|
|
|
6
10
|
* // A fixed grid of cards, each with its icon in a tile above the label and a check in the
|
|
7
11
|
* // corner once selected.
|
|
8
12
|
* props: { columns: '1 md:2 lg:3', icon: 'tile', indicator: 'corner' }
|
|
13
|
+
*
|
|
14
|
+
* // Eight options in four groups, as smaller cards with the icon beside a one-line label.
|
|
15
|
+
* props: { columns: '1 md:2', density: 'compact', icon: 'tile', indicator: 'corner' }
|
|
9
16
|
* ```
|
|
10
17
|
*/
|
|
11
18
|
export interface FormChoiceCardProps {
|
|
@@ -25,12 +32,58 @@ export interface FormChoiceCardProps {
|
|
|
25
32
|
indicator?: 'start' | 'end' | 'hidden' | 'corner' | 'switch';
|
|
26
33
|
/**
|
|
27
34
|
* Where an option's `icon` shows: before its label (`inline`, the default), or in a tile above
|
|
28
|
-
* it (`tile`) that takes the selection color. Style the parts the engine renders with the
|
|
35
|
+
* it (`tile`) that takes the selection color, beside the label in the `compact` density. Style the parts the engine renders with the
|
|
29
36
|
* `ui` slots `tile`, `tileIcon`, `optionIcon`, `check`, `checkIcon`, `switch`, and
|
|
30
37
|
* `switchThumb`, next to the Nuxt UI slots of the group. The tile and the switch carry the
|
|
31
38
|
* option value as `data-value`, so one option can take its own colors.
|
|
32
39
|
*/
|
|
33
40
|
icon?: 'inline' | 'tile';
|
|
41
|
+
/**
|
|
42
|
+
* Size of the cards. `compact` shrinks the padding and tile, puts the icon beside the label
|
|
43
|
+
* instead of above it, and keeps the title and the description on one line each, truncated with
|
|
44
|
+
* an ellipsis (the full description stays in the card's tooltip). Defaults to `default`.
|
|
45
|
+
*/
|
|
46
|
+
density?: FormChoiceCardDensity;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Option grouping of the radio and checkbox cards, declared on the field next to its `options`.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* // Rows from the API, each with a `section` to list the cards under.
|
|
54
|
+
* { type: 'radio-card', options: () => queryOptions({ queryKey: ['plans'], queryFn: fetchPlans }), groupKey: 'section' }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export interface FormChoiceCardGrouping {
|
|
58
|
+
/**
|
|
59
|
+
* Property of the options that holds the heading they are listed under. Options written inline
|
|
60
|
+
* in the schema group by their `group` without it. The records a function, a promise or a query
|
|
61
|
+
* returns group only when this names the property, so records that already carry a `group` for
|
|
62
|
+
* another purpose are never regrouped by surprise. `false` lists every option together, even
|
|
63
|
+
* inline options that carry a `group`.
|
|
64
|
+
*/
|
|
65
|
+
groupKey?: string | false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Option of a radio or checkbox card: the shared option shape plus the icon and the heading the
|
|
69
|
+
* cards can show.
|
|
70
|
+
*/
|
|
71
|
+
export interface FormChoiceCardOption<TValue extends FormOptionValue = FormOptionValue> extends FormOption<TValue> {
|
|
72
|
+
/** Icon shown with the label, inline or in a tile (see `props.icon`). */
|
|
73
|
+
icon?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Heading this option is listed under. Options sharing a heading are listed together, and the
|
|
76
|
+
* headings follow the order in which they first appear in the options. Options without one
|
|
77
|
+
* stay together, without a heading. Read from this property for options written inline in the
|
|
78
|
+
* schema; options a function, a promise or a query returns need `groupKey`.
|
|
79
|
+
*/
|
|
80
|
+
group?: FormText;
|
|
34
81
|
}
|
|
82
|
+
/** Authored option item accepted by radio and checkbox cards. */
|
|
83
|
+
export type FormChoiceCardOptionItem<TValue extends FormOptionValue = FormOptionValue> = TValue | FormChoiceCardOption<TValue>;
|
|
84
|
+
/** The card layouts: icon before the label, icon tile above it, or one compact row. */
|
|
85
|
+
export type ChoiceCardLayout = 'inline' | 'stacked' | 'compact';
|
|
35
86
|
/** The mark a card draws itself, in place of the Nuxt UI indicator. */
|
|
36
87
|
export type ChoiceCardMark = 'corner' | 'switch' | 'none';
|
|
88
|
+
/** The Nuxt UI group a card field wraps. */
|
|
89
|
+
export type ChoiceCardKind = 'checkbox' | 'radio';
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { FormControlUi } from '../../types/index.js';
|
|
2
|
-
import type { ChoiceCardMark, FormChoiceCardProps } from './types.js';
|
|
2
|
+
import type { ChoiceCardKind, ChoiceCardMark, FormChoiceCardProps } from './types.js';
|
|
3
3
|
/** Props the card fields render themselves, kept away from the Nuxt UI group. */
|
|
4
4
|
export declare const CHOICE_CARD_PROPS: string[];
|
|
5
5
|
/**
|
|
6
6
|
* Presentation of a radio or checkbox card group: the flowing row or fixed grid of cards, the
|
|
7
|
-
* selection mark,
|
|
8
|
-
* group, and what the card content renders
|
|
7
|
+
* selection mark, where option icons go, the density, and the headings of grouped options.
|
|
8
|
+
* Returns the props and `ui` slots for the Nuxt UI group, and what the card content renders
|
|
9
|
+
* (`compact`, `tile`, `mark`).
|
|
9
10
|
*/
|
|
10
11
|
export declare function useChoiceCard(params: {
|
|
12
|
+
kind: ChoiceCardKind;
|
|
11
13
|
props: () => FormChoiceCardProps;
|
|
12
14
|
ui: () => FormControlUi | undefined;
|
|
13
15
|
}): {
|
|
16
|
+
compact: import("vue").ComputedRef<boolean>;
|
|
17
|
+
groupsClass: import("vue").ComputedRef<string>;
|
|
14
18
|
indicator: import("vue").ComputedRef<"end" | "start" | "hidden" | undefined>;
|
|
15
19
|
mark: import("vue").ComputedRef<ChoiceCardMark>;
|
|
16
20
|
orientation: import("vue").ComputedRef<"horizontal" | "vertical" | undefined>;
|
|
@@ -2,7 +2,25 @@ import { computed } from "vue";
|
|
|
2
2
|
import { useResponsiveValue } from "../../../shared/composables/use-responsive-value.js";
|
|
3
3
|
import { mergeFormUiClass } from "../../utils/ui.js";
|
|
4
4
|
import { CARD_SELECTED_RING } from "../card-selection.js";
|
|
5
|
-
export const CHOICE_CARD_PROPS = ["columns", "icon", "indicator", "orientation"];
|
|
5
|
+
export const CHOICE_CARD_PROPS = ["columns", "density", "icon", "indicator", "orientation"];
|
|
6
|
+
const LAYOUT_UI = {
|
|
7
|
+
compact: {
|
|
8
|
+
gap: "gap-2",
|
|
9
|
+
item: "items-center p-2.5",
|
|
10
|
+
label: "grid min-w-0 grid-cols-[auto_minmax(0,1fr)] items-center",
|
|
11
|
+
wrapper: "min-w-0 items-stretch"
|
|
12
|
+
},
|
|
13
|
+
inline: { gap: "gap-2.5" },
|
|
14
|
+
stacked: {
|
|
15
|
+
gap: "gap-2.5",
|
|
16
|
+
label: "flex flex-col items-start gap-2.5 font-semibold",
|
|
17
|
+
wrapper: "flex flex-col items-start gap-1.5"
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const ICON_UI = {
|
|
21
|
+
checkbox: void 0,
|
|
22
|
+
radio: "hidden"
|
|
23
|
+
};
|
|
6
24
|
export function useChoiceCard(params) {
|
|
7
25
|
const columnsValue = useResponsiveValue(() => String(params.props().columns ?? ""));
|
|
8
26
|
const columns = computed(() => {
|
|
@@ -13,7 +31,12 @@ export function useChoiceCard(params) {
|
|
|
13
31
|
const value = params.props().indicator;
|
|
14
32
|
return value === "corner" || value === "switch" ? value : "none";
|
|
15
33
|
});
|
|
34
|
+
const compact = computed(() => params.props().density === "compact");
|
|
16
35
|
const tile = computed(() => params.props().icon === "tile");
|
|
36
|
+
const layout = computed(() => {
|
|
37
|
+
if (compact.value) return "compact";
|
|
38
|
+
return tile.value ? "stacked" : "inline";
|
|
39
|
+
});
|
|
17
40
|
const indicator = computed(() => {
|
|
18
41
|
const value = params.props().indicator;
|
|
19
42
|
return value === "corner" || value === "switch" ? "hidden" : value;
|
|
@@ -24,36 +47,35 @@ export function useChoiceCard(params) {
|
|
|
24
47
|
);
|
|
25
48
|
const ui = computed(() => {
|
|
26
49
|
const current = params.ui();
|
|
50
|
+
const layoutUi = LAYOUT_UI[layout.value];
|
|
51
|
+
const stacked = layout.value === "stacked";
|
|
27
52
|
return {
|
|
28
53
|
...current,
|
|
29
54
|
fieldset: mergeFormUiClass(
|
|
30
|
-
columns.value ?
|
|
55
|
+
columns.value ? `grid grid-cols-[repeat(var(--nut-choice-columns),minmax(0,1fr))] ${layoutUi.gap}` : `${layoutUi.gap}${orientation.value === "horizontal" ? " flex-wrap" : ""}`,
|
|
31
56
|
current?.fieldset
|
|
32
57
|
),
|
|
33
58
|
item: mergeFormUiClass(
|
|
34
|
-
`border-default ${CARD_SELECTED_RING}`,
|
|
59
|
+
`border-default ${CARD_SELECTED_RING} ${layoutUi.item ?? ""}`,
|
|
35
60
|
mark.value === "none" ? void 0 : "relative",
|
|
36
61
|
current?.item
|
|
37
62
|
),
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
label: mergeFormUiClass(
|
|
43
|
-
tile.value ? "flex flex-col items-start gap-2.5 font-semibold" : void 0,
|
|
44
|
-
current?.label
|
|
45
|
-
),
|
|
63
|
+
icon: mergeFormUiClass(ICON_UI[params.kind], current?.icon),
|
|
64
|
+
// The heading of a group of options, set above its cards.
|
|
65
|
+
legend: mergeFormUiClass("mb-2 text-sm font-medium text-muted", current?.legend),
|
|
66
|
+
label: mergeFormUiClass(layoutUi.label, current?.label),
|
|
46
67
|
// A hidden indicator centers the text: keep it aligned, and clear of an inline corner check.
|
|
47
68
|
wrapper: mergeFormUiClass(
|
|
48
|
-
|
|
69
|
+
layoutUi.wrapper,
|
|
49
70
|
[
|
|
50
71
|
indicator.value === "hidden" ? "ms-0 text-start" : "",
|
|
51
|
-
mark.value === "corner" && !
|
|
52
|
-
mark.value === "switch" && !
|
|
72
|
+
mark.value === "corner" && !stacked ? "pe-7" : "",
|
|
73
|
+
mark.value === "switch" && !stacked ? "pe-12" : ""
|
|
53
74
|
].join(" "),
|
|
54
75
|
current?.wrapper
|
|
55
76
|
)
|
|
56
77
|
};
|
|
57
78
|
});
|
|
58
|
-
|
|
79
|
+
const groupsClass = computed(() => mergeFormUiClass("flex flex-col gap-5", params.ui()?.groups));
|
|
80
|
+
return { compact, groupsClass, indicator, mark, orientation, style, tile, ui };
|
|
59
81
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FormObject } from '../../types/index.js';
|
|
2
|
+
export interface ChoiceCardGroup<TItem> {
|
|
3
|
+
/** Stable key of the group: its heading, or an empty string for the options without one. */
|
|
4
|
+
key: string;
|
|
5
|
+
/** Heading shown above the cards of the group; none for options that declare no group. */
|
|
6
|
+
heading?: string;
|
|
7
|
+
/** The group's options, as the mutable list the Nuxt UI groups take. */
|
|
8
|
+
items: TItem[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Lists options under their group headings. Headings follow the order in which they first appear
|
|
12
|
+
* in `items`, and each keeps its options in their authored order. The options without a group form
|
|
13
|
+
* one more group with no heading, placed where its first option appears. Without any group the
|
|
14
|
+
* result is a single headingless group, so an ungrouped list renders as it always did.
|
|
15
|
+
*/
|
|
16
|
+
export declare function groupChoiceCardItems<TItem extends {
|
|
17
|
+
group?: string;
|
|
18
|
+
}>(items: readonly TItem[]): readonly ChoiceCardGroup<TItem>[];
|
|
19
|
+
/**
|
|
20
|
+
* Attributes for the Nuxt UI group at `index` among the groups of one field. Every group carries
|
|
21
|
+
* the control attributes of the field, but an `id` names a single element: the first group keeps
|
|
22
|
+
* it, as the control the label points to, and each later group takes `<id>-group-<index>`. The other
|
|
23
|
+
* attributes (`data-form-field`, `aria-label`) stay on every group, so focus moving from one
|
|
24
|
+
* group to the next does not leave the field. Attributes without an `id` are returned as they
|
|
25
|
+
* are: each Nuxt UI group then generates its own.
|
|
26
|
+
*/
|
|
27
|
+
export declare function choiceCardGroupAttrs<TAttrs extends FormObject>(attrs: TAttrs, index: number): TAttrs;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isString } from "../../utils/predicate.js";
|
|
2
|
+
export function groupChoiceCardItems(items) {
|
|
3
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const item of items) {
|
|
5
|
+
const key = item.group?.trim() ?? "";
|
|
6
|
+
const members = groups.get(key);
|
|
7
|
+
if (members) members.push(item);
|
|
8
|
+
else groups.set(key, [item]);
|
|
9
|
+
}
|
|
10
|
+
if (groups.size === 0) return [{ items: [], key: "" }];
|
|
11
|
+
return [...groups].map(([key, members]) => ({ heading: key || void 0, items: members, key }));
|
|
12
|
+
}
|
|
13
|
+
export function choiceCardGroupAttrs(attrs, index) {
|
|
14
|
+
const id = attrs.id;
|
|
15
|
+
if (index === 0 || !isString(id)) return attrs;
|
|
16
|
+
return { ...attrs, id: `${id}-group-${index}` };
|
|
17
|
+
}
|
|
@@ -133,15 +133,16 @@ const selectedTreeItems = computed(() => {
|
|
|
133
133
|
return Array.isArray(selected) ? selected : [selected];
|
|
134
134
|
});
|
|
135
135
|
const treeSelectLabel = computed(() => {
|
|
136
|
+
const empty = placeholder.value ?? "";
|
|
136
137
|
if (!selectedTreeItems.value.length) {
|
|
137
|
-
return
|
|
138
|
+
return empty;
|
|
138
139
|
}
|
|
139
140
|
if (treeMultiple.value) {
|
|
140
141
|
return selectedTreeItems.value.map((item) => item.pathLabel).join(", ");
|
|
141
142
|
}
|
|
142
143
|
const [selected] = selectedTreeItems.value;
|
|
143
144
|
if (!selected) {
|
|
144
|
-
return
|
|
145
|
+
return empty;
|
|
145
146
|
}
|
|
146
147
|
return props.field.type === "tree-select" && fieldProps.value.showPath ? selected.pathLabel : String(selected.label);
|
|
147
148
|
});
|
|
@@ -545,6 +545,7 @@ export declare const formFieldKinds: readonly [{
|
|
|
545
545
|
};
|
|
546
546
|
readonly options: {
|
|
547
547
|
readonly enabled: true;
|
|
548
|
+
readonly groups: true;
|
|
548
549
|
};
|
|
549
550
|
readonly state: "stateful";
|
|
550
551
|
readonly transform: true;
|
|
@@ -561,6 +562,7 @@ export declare const formFieldKinds: readonly [{
|
|
|
561
562
|
};
|
|
562
563
|
readonly options: {
|
|
563
564
|
readonly enabled: true;
|
|
565
|
+
readonly groups: true;
|
|
564
566
|
};
|
|
565
567
|
readonly state: "stateful";
|
|
566
568
|
readonly transform: true;
|