@omnitend/dashboard-for-laravel 0.16.3 → 0.17.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/components/extended/DXField.vue.d.ts +75 -25
- package/dist/components/extended/DXForm.vue.d.ts +23 -1
- package/dist/components/extended/DXRepeater.vue.d.ts +16 -0
- package/dist/composables/useToast.d.ts +2 -1
- package/dist/dashboard-for-laravel.js +8952 -8542
- 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 +1 -1
- package/dist/types/index.d.ts +50 -1
- package/docs/public/api-reference.json +2 -2
- package/docs/public/docs-map.md +1 -1
- package/package.json +1 -1
- package/resources/css/theme.scss +28 -13
- package/resources/js/components/extended/DXField.vue +154 -7
- package/resources/js/components/extended/DXForm.vue +204 -80
- package/resources/js/components/extended/DXRepeater.vue +270 -4
- package/resources/js/composables/useToast.ts +53 -1
- package/resources/js/types/index.ts +52 -1
package/dist/types/index.d.ts
CHANGED
|
@@ -34,6 +34,25 @@ export interface FieldOption extends Record<string, unknown> {
|
|
|
34
34
|
* again on model change when `reloadOptionsOnChange` is set.
|
|
35
35
|
*/
|
|
36
36
|
export type OptionsLoader = (model: any) => Promise<FieldOption[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Label column width for horizontal layout, mirroring Bootstrap Vue Next's
|
|
39
|
+
* `BFormGroup` `labelCols`/`labelCols*` props: either a single width applied
|
|
40
|
+
* at all breakpoints, or a per-breakpoint object (omitted breakpoints fall
|
|
41
|
+
* back to BFormGroup's own defaults, which is how "collapse to vertical
|
|
42
|
+
* below a breakpoint" comes for free — e.g. `{ md: 3 }` stacks below `md`).
|
|
43
|
+
*
|
|
44
|
+
* Deliberately excludes `boolean` (which BFormGroup's own `labelCols` prop
|
|
45
|
+
* accepts): Vue's type-based `defineProps` auto-defaults an omitted prop to
|
|
46
|
+
* `false` (not `undefined`) whenever its declared type includes `boolean`,
|
|
47
|
+
* which would silently break the `field.labelCols ?? formLabelCols ?? 3`
|
|
48
|
+
* fallback chain used to resolve this value.
|
|
49
|
+
*/
|
|
50
|
+
export type LabelCols = number | {
|
|
51
|
+
sm?: number;
|
|
52
|
+
md?: number;
|
|
53
|
+
lg?: number;
|
|
54
|
+
xl?: number;
|
|
55
|
+
};
|
|
37
56
|
/**
|
|
38
57
|
* Field definition shared by every form renderer.
|
|
39
58
|
*
|
|
@@ -123,9 +142,21 @@ export interface FieldDefinition {
|
|
|
123
142
|
inputProps?: Record<string, any>;
|
|
124
143
|
/**
|
|
125
144
|
* Render the field full-width with no label wrapper, delegating its
|
|
126
|
-
* content to the `#span(<key>)` slot. Useful for custom blocks.
|
|
145
|
+
* content to the `#span(<key>)` slot. Useful for custom blocks. Also
|
|
146
|
+
* doubles as the "fill both columns" escape hatch in horizontal layout —
|
|
147
|
+
* a span field always bypasses the label/content column split.
|
|
127
148
|
*/
|
|
128
149
|
span?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Per-field override of DXForm's `layout` prop ("vertical" | "horizontal").
|
|
152
|
+
* Falls back to the form-level `layout` when omitted.
|
|
153
|
+
*/
|
|
154
|
+
layout?: "vertical" | "horizontal";
|
|
155
|
+
/**
|
|
156
|
+
* Per-field override of DXForm's `labelCols` prop, for horizontal
|
|
157
|
+
* layout. Falls back to the form-level `labelCols` when omitted.
|
|
158
|
+
*/
|
|
159
|
+
labelCols?: LabelCols;
|
|
129
160
|
/**
|
|
130
161
|
* Component rendered for `type: "component"` fields. Receives
|
|
131
162
|
* `modelValue`, `field`, `model` props and emits `update:modelValue`.
|
|
@@ -144,6 +175,24 @@ export interface FieldDefinition {
|
|
|
144
175
|
/** Minimum / maximum number of repeater rows. */
|
|
145
176
|
minItems?: number;
|
|
146
177
|
maxItems?: number;
|
|
178
|
+
/**
|
|
179
|
+
* For `repeater` fields: `"cards"` (default) stacks each row as its own
|
|
180
|
+
* card with sub-fields listed vertically — best for rows with several
|
|
181
|
+
* or complex sub-fields. `"table"` renders one row per item with
|
|
182
|
+
* sub-fields as columns — far more compact for simple child-row lists
|
|
183
|
+
* (e.g. 2-3 short sub-fields). Named `repeaterLayout` (not `layout`) to
|
|
184
|
+
* avoid colliding with the unrelated form-level `layout` prop
|
|
185
|
+
* ("vertical" | "horizontal") every field type accepts.
|
|
186
|
+
*/
|
|
187
|
+
repeaterLayout?: "cards" | "table";
|
|
188
|
+
/**
|
|
189
|
+
* For `repeater` fields in `cards` layout: show each row's 1-based
|
|
190
|
+
* position (e.g. "1", "2") next to its Remove button. Off by default —
|
|
191
|
+
* only meaningful once a consumer explicitly wants it (e.g. rows the
|
|
192
|
+
* user thinks of as numbered/ordered); most repeaters don't need it.
|
|
193
|
+
* Not applicable to `table` layout, which has no equivalent column.
|
|
194
|
+
*/
|
|
195
|
+
showRowIndex?: boolean;
|
|
147
196
|
/**
|
|
148
197
|
* For `repeater` fields backed by an upsert-children API contract (rows
|
|
149
198
|
* carrying a persisted `id` are only deleted server-side when submitted
|
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-06T20:01:04.293Z
|
|
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
|
@@ -171,6 +171,17 @@ $dashboard-navbar-height: 64px;
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// Bootstrap's `.input-group` defaults to `flex-wrap: wrap`, letting a
|
|
175
|
+
// narrow container (e.g. a compact table cell, a small screen) stack the
|
|
176
|
+
// prepend/append affix onto its own line below the input. That breaks the
|
|
177
|
+
// visual "one control" continuity an input-group exists to provide — an
|
|
178
|
+
// affix + input should shrink/scroll before it wraps, never stack. Applies
|
|
179
|
+
// globally (currency/percentage fields, DAutocomplete's own `:deep()`
|
|
180
|
+
// override becomes redundant but harmless here).
|
|
181
|
+
.input-group {
|
|
182
|
+
flex-wrap: nowrap;
|
|
183
|
+
}
|
|
184
|
+
|
|
174
185
|
// ----------------------------------------------------------------------------
|
|
175
186
|
// Dashboard Custom Styles
|
|
176
187
|
// ----------------------------------------------------------------------------
|
|
@@ -221,17 +232,24 @@ $dashboard-navbar-height: 64px;
|
|
|
221
232
|
// Toasts
|
|
222
233
|
// ----------------------------------------------------------------------------
|
|
223
234
|
// Bootstrap Vue Next renders toasts with Bootstrap's `.text-bg-{variant}`
|
|
224
|
-
// utility, which fills the whole toast with a saturated colour and
|
|
225
|
-
// loud "candy" blocks that are wrong for a dashboard. Restyle
|
|
226
|
-
// neutral surface with a subtle semantic tint
|
|
227
|
-
//
|
|
228
|
-
//
|
|
235
|
+
// utility by default, which fills the whole toast with a saturated colour and
|
|
236
|
+
// white text — loud "candy" blocks that are wrong for a dashboard. Restyle
|
|
237
|
+
// them as a calm, neutral surface with a subtle semantic tint. Colour still
|
|
238
|
+
// signals the toast type, but the bold palette stays where it belongs
|
|
239
|
+
// (actions, badges, selected states).
|
|
240
|
+
//
|
|
241
|
+
// `useToast()` (resources/js/composables/useToast.ts) intercepts a themed
|
|
242
|
+
// `variant` and swaps it for a `toast-{variant}` class instead of BVN's own
|
|
243
|
+
// `variant` prop, so these rules drive Bootstrap's own `--bs-toast-*`
|
|
244
|
+
// variables — no `!important` needed to fight the `.text-bg-*` utility, which
|
|
245
|
+
// sets its colours `!important` by design. An untouched (non-themed) variant
|
|
246
|
+
// still falls through to BVN's default `.text-bg-*` styling.
|
|
229
247
|
|
|
230
248
|
.toast {
|
|
231
249
|
overflow: hidden; // clip the bottom progress bar to the rounded corners
|
|
232
250
|
border: 1px solid $border-color;
|
|
233
251
|
border-radius: $border-radius;
|
|
234
|
-
|
|
252
|
+
--bs-toast-bg: #{$white};
|
|
235
253
|
box-shadow:
|
|
236
254
|
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
237
255
|
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
@@ -260,19 +278,16 @@ $dashboard-navbar-height: 64px;
|
|
|
260
278
|
display: none;
|
|
261
279
|
}
|
|
262
280
|
|
|
263
|
-
// Semantic variants: subtle tinted surface
|
|
264
|
-
// `.
|
|
265
|
-
// muted body from the base rules above stay.
|
|
281
|
+
// Semantic variants: subtle tinted surface, driven through the CSS variable
|
|
282
|
+
// the base `.toast` rule (above, and Bootstrap's own) already reads.
|
|
266
283
|
@each $name, $color in (
|
|
267
284
|
"success": $success,
|
|
268
285
|
"danger": $danger,
|
|
269
286
|
"warning": $warning,
|
|
270
287
|
"info": $info
|
|
271
288
|
) {
|
|
272
|
-
&.
|
|
273
|
-
|
|
274
|
-
// saturated background-color !important that BVN applies per variant.
|
|
275
|
-
background-color: mix($color, $white, 8%) !important;
|
|
289
|
+
&.toast-#{$name} {
|
|
290
|
+
--bs-toast-bg: #{mix($color, $white, 8%)};
|
|
276
291
|
}
|
|
277
292
|
}
|
|
278
293
|
}
|
|
@@ -21,7 +21,51 @@
|
|
|
21
21
|
/>
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
|
-
<!-- Checkbox
|
|
24
|
+
<!-- Checkbox (horizontal layout): wrapped in DFormGroup for the same
|
|
25
|
+
label/content column split as other field types, instead of the
|
|
26
|
+
label-beside-control layout below. The inline label beside the
|
|
27
|
+
checkbox itself is kept (not deduplicated against the outer label)
|
|
28
|
+
so the control's accessible name doesn't depend on aria-labelledby
|
|
29
|
+
association through the fieldset alone — but its `info` popover is
|
|
30
|
+
omitted here, since rendering the same popover trigger twice (once
|
|
31
|
+
per label) would be a duplicated interactive element, not just
|
|
32
|
+
duplicated text. -->
|
|
33
|
+
<DFormGroup
|
|
34
|
+
v-else-if="field.type === 'checkbox' && isHorizontal"
|
|
35
|
+
:class="field.class || 'mb-3'"
|
|
36
|
+
v-bind="horizontalAttrs"
|
|
37
|
+
>
|
|
38
|
+
<template #label>
|
|
39
|
+
<DXFieldLabel :label="resolvedLabel" :info="resolvedInfo" />
|
|
40
|
+
</template>
|
|
41
|
+
<slot
|
|
42
|
+
v-if="$slots.value"
|
|
43
|
+
name="value"
|
|
44
|
+
:field="field"
|
|
45
|
+
:model="model"
|
|
46
|
+
:value="fieldValue"
|
|
47
|
+
:update="setValue"
|
|
48
|
+
/>
|
|
49
|
+
<DFormCheckbox
|
|
50
|
+
v-else
|
|
51
|
+
v-model="fieldValue"
|
|
52
|
+
:disabled="isDisabled || isReadonly"
|
|
53
|
+
v-bind="field.inputProps"
|
|
54
|
+
>
|
|
55
|
+
<DXFieldLabel :label="resolvedLabel" />
|
|
56
|
+
</DFormCheckbox>
|
|
57
|
+
|
|
58
|
+
<DFormInvalidFeedback v-if="form.hasError(errorKey)" force-show>
|
|
59
|
+
{{ form.getError(errorKey) }}
|
|
60
|
+
</DFormInvalidFeedback>
|
|
61
|
+
<slot name="info" :field="field" :model="model" />
|
|
62
|
+
<DFormText v-if="resolvedHint || $slots.hint" class="text-muted">
|
|
63
|
+
<slot name="hint" :field="field" :model="model">{{ resolvedHint }}</slot>
|
|
64
|
+
</DFormText>
|
|
65
|
+
<DFormText v-if="field.help">{{ field.help }}</DFormText>
|
|
66
|
+
</DFormGroup>
|
|
67
|
+
|
|
68
|
+
<!-- Checkbox (vertical, default): no label wrapper, label sits beside the control -->
|
|
25
69
|
<div v-else-if="field.type === 'checkbox'" :class="field.class || 'mb-3'">
|
|
26
70
|
<!--
|
|
27
71
|
@slot Replaces the built-in control with a custom value editor.
|
|
@@ -67,7 +111,46 @@
|
|
|
67
111
|
<DFormText v-if="field.help">{{ field.help }}</DFormText>
|
|
68
112
|
</div>
|
|
69
113
|
|
|
70
|
-
<!-- Switch
|
|
114
|
+
<!-- Switch (horizontal layout): same column-split treatment as checkbox
|
|
115
|
+
above, including omitting `info` from the inner label to avoid a
|
|
116
|
+
duplicated popover trigger. -->
|
|
117
|
+
<DFormGroup
|
|
118
|
+
v-else-if="field.type === 'switch' && isHorizontal"
|
|
119
|
+
:class="[field.class || 'mb-3', 'dx-switch', { 'dx-switch--on': switchIsOn }]"
|
|
120
|
+
v-bind="horizontalAttrs"
|
|
121
|
+
>
|
|
122
|
+
<template #label>
|
|
123
|
+
<DXFieldLabel :label="resolvedLabel" :info="resolvedInfo" />
|
|
124
|
+
</template>
|
|
125
|
+
<slot
|
|
126
|
+
v-if="$slots.value"
|
|
127
|
+
name="value"
|
|
128
|
+
:field="field"
|
|
129
|
+
:model="model"
|
|
130
|
+
:value="fieldValue"
|
|
131
|
+
:update="setValue"
|
|
132
|
+
/>
|
|
133
|
+
<DFormCheckbox
|
|
134
|
+
v-else
|
|
135
|
+
v-model="switchModel"
|
|
136
|
+
switch
|
|
137
|
+
:disabled="isDisabled || isReadonly"
|
|
138
|
+
v-bind="field.inputProps"
|
|
139
|
+
>
|
|
140
|
+
<DXFieldLabel :label="switchText" />
|
|
141
|
+
</DFormCheckbox>
|
|
142
|
+
|
|
143
|
+
<DFormInvalidFeedback v-if="form.hasError(errorKey)" force-show>
|
|
144
|
+
{{ form.getError(errorKey) }}
|
|
145
|
+
</DFormInvalidFeedback>
|
|
146
|
+
<slot name="info" :field="field" :model="model" />
|
|
147
|
+
<DFormText v-if="resolvedHint || $slots.hint" class="text-muted">
|
|
148
|
+
<slot name="hint" :field="field" :model="model">{{ resolvedHint }}</slot>
|
|
149
|
+
</DFormText>
|
|
150
|
+
<DFormText v-if="field.help">{{ field.help }}</DFormText>
|
|
151
|
+
</DFormGroup>
|
|
152
|
+
|
|
153
|
+
<!-- Switch (vertical, default): toggle with contextual on/off text and an on-state style -->
|
|
71
154
|
<div
|
|
72
155
|
v-else-if="field.type === 'switch'"
|
|
73
156
|
:class="[field.class || 'mb-3', 'dx-switch', { 'dx-switch--on': switchIsOn }]"
|
|
@@ -119,7 +202,7 @@
|
|
|
119
202
|
|
|
120
203
|
<!-- Repeater: nested, repeatable sub-form -->
|
|
121
204
|
<div v-else-if="field.type === 'repeater'" :class="field.class || 'mb-3'">
|
|
122
|
-
<DFormGroup>
|
|
205
|
+
<DFormGroup v-bind="repeaterHorizontalAttrs">
|
|
123
206
|
<template #label>
|
|
124
207
|
<DXFieldLabel :label="resolvedLabel" :info="resolvedInfo" />
|
|
125
208
|
</template>
|
|
@@ -154,9 +237,11 @@
|
|
|
154
237
|
</div>
|
|
155
238
|
|
|
156
239
|
<!-- Standard labelled field -->
|
|
157
|
-
<DFormGroup v-else :class="field.class || 'mb-3'">
|
|
158
|
-
<!-- Label with optional info popover
|
|
159
|
-
|
|
240
|
+
<DFormGroup v-else :class="field.class || 'mb-3'" v-bind="horizontalAttrs">
|
|
241
|
+
<!-- Label with optional info popover. Omitted entirely (not just
|
|
242
|
+
emptied) when hideLabel is set, so BFormGroup doesn't reserve a
|
|
243
|
+
label column/row for it (see DXRepeater's table layout). -->
|
|
244
|
+
<template v-if="!hideLabel" #label>
|
|
160
245
|
<DXFieldLabel :label="resolvedLabel" :info="resolvedInfo" />
|
|
161
246
|
</template>
|
|
162
247
|
|
|
@@ -380,7 +465,7 @@ import DFormText from "../base/DFormText.vue";
|
|
|
380
465
|
import DInputGroup from "../base/DInputGroup.vue";
|
|
381
466
|
import DXFieldLabel from "./DXFieldLabel.vue";
|
|
382
467
|
import type { UseFormReturn } from "../../composables/useForm";
|
|
383
|
-
import type { FieldDefinition, FieldOption, FieldType } from "../../types";
|
|
468
|
+
import type { FieldDefinition, FieldOption, FieldType, LabelCols } from "../../types";
|
|
384
469
|
import { getByPath, setByPath } from "../../utils/objectPath";
|
|
385
470
|
|
|
386
471
|
// Async to break the DXField <-> DXRepeater circular import.
|
|
@@ -405,12 +490,74 @@ interface Props {
|
|
|
405
490
|
|
|
406
491
|
/** Error key for validation lookups (defaults to keyPath/field.key). */
|
|
407
492
|
errorKey?: string;
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Field layout: "vertical" (default, label above input) or "horizontal"
|
|
496
|
+
* (label left, input right). Normally set by DXForm (form-level, with
|
|
497
|
+
* `field.layout` taking precedence); pass directly when using DXField
|
|
498
|
+
* standalone.
|
|
499
|
+
*/
|
|
500
|
+
layout?: "vertical" | "horizontal";
|
|
501
|
+
|
|
502
|
+
/** Label column width for horizontal layout (mirrors BFormGroup). */
|
|
503
|
+
labelCols?: LabelCols;
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Skip rendering the field's own label entirely (only affects the
|
|
507
|
+
* standard labelled-field branch). Used by DXRepeater's `table` layout,
|
|
508
|
+
* where a column header already names the field and a per-row label
|
|
509
|
+
* would waste space in the cell.
|
|
510
|
+
*/
|
|
511
|
+
hideLabel?: boolean;
|
|
408
512
|
}
|
|
409
513
|
|
|
410
514
|
const props = defineProps<Props>();
|
|
411
515
|
|
|
412
516
|
const effectiveModel = computed(() => props.model ?? props.form.data);
|
|
413
517
|
|
|
518
|
+
// ————————————————— horizontal layout (#66)
|
|
519
|
+
|
|
520
|
+
// A `span` field always bypasses the label/content column split — it's
|
|
521
|
+
// rendered as fully custom full-width content via the very first template
|
|
522
|
+
// branch, before layout is ever considered.
|
|
523
|
+
const isHorizontal = computed(
|
|
524
|
+
() => props.layout === "horizontal" && !props.field.span,
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
/** Translate a `LabelCols` value into the BFormGroup attrs it expects. */
|
|
528
|
+
function labelColsAttrs(cols: LabelCols | undefined): Record<string, any> {
|
|
529
|
+
if (cols === undefined) return {};
|
|
530
|
+
// BFormGroup's own `labelCols` prop accepts a number OR a numeric string
|
|
531
|
+
// (BVN's `Numberish` type) — pass anything that isn't the breakpoint
|
|
532
|
+
// object straight through, rather than gatekeeping on `typeof === "number"`,
|
|
533
|
+
// so a numeric string (e.g. from an unbound template attribute or a
|
|
534
|
+
// JSON-driven field config) still activates horizontal layout instead of
|
|
535
|
+
// silently falling through to an empty breakpoint-object read.
|
|
536
|
+
if (typeof cols !== "object" || cols === null) {
|
|
537
|
+
return { labelCols: cols };
|
|
538
|
+
}
|
|
539
|
+
const attrs: Record<string, any> = {};
|
|
540
|
+
if (cols.sm !== undefined) attrs.labelColsSm = cols.sm;
|
|
541
|
+
if (cols.md !== undefined) attrs.labelColsMd = cols.md;
|
|
542
|
+
if (cols.lg !== undefined) attrs.labelColsLg = cols.lg;
|
|
543
|
+
if (cols.xl !== undefined) attrs.labelColsXl = cols.xl;
|
|
544
|
+
return attrs;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Default to a 3-column label when horizontal but no width was configured,
|
|
548
|
+
// so `layout: "horizontal"` alone is enough to see the effect.
|
|
549
|
+
const horizontalAttrs = computed<Record<string, any>>(() =>
|
|
550
|
+
isHorizontal.value ? labelColsAttrs(props.labelCols ?? 3) : {},
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
// A table-layout repeater is inherently wide — squeezing its own label into a
|
|
554
|
+
// narrow left column (as if it were a single-line input) reads badly, unlike
|
|
555
|
+
// a normal horizontal field. Its label always renders above, regardless of
|
|
556
|
+
// the form/field's horizontal layout setting (#68 follow-up).
|
|
557
|
+
const repeaterHorizontalAttrs = computed<Record<string, any>>(() =>
|
|
558
|
+
props.field.repeaterLayout === "table" ? {} : horizontalAttrs.value,
|
|
559
|
+
);
|
|
560
|
+
|
|
414
561
|
// Path semantics (getByPath/setByPath) are only used when a parent passes an
|
|
415
562
|
// explicit keyPath (e.g. a repeater binding `lines.0.price`). For top-level
|
|
416
563
|
// fields the key is used literally, so a field whose key legitimately contains
|