@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.
Files changed (57) hide show
  1. package/dist/charts.js +82 -90
  2. package/dist/charts.js.map +1 -1
  3. package/dist/charts.umd.cjs +1 -1
  4. package/dist/charts.umd.cjs.map +1 -1
  5. package/dist/components/base/DButton.vue.d.ts +1 -1
  6. package/dist/components/charts/DXBarChart.vue.d.ts +3 -18
  7. package/dist/components/charts/DXLineChart.vue.d.ts +3 -18
  8. package/dist/components/charts/useThemedChart.d.ts +35 -0
  9. package/dist/components/extended/DXChoiceField.vue.d.ts +26 -0
  10. package/dist/components/extended/DXDashboardSidebar.vue.d.ts +1 -1
  11. package/dist/components/extended/DXField.vue.d.ts +10 -113
  12. package/dist/components/extended/DXFieldShell.vue.d.ts +73 -0
  13. package/dist/components/extended/DXForm.vue.d.ts +28 -6
  14. package/dist/components/extended/DXNumericField.vue.d.ts +23 -0
  15. package/dist/components/extended/DXRepeater.vue.d.ts +4 -4
  16. package/dist/components/extended/DXRepeaterCards.vue.d.ts +45 -0
  17. package/dist/components/extended/DXTable.vue.d.ts +107 -2
  18. package/dist/components/extended/DXTableEditorModal.vue.d.ts +22 -2
  19. package/dist/components/extended/DXUserAvatar.vue.d.ts +1 -1
  20. package/dist/composables/useAsyncOptions.d.ts +20 -0
  21. package/dist/composables/useContainerWidth.d.ts +96 -0
  22. package/dist/composables/useResourceEditor.d.ts +2 -1
  23. package/dist/dashboard-for-laravel.js +10513 -10443
  24. package/dist/dashboard-for-laravel.js.map +1 -1
  25. package/dist/dashboard-for-laravel.umd.cjs +7 -7
  26. package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
  27. package/dist/index.d.ts +5 -2
  28. package/dist/style.css +1 -1
  29. package/dist/types/index.d.ts +146 -0
  30. package/dist/utils/formSchema.d.ts +59 -0
  31. package/docs/public/api-reference.json +494 -33
  32. package/docs/public/docs-map.md +1 -1
  33. package/docs/public/llms.txt +5 -2
  34. package/package.json +1 -1
  35. package/resources/css/theme.scss +80 -2
  36. package/resources/js/components/charts/DXBarChart.vue +4 -43
  37. package/resources/js/components/charts/DXLineChart.vue +4 -43
  38. package/resources/js/components/charts/useThemedChart.ts +69 -0
  39. package/resources/js/components/extended/DXChoiceField.vue +114 -0
  40. package/resources/js/components/extended/DXDashboardSidebar.vue +1 -1
  41. package/resources/js/components/extended/DXField.vue +328 -615
  42. package/resources/js/components/extended/DXFieldShell.vue +124 -0
  43. package/resources/js/components/extended/DXForm.vue +92 -14
  44. package/resources/js/components/extended/DXNumericField.vue +144 -0
  45. package/resources/js/components/extended/DXRepeater.vue +53 -175
  46. package/resources/js/components/extended/DXRepeaterCards.vue +133 -0
  47. package/resources/js/components/extended/DXTable.vue +418 -43
  48. package/resources/js/components/extended/DXTableEditorModal.vue +39 -2
  49. package/resources/js/components/extended/DXTablePagination.vue +19 -3
  50. package/resources/js/components/extended/DXTableShell.vue +70 -1
  51. package/resources/js/composables/defineForm.ts +8 -44
  52. package/resources/js/composables/useAsyncOptions.ts +61 -0
  53. package/resources/js/composables/useContainerWidth.ts +303 -0
  54. package/resources/js/composables/useResourceEditor.ts +24 -38
  55. package/resources/js/index.ts +40 -0
  56. package/resources/js/types/index.ts +231 -0
  57. package/resources/js/utils/formSchema.ts +126 -0
@@ -1,7 +1,7 @@
1
1
  # Documentation Map
2
2
 
3
3
  > Auto-generated hierarchical overview of all documentation
4
- > Last updated: 2026-07-20T14:11:55.365Z
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
 
@@ -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 70 Vue 3 components for building Laravel dashboards with Bootstrap Vue Next.
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 (57 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnitend/dashboard-for-laravel",
3
- "version": "0.33.1",
3
+ "version": "0.35.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",
@@ -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
- .text-bg-#{$name}:not(.toast) {
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
- .progress-bar.bg-#{$name} {
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
- registerCharts,
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<Props>(), {
25
+ const props = withDefaults(defineProps<ThemedChartProps<"bar">>(), {
49
26
  currencySymbol: "£",
50
27
  showLegend: undefined,
51
28
  });
52
29
 
53
- const chartData = computed(() => ({
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
- registerCharts,
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<Props>(), {
25
+ const props = withDefaults(defineProps<ThemedChartProps<"line">>(), {
49
26
  currencySymbol: "£",
50
27
  showLegend: undefined,
51
28
  });
52
29
 
53
- const chartData = computed(() => ({
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): greendragon's cutover values, adopted as the default —
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 {