@omnitend/dashboard-for-laravel 0.26.0 → 0.27.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/dashboard-for-laravel.js +6977 -6952
- 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/style.css +2 -2
- package/docs/public/api-reference.json +1 -1
- package/docs/public/docs-map.md +1 -1
- package/package.json +1 -1
- package/resources/css/theme.scss +140 -7
- package/resources/js/components/extended/DXTable.vue +121 -13
- package/resources/js/composables/defineForm.ts +14 -2
package/docs/public/docs-map.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Documentation Map
|
|
2
2
|
|
|
3
3
|
> Auto-generated hierarchical overview of all documentation
|
|
4
|
-
> Last updated: 2026-07-
|
|
4
|
+
> Last updated: 2026-07-18T05:05:37.057Z
|
|
5
5
|
|
|
6
6
|
This file provides a complete map of all available documentation for AI agents and developers.
|
|
7
7
|
|
package/package.json
CHANGED
package/resources/css/theme.scss
CHANGED
|
@@ -7,19 +7,31 @@
|
|
|
7
7
|
// The Sass module for `color.mix()` etc. `@use` must precede all other
|
|
8
8
|
// statements (variable overrides, `@import`), so it stays at the very top.
|
|
9
9
|
@use "sass:color";
|
|
10
|
+
@use "sass:map";
|
|
10
11
|
|
|
11
12
|
// ----------------------------------------------------------------------------
|
|
12
13
|
// Colors
|
|
13
14
|
// ----------------------------------------------------------------------------
|
|
14
15
|
|
|
15
|
-
$
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
// Base $theme-colors carry each hue's READABLE-ON-LIGHT identity (its "emphasis"
|
|
17
|
+
// shade) — this is what `.text-*`, `.link-*`, `.border-*` and outline buttons
|
|
18
|
+
// use, so every one must clear AA on white. Bold solid FILLS (primary/danger
|
|
19
|
+
// buttons) and SOFT tints (soft buttons, badges, alerts) are applied as explicit
|
|
20
|
+
// overrides after the Bootstrap import, from the $dx-variants map. See
|
|
21
|
+
// plans/2026-07-18-semantic-colour-system.md.
|
|
22
|
+
$primary: #151e2d; // Omni Tend brand navy (16.73:1 on white)
|
|
23
|
+
$secondary: #475569; // Slate-600 (7.58:1 on white)
|
|
24
|
+
$success: #4d7c0f; // Lime-700 emphasis (4.99:1 on white); soft/vivid tints in $dx-variants
|
|
25
|
+
$danger: #dc2626; // Red-600 (4.83:1 on white)
|
|
26
|
+
$warning: #b45309; // Amber-700 emphasis (5.02:1 on white)
|
|
27
|
+
$info: #2563eb; // Blue-600 (5.17:1 on white)
|
|
21
28
|
$light: #f8fafc; // Slate-50 (uses dark text)
|
|
22
|
-
$dark: #
|
|
29
|
+
$dark: #151e2d; // Brand navy — sidebar/high-contrast surfaces (was Slate-900 #0f172a)
|
|
30
|
+
|
|
31
|
+
// Links are the info-blue, independent of the near-black brand primary (a navy
|
|
32
|
+
// link would read as bold text, not a link).
|
|
33
|
+
$link-color: #2563eb;
|
|
34
|
+
$link-shade-percentage: 15%;
|
|
23
35
|
|
|
24
36
|
// ----------------------------------------------------------------------------
|
|
25
37
|
// Contrast Settings
|
|
@@ -154,6 +166,21 @@ $badge-padding-y: 0.25rem;
|
|
|
154
166
|
$badge-padding-x: 0.625rem;
|
|
155
167
|
$badge-font-weight: 500;
|
|
156
168
|
|
|
169
|
+
// The ONE source of truth for the semantic colour system. Each variant carries
|
|
170
|
+
// its SOLID fill + same-hue text (primary/danger buttons, solid fills), its SOFT
|
|
171
|
+
// tint pair (soft buttons, badges, alerts, status), its EMPHASIS shade (outline
|
|
172
|
+
// buttons, coloured links, .text-*), and whether its button is solid or soft.
|
|
173
|
+
// The overrides after the Bootstrap import loop over this map. All pairs are
|
|
174
|
+
// WCAG AA verified. See plans/2026-07-18-semantic-colour-system.md.
|
|
175
|
+
$dx-variants: (
|
|
176
|
+
"primary": (solid-bg: #151e2d, solid-text: #e9f0f8, soft-bg: #e9f0f8, soft-text: #151e2d, emphasis: #151e2d, button: "solid"),
|
|
177
|
+
"secondary": (solid-bg: #475569, solid-text: #e6ebf2, soft-bg: #e6ebf2, soft-text: #29374a, emphasis: #475569, button: "soft"),
|
|
178
|
+
"success": (solid-bg: #84cc16, solid-text: #203b0e, soft-bg: #cdf9b2, soft-text: #203b0e, emphasis: #4d7c0f, button: "soft"),
|
|
179
|
+
"danger": (solid-bg: #dc2626, solid-text: #ffffff, soft-bg: #f5dff1, soft-text: #59194a, emphasis: #dc2626, button: "solid"),
|
|
180
|
+
"warning": (solid-bg: #f59e0b, solid-text: #512d05, soft-bg: #fce5c4, soft-text: #512d05, emphasis: #b45309, button: "soft"),
|
|
181
|
+
"info": (solid-bg: #2563eb, solid-text: #eef4ff, soft-bg: #deebff, soft-text: #12376c, emphasis: #2563eb, button: "soft"),
|
|
182
|
+
);
|
|
183
|
+
|
|
157
184
|
// ----------------------------------------------------------------------------
|
|
158
185
|
// Dashboard
|
|
159
186
|
// ----------------------------------------------------------------------------
|
|
@@ -420,6 +447,112 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
420
447
|
}
|
|
421
448
|
}
|
|
422
449
|
|
|
450
|
+
// ----------------------------------------------------------------------------
|
|
451
|
+
// Semantic colour system (soft-first)
|
|
452
|
+
// ----------------------------------------------------------------------------
|
|
453
|
+
// One loop over $dx-variants applies the whole system on top of Bootstrap:
|
|
454
|
+
//
|
|
455
|
+
// - Buttons: primary/danger are SOLID (fill from the base colour; we only
|
|
456
|
+
// override the label to the same-hue tint, letting Bootstrap shade hover/
|
|
457
|
+
// active). secondary/success/warning/info are SOFT (light tint bg + dark
|
|
458
|
+
// text, fully overridden incl. hover/active).
|
|
459
|
+
// - Outline buttons: the emphasis shade for border/label, the soft tint on hover.
|
|
460
|
+
// - Badges: `.badge.text-bg-*` re-tinted soft — needs `!important` because
|
|
461
|
+
// Bootstrap's `.text-bg-*` helper sets its colours `!important` by design.
|
|
462
|
+
// - Alerts: the soft tint.
|
|
463
|
+
// - Links/text: `.link-*` and `.text-*` take the emphasis shade so they read
|
|
464
|
+
// on white (the base colours already carry emphasis for most variants; this
|
|
465
|
+
// pins the two — success/warning — whose base differs).
|
|
466
|
+
@each $name, $v in $dx-variants {
|
|
467
|
+
$solid-bg: map.get($v, "solid-bg");
|
|
468
|
+
$solid-text: map.get($v, "solid-text");
|
|
469
|
+
$soft-bg: map.get($v, "soft-bg");
|
|
470
|
+
$soft-text: map.get($v, "soft-text");
|
|
471
|
+
$emphasis: map.get($v, "emphasis");
|
|
472
|
+
$is-solid: map.get($v, "button") == "solid";
|
|
473
|
+
|
|
474
|
+
@if $is-solid {
|
|
475
|
+
// Solid button: keep Bootstrap's fill + hover/active shading (from the base
|
|
476
|
+
// colour), override only the LABEL to the same-hue tint.
|
|
477
|
+
.btn-#{$name} {
|
|
478
|
+
--bs-btn-color: #{$solid-text};
|
|
479
|
+
--bs-btn-hover-color: #{$solid-text};
|
|
480
|
+
--bs-btn-active-color: #{$solid-text};
|
|
481
|
+
--bs-btn-disabled-color: #{$solid-text};
|
|
482
|
+
}
|
|
483
|
+
} @else {
|
|
484
|
+
// Soft button: light tint bg + dark same-hue label; darken the tint slightly
|
|
485
|
+
// on hover/active for feedback.
|
|
486
|
+
.btn-#{$name} {
|
|
487
|
+
--bs-btn-color: #{$soft-text};
|
|
488
|
+
--bs-btn-bg: #{$soft-bg};
|
|
489
|
+
--bs-btn-border-color: #{$soft-bg};
|
|
490
|
+
--bs-btn-hover-color: #{$soft-text};
|
|
491
|
+
--bs-btn-hover-bg: #{color.scale($soft-bg, $lightness: -6%)};
|
|
492
|
+
--bs-btn-hover-border-color: #{color.scale($soft-bg, $lightness: -6%)};
|
|
493
|
+
--bs-btn-active-color: #{$soft-text};
|
|
494
|
+
--bs-btn-active-bg: #{color.scale($soft-bg, $lightness: -10%)};
|
|
495
|
+
--bs-btn-active-border-color: #{color.scale($soft-bg, $lightness: -10%)};
|
|
496
|
+
--bs-btn-disabled-color: #{$soft-text};
|
|
497
|
+
--bs-btn-disabled-bg: #{$soft-bg};
|
|
498
|
+
--bs-btn-disabled-border-color: #{$soft-bg};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Outline button: emphasis border/label, fills to the soft tint on hover so it
|
|
503
|
+
// stays calm (not a sudden saturated block).
|
|
504
|
+
.btn-outline-#{$name} {
|
|
505
|
+
--bs-btn-color: #{$emphasis};
|
|
506
|
+
--bs-btn-border-color: #{$emphasis};
|
|
507
|
+
--bs-btn-hover-color: #{$soft-text};
|
|
508
|
+
--bs-btn-hover-bg: #{$soft-bg};
|
|
509
|
+
--bs-btn-hover-border-color: #{$emphasis};
|
|
510
|
+
--bs-btn-active-color: #{$soft-text};
|
|
511
|
+
--bs-btn-active-bg: #{$soft-bg};
|
|
512
|
+
--bs-btn-active-border-color: #{$emphasis};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Badge — soft tint. `!important` beats Bootstrap's `.text-bg-*` helper.
|
|
516
|
+
.badge.text-bg-#{$name} {
|
|
517
|
+
background-color: #{$soft-bg} !important;
|
|
518
|
+
color: #{$soft-text} !important;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Alert — soft tint, with the emphasis shade as the accent border.
|
|
522
|
+
.alert-#{$name} {
|
|
523
|
+
--bs-alert-bg: #{$soft-bg};
|
|
524
|
+
--bs-alert-color: #{$soft-text};
|
|
525
|
+
--bs-alert-border-color: #{color.scale($soft-bg, $lightness: -8%)};
|
|
526
|
+
--bs-alert-link-color: #{$emphasis};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// `.text-#{$name}` and `.link-#{$name}` need no override: each variant's base
|
|
530
|
+
// colour IS its emphasis shade, so Bootstrap already renders them legible.
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// primary is near-black navy, so Bootstrap's darken-on-hover is imperceptible —
|
|
534
|
+
// the main action button would have no visible hover feedback. Lighten instead.
|
|
535
|
+
// (danger's red darkens visibly, so the generic solid path is fine for it.)
|
|
536
|
+
.btn-primary {
|
|
537
|
+
--bs-btn-hover-bg: #{color.scale($primary, $lightness: 20%)};
|
|
538
|
+
--bs-btn-hover-border-color: #{color.scale($primary, $lightness: 20%)};
|
|
539
|
+
--bs-btn-active-bg: #{color.scale($primary, $lightness: 12%)};
|
|
540
|
+
--bs-btn-active-border-color: #{color.scale($primary, $lightness: 12%)};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Tertiary "ghost" button: a `link`-variant button restyled to read as a quiet
|
|
544
|
+
// action, not a hyperlink — body colour, no underline, faint hover surface.
|
|
545
|
+
.btn-link {
|
|
546
|
+
--bs-btn-color: var(--bs-body-color);
|
|
547
|
+
--bs-btn-hover-color: var(--bs-body-color);
|
|
548
|
+
--bs-btn-active-color: var(--bs-body-color);
|
|
549
|
+
text-decoration: none;
|
|
550
|
+
|
|
551
|
+
&:hover {
|
|
552
|
+
background-color: var(--bs-light);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
423
556
|
// ----------------------------------------------------------------------------
|
|
424
557
|
// Autocomplete
|
|
425
558
|
// ----------------------------------------------------------------------------
|
|
@@ -137,6 +137,17 @@
|
|
|
137
137
|
</div>
|
|
138
138
|
</template>
|
|
139
139
|
|
|
140
|
+
<!-- A dotted field key (`paid_by.card`) renders as an EMPTY cell in
|
|
141
|
+
bootstrap-vue-next, in every payload shape, so DXTable resolves
|
|
142
|
+
those columns itself (#121). A consumer's own cell(<key>) slot
|
|
143
|
+
wins — `dottedCellFields` excludes it, and the forwarding below
|
|
144
|
+
would otherwise declare the same slot name twice. -->
|
|
145
|
+
<template
|
|
146
|
+
v-for="field in dottedCellFields($slots)"
|
|
147
|
+
:key="`dotted-cell-${field.key}`"
|
|
148
|
+
#[`cell(${field.key})`]="{ item }"
|
|
149
|
+
>{{ fieldValueOf(item, field.key) }}</template>
|
|
150
|
+
|
|
140
151
|
<!-- Forward every slot the inner table understands: cell(<key>),
|
|
141
152
|
foot(<key>), custom-foot, empty, empty-filtered, row-expansion,
|
|
142
153
|
table-busy/caption/colgroup, thead-sub, top-row, bottom-row.
|
|
@@ -245,6 +256,17 @@
|
|
|
245
256
|
</div>
|
|
246
257
|
</template>
|
|
247
258
|
|
|
259
|
+
<!-- A dotted field key (`paid_by.card`) renders as an EMPTY cell in
|
|
260
|
+
bootstrap-vue-next, in every payload shape, so DXTable resolves
|
|
261
|
+
those columns itself (#121). A consumer's own cell(<key>) slot
|
|
262
|
+
wins — `dottedCellFields` excludes it, and the forwarding below
|
|
263
|
+
would otherwise declare the same slot name twice. -->
|
|
264
|
+
<template
|
|
265
|
+
v-for="field in dottedCellFields($slots)"
|
|
266
|
+
:key="`dotted-cell-${field.key}`"
|
|
267
|
+
#[`cell(${field.key})`]="{ item }"
|
|
268
|
+
>{{ fieldValueOf(item, field.key) }}</template>
|
|
269
|
+
|
|
248
270
|
<!-- Forward every slot the inner table understands: cell(<key>),
|
|
249
271
|
foot(<key>), custom-foot, empty, empty-filtered, row-expansion,
|
|
250
272
|
table-busy/caption/colgroup, thead-sub, top-row, bottom-row.
|
|
@@ -354,6 +376,17 @@
|
|
|
354
376
|
</div>
|
|
355
377
|
</template>
|
|
356
378
|
|
|
379
|
+
<!-- A dotted field key (`paid_by.card`) renders as an EMPTY cell in
|
|
380
|
+
bootstrap-vue-next, in every payload shape, so DXTable resolves
|
|
381
|
+
those columns itself (#121). A consumer's own cell(<key>) slot
|
|
382
|
+
wins — `dottedCellFields` excludes it, and the forwarding below
|
|
383
|
+
would otherwise declare the same slot name twice. -->
|
|
384
|
+
<template
|
|
385
|
+
v-for="field in dottedCellFields($slots)"
|
|
386
|
+
:key="`dotted-cell-${field.key}`"
|
|
387
|
+
#[`cell(${field.key})`]="{ item }"
|
|
388
|
+
>{{ fieldValueOf(item, field.key) }}</template>
|
|
389
|
+
|
|
357
390
|
<!-- Forward every slot the inner table understands: cell(<key>),
|
|
358
391
|
foot(<key>), custom-foot, empty, empty-filtered, row-expansion,
|
|
359
392
|
table-busy/caption/colgroup, thead-sub, top-row, bottom-row.
|
|
@@ -677,6 +710,7 @@ import axios from "axios";
|
|
|
677
710
|
import pluralize from "pluralize";
|
|
678
711
|
import { useToast } from "../../composables/useToast";
|
|
679
712
|
import { useForm } from "../../composables/useForm";
|
|
713
|
+
import { getByPath } from "../../utils/objectPath";
|
|
680
714
|
import DContainer from "../base/DContainer.vue";
|
|
681
715
|
import DRow from "../base/DRow.vue";
|
|
682
716
|
import DCol from "../base/DCol.vue";
|
|
@@ -1222,10 +1256,11 @@ const clientSideFilteredItems = computed<T[]>(() => {
|
|
|
1222
1256
|
// `customer_id`). Match on the filter key when the row has it,
|
|
1223
1257
|
// otherwise fall back to the column's own value — which is also the
|
|
1224
1258
|
// one the user can actually see and type against.
|
|
1259
|
+
// Both lookups are dot-path aware, so a nested payload filters (#121).
|
|
1225
1260
|
const itemValue =
|
|
1226
|
-
field &&
|
|
1227
|
-
? (item
|
|
1228
|
-
: (item
|
|
1261
|
+
field && hasFieldValue(item, key)
|
|
1262
|
+
? fieldValueOf(item, key)
|
|
1263
|
+
: fieldValueOf(item, field?.key ?? key);
|
|
1229
1264
|
|
|
1230
1265
|
// The "no value" option (e.g. Unassigned) is the one filter that
|
|
1231
1266
|
// MATCHES an absent value rather than failing on it.
|
|
@@ -1279,13 +1314,16 @@ const clientSideSortedItems = computed<T[]>(() => {
|
|
|
1279
1314
|
const direction = order === 'desc' ? -1 : 1;
|
|
1280
1315
|
|
|
1281
1316
|
return items.sort((a, b) => {
|
|
1282
|
-
|
|
1283
|
-
const
|
|
1317
|
+
// Dot-path aware, so `paid_by.card` sorts a nested payload (#121).
|
|
1318
|
+
const aVal = fieldValueOf(a, key);
|
|
1319
|
+
const bVal = fieldValueOf(b, key);
|
|
1284
1320
|
|
|
1285
1321
|
// Handle null/undefined
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
if (
|
|
1322
|
+
const aEmpty = aVal === null || aVal === undefined;
|
|
1323
|
+
const bEmpty = bVal === null || bVal === undefined;
|
|
1324
|
+
if (aEmpty && bEmpty) return 0;
|
|
1325
|
+
if (aEmpty) return direction;
|
|
1326
|
+
if (bEmpty) return -direction;
|
|
1289
1327
|
|
|
1290
1328
|
// Numeric comparison
|
|
1291
1329
|
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
@@ -1300,6 +1338,54 @@ const clientSideSortedItems = computed<T[]>(() => {
|
|
|
1300
1338
|
// API mode filter values (extracted from responses)
|
|
1301
1339
|
const apiFilterValues = ref<Record<string, string[]>>({});
|
|
1302
1340
|
|
|
1341
|
+
/*
|
|
1342
|
+
* Resolving a field's value from a row, dot-path aware (#121).
|
|
1343
|
+
*
|
|
1344
|
+
* A key containing a dot (`paid_by.card`) was unusable. Two layers disagreed:
|
|
1345
|
+
* bootstrap-vue-next's `mapItem` UN-flattens any item key containing a dot
|
|
1346
|
+
* (`{'paid_by.card': v}` becomes `{paid_by: {card: v}}`) and then its cell reads
|
|
1347
|
+
* `item['paid_by.card']` FLAT and gets undefined — so bvn renders a dotted key
|
|
1348
|
+
* as an empty cell in BOTH payload shapes. Meanwhile our own client-side sort
|
|
1349
|
+
* and filter also read flat, which a NESTED payload (what Laravel serialises for
|
|
1350
|
+
* a relation) never satisfies. Consumers worked around it by flattening every
|
|
1351
|
+
* nested key to an `_x` alias in each client-side port.
|
|
1352
|
+
*
|
|
1353
|
+
* LITERAL key first, then dot-path. That single order covers every shape:
|
|
1354
|
+
* an ordinary key (`name`), a dots-flat payload (the literal `paid_by.card`
|
|
1355
|
+
* key), and a nested payload (walk `paid_by` -> `card`). It also means bvn's
|
|
1356
|
+
* un-flattening is harmless rather than hostile — mapItem normalises flat to
|
|
1357
|
+
* nested, and the path read finds it either way.
|
|
1358
|
+
*/
|
|
1359
|
+
const fieldValueOf = (item: unknown, key: string): any => {
|
|
1360
|
+
if (item === null || item === undefined) return undefined;
|
|
1361
|
+
if (Object.prototype.hasOwnProperty.call(item, key)) {
|
|
1362
|
+
return (item as Record<string, any>)[key];
|
|
1363
|
+
}
|
|
1364
|
+
return getByPath(item as Record<string, any>, key);
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
/*
|
|
1368
|
+
* Whether a row carries a value for `key` at all — presence, not truthiness.
|
|
1369
|
+
* Mirrors the `key in item` check this replaced, extended to dot paths.
|
|
1370
|
+
*/
|
|
1371
|
+
const hasFieldValue = (item: unknown, key: string): boolean => {
|
|
1372
|
+
if (item === null || item === undefined) return false;
|
|
1373
|
+
if (Object.prototype.hasOwnProperty.call(item, key)) return true;
|
|
1374
|
+
return getByPath(item as Record<string, any>, key) !== undefined;
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
/*
|
|
1378
|
+
* Dotted-key columns DXTable must render itself, because bvn renders them empty
|
|
1379
|
+
* (see `fieldValueOf`). A consumer's own `cell(<key>)` slot always wins — it is
|
|
1380
|
+
* forwarded separately, and declaring the same slot name twice would clobber it.
|
|
1381
|
+
*
|
|
1382
|
+
* A FUNCTION, not a computed, for the same reason as `forwardableSlotNames`:
|
|
1383
|
+
* `useSlots()` returns an object Vue mutates in place with nothing to track, so
|
|
1384
|
+
* a computed over its keys is captured on first render and never updates (#114).
|
|
1385
|
+
*/
|
|
1386
|
+
const dottedCellFields = (slots: Record<string, unknown>): TableField[] =>
|
|
1387
|
+
props.fields.filter((field) => field.key.includes('.') && !slots[`cell(${field.key})`]);
|
|
1388
|
+
|
|
1303
1389
|
// Computed: Get effective filter options for a field
|
|
1304
1390
|
// The key a column's filter is SENT under, which is not always the column's own
|
|
1305
1391
|
// key: a display column can filter on a different server param via `filterKey`.
|
|
@@ -1855,6 +1941,7 @@ const handleFilterChange = (fieldKey: string, value: string) => {
|
|
|
1855
1941
|
{
|
|
1856
1942
|
page: 1, // Reset to first page when filtering
|
|
1857
1943
|
filters: newFilters,
|
|
1944
|
+
perPage: effectivePerPage.value,
|
|
1858
1945
|
...sortParams(effectiveSortBy.value),
|
|
1859
1946
|
},
|
|
1860
1947
|
{ preserveState: true }
|
|
@@ -1929,6 +2016,18 @@ try {
|
|
|
1929
2016
|
// Forward only the keyed edit slots the consumer actually provided, so
|
|
1930
2017
|
// DXForm doesn't mistake an always-present (but empty) wrapper for
|
|
1931
2018
|
// a real custom-value override.
|
|
2019
|
+
/*
|
|
2020
|
+
* A field's own starting value, used to seed a CREATE form and as the fallback
|
|
2021
|
+
* when a row lacks the key (#122).
|
|
2022
|
+
*
|
|
2023
|
+
* `field.default ?? ''` can't express a NULL default: a select whose "none"
|
|
2024
|
+
* option is `value: null` — which is what the column stores — seeds '' instead,
|
|
2025
|
+
* matches no option, and renders blank. Presence, not nullishness, decides here
|
|
2026
|
+
* too, so the create and edit paths agree about the same field.
|
|
2027
|
+
*/
|
|
2028
|
+
const defaultValueFor = (field: any): any =>
|
|
2029
|
+
Object.prototype.hasOwnProperty.call(field, 'default') ? field.default : '';
|
|
2030
|
+
|
|
1932
2031
|
/*
|
|
1933
2032
|
* Seeding an EDIT form from a row (#117).
|
|
1934
2033
|
*
|
|
@@ -1941,7 +2040,7 @@ const seedValueFor = (field: any, row: T): any => {
|
|
|
1941
2040
|
if (Object.prototype.hasOwnProperty.call(row as any, field.key)) {
|
|
1942
2041
|
return (row as any)[field.key];
|
|
1943
2042
|
}
|
|
1944
|
-
return field
|
|
2043
|
+
return defaultValueFor(field);
|
|
1945
2044
|
};
|
|
1946
2045
|
|
|
1947
2046
|
/*
|
|
@@ -2063,8 +2162,17 @@ const isTableSlot = (name: string) =>
|
|
|
2063
2162
|
const forwardableSlotNames = (slots: Record<string, unknown>): string[] =>
|
|
2064
2163
|
Object.keys(slots).filter((name) => isTableSlot(name));
|
|
2065
2164
|
|
|
2066
|
-
const tableSlotSignature = (slots: Record<string, unknown>): string =>
|
|
2067
|
-
|
|
2165
|
+
const tableSlotSignature = (slots: Record<string, unknown>): string => {
|
|
2166
|
+
// DXTable's OWN dotted-cell slots count too (#121). bvn captures its cell
|
|
2167
|
+
// slot set at mount (#114), so a dotted-key column that appears LATER —
|
|
2168
|
+
// data-driven columns resolved after a fetch — adds a `cell(...)` slot bvn
|
|
2169
|
+
// ignores, and the cell renders empty. Folding the dotted-cell field keys
|
|
2170
|
+
// into the signature remounts the inner table when that set changes, exactly
|
|
2171
|
+
// as it does for a consumer's late `cell(...)` slot.
|
|
2172
|
+
const consumerSlots = Object.keys(slots).filter(isTableSlot);
|
|
2173
|
+
const dottedCellSlots = dottedCellFields(slots).map((field) => `cell(${field.key})`);
|
|
2174
|
+
return [...consumerSlots, ...dottedCellSlots].sort().join('|');
|
|
2175
|
+
};
|
|
2068
2176
|
|
|
2069
2177
|
/*
|
|
2070
2178
|
* The template iterates `$slots` DIRECTLY rather than a computed over
|
|
@@ -2245,13 +2353,13 @@ const handleCreateNew = () => {
|
|
|
2245
2353
|
if (!editForm.value) {
|
|
2246
2354
|
const formData: Record<string, any> = {};
|
|
2247
2355
|
submittableEditFields.value.forEach(field => {
|
|
2248
|
-
formData[field.key] = field
|
|
2356
|
+
formData[field.key] = defaultValueFor(field);
|
|
2249
2357
|
});
|
|
2250
2358
|
editForm.value = useForm(formData);
|
|
2251
2359
|
} else {
|
|
2252
2360
|
// Reset existing form to defaults
|
|
2253
2361
|
submittableEditFields.value.forEach(field => {
|
|
2254
|
-
editForm.value.data[field.key] = field
|
|
2362
|
+
editForm.value.data[field.key] = defaultValueFor(field);
|
|
2255
2363
|
});
|
|
2256
2364
|
editForm.value.clearErrors();
|
|
2257
2365
|
}
|
|
@@ -65,10 +65,22 @@ export function defineForm<const TFields extends readonly FormFieldDefinition[]>
|
|
|
65
65
|
): DefineFormReturn<InferFormData<TFields>> {
|
|
66
66
|
type FormData = InferFormData<TFields>;
|
|
67
67
|
|
|
68
|
-
// Extract initial data from field definitions
|
|
68
|
+
// Extract initial data from field definitions.
|
|
69
|
+
//
|
|
70
|
+
// #125: `field.default ?? getDefaultValueForType(type)` cannot express a
|
|
71
|
+
// *null* default — a select whose "none" option is `value: null` (which is
|
|
72
|
+
// what the column stores) seeded '' (the type default), matched no option,
|
|
73
|
+
// and rendered blank. This is the same bug #122 fixed in DXTable. Decide on
|
|
74
|
+
// definedness, not nullishness, so null survives; an absent (or explicitly
|
|
75
|
+
// `undefined`) default still falls back to the type default rather than
|
|
76
|
+
// seeding `undefined`, which would drop the key from the JSON payload. This
|
|
77
|
+
// matches DXRepeater's `!== undefined` rule, so all seeding sites agree.
|
|
69
78
|
const initialData = fieldDefinitions.reduce(
|
|
70
79
|
(acc, field) => {
|
|
71
|
-
acc[field.key] =
|
|
80
|
+
acc[field.key] =
|
|
81
|
+
field.default !== undefined
|
|
82
|
+
? field.default
|
|
83
|
+
: getDefaultValueForType(field.type);
|
|
72
84
|
return acc;
|
|
73
85
|
},
|
|
74
86
|
{} as Record<string, any>,
|