@omnitend/dashboard-for-laravel 0.33.1 → 0.35.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.js +82 -90
- package/dist/charts.js.map +1 -1
- package/dist/charts.umd.cjs +1 -1
- package/dist/charts.umd.cjs.map +1 -1
- package/dist/components/base/DButton.vue.d.ts +1 -1
- package/dist/components/charts/DXBarChart.vue.d.ts +3 -18
- package/dist/components/charts/DXLineChart.vue.d.ts +3 -18
- package/dist/components/charts/useThemedChart.d.ts +35 -0
- package/dist/components/extended/DXChoiceField.vue.d.ts +26 -0
- package/dist/components/extended/DXDashboardSidebar.vue.d.ts +1 -1
- package/dist/components/extended/DXField.vue.d.ts +10 -113
- package/dist/components/extended/DXFieldShell.vue.d.ts +73 -0
- package/dist/components/extended/DXForm.vue.d.ts +28 -6
- package/dist/components/extended/DXNumericField.vue.d.ts +23 -0
- package/dist/components/extended/DXRepeater.vue.d.ts +4 -4
- package/dist/components/extended/DXRepeaterCards.vue.d.ts +45 -0
- package/dist/components/extended/DXTable.vue.d.ts +107 -2
- package/dist/components/extended/DXTableEditorModal.vue.d.ts +22 -2
- package/dist/components/extended/DXUserAvatar.vue.d.ts +1 -1
- package/dist/composables/useAsyncOptions.d.ts +20 -0
- package/dist/composables/useContainerWidth.d.ts +96 -0
- package/dist/composables/useResourceEditor.d.ts +2 -1
- package/dist/dashboard-for-laravel.js +10513 -10443
- 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 +5 -2
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +146 -0
- package/dist/utils/formSchema.d.ts +59 -0
- package/docs/public/api-reference.json +494 -33
- package/docs/public/docs-map.md +1 -1
- package/docs/public/llms.txt +5 -2
- package/package.json +1 -1
- package/resources/css/theme.scss +80 -2
- package/resources/js/components/charts/DXBarChart.vue +4 -43
- package/resources/js/components/charts/DXLineChart.vue +4 -43
- package/resources/js/components/charts/useThemedChart.ts +69 -0
- package/resources/js/components/extended/DXChoiceField.vue +114 -0
- package/resources/js/components/extended/DXDashboardSidebar.vue +1 -1
- package/resources/js/components/extended/DXField.vue +328 -615
- package/resources/js/components/extended/DXFieldShell.vue +124 -0
- package/resources/js/components/extended/DXForm.vue +92 -14
- package/resources/js/components/extended/DXNumericField.vue +144 -0
- package/resources/js/components/extended/DXRepeater.vue +53 -175
- package/resources/js/components/extended/DXRepeaterCards.vue +133 -0
- package/resources/js/components/extended/DXTable.vue +418 -43
- package/resources/js/components/extended/DXTableEditorModal.vue +39 -2
- package/resources/js/components/extended/DXTablePagination.vue +19 -3
- package/resources/js/components/extended/DXTableShell.vue +70 -1
- package/resources/js/composables/defineForm.ts +8 -44
- package/resources/js/composables/useAsyncOptions.ts +61 -0
- package/resources/js/composables/useContainerWidth.ts +303 -0
- package/resources/js/composables/useResourceEditor.ts +24 -38
- package/resources/js/index.ts +40 -0
- package/resources/js/types/index.ts +231 -0
- package/resources/js/utils/formSchema.ts +126 -0
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-21T12:16:47.128Z
|
|
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 73 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 (60 components)
|
|
23
23
|
|
|
24
24
|
Lightweight type-safe wrappers around Bootstrap Vue Next components providing API stability and consistent theming.
|
|
25
25
|
|
|
@@ -35,6 +35,8 @@ Lightweight type-safe wrappers around Bootstrap Vue Next components providing AP
|
|
|
35
35
|
- [DButtonGroup](/components/base/dbuttongroup): Bootstrap Vue Next ButtonGroup wrapper
|
|
36
36
|
- [DButtonToolbar](/components/base/dbuttontoolbar): Bootstrap Vue Next ButtonToolbar wrapper
|
|
37
37
|
- [DCard](/components/base/dcard): Bootstrap Vue Next Card wrapper
|
|
38
|
+
- [DCarousel](/components/base/dcarousel): Bootstrap Vue Next Carousel wrapper
|
|
39
|
+
- [DCarouselSlide](/components/base/dcarouselslide): Bootstrap Vue Next CarouselSlide wrapper
|
|
38
40
|
- [DCol](/components/base/dcol): Bootstrap Vue Next Col wrapper
|
|
39
41
|
- [DCollapse](/components/base/dcollapse): Bootstrap Vue Next Collapse wrapper
|
|
40
42
|
- [DContainer](/components/base/dcontainer): Bootstrap Vue Next Container wrapper
|
|
@@ -75,6 +77,7 @@ Lightweight type-safe wrappers around Bootstrap Vue Next components providing AP
|
|
|
75
77
|
- [DProgress](/components/base/dprogress): Bootstrap Vue Next Progress wrapper
|
|
76
78
|
- [DRow](/components/base/drow): Bootstrap Vue Next Row wrapper
|
|
77
79
|
- [DSpinner](/components/base/dspinner): Bootstrap Vue Next Spinner wrapper
|
|
80
|
+
- [DTab](/components/base/dtab): Bootstrap Vue Next Tab wrapper
|
|
78
81
|
- [DTable](/components/base/dtable): Bootstrap Vue Next Table wrapper
|
|
79
82
|
- [DTabs](/components/base/dtabs): Bootstrap Vue Next Tabs wrapper
|
|
80
83
|
- [DToast](/components/base/dtoast): Bootstrap Vue Next Toast wrapper
|
package/package.json
CHANGED
package/resources/css/theme.scss
CHANGED
|
@@ -313,6 +313,32 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
313
313
|
flex-wrap: nowrap;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
// ----------------------------------------------------------------------------
|
|
317
|
+
// Nav tabs
|
|
318
|
+
// ----------------------------------------------------------------------------
|
|
319
|
+
// Chrome recedes, content leads — the same ethos as the muted table headers
|
|
320
|
+
// (#157). Bootstrap gives an INACTIVE tab `--bs-nav-link-color:
|
|
321
|
+
// var(--bs-link-color)`, i.e. the saturated link-blue, so a row of tabs above a
|
|
322
|
+
// form reads as a row of links competing with the fields below. Mute the
|
|
323
|
+
// inactive tabs and let the ACTIVE one (Bootstrap's
|
|
324
|
+
// `--bs-nav-tabs-link-active-color`, the near-black emphasis colour) carry the
|
|
325
|
+
// selection on its own.
|
|
326
|
+
//
|
|
327
|
+
// Driven through Bootstrap's own `--bs-nav-*` variables rather than a raw
|
|
328
|
+
// `color:` — so a consumer can re-louden one tab set by resetting the same
|
|
329
|
+
// variable, `[data-bs-theme="dark"]` remaps the tokens for free, and Bootstrap
|
|
330
|
+
// keeps ownership of the `.active`/`.disabled` resolution order.
|
|
331
|
+
//
|
|
332
|
+
// `--bs-secondary-color` is `rgba(var(--bs-body-color-rgb), .75)` → ~#585b5e on
|
|
333
|
+
// white: 6.83:1, comfortably AA (AAA for body text); 6.53:1 on the theme's
|
|
334
|
+
// `$light` #f8fafc panel. Hover goes to the full body colour (#212529, 16.1:1)
|
|
335
|
+
// so hovering reads as "heading toward selected" rather than "turning into a
|
|
336
|
+
// link".
|
|
337
|
+
.nav-tabs {
|
|
338
|
+
--bs-nav-link-color: var(--bs-secondary-color);
|
|
339
|
+
--bs-nav-link-hover-color: var(--bs-body-color);
|
|
340
|
+
}
|
|
341
|
+
|
|
316
342
|
// ----------------------------------------------------------------------------
|
|
317
343
|
// Dashboard Custom Styles
|
|
318
344
|
// ----------------------------------------------------------------------------
|
|
@@ -433,6 +459,40 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
433
459
|
color: $secondary; // muted message text
|
|
434
460
|
}
|
|
435
461
|
|
|
462
|
+
// HEADER-LESS toast (`body` but no `title` — the shape every toast created
|
|
463
|
+
// through `useToast().create({ body })` takes). BVN renders it as
|
|
464
|
+
// .toast > .d-flex > (.toast-body + .btn-close)
|
|
465
|
+
// and BVN's own `.toast .btn-close-custom { margin: <pad> <pad> auto }` pins
|
|
466
|
+
// the close X to the TOP of that row, while the `.toast-body` above still
|
|
467
|
+
// carries the reduced `padding-top` that only makes sense when it sits under
|
|
468
|
+
// a `.toast-header`. Result: the message rides high, the X sits lower, and a
|
|
469
|
+
// one-line toast reads as misaligned.
|
|
470
|
+
//
|
|
471
|
+
// With no header there is nothing above the body, so restore symmetric
|
|
472
|
+
// padding (Bootstrap's `.toast-body` is `padding: var(--bs-toast-padding-x)`
|
|
473
|
+
// all round) and centre the row. The `margin-bottom: auto` has to go too —
|
|
474
|
+
// an auto margin outranks `align-items` in flex alignment, so centring alone
|
|
475
|
+
// would leave the X pinned; `margin-block: 0` hands alignment back to the
|
|
476
|
+
// container while keeping BVN's horizontal margin.
|
|
477
|
+
//
|
|
478
|
+
// `:has()` is the structural test — it is the only way to key off "this toast
|
|
479
|
+
// has no header" from CSS, and it is supported in every evergreen browser
|
|
480
|
+
// (Chrome/Edge 105+, Safari 15.4+, Firefox 121+). A browser without it simply
|
|
481
|
+
// keeps today's slightly-off alignment; nothing breaks.
|
|
482
|
+
&:not(:has(.toast-header)) {
|
|
483
|
+
> .d-flex {
|
|
484
|
+
align-items: center;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.toast-body {
|
|
488
|
+
padding-top: var(--bs-toast-padding-x);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.btn-close-custom {
|
|
492
|
+
margin-block: 0;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
436
496
|
// Hide the auto-dismiss countdown bar — the ticking animation reads as
|
|
437
497
|
// stressful/urgent, which is wrong for a passive notification. Auto-dismiss
|
|
438
498
|
// still works; only its visual indicator is removed.
|
|
@@ -530,7 +590,18 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
530
590
|
// (which sets its colours `!important` by design). `.toast` is excluded: it
|
|
531
591
|
// has its own soft handling (a fainter 50%-white mix via `--bs-toast-bg` for
|
|
532
592
|
// its large surface) and themed toasts don't carry `.text-bg-*` anyway.
|
|
533
|
-
|
|
593
|
+
//
|
|
594
|
+
// The exclusion is wrapped in `:where()` DELIBERATELY: `:where()` always
|
|
595
|
+
// contributes ZERO specificity, so this stays a single-class 0,1,0 selector
|
|
596
|
+
// while matching exactly what a bare `:not(.toast)` would. Written bare it is
|
|
597
|
+
// 0,2,0 — and because the declarations are `!important`, a consumer painting
|
|
598
|
+
// a fully custom badge (`.my-status-badge { background: …; }`) could not win
|
|
599
|
+
// with a single-class `!important` rule and had to escalate to a
|
|
600
|
+
// repeated-class hack. One class in, one class out: a consumer's own
|
|
601
|
+
// `!important` single-class rule now overrides us on source order, as it
|
|
602
|
+
// should. (Everything Bootstrap defines for `.text-bg-*` is also 0,1,0
|
|
603
|
+
// `!important` and is imported ABOVE this block, so we still win it.)
|
|
604
|
+
.text-bg-#{$name}:where(:not(.toast)) {
|
|
534
605
|
background-color: #{$soft-bg} !important;
|
|
535
606
|
color: #{$soft-text} !important;
|
|
536
607
|
}
|
|
@@ -551,7 +622,14 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
|
|
|
551
622
|
// (danger/info's solid IS their emphasis). The variant-less default bar
|
|
552
623
|
// stays `$progress-bar-bg` (brand navy). `!important` beats the `.bg-*`
|
|
553
624
|
// utility, which sets its colour `!important` by design.
|
|
554
|
-
|
|
625
|
+
//
|
|
626
|
+
// `.progress-bar` is only here to SCOPE the rule (large fills, not every
|
|
627
|
+
// `.bg-*` element), so it goes in `:where()` — zero specificity, same
|
|
628
|
+
// matching, and the selector stays 0,1,0 like Bootstrap's own `.bg-*`. Same
|
|
629
|
+
// reasoning as the `.text-bg-*` rule above: at 0,2,0 `!important` a consumer
|
|
630
|
+
// recolouring one bar had to out-specify us instead of just writing
|
|
631
|
+
// `.my-bar { background-color: … !important }`.
|
|
632
|
+
:where(.progress-bar).bg-#{$name} {
|
|
555
633
|
background-color: #{$solid-bg} !important;
|
|
556
634
|
color: #{$solid-text};
|
|
557
635
|
}
|
|
@@ -14,57 +14,18 @@
|
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<script setup lang="ts">
|
|
17
|
-
import { computed } from "vue";
|
|
18
17
|
import { Bar } from "vue-chartjs";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
baseOptions,
|
|
22
|
-
applyPalette,
|
|
23
|
-
mergeOptions,
|
|
24
|
-
type ChartValueFormat,
|
|
25
|
-
} from "./chartTheme";
|
|
18
|
+
import { registerCharts } from "./chartTheme";
|
|
19
|
+
import { useThemedChart, type ThemedChartProps } from "./useThemedChart";
|
|
26
20
|
|
|
27
21
|
registerCharts();
|
|
28
22
|
|
|
29
|
-
interface Props {
|
|
30
|
-
/** X-axis category labels. */
|
|
31
|
-
labels: string[];
|
|
32
|
-
/** Chart.js datasets. Colours are themed when a dataset omits them. */
|
|
33
|
-
datasets: any[];
|
|
34
|
-
/** Chart.js options; deep-merged over the themed defaults. */
|
|
35
|
-
options?: Record<string, any>;
|
|
36
|
-
/** Format value-axis ticks and tooltips. */
|
|
37
|
-
valueFormat?: ChartValueFormat;
|
|
38
|
-
/** Currency symbol for valueFormat: "currency" (default "£"). */
|
|
39
|
-
currencySymbol?: string;
|
|
40
|
-
/** Locale for number formatting. */
|
|
41
|
-
locale?: string;
|
|
42
|
-
/** Show the legend (default: only when there is more than one dataset). */
|
|
43
|
-
showLegend?: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
23
|
// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the
|
|
47
24
|
// `?? datasets.length > 1` default can actually fire when it isn't passed.
|
|
48
|
-
const props = withDefaults(defineProps<
|
|
25
|
+
const props = withDefaults(defineProps<ThemedChartProps<"bar">>(), {
|
|
49
26
|
currencySymbol: "£",
|
|
50
27
|
showLegend: undefined,
|
|
51
28
|
});
|
|
52
29
|
|
|
53
|
-
const chartData =
|
|
54
|
-
labels: props.labels,
|
|
55
|
-
datasets: applyPalette(props.datasets, "bar", props.labels.length),
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
const mergedOptions = computed(() =>
|
|
59
|
-
mergeOptions(
|
|
60
|
-
baseOptions({
|
|
61
|
-
valueFormat: props.valueFormat,
|
|
62
|
-
currencySymbol: props.currencySymbol,
|
|
63
|
-
locale: props.locale,
|
|
64
|
-
showLegend: props.showLegend ?? props.datasets.length > 1,
|
|
65
|
-
hasValueAxis: true,
|
|
66
|
-
}),
|
|
67
|
-
props.options ?? {},
|
|
68
|
-
),
|
|
69
|
-
);
|
|
30
|
+
const { chartData, mergedOptions } = useThemedChart("bar", props);
|
|
70
31
|
</script>
|
|
@@ -14,57 +14,18 @@
|
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<script setup lang="ts">
|
|
17
|
-
import { computed } from "vue";
|
|
18
17
|
import { Line } from "vue-chartjs";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
baseOptions,
|
|
22
|
-
applyPalette,
|
|
23
|
-
mergeOptions,
|
|
24
|
-
type ChartValueFormat,
|
|
25
|
-
} from "./chartTheme";
|
|
18
|
+
import { registerCharts } from "./chartTheme";
|
|
19
|
+
import { useThemedChart, type ThemedChartProps } from "./useThemedChart";
|
|
26
20
|
|
|
27
21
|
registerCharts();
|
|
28
22
|
|
|
29
|
-
interface Props {
|
|
30
|
-
/** X-axis category labels. */
|
|
31
|
-
labels: string[];
|
|
32
|
-
/** Chart.js datasets. Colours are themed when a dataset omits them. */
|
|
33
|
-
datasets: any[];
|
|
34
|
-
/** Chart.js options; deep-merged over the themed defaults. */
|
|
35
|
-
options?: Record<string, any>;
|
|
36
|
-
/** Format value-axis ticks and tooltips. */
|
|
37
|
-
valueFormat?: ChartValueFormat;
|
|
38
|
-
/** Currency symbol for valueFormat: "currency" (default "£"). */
|
|
39
|
-
currencySymbol?: string;
|
|
40
|
-
/** Locale for number formatting. */
|
|
41
|
-
locale?: string;
|
|
42
|
-
/** Show the legend (default: only when there is more than one dataset). */
|
|
43
|
-
showLegend?: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
23
|
// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the
|
|
47
24
|
// `?? datasets.length > 1` default can actually fire when it isn't passed.
|
|
48
|
-
const props = withDefaults(defineProps<
|
|
25
|
+
const props = withDefaults(defineProps<ThemedChartProps<"line">>(), {
|
|
49
26
|
currencySymbol: "£",
|
|
50
27
|
showLegend: undefined,
|
|
51
28
|
});
|
|
52
29
|
|
|
53
|
-
const chartData =
|
|
54
|
-
labels: props.labels,
|
|
55
|
-
datasets: applyPalette(props.datasets, "line", props.labels.length),
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
const mergedOptions = computed(() =>
|
|
59
|
-
mergeOptions(
|
|
60
|
-
baseOptions({
|
|
61
|
-
valueFormat: props.valueFormat,
|
|
62
|
-
currencySymbol: props.currencySymbol,
|
|
63
|
-
locale: props.locale,
|
|
64
|
-
showLegend: props.showLegend ?? props.datasets.length > 1,
|
|
65
|
-
hasValueAxis: true,
|
|
66
|
-
}),
|
|
67
|
-
props.options ?? {},
|
|
68
|
-
),
|
|
69
|
-
);
|
|
30
|
+
const { chartData, mergedOptions } = useThemedChart("line", props);
|
|
70
31
|
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { computed, type ComputedRef } from "vue";
|
|
2
|
+
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
|
3
|
+
import {
|
|
4
|
+
baseOptions,
|
|
5
|
+
applyPalette,
|
|
6
|
+
mergeOptions,
|
|
7
|
+
type ChartValueFormat,
|
|
8
|
+
} from "./chartTheme";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Shared props for the value-axis chart wrappers (`DXBarChart`, `DXLineChart`).
|
|
12
|
+
* `DXBarChart` and `DXLineChart` differ ONLY in the chart.js component they
|
|
13
|
+
* render and the palette kind — everything else (theming, option-merge,
|
|
14
|
+
* legend-default) is identical, and lived duplicated in both SFCs (#135).
|
|
15
|
+
*/
|
|
16
|
+
export interface ThemedChartProps<TType extends "bar" | "line"> {
|
|
17
|
+
/** X-axis category labels. */
|
|
18
|
+
labels: string[];
|
|
19
|
+
/** Chart.js datasets. Colours are themed when a dataset omits them. */
|
|
20
|
+
datasets: ChartDataset<TType>[];
|
|
21
|
+
/** Chart.js options; deep-merged over the themed defaults. */
|
|
22
|
+
options?: ChartOptions<TType>;
|
|
23
|
+
/** Format value-axis ticks and tooltips. */
|
|
24
|
+
valueFormat?: ChartValueFormat;
|
|
25
|
+
/** Currency symbol for valueFormat: "currency" (default "£"). */
|
|
26
|
+
currencySymbol?: string;
|
|
27
|
+
/** Locale for number formatting. */
|
|
28
|
+
locale?: string;
|
|
29
|
+
/** Show the legend (default: only when there is more than one dataset). */
|
|
30
|
+
showLegend?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The themed-chart body shared by `DXBarChart` and `DXLineChart`: applies the
|
|
35
|
+
* data-viz palette to un-coloured datasets and deep-merges the consumer's
|
|
36
|
+
* `options` over the dashboard-friendly defaults. Pass the reactive `props`
|
|
37
|
+
* object (property access stays reactive inside the computeds).
|
|
38
|
+
*/
|
|
39
|
+
export function useThemedChart<TType extends "bar" | "line">(
|
|
40
|
+
type: TType,
|
|
41
|
+
props: ThemedChartProps<TType>,
|
|
42
|
+
): {
|
|
43
|
+
chartData: ComputedRef<ChartData<TType>>;
|
|
44
|
+
mergedOptions: ComputedRef<ChartOptions<TType>>;
|
|
45
|
+
} {
|
|
46
|
+
const chartData = computed(
|
|
47
|
+
() =>
|
|
48
|
+
({
|
|
49
|
+
labels: props.labels,
|
|
50
|
+
datasets: applyPalette(props.datasets, type, props.labels.length),
|
|
51
|
+
}) as ChartData<TType>,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const mergedOptions = computed(
|
|
55
|
+
() =>
|
|
56
|
+
mergeOptions(
|
|
57
|
+
baseOptions({
|
|
58
|
+
valueFormat: props.valueFormat,
|
|
59
|
+
currencySymbol: props.currencySymbol,
|
|
60
|
+
locale: props.locale,
|
|
61
|
+
showLegend: props.showLegend ?? props.datasets.length > 1,
|
|
62
|
+
hasValueAxis: true,
|
|
63
|
+
}),
|
|
64
|
+
props.options ?? {},
|
|
65
|
+
) as ChartOptions<TType>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return { chartData, mergedOptions };
|
|
69
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component (internal — not exported)
|
|
3
|
+
The choice control family for DXField: `select` (plain or `searchable`),
|
|
4
|
+
`radio`, and `checkbox-group`. Extracted from DXField (#135) unchanged. These
|
|
5
|
+
share the "no native readonly" convention (read-only is expressed as
|
|
6
|
+
`disabled`, hence the pre-merged `disabled` + `controlProps`). The parent
|
|
7
|
+
DXField still owns the value/options/guards and the `setValue` coercion; this
|
|
8
|
+
component only renders the control and emits the edited value up.
|
|
9
|
+
|
|
10
|
+
Plain-text `autocomplete` (a text input + datalist) stays in DXField itself,
|
|
11
|
+
with the text-family controls, because it uses the native-readonly guard path
|
|
12
|
+
rather than this disabled-only convention.
|
|
13
|
+
-->
|
|
14
|
+
<template>
|
|
15
|
+
<!-- Searchable select: type to filter a long option list, while the model
|
|
16
|
+
keeps the option's VALUE (an id), not the typed text. -->
|
|
17
|
+
<template v-if="field.type === 'select' && field.searchable">
|
|
18
|
+
<DAutocomplete
|
|
19
|
+
v-if="optionsReady"
|
|
20
|
+
:model-value="modelValue"
|
|
21
|
+
:options="options"
|
|
22
|
+
:placeholder="field.placeholder"
|
|
23
|
+
:required="field.required"
|
|
24
|
+
:state="state"
|
|
25
|
+
:disabled="disabled"
|
|
26
|
+
open-on-focus
|
|
27
|
+
v-bind="controlProps"
|
|
28
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
29
|
+
/>
|
|
30
|
+
<!-- BAutocomplete resolves the input's display text from the model AT
|
|
31
|
+
MOUNT and doesn't re-derive it when `options` arrive later, so an
|
|
32
|
+
async list would leave the raw id showing ("51" instead of
|
|
33
|
+
"Waitrose") — and a long list is exactly the case that loads async.
|
|
34
|
+
Hold the control until the options are in. -->
|
|
35
|
+
<DFormInput v-else placeholder="Loading…" disabled />
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<!-- Select (sync or async options) -->
|
|
39
|
+
<DFormSelect
|
|
40
|
+
v-else-if="field.type === 'select'"
|
|
41
|
+
:model-value="modelValue"
|
|
42
|
+
:required="field.required"
|
|
43
|
+
:options="options"
|
|
44
|
+
:state="state"
|
|
45
|
+
:disabled="disabled"
|
|
46
|
+
v-bind="controlProps"
|
|
47
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
<!-- Radio group -->
|
|
51
|
+
<DFormRadioGroup
|
|
52
|
+
v-else-if="field.type === 'radio'"
|
|
53
|
+
:model-value="modelValue"
|
|
54
|
+
:options="options"
|
|
55
|
+
:required="field.required"
|
|
56
|
+
:state="state"
|
|
57
|
+
:disabled="disabled"
|
|
58
|
+
v-bind="controlProps"
|
|
59
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<!-- Checkbox group: "pick any of N" — the model is an ARRAY of the checked
|
|
63
|
+
option values (default the field to []). -->
|
|
64
|
+
<DFormCheckboxGroup
|
|
65
|
+
v-else
|
|
66
|
+
:model-value="modelValue"
|
|
67
|
+
:options="options"
|
|
68
|
+
:state="state"
|
|
69
|
+
:disabled="disabled"
|
|
70
|
+
v-bind="controlProps"
|
|
71
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
72
|
+
/>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
import DFormSelect from "../base/DFormSelect.vue";
|
|
77
|
+
import DFormRadioGroup from "../base/DFormRadioGroup.vue";
|
|
78
|
+
import DFormCheckboxGroup from "../base/DFormCheckboxGroup.vue";
|
|
79
|
+
import DFormInput from "../base/DFormInput.vue";
|
|
80
|
+
import DAutocomplete from "../base/DAutocomplete.vue";
|
|
81
|
+
import type { FieldDefinition, FieldOption } from "../../types";
|
|
82
|
+
|
|
83
|
+
interface Props {
|
|
84
|
+
/** Field definition (type select/radio/checkbox-group + `searchable`). */
|
|
85
|
+
field: FieldDefinition;
|
|
86
|
+
|
|
87
|
+
/** Current model value. */
|
|
88
|
+
modelValue: any;
|
|
89
|
+
|
|
90
|
+
/** Resolved options (async-loaded or static). */
|
|
91
|
+
options?: FieldOption[];
|
|
92
|
+
|
|
93
|
+
/** Validation state passed to the control. */
|
|
94
|
+
state?: boolean | null;
|
|
95
|
+
|
|
96
|
+
/** Pre-merged disabled state (`isDisabled || isReadonly || isPlaintext`). */
|
|
97
|
+
disabled?: boolean;
|
|
98
|
+
|
|
99
|
+
/** Escape-hatch control props, already guard-merged by DXField. */
|
|
100
|
+
controlProps?: Record<string, any>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Whether async options have resolved (a searchable select must not render
|
|
104
|
+
* before its options load, or it shows the raw id instead of the label).
|
|
105
|
+
*/
|
|
106
|
+
optionsReady?: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
defineProps<Props>();
|
|
110
|
+
|
|
111
|
+
const emit = defineEmits<{
|
|
112
|
+
(event: "update:modelValue", value: any): void;
|
|
113
|
+
}>();
|
|
114
|
+
</script>
|
|
@@ -426,7 +426,7 @@ watch(activeGroupIndex, () => {
|
|
|
426
426
|
overflow-x: hidden;
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
/* Dense rhythm (#95):
|
|
429
|
+
/* Dense rhythm (#95): values proven in a production dashboard, adopted as the default —
|
|
430
430
|
natural-case headers at 0.875rem (no uppercase/letter-spacing), tight group
|
|
431
431
|
spacing. */
|
|
432
432
|
.nav-group {
|