@omnitend/dashboard-for-laravel 0.28.0 → 0.30.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/charts.d.ts +19 -0
- package/dist/charts.js +225 -0
- package/dist/charts.js.map +1 -0
- package/dist/charts.umd.cjs +2 -0
- package/dist/charts.umd.cjs.map +1 -0
- package/dist/components/base/DAlert.vue.d.ts +6 -1
- package/dist/components/base/DAutocomplete.vue.d.ts +21 -1
- package/dist/components/base/DFormCheckboxGroup.vue.d.ts +12 -0
- package/dist/components/extended/DXDashboardSidebar.vue.d.ts +2 -2
- package/dist/components/extended/DXField.vue.d.ts +4 -4
- package/dist/components/extended/DXForm.vue.d.ts +15 -4
- 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 +8826 -8937
- 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 -3
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +1 -1
- package/docs/public/api-reference.json +84 -9
- package/docs/public/docs-map.md +1 -1
- package/docs/public/llms.txt +3 -2
- package/package.json +11 -5
- package/resources/css/theme.scss +21 -7
- package/resources/js/charts.ts +19 -0
- package/resources/js/components/base/DAlert.vue +19 -1
- package/resources/js/components/base/DAutocomplete.vue +96 -4
- package/resources/js/components/base/DFormCheckboxGroup.vue +22 -0
- package/resources/js/components/charts/DXBarChart.vue +0 -9
- package/resources/js/components/charts/DXDoughnutChart.vue +0 -9
- package/resources/js/components/charts/DXLineChart.vue +0 -9
- package/resources/js/components/extended/DXField.vue +12 -0
- package/resources/js/components/extended/DXForm.vue +25 -5
- 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 +30 -3
- package/resources/js/index.ts +4 -3
- package/resources/js/types/index.ts +1 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated": "2026-07-
|
|
2
|
+
"generated": "2026-07-20T07:09:59.465Z",
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@omnitend/dashboard-for-laravel",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.30.0"
|
|
6
6
|
},
|
|
7
7
|
"components": {
|
|
8
8
|
"base": [
|
|
@@ -53,7 +53,15 @@
|
|
|
53
53
|
"category": "base",
|
|
54
54
|
"filePath": "resources/js/components/base/DAlert.vue",
|
|
55
55
|
"description": "",
|
|
56
|
-
"props": [
|
|
56
|
+
"props": [
|
|
57
|
+
{
|
|
58
|
+
"name": "modelValue",
|
|
59
|
+
"type": "union",
|
|
60
|
+
"required": false,
|
|
61
|
+
"default": "true",
|
|
62
|
+
"description": ""
|
|
63
|
+
}
|
|
64
|
+
],
|
|
57
65
|
"events": [],
|
|
58
66
|
"slots": [
|
|
59
67
|
{
|
|
@@ -94,8 +102,27 @@
|
|
|
94
102
|
"name": "DAutocomplete",
|
|
95
103
|
"category": "base",
|
|
96
104
|
"filePath": "resources/js/components/base/DAutocomplete.vue",
|
|
97
|
-
"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`.",
|
|
98
|
-
"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
|
+
],
|
|
99
126
|
"events": [],
|
|
100
127
|
"slots": [
|
|
101
128
|
{
|
|
@@ -481,6 +508,27 @@
|
|
|
481
508
|
],
|
|
482
509
|
"methods": []
|
|
483
510
|
},
|
|
511
|
+
{
|
|
512
|
+
"name": "DFormCheckboxGroup",
|
|
513
|
+
"category": "base",
|
|
514
|
+
"filePath": "resources/js/components/base/DFormCheckboxGroup.vue",
|
|
515
|
+
"description": "Type-safe wrapper around Bootstrap Vue Next BFormCheckboxGroup component. Renders a group of checkboxes from an `options` array (or slot content); the `v-model` is the ARRAY of checked option values (\"pick any of N\").",
|
|
516
|
+
"props": [],
|
|
517
|
+
"events": [],
|
|
518
|
+
"slots": [
|
|
519
|
+
{
|
|
520
|
+
"name": "name",
|
|
521
|
+
"description": "",
|
|
522
|
+
"bindings": [
|
|
523
|
+
{
|
|
524
|
+
"name": "name",
|
|
525
|
+
"title": "binding"
|
|
526
|
+
}
|
|
527
|
+
]
|
|
528
|
+
}
|
|
529
|
+
],
|
|
530
|
+
"methods": []
|
|
531
|
+
},
|
|
484
532
|
{
|
|
485
533
|
"name": "DFormGroup",
|
|
486
534
|
"category": "base",
|
|
@@ -2085,7 +2133,14 @@
|
|
|
2085
2133
|
"type": "boolean",
|
|
2086
2134
|
"required": false,
|
|
2087
2135
|
"default": "false",
|
|
2088
|
-
"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."
|
|
2089
2144
|
},
|
|
2090
2145
|
{
|
|
2091
2146
|
"name": "layout",
|
|
@@ -2750,6 +2805,13 @@
|
|
|
2750
2805
|
"default": "true",
|
|
2751
2806
|
"description": "Show per-page selector"
|
|
2752
2807
|
},
|
|
2808
|
+
{
|
|
2809
|
+
"name": "showCount",
|
|
2810
|
+
"type": "boolean",
|
|
2811
|
+
"required": false,
|
|
2812
|
+
"default": "true",
|
|
2813
|
+
"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)."
|
|
2814
|
+
},
|
|
2753
2815
|
{
|
|
2754
2816
|
"name": "perPageOptions",
|
|
2755
2817
|
"type": "Array",
|
|
@@ -2768,7 +2830,7 @@
|
|
|
2768
2830
|
"name": "perPage",
|
|
2769
2831
|
"type": "number",
|
|
2770
2832
|
"required": false,
|
|
2771
|
-
"description": "Items per page (
|
|
2833
|
+
"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."
|
|
2772
2834
|
},
|
|
2773
2835
|
{
|
|
2774
2836
|
"name": "striped",
|
|
@@ -2924,6 +2986,13 @@
|
|
|
2924
2986
|
"type": "boolean",
|
|
2925
2987
|
"required": false,
|
|
2926
2988
|
"description": "Enable client-side filtering, sorting, and pagination on items array"
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
"name": "fixedLayout",
|
|
2992
|
+
"type": "boolean",
|
|
2993
|
+
"required": false,
|
|
2994
|
+
"default": "false",
|
|
2995
|
+
"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."
|
|
2927
2996
|
}
|
|
2928
2997
|
],
|
|
2929
2998
|
"events": [
|
|
@@ -3363,6 +3432,12 @@
|
|
|
3363
3432
|
"required": true,
|
|
3364
3433
|
"description": "Whether to render the per-page selector."
|
|
3365
3434
|
},
|
|
3435
|
+
{
|
|
3436
|
+
"name": "showCount",
|
|
3437
|
+
"type": "boolean",
|
|
3438
|
+
"required": true,
|
|
3439
|
+
"description": "Whether to render the item-count caption (the \"X out of Z items\" line)."
|
|
3440
|
+
},
|
|
3366
3441
|
{
|
|
3367
3442
|
"name": "singularItemName",
|
|
3368
3443
|
"type": "string",
|
|
@@ -3481,8 +3556,8 @@
|
|
|
3481
3556
|
]
|
|
3482
3557
|
},
|
|
3483
3558
|
"stats": {
|
|
3484
|
-
"totalComponents":
|
|
3485
|
-
"baseComponents":
|
|
3559
|
+
"totalComponents": 76,
|
|
3560
|
+
"baseComponents": 57,
|
|
3486
3561
|
"extendedComponents": 19
|
|
3487
3562
|
}
|
|
3488
3563
|
}
|
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-20T07:09:59.531Z
|
|
5
5
|
|
|
6
6
|
This file provides a complete map of all available documentation for AI agents and developers.
|
|
7
7
|
|
package/docs/public/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Vue 3 dashboard components for Laravel with Bootstrap Vue Next
|
|
4
4
|
>
|
|
5
|
-
> A dual-package library (NPM + Composer) providing
|
|
5
|
+
> A dual-package library (NPM + Composer) providing 69 Vue 3 components for building Laravel dashboards with Bootstrap Vue Next.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- [Theming](/guide/theming): Customize appearance with CSS variables
|
|
20
20
|
- [TypeScript](/guide/typescript): TypeScript types and best practices
|
|
21
21
|
|
|
22
|
-
## Base Components (
|
|
22
|
+
## Base Components (57 components)
|
|
23
23
|
|
|
24
24
|
Lightweight type-safe wrappers around Bootstrap Vue Next components providing API stability and consistent theming.
|
|
25
25
|
|
|
@@ -43,6 +43,7 @@ Lightweight type-safe wrappers around Bootstrap Vue Next components providing AP
|
|
|
43
43
|
- [DDropdownItem](/components/base/ddropdownitem): Bootstrap Vue Next DropdownItem wrapper
|
|
44
44
|
- [DForm](/components/base/dform): Bootstrap Vue Next Form wrapper
|
|
45
45
|
- [DFormCheckbox](/components/base/dformcheckbox): Bootstrap Vue Next FormCheckbox wrapper
|
|
46
|
+
- [DFormCheckboxGroup](/components/base/dformcheckboxgroup): Bootstrap Vue Next FormCheckboxGroup wrapper
|
|
46
47
|
- [DFormGroup](/components/base/dformgroup): Bootstrap Vue Next FormGroup wrapper
|
|
47
48
|
- [DFormInput](/components/base/dforminput): Bootstrap Vue Next FormInput wrapper
|
|
48
49
|
- [DFormInvalidFeedback](/components/base/dforminvalidfeedback): Bootstrap Vue Next FormInvalidFeedback wrapper
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnitend/dashboard-for-laravel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Vue 3 dashboard components for Laravel with Bootstrap Vue Next",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/dashboard-for-laravel.umd.cjs",
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"import": "./dist/dashboard-for-laravel.js",
|
|
16
16
|
"require": "./dist/dashboard-for-laravel.umd.cjs"
|
|
17
17
|
},
|
|
18
|
+
"./charts": {
|
|
19
|
+
"types": "./dist/charts.d.ts",
|
|
20
|
+
"import": "./dist/charts.js",
|
|
21
|
+
"require": "./dist/charts.umd.cjs"
|
|
22
|
+
},
|
|
18
23
|
"./style.css": "./dist/style.css",
|
|
19
24
|
"./theme.css": "./dist/style.css",
|
|
20
25
|
"./theme.scss": "./resources/css/theme.scss"
|
|
@@ -29,13 +34,14 @@
|
|
|
29
34
|
],
|
|
30
35
|
"scripts": {
|
|
31
36
|
"dev": "vite build --watch",
|
|
32
|
-
"build": "vite build && node scripts/extract-icon-font.mjs
|
|
37
|
+
"build:lib": "vite build && vite build --config vite.config.charts.ts && node scripts/extract-icon-font.mjs",
|
|
38
|
+
"build": "npm run build:lib && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
33
39
|
"typecheck": "vue-tsc --noEmit",
|
|
34
|
-
"pretest": "
|
|
40
|
+
"pretest": "npm run build:lib",
|
|
35
41
|
"test": "vitest",
|
|
36
|
-
"pretest:ui": "
|
|
42
|
+
"pretest:ui": "npm run build:lib",
|
|
37
43
|
"test:ui": "vitest --ui",
|
|
38
|
-
"pretest:headless": "
|
|
44
|
+
"pretest:headless": "npm run build:lib",
|
|
39
45
|
"test:headless": "vitest --run --browser.headless",
|
|
40
46
|
"docs:dev": "astro dev",
|
|
41
47
|
"docs:build": "npm run docs:generate:ai && astro build && node scripts/check-docs-base-links.mjs",
|
package/resources/css/theme.scss
CHANGED
|
@@ -436,14 +436,17 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
436
436
|
|
|
437
437
|
// Semantic variants: subtle tinted surface, driven through the CSS variable
|
|
438
438
|
// the base `.toast` rule (above, and Bootstrap's own) already reads.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
)
|
|
439
|
+
//
|
|
440
|
+
// Sourced from each variant's `soft-bg` in $dx-variants (#143) — the same
|
|
441
|
+
// vivid-derived soft family badges/alerts use — so the toast hue tracks the
|
|
442
|
+
// palette and editing a `soft-bg` moves toasts too. Previously these mixed the
|
|
443
|
+
// dark *emphasis* base colours (greyer success/warning) and danger's saturated
|
|
444
|
+
// red (a pink, not the soft system's plum). Mixed 50% with white so a large
|
|
445
|
+
// toast surface stays fainter than a small badge while keeping the hue.
|
|
446
|
+
@each $name in ("success", "danger", "warning", "info") {
|
|
447
|
+
$variant: map.get($dx-variants, $name);
|
|
445
448
|
&.toast-#{$name} {
|
|
446
|
-
--bs-toast-bg: #{color.mix($
|
|
449
|
+
--bs-toast-bg: #{color.mix(map.get($variant, "soft-bg"), $white, 50%)};
|
|
447
450
|
}
|
|
448
451
|
}
|
|
449
452
|
}
|
|
@@ -581,6 +584,17 @@ $dx-chart-palette: (#2563eb, #65a30d, #7c3aed, #0d9488, #ea580c, #0891b2, #d9770
|
|
|
581
584
|
}
|
|
582
585
|
}
|
|
583
586
|
|
|
587
|
+
// The chart container used by DXBarChart/DXLineChart/DXDoughnutChart (shipped
|
|
588
|
+
// from the `@omnitend/dashboard-for-laravel/charts` entry, #142). Global rather
|
|
589
|
+
// than a component scoped style so the charts entry emits NO CSS — chart
|
|
590
|
+
// consumers already import this stylesheet for the theme + palette variables.
|
|
591
|
+
.dx-chart {
|
|
592
|
+
position: relative;
|
|
593
|
+
width: 100%;
|
|
594
|
+
height: 100%;
|
|
595
|
+
min-height: 240px;
|
|
596
|
+
}
|
|
597
|
+
|
|
584
598
|
// ----------------------------------------------------------------------------
|
|
585
599
|
// Autocomplete
|
|
586
600
|
// ----------------------------------------------------------------------------
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart entry point — `@omnitend/dashboard-for-laravel/charts` (#142).
|
|
3
|
+
*
|
|
4
|
+
* The chart components are the ONLY ones that need `chart.js` / `vue-chartjs`,
|
|
5
|
+
* so they live behind this separate subpath entry. The main entry
|
|
6
|
+
* (`@omnitend/dashboard-for-laravel`) never references the chart libraries, so
|
|
7
|
+
* they are genuinely OPTIONAL peers: a consumer that renders no charts installs
|
|
8
|
+
* neither and their build no longer fails resolving them.
|
|
9
|
+
*
|
|
10
|
+
* import { DXBarChart } from '@omnitend/dashboard-for-laravel/charts';
|
|
11
|
+
*
|
|
12
|
+
* Chart containers are styled by `.dx-chart` in the main stylesheet
|
|
13
|
+
* (`@omnitend/dashboard-for-laravel/style.css`), which chart consumers already
|
|
14
|
+
* import for the theme and the `--dx-chart-*` palette variables — so this entry
|
|
15
|
+
* ships no CSS of its own.
|
|
16
|
+
*/
|
|
17
|
+
export { default as DXBarChart } from "./components/charts/DXBarChart.vue";
|
|
18
|
+
export { default as DXLineChart } from "./components/charts/DXLineChart.vue";
|
|
19
|
+
export { default as DXDoughnutChart } from "./components/charts/DXDoughnutChart.vue";
|
|
@@ -2,11 +2,29 @@
|
|
|
2
2
|
import { BAlert } from "bootstrap-vue-next";
|
|
3
3
|
import { useSlots } from "vue";
|
|
4
4
|
|
|
5
|
+
/*
|
|
6
|
+
* bootstrap-vue-next's BAlert defaults `modelValue` to `false`, so a bare static
|
|
7
|
+
* `<DAlert variant="info">…</DAlert>` silently renders NOTHING — a footgun that
|
|
8
|
+
* bit our own docs repeatedly (#144). Default it to `true` (visible) at the
|
|
9
|
+
* wrapper; every call site that already passes `v-model`/`:model-value` is
|
|
10
|
+
* unaffected — only the bare-static case (which was always a bug) changes.
|
|
11
|
+
*
|
|
12
|
+
* Care (see DIVERGENCES.md): declaring `modelValue` as a prop strips it from
|
|
13
|
+
* `$attrs`, so it is re-bound explicitly below. `emits` is left UNDECLARED so an
|
|
14
|
+
* `update:modelValue` listener keeps flowing through `$attrs` to BAlert — that's
|
|
15
|
+
* what lets `v-model` dismissal (and the auto-dismiss countdown) still
|
|
16
|
+
* round-trip. The type stays `boolean | number` (a number is the auto-dismiss
|
|
17
|
+
* duration in ms) — don't narrow it.
|
|
18
|
+
*/
|
|
19
|
+
const props = withDefaults(defineProps<{ modelValue?: boolean | number }>(), {
|
|
20
|
+
modelValue: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
5
23
|
const slots = useSlots();
|
|
6
24
|
</script>
|
|
7
25
|
|
|
8
26
|
<template>
|
|
9
|
-
<BAlert v-bind="$attrs">
|
|
27
|
+
<BAlert :model-value="props.modelValue" v-bind="$attrs">
|
|
10
28
|
<template v-for="(_, name) in slots" :key="name" #[name]>
|
|
11
29
|
<slot :name="name" />
|
|
12
30
|
</template>
|
|
@@ -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" />
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @component
|
|
3
|
+
* Type-safe wrapper around Bootstrap Vue Next BFormCheckboxGroup component.
|
|
4
|
+
* Renders a group of checkboxes from an `options` array (or slot content); the
|
|
5
|
+
* `v-model` is the ARRAY of checked option values ("pick any of N").
|
|
6
|
+
*/
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { BFormCheckboxGroup } from "bootstrap-vue-next";
|
|
9
|
+
|
|
10
|
+
defineOptions({
|
|
11
|
+
inheritAttrs: false,
|
|
12
|
+
});
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<BFormCheckboxGroup v-bind="$attrs">
|
|
17
|
+
<!-- Dynamically pass through all named slots with their props -->
|
|
18
|
+
<template v-for="(_, name) in $slots" :key="name" #[name]="slotProps">
|
|
19
|
+
<slot :name="name" v-bind="slotProps" />
|
|
20
|
+
</template>
|
|
21
|
+
</BFormCheckboxGroup>
|
|
22
|
+
</template>
|
|
@@ -370,6 +370,17 @@
|
|
|
370
370
|
v-bind="controlPropsWithGuards"
|
|
371
371
|
/>
|
|
372
372
|
|
|
373
|
+
<!-- Checkbox group: "pick any of N" — the model is an ARRAY of the
|
|
374
|
+
checked option values (default the field to []). -->
|
|
375
|
+
<DFormCheckboxGroup
|
|
376
|
+
v-else-if="field.type === 'checkbox-group'"
|
|
377
|
+
v-model="fieldValue"
|
|
378
|
+
:options="resolvedOptions"
|
|
379
|
+
:state="fieldState"
|
|
380
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
381
|
+
v-bind="controlPropsWithGuards"
|
|
382
|
+
/>
|
|
383
|
+
|
|
373
384
|
<!-- File / image upload -->
|
|
374
385
|
<div v-else-if="field.type === 'image' || field.type === 'file'">
|
|
375
386
|
<DFormInput
|
|
@@ -554,6 +565,7 @@ import DFormInput from "../base/DFormInput.vue";
|
|
|
554
565
|
import DFormTextarea from "../base/DFormTextarea.vue";
|
|
555
566
|
import DFormSelect from "../base/DFormSelect.vue";
|
|
556
567
|
import DFormRadioGroup from "../base/DFormRadioGroup.vue";
|
|
568
|
+
import DFormCheckboxGroup from "../base/DFormCheckboxGroup.vue";
|
|
557
569
|
import DFormCheckbox from "../base/DFormCheckbox.vue";
|
|
558
570
|
import DXSwitch from "./DXSwitch.vue";
|
|
559
571
|
import DFormInvalidFeedback from "../base/DFormInvalidFeedback.vue";
|
|
@@ -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])
|