@omnitend/dashboard-for-laravel 0.29.0 → 0.31.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/base/DAutocomplete.vue.d.ts +21 -1
- package/dist/components/extended/DXForm.vue.d.ts +14 -3
- package/dist/components/extended/DXSwitch.vue.d.ts +9 -0
- package/dist/components/extended/DXTable.vue.d.ts +39 -1
- package/dist/components/extended/DXTablePagination.vue.d.ts +2 -0
- package/dist/dashboard-for-laravel.js +8101 -8025
- 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 +8 -0
- package/docs/public/api-reference.json +60 -7
- package/docs/public/docs-map.md +1 -1
- package/package.json +1 -1
- package/resources/css/theme.scss +126 -12
- package/resources/js/components/base/DAutocomplete.vue +96 -4
- package/resources/js/components/extended/DXField.vue +2 -0
- package/resources/js/components/extended/DXForm.vue +25 -5
- package/resources/js/components/extended/DXSwitch.vue +67 -18
- package/resources/js/components/extended/DXTable.vue +139 -19
- package/resources/js/components/extended/DXTableEditorModal.vue +4 -0
- package/resources/js/components/extended/DXTablePagination.vue +5 -2
- package/resources/js/composables/useForm.ts +20 -2
- package/resources/js/types/index.ts +9 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated": "2026-07-
|
|
2
|
+
"generated": "2026-07-20T09:28:21.390Z",
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@omnitend/dashboard-for-laravel",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.31.0"
|
|
6
6
|
},
|
|
7
7
|
"components": {
|
|
8
8
|
"base": [
|
|
@@ -102,8 +102,27 @@
|
|
|
102
102
|
"name": "DAutocomplete",
|
|
103
103
|
"category": "base",
|
|
104
104
|
"filePath": "resources/js/components/base/DAutocomplete.vue",
|
|
105
|
-
"description": "Type-safe wrapper around Bootstrap Vue Next BAutocomplete component. Text input with a filtered dropdown of suggestions (typeahead/combobox). Pass `options` and bind `v-model`; supports `multiple` and a custom `filterFunction`.",
|
|
106
|
-
"props": [
|
|
105
|
+
"description": "Type-safe wrapper around Bootstrap Vue Next BAutocomplete component. Text input with a filtered dropdown of suggestions (typeahead/combobox). Pass `options` and bind `v-model`; supports `multiple` and a custom `filterFunction`. `null-option` adds an opt-in, pinned \"no selection\" row (see below) so a searchable single-select can always reach \"none\" in-list (#138).",
|
|
106
|
+
"props": [
|
|
107
|
+
{
|
|
108
|
+
"name": "options",
|
|
109
|
+
"type": "AutocompleteOptions",
|
|
110
|
+
"required": false,
|
|
111
|
+
"description": "Options passed straight through to BAutocomplete (see its `options` prop)."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "filterFunction",
|
|
115
|
+
"type": "FilterFunction",
|
|
116
|
+
"required": false,
|
|
117
|
+
"description": "A custom filter. Composed with `null-option` rather than replaced: when a\nnull option is present it stays pinned and this runs on the rest."
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "nullOption",
|
|
121
|
+
"type": "union",
|
|
122
|
+
"required": false,
|
|
123
|
+
"description": "Prepend a pinned, never-filtered, selectable \"no selection\" row whose value\nis `null` (#138). `true` → default label \"None\"; a string → that label.\nSelecting it sets the model to `null` (bvn reads that as no selection, so\nthe ✕ hides and the placeholder shows). Off by default — unchanged\nbehaviour. DXTable does NOT use this; it builds its own \"All\" option."
|
|
124
|
+
}
|
|
125
|
+
],
|
|
107
126
|
"events": [],
|
|
108
127
|
"slots": [
|
|
109
128
|
{
|
|
@@ -2114,7 +2133,14 @@
|
|
|
2114
2133
|
"type": "boolean",
|
|
2115
2134
|
"required": false,
|
|
2116
2135
|
"default": "false",
|
|
2117
|
-
"description": "Wrap the form in a card for a visual boundary (mirrors DXTable's\n`card` prop).
|
|
2136
|
+
"description": "Wrap the form in a card for a visual boundary (mirrors DXTable's\n`card` prop). Off by default since DXForm is commonly embedded in a\npage card or modal already. Tabbed forms are wrapped by default\nregardless — see `cardTabs`; setting `card` also forces the tabbed\ncard on even when `cardTabs` is disabled."
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
"name": "cardTabs",
|
|
2140
|
+
"type": "boolean",
|
|
2141
|
+
"required": false,
|
|
2142
|
+
"default": "true",
|
|
2143
|
+
"description": "Wrap a TABBED form's content in a card panel so the active tab reads\nas a finished panel connected to the tab strip (the standard\nBootstrap card-with-tabs pattern) rather than floating on the bare\npage background (#159). On by default. Set `false` for bare tabs\n(e.g. inside a modal that already provides a boundary). Ignored for\nflat (non-tabbed) forms — use `card` for those."
|
|
2118
2144
|
},
|
|
2119
2145
|
{
|
|
2120
2146
|
"name": "layout",
|
|
@@ -2610,7 +2636,7 @@
|
|
|
2610
2636
|
"name": "DXSwitch",
|
|
2611
2637
|
"category": "extended",
|
|
2612
2638
|
"filePath": "resources/js/components/extended/DXSwitch.vue",
|
|
2613
|
-
"description": "DXSwitch — a \"filled box\" toggle switch. An opinionated switch built on `DFormCheckbox` (`switch`): a bordered box sized like a `.form-control` with the label on the left and the toggle on the right, colour-coding its state (a neutral box when off, a
|
|
2639
|
+
"description": "DXSwitch — a \"filled box\" toggle switch. An opinionated switch built on `DFormCheckbox` (`switch`): a bordered box sized like a `.form-control` with the label on the left and the toggle on the right, colour-coding its state. By default (`onVariant=\"success\"`) it reads the house green-on / red-off style — a neutral box + red toggle when off, a soft-green box + green toggle when on. `onVariant=\"neutral\"` (brand primary on / grey off) is for semantically-mixed switches that shouldn't imply good/bad (#158). Used standalone or, internally, by `DXField`'s `switch` field type so a form switch and a lone switch share one look. Usage: ```vue <DXSwitch v-model=\"isCurrent\" label=\"Product is current\" /> ``` For a form field, prefer a `DXField`/`DXForm` `type: 'switch'`; reach for `DXSwitch` directly when you want the styled toggle outside a form. Pass a `label` prop for simple text, or the default slot for richer label content.",
|
|
2614
2640
|
"props": [
|
|
2615
2641
|
{
|
|
2616
2642
|
"name": "modelValue",
|
|
@@ -2636,6 +2662,13 @@
|
|
|
2636
2662
|
"type": "string",
|
|
2637
2663
|
"required": false,
|
|
2638
2664
|
"description": "Text shown when off — overrides `label` in the off state (e.g. \"Product is not current\")."
|
|
2665
|
+
},
|
|
2666
|
+
{
|
|
2667
|
+
"name": "onVariant",
|
|
2668
|
+
"type": "union",
|
|
2669
|
+
"required": false,
|
|
2670
|
+
"default": "\"success\"",
|
|
2671
|
+
"description": "On-state colour. `\"success\"` (default) reads the house green-on / red-off\nstyle — an active/enabled/good switch (\"Product is current\", \"Stock is\ntracked\", \"Visible\"). `\"neutral\"` is for semantically-mixed switches\n(\"contains alcohol\", an allergen toggle, \"hidden on web shop\") that\nshouldn't imply good/bad — it uses the brand primary on / grey off (#158)."
|
|
2639
2672
|
}
|
|
2640
2673
|
],
|
|
2641
2674
|
"events": [
|
|
@@ -2779,6 +2812,13 @@
|
|
|
2779
2812
|
"default": "true",
|
|
2780
2813
|
"description": "Show per-page selector"
|
|
2781
2814
|
},
|
|
2815
|
+
{
|
|
2816
|
+
"name": "showCount",
|
|
2817
|
+
"type": "boolean",
|
|
2818
|
+
"required": false,
|
|
2819
|
+
"default": "true",
|
|
2820
|
+
"description": "Show the footer's item-count caption (\"N items.\" / \"X to Y out of Z\nitems.\"). Defaults to `true`. Set `false` to suppress just the caption,\nindependent of the pager — a page ported from a plain `<b-table>` that\nhides the pager with `:show-pagination=\"false\"` would otherwise gain a\ncaption it never had (#127)."
|
|
2821
|
+
},
|
|
2782
2822
|
{
|
|
2783
2823
|
"name": "perPageOptions",
|
|
2784
2824
|
"type": "Array",
|
|
@@ -2797,7 +2837,7 @@
|
|
|
2797
2837
|
"name": "perPage",
|
|
2798
2838
|
"type": "number",
|
|
2799
2839
|
"required": false,
|
|
2800
|
-
"description": "Items per page (
|
|
2840
|
+
"description": "Items per page. With a `v-model:per-page` it is controlled state; a bare\n`:per-page=\"20\"` is the INITIAL page size (see the initial-value vs\ncontrolled note above). When passed explicitly it takes PRECEDENCE over any\nper-page preference persisted in localStorage — `:per-page=\"20\"` always\nstarts at 20, never a size the user once picked on another table (#124).\nOmit it to let a stored preference (URL-keyed tables only) or the default\napply."
|
|
2801
2841
|
},
|
|
2802
2842
|
{
|
|
2803
2843
|
"name": "striped",
|
|
@@ -2953,6 +2993,13 @@
|
|
|
2953
2993
|
"type": "boolean",
|
|
2954
2994
|
"required": false,
|
|
2955
2995
|
"description": "Enable client-side filtering, sorting, and pagination on items array"
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
"name": "fixedLayout",
|
|
2999
|
+
"type": "boolean",
|
|
3000
|
+
"required": false,
|
|
3001
|
+
"default": "false",
|
|
3002
|
+
"description": "Apply `table-layout: fixed` to the underlying table. Off by default, so\nexisting tables keep their content-sized columns. When on, column widths\nstop depending on cell content, so they no longer reshuffle when a filter\nnarrows the rows — the table stays put as you filter (#156). Combine with\nper-field `width`/`minWidth` to control the proportions; columns with no\ndeclared width share the remaining space equally."
|
|
2956
3003
|
}
|
|
2957
3004
|
],
|
|
2958
3005
|
"events": [
|
|
@@ -3392,6 +3439,12 @@
|
|
|
3392
3439
|
"required": true,
|
|
3393
3440
|
"description": "Whether to render the per-page selector."
|
|
3394
3441
|
},
|
|
3442
|
+
{
|
|
3443
|
+
"name": "showCount",
|
|
3444
|
+
"type": "boolean",
|
|
3445
|
+
"required": true,
|
|
3446
|
+
"description": "Whether to render the item-count caption (the \"X out of Z items\" line)."
|
|
3447
|
+
},
|
|
3395
3448
|
{
|
|
3396
3449
|
"name": "singularItemName",
|
|
3397
3450
|
"type": "string",
|
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-20T09:28:21.457Z
|
|
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
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
// Base $theme-colors carry each hue's READABLE-ON-LIGHT identity (its "emphasis"
|
|
18
18
|
// shade) — this is what `.text-*`, `.link-*`, `.border-*` and outline buttons
|
|
19
|
-
// use, so every one must clear AA on white.
|
|
20
|
-
//
|
|
21
|
-
// overrides after the Bootstrap import, from the $dx-variants map. See
|
|
19
|
+
// use, so every one must clear AA on white. The one bold solid FILL (the primary
|
|
20
|
+
// button) and SOFT tints (soft buttons incl. danger, badges, alerts) are applied
|
|
21
|
+
// as explicit overrides after the Bootstrap import, from the $dx-variants map. See
|
|
22
22
|
// plans/2026-07-18-semantic-colour-system.md.
|
|
23
23
|
$primary: #151e2d; // Omni Tend brand navy (16.73:1 on white)
|
|
24
24
|
$secondary: #475569; // Slate-600 (7.58:1 on white)
|
|
@@ -168,16 +168,22 @@ $badge-padding-x: 0.625rem;
|
|
|
168
168
|
$badge-font-weight: 500;
|
|
169
169
|
|
|
170
170
|
// The ONE source of truth for the semantic colour system. Each variant carries
|
|
171
|
-
// its SOLID fill + same-hue text (primary
|
|
171
|
+
// its SOLID fill + same-hue text (the primary button, solid fills), its SOFT
|
|
172
172
|
// tint pair (soft buttons, badges, alerts, status), its EMPHASIS shade (outline
|
|
173
173
|
// buttons, coloured links, .text-*), and whether its button is solid or soft.
|
|
174
174
|
// The overrides after the Bootstrap import loop over this map. All pairs are
|
|
175
175
|
// WCAG AA verified. See plans/2026-07-18-semantic-colour-system.md.
|
|
176
|
+
//
|
|
177
|
+
// Only PRIMARY is a bold solid button — one loud action (the brand). Everything
|
|
178
|
+
// else, INCLUDING danger, is soft: a soft red "Delete" reads as danger without
|
|
179
|
+
// a heavy fill, and one solid button per screen keeps the hierarchy clear. (Was
|
|
180
|
+
// primary+danger solid; danger's soft was also an off-hue plum — it is now a
|
|
181
|
+
// same-hue light red, matching the danger emphasis and the switch OFF.)
|
|
176
182
|
$dx-variants: (
|
|
177
183
|
"primary": (solid-bg: #151e2d, solid-text: #e9f0f8, soft-bg: #e9f0f8, soft-text: #151e2d, emphasis: #151e2d, button: "solid"),
|
|
178
184
|
"secondary": (solid-bg: #475569, solid-text: #e6ebf2, soft-bg: #e6ebf2, soft-text: #29374a, emphasis: #475569, button: "soft"),
|
|
179
185
|
"success": (solid-bg: #84cc16, solid-text: #203b0e, soft-bg: #cdf9b2, soft-text: #203b0e, emphasis: #4d7c0f, button: "soft"),
|
|
180
|
-
"danger": (solid-bg: #dc2626, solid-text: #ffffff, soft-bg: #
|
|
186
|
+
"danger": (solid-bg: #dc2626, solid-text: #ffffff, soft-bg: #f8d4d4, soft-text: #7a1a1a, emphasis: #dc2626, button: "soft"),
|
|
181
187
|
"warning": (solid-bg: #f59e0b, solid-text: #512d05, soft-bg: #fce5c4, soft-text: #512d05, emphasis: #b45309, button: "soft"),
|
|
182
188
|
"info": (solid-bg: #2563eb, solid-text: #eef4ff, soft-bg: #deebff, soft-text: #12376c, emphasis: #2563eb, button: "soft"),
|
|
183
189
|
);
|
|
@@ -456,10 +462,10 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
456
462
|
// ----------------------------------------------------------------------------
|
|
457
463
|
// One loop over $dx-variants applies the whole system on top of Bootstrap:
|
|
458
464
|
//
|
|
459
|
-
// - Buttons:
|
|
465
|
+
// - Buttons: only PRIMARY is SOLID (fill from the base colour; we only
|
|
460
466
|
// override the label to the same-hue tint, letting Bootstrap shade hover/
|
|
461
|
-
// active). secondary/success/warning/info are SOFT (light tint bg +
|
|
462
|
-
// text, fully overridden incl. hover/active).
|
|
467
|
+
// active). secondary/success/danger/warning/info are SOFT (light tint bg +
|
|
468
|
+
// dark text, fully overridden incl. hover/active).
|
|
463
469
|
// - Outline buttons: the emphasis shade for border/label, the soft tint on hover.
|
|
464
470
|
// - Badges: `.badge.text-bg-*` re-tinted soft — needs `!important` because
|
|
465
471
|
// Bootstrap's `.text-bg-*` helper sets its colours `!important` by design.
|
|
@@ -516,8 +522,15 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
516
522
|
--bs-btn-active-border-color: #{$emphasis};
|
|
517
523
|
}
|
|
518
524
|
|
|
519
|
-
//
|
|
520
|
-
|
|
525
|
+
// `.text-bg-*` — soft tint, everywhere (not just `.badge`). Soft-first is the
|
|
526
|
+
// default across the board: a `.badge`, an `.input-group-text`, a chip or a
|
|
527
|
+
// small status bar built with `.text-bg-success` all get the same soft green
|
|
528
|
+
// as the badge, so a stock indicator can match a "current" badge without
|
|
529
|
+
// hardcoding the hue. `!important` beats Bootstrap's `.text-bg-*` helper
|
|
530
|
+
// (which sets its colours `!important` by design). `.toast` is excluded: it
|
|
531
|
+
// has its own soft handling (a fainter 50%-white mix via `--bs-toast-bg` for
|
|
532
|
+
// its large surface) and themed toasts don't carry `.text-bg-*` anyway.
|
|
533
|
+
.text-bg-#{$name}:not(.toast) {
|
|
521
534
|
background-color: #{$soft-bg} !important;
|
|
522
535
|
color: #{$soft-text} !important;
|
|
523
536
|
}
|
|
@@ -534,9 +547,23 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
534
547
|
// colour IS its emphasis shade, so Bootstrap already renders them legible.
|
|
535
548
|
}
|
|
536
549
|
|
|
550
|
+
// Publish each variant's soft/emphasis shades as CSS variables so components
|
|
551
|
+
// (e.g. DXSwitch's on-box) and consumers can reference the exact soft-first
|
|
552
|
+
// tints on any element — Bootstrap's own `--bs-*-bg-subtle` are a different,
|
|
553
|
+
// greyer family derived from the dark emphasis base, so they don't match the
|
|
554
|
+
// soft badge/alert green. These are the same values the $dx-variants loop paints.
|
|
555
|
+
:root {
|
|
556
|
+
@each $name, $v in $dx-variants {
|
|
557
|
+
--dx-#{$name}-soft-bg: #{map.get($v, "soft-bg")};
|
|
558
|
+
--dx-#{$name}-soft-text: #{map.get($v, "soft-text")};
|
|
559
|
+
--dx-#{$name}-solid-bg: #{map.get($v, "solid-bg")};
|
|
560
|
+
--dx-#{$name}-emphasis: #{map.get($v, "emphasis")};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
537
564
|
// primary is near-black navy, so Bootstrap's darken-on-hover is imperceptible —
|
|
538
|
-
// the main
|
|
539
|
-
//
|
|
565
|
+
// the main (and now only) solid button would have no visible hover feedback.
|
|
566
|
+
// Lighten instead. (Other variants are soft; their hover is handled in the loop.)
|
|
540
567
|
.btn-primary {
|
|
541
568
|
--bs-btn-hover-bg: #{color.scale($primary, $lightness: 20%)};
|
|
542
569
|
--bs-btn-hover-border-color: #{color.scale($primary, $lightness: 20%)};
|
|
@@ -557,6 +584,93 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
557
584
|
}
|
|
558
585
|
}
|
|
559
586
|
|
|
587
|
+
// ----------------------------------------------------------------------------
|
|
588
|
+
// Switches — green when ON, red when OFF, by default (#158)
|
|
589
|
+
// ----------------------------------------------------------------------------
|
|
590
|
+
// House style (James): an active switch — "Product is current", "Stock is
|
|
591
|
+
// tracked", "Visible" — should read success GREEN when on and RED when off,
|
|
592
|
+
// OUT OF THE BOX, not only when a consumer opts in. Bootstrap's default is a
|
|
593
|
+
// primary (navy) track when on / grey when off. We flip the default here.
|
|
594
|
+
//
|
|
595
|
+
// Semantically-MIXED switches ("contains alcohol", an allergen toggle, "hidden
|
|
596
|
+
// on web shop") shouldn't imply good/bad — those opt OUT with `.switch-neutral`
|
|
597
|
+
// on the `.form-switch`, which restores Bootstrap's grey-off / primary-on.
|
|
598
|
+
//
|
|
599
|
+
// Applies to any bare `.form-switch` (DFormCheckbox `switch`); DXSwitch is the
|
|
600
|
+
// "filled box" form where the whole control (not the toggle) carries the colour
|
|
601
|
+
// and the pill stays a neutral affordance — it reads the same tokens below.
|
|
602
|
+
$dx-switch-on: map.get(map.get($dx-variants, "success"), "solid-bg"); // vivid lime
|
|
603
|
+
$dx-switch-off: map.get(map.get($dx-variants, "danger"), "solid-bg"); // vivid red
|
|
604
|
+
$dx-switch-thumb-white: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
|
|
605
|
+
// A red thumb reads better than white on the light-red OFF track.
|
|
606
|
+
$dx-switch-thumb-red: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23dc2626'/%3e%3c/svg%3e");
|
|
607
|
+
|
|
608
|
+
// Switch fill tokens. The ON fill is the SAME soft green as the success
|
|
609
|
+
// badge/button (its `soft-bg`), so a switch and a success badge read as one
|
|
610
|
+
// green — not a separate, punchier lime. OFF is a LIGHT red (a light tint of the
|
|
611
|
+
// danger solid, NOT the plum `danger` soft — a switch off wants to read "red /
|
|
612
|
+
// stop", which the plum status tint doesn't). Derived from the base tokens so
|
|
613
|
+
// one edit moves them. Used by the DXSwitch filled box; the OFF track reuses them.
|
|
614
|
+
$dx-switch-on-soft: map.get(map.get($dx-variants, "success"), "soft-bg"); // #cdf9b2
|
|
615
|
+
:root {
|
|
616
|
+
--dx-switch-on-bg: #{$dx-switch-on-soft};
|
|
617
|
+
--dx-switch-on-well: #{color.mix($dx-switch-on-soft, #fff, 55%)};
|
|
618
|
+
--dx-switch-on-line: #{color.mix($dx-switch-on-soft, $dx-switch-on, 55%)};
|
|
619
|
+
--dx-switch-on-ink: #{map.get(map.get($dx-variants, "success"), "soft-text")};
|
|
620
|
+
--dx-switch-off-bg: #{color.mix($dx-switch-off, #fff, 20%)};
|
|
621
|
+
--dx-switch-off-well: #{color.mix($dx-switch-off, #fff, 10%)};
|
|
622
|
+
--dx-switch-off-line: #{color.mix($dx-switch-off, #fff, 45%)};
|
|
623
|
+
--dx-switch-off-ink: #{color.mix($dx-switch-off, #000, 30%)};
|
|
624
|
+
--dx-switch-pill: var(--bs-gray-400, #ced4da); // neutral pill in both states
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.form-switch .form-check-input {
|
|
628
|
+
// OFF: light-red track, red thumb (left).
|
|
629
|
+
background-color: var(--dx-switch-off-bg);
|
|
630
|
+
border-color: var(--dx-switch-off-line);
|
|
631
|
+
--bs-form-switch-bg: #{$dx-switch-thumb-red};
|
|
632
|
+
|
|
633
|
+
&:checked {
|
|
634
|
+
// ON: vivid green track, white thumb (right — Bootstrap positions it).
|
|
635
|
+
background-color: #{$dx-switch-on};
|
|
636
|
+
border-color: #{$dx-switch-on};
|
|
637
|
+
--bs-form-switch-bg: #{$dx-switch-thumb-white};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Neutral grey focus ring (the track carries the state colour, so the brand
|
|
641
|
+
// navy ring would clash); keep the state thumb rather than Bootstrap's navy.
|
|
642
|
+
&:focus {
|
|
643
|
+
--bs-form-switch-bg: #{$dx-switch-thumb-red};
|
|
644
|
+
border-color: var(--dx-switch-off-line);
|
|
645
|
+
box-shadow: 0 0 0 0.25rem rgba($dark, 0.15);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
&:checked:focus {
|
|
649
|
+
border-color: #{$dx-switch-on};
|
|
650
|
+
--bs-form-switch-bg: #{$dx-switch-thumb-white};
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Neutral opt-out for mixed-semantics switches: restore Bootstrap's grey-off /
|
|
655
|
+
// primary-on (no good/bad valence).
|
|
656
|
+
.form-switch.switch-neutral .form-check-input {
|
|
657
|
+
background-color: var(--bs-body-bg);
|
|
658
|
+
border-color: var(--bs-border-color);
|
|
659
|
+
// Bootstrap's default faint-grey OFF thumb.
|
|
660
|
+
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
|
|
661
|
+
|
|
662
|
+
&:checked {
|
|
663
|
+
background-color: var(--bs-primary);
|
|
664
|
+
border-color: var(--bs-primary);
|
|
665
|
+
--bs-form-switch-bg: #{$dx-switch-thumb-white};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
&:focus {
|
|
669
|
+
border-color: var(--bs-primary);
|
|
670
|
+
box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
560
674
|
// ----------------------------------------------------------------------------
|
|
561
675
|
// Chart palette (data-viz)
|
|
562
676
|
// ----------------------------------------------------------------------------
|
|
@@ -4,10 +4,38 @@
|
|
|
4
4
|
* Text input with a filtered dropdown of suggestions (typeahead/combobox).
|
|
5
5
|
* Pass `options` and bind `v-model`; supports `multiple` and a custom
|
|
6
6
|
* `filterFunction`.
|
|
7
|
+
*
|
|
8
|
+
* `null-option` adds an opt-in, pinned "no selection" row (see below) so a
|
|
9
|
+
* searchable single-select can always reach "none" in-list (#138).
|
|
7
10
|
*/
|
|
8
11
|
<script setup lang="ts">
|
|
9
12
|
import { computed, useAttrs } from "vue";
|
|
10
13
|
import { BAutocomplete } from "bootstrap-vue-next";
|
|
14
|
+
import type { SelectOption } from "bootstrap-vue-next";
|
|
15
|
+
|
|
16
|
+
// bvn's own `options` prop type — kept identical so this stays a pass-through.
|
|
17
|
+
type AutocompleteOptions = readonly (object | string | number | boolean)[];
|
|
18
|
+
type FilterFunction = (options: SelectOption[], search: string) => SelectOption[];
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
/** Options passed straight through to BAutocomplete (see its `options` prop). */
|
|
22
|
+
options?: AutocompleteOptions;
|
|
23
|
+
/**
|
|
24
|
+
* A custom filter. Composed with `null-option` rather than replaced: when a
|
|
25
|
+
* null option is present it stays pinned and this runs on the rest.
|
|
26
|
+
*/
|
|
27
|
+
filterFunction?: FilterFunction;
|
|
28
|
+
/**
|
|
29
|
+
* Prepend a pinned, never-filtered, selectable "no selection" row whose value
|
|
30
|
+
* is `null` (#138). `true` → default label "None"; a string → that label.
|
|
31
|
+
* Selecting it sets the model to `null` (bvn reads that as no selection, so
|
|
32
|
+
* the ✕ hides and the placeholder shows). Off by default — unchanged
|
|
33
|
+
* behaviour. DXTable does NOT use this; it builds its own "All" option.
|
|
34
|
+
*/
|
|
35
|
+
nullOption?: boolean | string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const props = defineProps<Props>();
|
|
11
39
|
|
|
12
40
|
defineOptions({
|
|
13
41
|
inheritAttrs: false,
|
|
@@ -15,6 +43,68 @@ defineOptions({
|
|
|
15
43
|
|
|
16
44
|
const attrs = useAttrs();
|
|
17
45
|
|
|
46
|
+
const attr = (name: string, hyphenated: string) =>
|
|
47
|
+
(attrs as Record<string, unknown>)[name] ?? (attrs as Record<string, unknown>)[hyphenated];
|
|
48
|
+
|
|
49
|
+
const DEFAULT_NULL_OPTION_LABEL = "None";
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* The null option is a raw option that bvn will normalise through the SAME
|
|
53
|
+
* `value-field`/`text-field` the consumer configured (default `value`/`text`).
|
|
54
|
+
* Build it with those effective field names so it normalises to
|
|
55
|
+
* `{ value: null, text: label }` — otherwise a consumer using e.g.
|
|
56
|
+
* `value-field="id" text-field="label"` would normalise our `{ value, text }`
|
|
57
|
+
* to `{ value: undefined, text: undefined }`, and the row would be filtered out
|
|
58
|
+
* and select as `undefined` instead of `null` (#138 review).
|
|
59
|
+
*/
|
|
60
|
+
const nullOption = computed<Record<string, unknown> | null>(() => {
|
|
61
|
+
const raw = props.nullOption;
|
|
62
|
+
if (raw === false || raw === undefined) return null;
|
|
63
|
+
const text = typeof raw === "string" ? raw : DEFAULT_NULL_OPTION_LABEL;
|
|
64
|
+
const valueField = (attr("valueField", "value-field") as string | undefined) ?? "value";
|
|
65
|
+
const textField = (attr("textField", "text-field") as string | undefined) ?? "text";
|
|
66
|
+
return { [valueField]: null, [textField]: text };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const consumerOptions = computed<AutocompleteOptions>(() =>
|
|
70
|
+
Array.isArray(props.options) ? props.options : [],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const computedOptions = computed<AutocompleteOptions>(() => {
|
|
74
|
+
const pinned = nullOption.value;
|
|
75
|
+
if (!pinned) return props.options ?? [];
|
|
76
|
+
return [pinned, ...consumerOptions.value];
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const isNullOption = (option: SelectOption) => option?.value === null;
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* When a null option is present it must stay pinned (top of the list, immune to
|
|
83
|
+
* the search term); the consumer's `filterFunction` — or bvn's default
|
|
84
|
+
* substring-on-text match — applies only to the rest, so the two compose rather
|
|
85
|
+
* than clobber. With no null option we forward the consumer's filter unchanged
|
|
86
|
+
* (possibly undefined, letting bvn use its own default).
|
|
87
|
+
*/
|
|
88
|
+
const effectiveFilterFunction = computed<FilterFunction | undefined>(() => {
|
|
89
|
+
const pinned = nullOption.value;
|
|
90
|
+
const consumerFilter = props.filterFunction;
|
|
91
|
+
if (!pinned) return consumerFilter;
|
|
92
|
+
|
|
93
|
+
return (options, search) => {
|
|
94
|
+
const rest = options.filter((option) => !isNullOption(option));
|
|
95
|
+
const filteredRest = consumerFilter
|
|
96
|
+
? consumerFilter(rest, search)
|
|
97
|
+
: search
|
|
98
|
+
? rest.filter((option) =>
|
|
99
|
+
String(option.text ?? "")
|
|
100
|
+
.toLowerCase()
|
|
101
|
+
.includes(search.toLowerCase()),
|
|
102
|
+
)
|
|
103
|
+
: rest;
|
|
104
|
+
return [...options.filter(isNullOption), ...filteredRest];
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
18
108
|
/*
|
|
19
109
|
* Diverges from bvn: the clear (✕) is hidden when there is nothing to clear.
|
|
20
110
|
*
|
|
@@ -28,9 +118,6 @@ const attrs = useAttrs();
|
|
|
28
118
|
* A consumer's explicit `no-clear-button` still wins; this only ever hides the
|
|
29
119
|
* button, never forces it on.
|
|
30
120
|
*/
|
|
31
|
-
const attr = (name: string, hyphenated: string) =>
|
|
32
|
-
(attrs as Record<string, unknown>)[name] ?? (attrs as Record<string, unknown>)[hyphenated];
|
|
33
|
-
|
|
34
121
|
const hasValue = computed(() => {
|
|
35
122
|
const value = attr("modelValue", "model-value");
|
|
36
123
|
if (Array.isArray(value)) return value.length > 0;
|
|
@@ -52,7 +139,12 @@ const noClearButton = computed(
|
|
|
52
139
|
(#53). A plain element root makes it deterministic.
|
|
53
140
|
-->
|
|
54
141
|
<div class="d-autocomplete">
|
|
55
|
-
<BAutocomplete
|
|
142
|
+
<BAutocomplete
|
|
143
|
+
v-bind="$attrs"
|
|
144
|
+
:options="computedOptions"
|
|
145
|
+
:filter-function="effectiveFilterFunction"
|
|
146
|
+
:no-clear-button="noClearButton"
|
|
147
|
+
>
|
|
56
148
|
<!-- Dynamically pass through all named slots with their props -->
|
|
57
149
|
<template v-for="(_, name) in $slots" :key="name" #[name]="slotProps">
|
|
58
150
|
<slot :name="name" v-bind="slotProps" />
|
|
@@ -159,6 +159,7 @@
|
|
|
159
159
|
<DXSwitch
|
|
160
160
|
v-else
|
|
161
161
|
v-model="switchModel"
|
|
162
|
+
:on-variant="field.switchVariant"
|
|
162
163
|
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
163
164
|
v-bind="controlPropsWithGuards"
|
|
164
165
|
>
|
|
@@ -201,6 +202,7 @@
|
|
|
201
202
|
<DXSwitch
|
|
202
203
|
v-else
|
|
203
204
|
v-model="switchModel"
|
|
205
|
+
:on-variant="field.switchVariant"
|
|
204
206
|
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
205
207
|
v-bind="controlPropsWithGuards"
|
|
206
208
|
>
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
`.card-body` wrapper — DTabs already provides `.card-header`/
|
|
37
37
|
`.card-body` internally via its own `card` prop, so wrapping
|
|
38
38
|
that in another `.card-body` would double up. -->
|
|
39
|
-
<component :is="
|
|
40
|
-
<DTabs v-model:index="activeTab" :card="
|
|
39
|
+
<component :is="tabsInCard ? DCard : 'div'" v-if="hasTabs" v-bind="tabsInCard ? { noBody: true } : {}">
|
|
40
|
+
<DTabs v-model:index="activeTab" :card="tabsInCard">
|
|
41
41
|
<DTab
|
|
42
42
|
v-for="(tab, index) in visibleTabs"
|
|
43
43
|
:key="tab.key"
|
|
@@ -213,12 +213,23 @@ interface Props {
|
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
215
|
* Wrap the form in a card for a visual boundary (mirrors DXTable's
|
|
216
|
-
* `card` prop).
|
|
217
|
-
* card
|
|
218
|
-
*
|
|
216
|
+
* `card` prop). Off by default since DXForm is commonly embedded in a
|
|
217
|
+
* page card or modal already. Tabbed forms are wrapped by default
|
|
218
|
+
* regardless — see `cardTabs`; setting `card` also forces the tabbed
|
|
219
|
+
* card on even when `cardTabs` is disabled.
|
|
219
220
|
*/
|
|
220
221
|
card?: boolean;
|
|
221
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Wrap a TABBED form's content in a card panel so the active tab reads
|
|
225
|
+
* as a finished panel connected to the tab strip (the standard
|
|
226
|
+
* Bootstrap card-with-tabs pattern) rather than floating on the bare
|
|
227
|
+
* page background (#159). On by default. Set `false` for bare tabs
|
|
228
|
+
* (e.g. inside a modal that already provides a boundary). Ignored for
|
|
229
|
+
* flat (non-tabbed) forms — use `card` for those.
|
|
230
|
+
*/
|
|
231
|
+
cardTabs?: boolean;
|
|
232
|
+
|
|
222
233
|
/**
|
|
223
234
|
* Form-wide field layout: "vertical" (default, label above input) or
|
|
224
235
|
* "horizontal" (label left, input right). Overridable per-field via
|
|
@@ -241,6 +252,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
241
252
|
showSubmit: true,
|
|
242
253
|
autoErrorTab: true,
|
|
243
254
|
card: false,
|
|
255
|
+
cardTabs: true,
|
|
244
256
|
layout: "vertical",
|
|
245
257
|
});
|
|
246
258
|
|
|
@@ -318,6 +330,14 @@ const hasTabs = computed(
|
|
|
318
330
|
() => !!props.tabs && props.tabs.length > 0,
|
|
319
331
|
);
|
|
320
332
|
|
|
333
|
+
// Tabbed forms render inside a card panel by default (#159) so the tab
|
|
334
|
+
// content reads as a finished panel rather than floating on the page.
|
|
335
|
+
// `cardTabs` is the tabbed-only toggle (on by default); `card` (which also
|
|
336
|
+
// wraps flat forms) still forces it on. Flat forms are unaffected.
|
|
337
|
+
const tabsInCard = computed(
|
|
338
|
+
() => hasTabs.value && (props.card || props.cardTabs),
|
|
339
|
+
);
|
|
340
|
+
|
|
321
341
|
function visibleFieldsFor(tab: FormTab): FieldDefinition[] {
|
|
322
342
|
return tab.fieldKeys
|
|
323
343
|
.map((key) => fieldByKey.value[key])
|