@omnitend/dashboard-for-laravel 0.47.0 → 0.47.2
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/components/extended/DXField.vue.d.ts +17 -3
- package/dist/components/extended/DXForm.vue.d.ts +5 -1
- package/dist/components/extended/DXFormErrorSummary.vue.d.ts +10 -0
- package/dist/composables/useForm.d.ts +30 -0
- package/dist/dashboard-for-laravel.js +7813 -7723
- package/dist/dashboard-for-laravel.js.map +1 -1
- package/dist/dashboard-for-laravel.umd.cjs +7 -7
- package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +12 -6
- package/dist/utils/formErrorTargets.d.ts +23 -11
- package/docs/public/api-reference.json +5 -5
- package/docs/public/docs-map.md +1 -1
- package/package.json +1 -1
- package/resources/js/components/extended/DXField.vue +65 -6
- package/resources/js/components/extended/DXForm.vue +57 -6
- package/resources/js/components/extended/DXFormErrorSummary.vue +14 -0
- package/resources/js/components/extended/DXTableEditorModal.vue +2 -2
- package/resources/js/composables/useForm.ts +86 -0
- package/resources/js/index.ts +1 -0
- package/resources/js/types/index.ts +12 -6
- package/resources/js/utils/formErrorTargets.ts +94 -16
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* `when`/`context`-aware predicates and a standalone summary can use the
|
|
8
8
|
* defaults below.
|
|
9
9
|
*/
|
|
10
|
+
import { toRaw } from "vue";
|
|
10
11
|
import type { FieldDefinition, FormTab } from "../types";
|
|
11
|
-
import type { ValidationErrors } from "../composables/useForm";
|
|
12
|
+
import type { FailedRow, ValidationErrors } from "../composables/useForm";
|
|
12
13
|
import {
|
|
13
14
|
isFieldVisible as isFieldVisibleFor,
|
|
14
15
|
resolvePredicate,
|
|
@@ -19,6 +20,16 @@ import { getByPath } from "./objectPath";
|
|
|
19
20
|
export interface ErrorTarget {
|
|
20
21
|
/** The key as the server sent it (`lines.0.price`). */
|
|
21
22
|
errorKey: string;
|
|
23
|
+
/**
|
|
24
|
+
* Where to take the user for this error now. The same as `errorKey`,
|
|
25
|
+
* except for a row error resolved with `failedRows`: the key at the
|
|
26
|
+
* failed row's CURRENT index (`lines.1.price` once row 0 is removed),
|
|
27
|
+
* or the owning field's key when that row is gone (removed, replaced, or
|
|
28
|
+
* flagged by the field's `softDeleteKey`); DXForm then focuses the
|
|
29
|
+
* field's container, not a row. Always set by `resolveErrorTargets`;
|
|
30
|
+
* optional so targets built by hand still type.
|
|
31
|
+
*/
|
|
32
|
+
focusKey?: string;
|
|
22
33
|
/** The server's messages for that key, as sent. */
|
|
23
34
|
messages: string[];
|
|
24
35
|
/**
|
|
@@ -65,6 +76,14 @@ export interface ResolveErrorTargetsOptions {
|
|
|
65
76
|
field: FieldDefinition,
|
|
66
77
|
labelModel: Record<string, any>,
|
|
67
78
|
) => string | null | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The rows a failed submit's errors named, as captured then
|
|
81
|
+
* (`SubmitFailure.failedRows`). Pass it when resolving that failure's
|
|
82
|
+
* errors after the form may have changed (the error summary does), so a
|
|
83
|
+
* row error keeps the name of the row that failed and points at where
|
|
84
|
+
* that row is now. Leave it out for the form's live errors.
|
|
85
|
+
*/
|
|
86
|
+
failedRows?: Record<string, FailedRow>;
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
interface Candidate {
|
|
@@ -175,7 +194,10 @@ function rowLabelFor(
|
|
|
175
194
|
* Labels: the owning field's label; for `field.N.sub` the sub-field's label
|
|
176
195
|
* (when the field defines `fields` and one has that key) or the field's own,
|
|
177
196
|
* plus "(line N+1)" — unless the field's `rowLabel` names that row, when
|
|
178
|
-
* the row's name is the whole label
|
|
197
|
+
* the row's name is the whole label. With `failedRows`, a row key's label
|
|
198
|
+
* reads the row as it was when the submit failed, and its `focusKey` and
|
|
199
|
+
* line number follow that row to where it is now (see `ownedTarget`); for
|
|
200
|
+
* any other nested key (a media map's
|
|
179
201
|
* `image_media.<uuid>`) the field's label. An unowned key is humanised.
|
|
180
202
|
*
|
|
181
203
|
* Display lines: "Label: message", except that the label is left off when
|
|
@@ -266,22 +288,78 @@ export function resolveErrorTargets(
|
|
|
266
288
|
return longest;
|
|
267
289
|
};
|
|
268
290
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Where a failed row is now: its index in the field's live rows, found by
|
|
293
|
+
* identity, or -1 when it is gone (removed, the rows were replaced, or
|
|
294
|
+
* the row carries the field's `softDeleteKey` flag: DXRepeater keeps
|
|
295
|
+
* such a row in the array, so it is submitted, but no longer renders it).
|
|
296
|
+
*/
|
|
297
|
+
const currentIndexOf = (field: FieldDefinition, failedRow: FailedRow): number => {
|
|
298
|
+
const liveRows = getByPath(model, field.key);
|
|
299
|
+
if (!Array.isArray(liveRows)) return -1;
|
|
300
|
+
const index = liveRows.findIndex((row) => toRaw(row) === failedRow.row);
|
|
301
|
+
if (index < 0) return -1;
|
|
302
|
+
// Read the flag through the LIVE row (a reactive proxy in a form),
|
|
303
|
+
// not the raw `failedRow.row`, so a caller's computed re-resolves
|
|
304
|
+
// when the row is flagged.
|
|
305
|
+
const softDeleteKey = field.softDeleteKey;
|
|
306
|
+
if (softDeleteKey && Boolean(liveRows[index]?.[softDeleteKey])) return -1;
|
|
307
|
+
return index;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The label and focus key for an owned error key. For a row key
|
|
312
|
+
* (`field.N.sub`) with a captured failed row, both follow that row: the
|
|
313
|
+
* label is read from the row as it was when the submit failed, and the
|
|
314
|
+
* focus key uses the row's current index, or is the field's key when the
|
|
315
|
+
* row is gone. "(line N)" then counts the row's CURRENT position while it
|
|
316
|
+
* exists (the line a click takes the user to), and the position it failed
|
|
317
|
+
* at once it is gone. `rowLabel(row, index)` is given that same index, so
|
|
318
|
+
* a name built from the index agrees with the line form.
|
|
319
|
+
*/
|
|
320
|
+
const ownedTarget = (
|
|
321
|
+
errorKey: string,
|
|
322
|
+
field: FieldDefinition,
|
|
323
|
+
): { label: string; focusKey: string } => {
|
|
324
|
+
if (!isNestedUnder(errorKey, field.key)) {
|
|
325
|
+
return { label: labelFor(field), focusKey: errorKey };
|
|
326
|
+
}
|
|
327
|
+
const [first, second, ...deeper] = errorKey
|
|
328
|
+
.slice(field.key.length + 1)
|
|
329
|
+
.split(".");
|
|
330
|
+
if (!NUMERIC_SEGMENT.test(first)) {
|
|
331
|
+
return { label: labelFor(field), focusKey: errorKey };
|
|
332
|
+
}
|
|
333
|
+
const failedIndex = Number(first);
|
|
273
334
|
const subField =
|
|
274
335
|
second !== undefined
|
|
275
336
|
? field.fields?.find((candidate) => candidate.key === second)
|
|
276
337
|
: undefined;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (
|
|
338
|
+
const failedRow = options.failedRows?.[`${field.key}.${first}`];
|
|
339
|
+
|
|
340
|
+
let rowModel: Record<string, any>;
|
|
341
|
+
let lineIndex = failedIndex;
|
|
342
|
+
let focusKey = errorKey;
|
|
343
|
+
if (failedRow !== undefined) {
|
|
344
|
+
rowModel = failedRow.snapshot;
|
|
345
|
+
const currentIndex = currentIndexOf(field, failedRow);
|
|
346
|
+
if (currentIndex >= 0) {
|
|
347
|
+
lineIndex = currentIndex;
|
|
348
|
+
const rest = second !== undefined ? [second, ...deeper] : [];
|
|
349
|
+
focusKey = [field.key, String(currentIndex), ...rest].join(".");
|
|
350
|
+
} else {
|
|
351
|
+
focusKey = field.key;
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
// DXRepeater renders a sub-field with the ROW as its model, so its
|
|
355
|
+
// label resolves against the row here too.
|
|
356
|
+
const row = getByPath(model, `${field.key}.${first}`);
|
|
357
|
+
rowModel = row !== null && typeof row === "object" ? row : {};
|
|
358
|
+
}
|
|
359
|
+
const rowName = rowLabelFor(field, rowModel, lineIndex);
|
|
360
|
+
if (rowName !== null) return { label: rowName, focusKey };
|
|
283
361
|
const base = subField ? labelFor(subField, rowModel) : labelFor(field);
|
|
284
|
-
return `${base} (line ${
|
|
362
|
+
return { label: `${base} (line ${lineIndex + 1})`, focusKey };
|
|
285
363
|
};
|
|
286
364
|
|
|
287
365
|
// ——— one target per error key with at least one message
|
|
@@ -298,15 +376,15 @@ export function resolveErrorTargets(
|
|
|
298
376
|
);
|
|
299
377
|
if (messages.length === 0) return;
|
|
300
378
|
const owner = ownerOf(errorKey);
|
|
379
|
+
const owned = owner ? ownedTarget(errorKey, owner.field) : null;
|
|
301
380
|
pending.push({
|
|
302
381
|
target: {
|
|
303
382
|
errorKey,
|
|
383
|
+
focusKey: owned ? owned.focusKey : errorKey,
|
|
304
384
|
messages,
|
|
305
385
|
fieldKey: owner ? owner.field.key : null,
|
|
306
386
|
tabKey: owner ? owner.tabKey : null,
|
|
307
|
-
label:
|
|
308
|
-
? ownedLabel(errorKey, owner.field)
|
|
309
|
-
: humaniseErrorKey(errorKey),
|
|
387
|
+
label: owned ? owned.label : humaniseErrorKey(errorKey),
|
|
310
388
|
},
|
|
311
389
|
rank: owner ? owner.rank : [UNOWNED_RANK, UNOWNED_RANK],
|
|
312
390
|
order,
|