nuxt-ui-tools 1.13.0 → 1.14.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/fields/matrix/component.vue +79 -4
- package/dist/runtime/form/fields/matrix/types.d.ts +15 -3
- package/dist/runtime/form/fields/matrix/utils.d.ts +10 -0
- package/dist/runtime/form/fields/matrix/utils.js +19 -0
- package/dist/runtime/form/fields/password/component.vue +4 -0
- package/dist/runtime/form/types/field.d.ts +1 -1
- package/dist/runtime/form/types/output.d.ts +1 -3
- package/dist/runtime/form/types/ui.d.ts +6 -0
- package/dist/runtime/form/utils/state.js +1 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -7,6 +7,7 @@ import { useFormUi } from "../../composables/use-form-ui";
|
|
|
7
7
|
import { isNumber } from "../../utils/predicate";
|
|
8
8
|
import { resolveFormText } from "../../utils/text";
|
|
9
9
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
10
|
+
import { groupMatrixEntries } from "./utils";
|
|
10
11
|
const props = defineProps({
|
|
11
12
|
field: { type: Object, required: true },
|
|
12
13
|
path: { type: Array, required: true }
|
|
@@ -20,6 +21,7 @@ const matrixInstanceId = useId();
|
|
|
20
21
|
const visibleFields = computed(
|
|
21
22
|
() => props.field.fields.filter((field) => field.type !== "hidden" && field.ignore !== true)
|
|
22
23
|
);
|
|
24
|
+
const groups = computed(() => groupMatrixEntries(props.field.rows));
|
|
23
25
|
const matrixId = computed(() => `${matrixInstanceId}-${props.path.join("-")}`);
|
|
24
26
|
const minWidth = computed(
|
|
25
27
|
() => isNumber(fieldProps.value.minWidth) ? `${fieldProps.value.minWidth}px` : fieldProps.value.minWidth ?? "40rem"
|
|
@@ -31,6 +33,7 @@ const bordered = computed(() => fieldProps.value.bordered !== false);
|
|
|
31
33
|
const hoverable = computed(() => fieldProps.value.hoverable !== false);
|
|
32
34
|
const rowPadding = computed(() => fieldProps.value.compact ? "px-3 py-2" : "px-4 py-3");
|
|
33
35
|
const cellPadding = computed(() => fieldProps.value.compact ? "px-2 py-1.5" : "px-3 py-2.5");
|
|
36
|
+
const sectionPadding = computed(() => fieldProps.value.compact ? "px-3 py-1.5" : "px-4 py-2");
|
|
34
37
|
function rowPath(row) {
|
|
35
38
|
return [...props.path, row.key];
|
|
36
39
|
}
|
|
@@ -40,9 +43,19 @@ function fieldLabel(field) {
|
|
|
40
43
|
function rowLabelId(row) {
|
|
41
44
|
return `${matrixId.value}-row-${row.key}`;
|
|
42
45
|
}
|
|
46
|
+
function sectionId(index) {
|
|
47
|
+
return `${matrixId.value}-section-${index}`;
|
|
48
|
+
}
|
|
43
49
|
function columnLabelId(field) {
|
|
44
50
|
return `${matrixId.value}-column-${field.key}`;
|
|
45
51
|
}
|
|
52
|
+
function cellHeaders(options) {
|
|
53
|
+
return [
|
|
54
|
+
options.section === null ? null : sectionId(options.section),
|
|
55
|
+
rowLabelId(options.row),
|
|
56
|
+
columnLabelId(options.column)
|
|
57
|
+
].filter(Boolean).join(" ");
|
|
58
|
+
}
|
|
46
59
|
function controlClass(field) {
|
|
47
60
|
const compact = field.type === "checkbox" || field.type === "switch" || field.type === "rating" || field.type === "color-picker";
|
|
48
61
|
return mergeFormUiClass(
|
|
@@ -105,9 +118,58 @@ function controlClass(field) {
|
|
|
105
118
|
</th>
|
|
106
119
|
</tr>
|
|
107
120
|
</thead>
|
|
108
|
-
<tbody
|
|
121
|
+
<tbody
|
|
122
|
+
v-for="(group, groupIndex) in groups"
|
|
123
|
+
:key="group.section ? `section-${groupIndex}` : `rows-${groupIndex}`"
|
|
124
|
+
:class="
|
|
125
|
+
mergeFormUiClass(
|
|
126
|
+
'[&:not(:last-child)>tr:last-child>*]:border-b [&:not(:last-child)>tr:last-child>*]:border-default',
|
|
127
|
+
formUi.ui.value.matrix?.ui?.body
|
|
128
|
+
)
|
|
129
|
+
"
|
|
130
|
+
data-matrix-group
|
|
131
|
+
>
|
|
109
132
|
<tr
|
|
110
|
-
v-
|
|
133
|
+
v-if="group.section"
|
|
134
|
+
:class="mergeFormUiClass('bg-elevated/50', formUi.ui.value.matrix?.ui?.section)"
|
|
135
|
+
data-matrix-section
|
|
136
|
+
>
|
|
137
|
+
<th
|
|
138
|
+
:id="sectionId(groupIndex)"
|
|
139
|
+
scope="rowgroup"
|
|
140
|
+
:colspan="visibleFields.length + 1"
|
|
141
|
+
:class="
|
|
142
|
+
mergeFormUiClass(
|
|
143
|
+
[sectionPadding, 'border-b border-default text-left font-normal'].join(' '),
|
|
144
|
+
formUi.ui.value.matrix?.ui?.sectionHeader
|
|
145
|
+
)
|
|
146
|
+
"
|
|
147
|
+
>
|
|
148
|
+
<span
|
|
149
|
+
:class="
|
|
150
|
+
mergeFormUiClass(
|
|
151
|
+
'block text-xs font-semibold text-highlighted',
|
|
152
|
+
formUi.ui.value.matrix?.ui?.sectionLabel
|
|
153
|
+
)
|
|
154
|
+
"
|
|
155
|
+
>
|
|
156
|
+
{{ resolveFormText(group.section.label) }}
|
|
157
|
+
</span>
|
|
158
|
+
<span
|
|
159
|
+
v-if="group.section.description"
|
|
160
|
+
:class="
|
|
161
|
+
mergeFormUiClass(
|
|
162
|
+
'mt-0.5 block text-xs text-muted',
|
|
163
|
+
formUi.ui.value.matrix?.ui?.sectionDescription
|
|
164
|
+
)
|
|
165
|
+
"
|
|
166
|
+
>
|
|
167
|
+
{{ resolveFormText(group.section.description) }}
|
|
168
|
+
</span>
|
|
169
|
+
</th>
|
|
170
|
+
</tr>
|
|
171
|
+
<tr
|
|
172
|
+
v-for="row in group.rows"
|
|
111
173
|
:key="row.key"
|
|
112
174
|
:class="
|
|
113
175
|
mergeFormUiClass(
|
|
@@ -136,12 +198,25 @@ function controlClass(field) {
|
|
|
136
198
|
"
|
|
137
199
|
:style="{ width: rowHeaderWidth, minWidth: rowHeaderWidth }"
|
|
138
200
|
>
|
|
139
|
-
|
|
201
|
+
<span :class="mergeFormUiClass('block', formUi.ui.value.matrix?.ui?.rowLabel)">
|
|
202
|
+
{{ resolveFormText(row.label) ?? row.key }}
|
|
203
|
+
</span>
|
|
204
|
+
<span
|
|
205
|
+
v-if="row.description"
|
|
206
|
+
:class="
|
|
207
|
+
mergeFormUiClass(
|
|
208
|
+
'mt-0.5 block text-xs font-normal text-muted',
|
|
209
|
+
formUi.ui.value.matrix?.ui?.rowDescription
|
|
210
|
+
)
|
|
211
|
+
"
|
|
212
|
+
>
|
|
213
|
+
{{ resolveFormText(row.description) }}
|
|
214
|
+
</span>
|
|
140
215
|
</th>
|
|
141
216
|
<td
|
|
142
217
|
v-for="column in visibleFields"
|
|
143
218
|
:key="column.key"
|
|
144
|
-
:headers="
|
|
219
|
+
:headers="cellHeaders({ row, column, section: group.section ? groupIndex : null })"
|
|
145
220
|
:class="
|
|
146
221
|
mergeFormUiClass(
|
|
147
222
|
[
|
|
@@ -4,7 +4,16 @@ import type { FormText } from '../../types/utils.js';
|
|
|
4
4
|
export interface FormMatrixRow<TKey extends string = string> {
|
|
5
5
|
key: TKey;
|
|
6
6
|
label: FormText;
|
|
7
|
+
/** Supporting copy rendered under the row label. */
|
|
8
|
+
description?: FormText;
|
|
7
9
|
}
|
|
10
|
+
/** Full-width row that opens a group of matrix rows. It holds no value. */
|
|
11
|
+
export interface FormMatrixSection {
|
|
12
|
+
type: 'section';
|
|
13
|
+
label: FormText;
|
|
14
|
+
description?: FormText;
|
|
15
|
+
}
|
|
16
|
+
export type FormMatrixEntry = FormMatrixRow | FormMatrixSection;
|
|
8
17
|
export interface FormMatrixProps {
|
|
9
18
|
minWidth?: number | string;
|
|
10
19
|
rowHeaderWidth?: number | string;
|
|
@@ -14,9 +23,12 @@ export interface FormMatrixProps {
|
|
|
14
23
|
compact?: boolean;
|
|
15
24
|
}
|
|
16
25
|
export interface FormMatrixField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> extends FormStatefulFieldBase<'matrix', object, TContext, TDeps, FormMatrixProps> {
|
|
17
|
-
rows
|
|
26
|
+
/** Value rows, optionally grouped by section entries placed before the rows they head. */
|
|
27
|
+
rows: readonly FormMatrixEntry[];
|
|
18
28
|
fields: readonly FormField<TContext, TDeps>[];
|
|
19
29
|
}
|
|
20
|
-
export type MatrixFieldOutput<TRows extends readonly
|
|
21
|
-
[TRow in TRows[number]
|
|
30
|
+
export type MatrixFieldOutput<TRows extends readonly object[], TChildren> = {
|
|
31
|
+
[TRow in Extract<TRows[number], {
|
|
32
|
+
key: string;
|
|
33
|
+
}> as TRow['key']]: TChildren;
|
|
22
34
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FormMatrixEntry, FormMatrixRow, FormMatrixSection } from './types.js';
|
|
2
|
+
export declare function isMatrixSection(entry: FormMatrixEntry): entry is FormMatrixSection;
|
|
3
|
+
/**
|
|
4
|
+
* Splits matrix entries into table bodies: each section opens a group, and rows placed before
|
|
5
|
+
* the first section form a group without a header.
|
|
6
|
+
*/
|
|
7
|
+
export declare function groupMatrixEntries(entries: readonly FormMatrixEntry[]): {
|
|
8
|
+
section: FormMatrixSection | null;
|
|
9
|
+
rows: FormMatrixRow[];
|
|
10
|
+
}[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function isMatrixSection(entry) {
|
|
2
|
+
return "type" in entry && entry.type === "section";
|
|
3
|
+
}
|
|
4
|
+
export function groupMatrixEntries(entries) {
|
|
5
|
+
const groups = [];
|
|
6
|
+
for (const entry of entries) {
|
|
7
|
+
if (isMatrixSection(entry)) {
|
|
8
|
+
groups.push({ section: entry, rows: [] });
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const current = groups.at(-1);
|
|
12
|
+
if (current) {
|
|
13
|
+
current.rows.push(entry);
|
|
14
|
+
} else {
|
|
15
|
+
groups.push({ section: null, rows: [entry] });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return groups;
|
|
19
|
+
}
|
|
@@ -204,3 +204,7 @@ const model = computed({
|
|
|
204
204
|
</UPopover>
|
|
205
205
|
</FormFieldShell>
|
|
206
206
|
</template>
|
|
207
|
+
|
|
208
|
+
<style>
|
|
209
|
+
[data-reka-popper-content-wrapper]:has([data-form-password-requirements]),[data-reka-popper-content-wrapper]:has([data-form-password-requirements])>*{pointer-events:none}
|
|
210
|
+
</style>
|
|
@@ -69,7 +69,7 @@ export type { FormGroupField } from '../fields/group/types.js';
|
|
|
69
69
|
export type { FormCascaderField, FormHierarchyField, FormHierarchyOption, FormTreeField, FormTreeSelectField, } from '../fields/hierarchy/types.js';
|
|
70
70
|
export type { FormInfoField } from '../fields/info/types.js';
|
|
71
71
|
export type { FormInputGroupField } from '../fields/input-group/types.js';
|
|
72
|
-
export type { FormMatrixField, FormMatrixRow } from '../fields/matrix/types.js';
|
|
72
|
+
export type { FormMatrixEntry, FormMatrixField, FormMatrixRow, FormMatrixSection, } from '../fields/matrix/types.js';
|
|
73
73
|
export type { FormNumberField } from '../fields/number/types.js';
|
|
74
74
|
export type { FormOneTimeCodeField } from '../fields/one-time-code/types.js';
|
|
75
75
|
export type { FormObjectField } from '../fields/object/types.js';
|
|
@@ -55,9 +55,7 @@ type ModeFieldValueObject<TField, TMode extends FormStateMode, TValue> = TMode e
|
|
|
55
55
|
type ObjectFieldValue<TField, TMode extends FormStateMode> = ApplyOutputMode<TField, TMode, ObjectFieldOutput<FieldsValue<ChildFields<TField>, TMode>>>;
|
|
56
56
|
type MatrixRows<TField> = TField extends {
|
|
57
57
|
readonly rows: infer TRows;
|
|
58
|
-
} ? TRows extends readonly
|
|
59
|
-
key: string;
|
|
60
|
-
}[] ? TRows : readonly [] : readonly [];
|
|
58
|
+
} ? TRows extends readonly object[] ? TRows : readonly [] : readonly [];
|
|
61
59
|
type MatrixFieldValue<TField, TMode extends FormStateMode> = ApplyOutputMode<TField, TMode, MatrixFieldOutput<MatrixRows<TField>, FieldsValue<ChildFields<TField>, TMode>>>;
|
|
62
60
|
type StatefulFieldObject<TField, TMode extends FormStateMode> = TMode extends 'input' ? OptionalFieldValueObject<TField, ApplyOutputMode<TField, TMode, ResolveFormFieldValue<TField>>> : TMode extends 'output' ? TField extends {
|
|
63
61
|
submit: {
|
|
@@ -106,6 +106,12 @@ export interface FormMatrixUi {
|
|
|
106
106
|
body?: FormUiClass;
|
|
107
107
|
row?: FormUiClass;
|
|
108
108
|
rowHeader?: FormUiClass;
|
|
109
|
+
rowLabel?: FormUiClass;
|
|
110
|
+
rowDescription?: FormUiClass;
|
|
111
|
+
section?: FormUiClass;
|
|
112
|
+
sectionHeader?: FormUiClass;
|
|
113
|
+
sectionLabel?: FormUiClass;
|
|
114
|
+
sectionDescription?: FormUiClass;
|
|
109
115
|
cell?: FormUiClass;
|
|
110
116
|
control?: FormUiClass;
|
|
111
117
|
}
|
|
@@ -434,7 +434,7 @@ export function getMatrixRows(field) {
|
|
|
434
434
|
return [];
|
|
435
435
|
}
|
|
436
436
|
return rows.flatMap((row) => {
|
|
437
|
-
if (!isRecord(row) || !isString(row.key)) {
|
|
437
|
+
if (!isRecord(row) || row.type === "section" || !isString(row.key)) {
|
|
438
438
|
return [];
|
|
439
439
|
}
|
|
440
440
|
return row.key;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Nuxt module and runtime toolkit for UI-heavy Nuxt applications",
|
|
5
5
|
"homepage": "https://github.com/ChronicStone/nuxt-ui-tools#readme",
|
|
6
6
|
"bugs": "https://github.com/ChronicStone/nuxt-ui-tools/issues",
|