@omnitend/dashboard-for-laravel 0.42.2 → 0.44.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 (54) hide show
  1. package/dist/components/base/DFormSelect.vue.d.ts +1 -1
  2. package/dist/components/extended/DXChoiceField.vue.d.ts +3 -3
  3. package/dist/components/extended/DXCurrencyInput.vue.d.ts +2 -2
  4. package/dist/components/extended/DXDashboard.vue.d.ts +14 -6
  5. package/dist/components/extended/DXDashboardNavbar.vue.d.ts +12 -3
  6. package/dist/components/extended/DXDashboardSidebar.vue.d.ts +21 -1
  7. package/dist/components/extended/DXForm.vue.d.ts +66 -1
  8. package/dist/components/extended/DXFormErrorSummary.vue.d.ts +70 -0
  9. package/dist/components/extended/DXFormField.vue.d.ts +3 -0
  10. package/dist/components/extended/DXStackingTable.vue.d.ts +25 -0
  11. package/dist/components/extended/DXTable.vue.d.ts +20 -0
  12. package/dist/components/extended/DXTableEditorModal.vue.d.ts +14 -10
  13. package/dist/composables/useAsyncOptions.d.ts +14 -1
  14. package/dist/composables/useForm.d.ts +26 -0
  15. package/dist/dashboard-for-laravel.js +12002 -11223
  16. package/dist/dashboard-for-laravel.js.map +1 -1
  17. package/dist/dashboard-for-laravel.umd.cjs +8 -8
  18. package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
  19. package/dist/index.d.ts +7 -2
  20. package/dist/style.css +1 -1
  21. package/dist/types/index.d.ts +12 -0
  22. package/dist/types/navigation.d.ts +8 -0
  23. package/dist/utils/formErrorTargets.d.ts +86 -0
  24. package/dist/utils/pageScrollLock.d.ts +9 -0
  25. package/docs/public/api-reference.json +223 -9
  26. package/docs/public/docs-map.md +4 -2
  27. package/docs/public/llms.txt +4 -2
  28. package/package.json +9 -8
  29. package/resources/css/theme.scss +508 -36
  30. package/resources/js/components/base/DFormSelect.vue +5 -2
  31. package/resources/js/components/base/DModal.vue +20 -2
  32. package/resources/js/components/extended/DXChoiceField.vue +42 -26
  33. package/resources/js/components/extended/DXCurrencyInput.vue +3 -1
  34. package/resources/js/components/extended/DXDashboard.vue +236 -43
  35. package/resources/js/components/extended/DXDashboardNavbar.vue +23 -13
  36. package/resources/js/components/extended/DXDashboardSidebar.vue +103 -29
  37. package/resources/js/components/extended/DXField.vue +15 -9
  38. package/resources/js/components/extended/DXForm.vue +676 -28
  39. package/resources/js/components/extended/DXFormErrorSummary.vue +232 -0
  40. package/resources/js/components/extended/DXFormField.vue +11 -2
  41. package/resources/js/components/extended/DXRepeater.vue +30 -4
  42. package/resources/js/components/extended/DXStackingTable.vue +264 -0
  43. package/resources/js/components/extended/DXTable.vue +36 -4
  44. package/resources/js/components/extended/DXTableEditorModal.vue +29 -1
  45. package/resources/js/composables/useAsyncOptions.ts +30 -5
  46. package/resources/js/composables/useContainerWidth.ts +12 -3
  47. package/resources/js/composables/useForm.ts +196 -18
  48. package/resources/js/composables/useResourceEditor.ts +140 -107
  49. package/resources/js/index.ts +17 -0
  50. package/resources/js/types/index.ts +13 -0
  51. package/resources/js/types/navigation.ts +9 -0
  52. package/resources/js/utils/formErrorTargets.ts +317 -0
  53. package/resources/js/utils/pageScrollLock.ts +27 -0
  54. package/scripts/mcp-server.mjs +58 -6
@@ -1,4 +1,4 @@
1
- export type OptionItem = string | number | boolean | Record<string, unknown>;
1
+ export type OptionItem = string | number | boolean | object;
2
2
  export interface Props<TValue = unknown> {
3
3
  modelValue?: TValue | TValue[] | null;
4
4
  options?: readonly OptionItem[];
@@ -13,10 +13,10 @@ interface Props {
13
13
  /** Escape-hatch control props, already guard-merged by DXField. */
14
14
  controlProps?: Record<string, any>;
15
15
  /**
16
- * Whether async options have resolved (a searchable select must not render
17
- * before its options load, or it shows the raw id instead of the label).
16
+ * The field's first async options load is still in flight. A searchable
17
+ * select then shows "Loading…" instead of its value's raw id.
18
18
  */
19
- optionsReady?: boolean;
19
+ optionsPending?: boolean;
20
20
  }
21
21
  declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
22
22
  "update:modelValue": (value: any) => any;
@@ -17,9 +17,9 @@ interface Props {
17
17
  * slot overrides it when the affix needs to be more than a string. */
18
18
  append?: string;
19
19
  }
20
- declare var __VLS_14: {};
20
+ declare var __VLS_13: {};
21
21
  type __VLS_Slots = {} & {
22
- append?: (props: typeof __VLS_14) => any;
22
+ append?: (props: typeof __VLS_13) => any;
23
23
  };
24
24
  declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
25
25
  "update:modelValue": (value: number | null) => any;
@@ -1,4 +1,4 @@
1
- import type { Navigation, NavbarActionsOnMobile, NavbarSearchAlign } from '../../types/navigation';
1
+ import type { Navigation, NavbarActionsOnMobile, NavbarSearchAlign, SidebarVariant } from '../../types/navigation';
2
2
  interface Props {
3
3
  /** Navigation structure for sidebar */
4
4
  navigation: Navigation;
@@ -50,6 +50,13 @@ interface Props {
50
50
  * (single-open accordion). `false`: all groups start open, toggled independently.
51
51
  */
52
52
  autoCollapseInactiveGroups?: boolean;
53
+ /**
54
+ * Sidebar colour scheme, forwarded to DXDashboardSidebar as `variant`:
55
+ * `'light'` (default: white pane, tinted group headers, green active item,
56
+ * Poppins) or `'dark'` (the navy rail). Colours come from the
57
+ * `--dx-sidebar-*` custom properties, so either can be rebranded in CSS.
58
+ */
59
+ sidebarVariant?: SidebarVariant;
53
60
  /** LocalStorage key for sidebar state persistence */
54
61
  storageKey?: string;
55
62
  /**
@@ -59,15 +66,15 @@ interface Props {
59
66
  */
60
67
  dashboardId?: string;
61
68
  }
62
- declare var __VLS_9: string, __VLS_10: any, __VLS_20: string, __VLS_21: any, __VLS_26: {}, __VLS_37: {};
69
+ declare var __VLS_13: string, __VLS_14: any, __VLS_24: string, __VLS_25: any, __VLS_30: {}, __VLS_41: {};
63
70
  type __VLS_Slots = {} & {
64
- [K in NonNullable<typeof __VLS_9>]?: (props: typeof __VLS_10) => any;
71
+ [K in NonNullable<typeof __VLS_13>]?: (props: typeof __VLS_14) => any;
65
72
  } & {
66
- [K in NonNullable<typeof __VLS_20>]?: (props: typeof __VLS_21) => any;
73
+ [K in NonNullable<typeof __VLS_24>]?: (props: typeof __VLS_25) => any;
67
74
  } & {
68
- default?: (props: typeof __VLS_26) => any;
75
+ default?: (props: typeof __VLS_30) => any;
69
76
  } & {
70
- default?: (props: typeof __VLS_37) => any;
77
+ default?: (props: typeof __VLS_41) => any;
71
78
  };
72
79
  declare const __VLS_component: import("vue").DefineComponent<Props, {
73
80
  /**
@@ -93,6 +100,7 @@ declare const __VLS_component: import("vue").DefineComponent<Props, {
93
100
  actionsOnMobile: NavbarActionsOnMobile;
94
101
  userMenuLabel: string;
95
102
  contentMaxWidth: string;
103
+ sidebarVariant: SidebarVariant;
96
104
  storageKey: string;
97
105
  dashboardId: string;
98
106
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -10,9 +10,8 @@ type __VLS_Props = {
10
10
  pageTitle?: string;
11
11
  /**
12
12
  * Horizontal alignment of the search slot content within its region
13
- * (`"start"` = flush left, `"center"` = centred). Applies at every size:
14
- * the region sits inline after the title from `md` up, and is its own
15
- * full-width row below.
13
+ * (`"start"` = flush left, `"center"` = centred). The region sits inline
14
+ * between the toggle/title and the user menu at every width.
16
15
  */
17
16
  searchAlign?: NavbarSearchAlign;
18
17
  /**
@@ -26,6 +25,14 @@ type __VLS_Props = {
26
25
  * announces the trigger as the avatar's initial ("J") rather than as a menu.
27
26
  */
28
27
  userMenuLabel?: string;
28
+ /**
29
+ * Whether the sidebar the toggle controls is showing, exposed as the
30
+ * toggle's `aria-expanded`. Left unset, the toggle carries none.
31
+ * DXDashboard sets it.
32
+ */
33
+ sidebarExpanded?: boolean;
34
+ /** `id` of the sidebar the toggle controls (its `aria-controls`). */
35
+ sidebarId?: string;
29
36
  };
30
37
  declare var __VLS_11: {}, __VLS_13: {}, __VLS_15: {
31
38
  pageTitle: string;
@@ -76,6 +83,8 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}
76
83
  searchAlign: NavbarSearchAlign;
77
84
  actionsOnMobile: NavbarActionsOnMobile;
78
85
  userMenuLabel: string;
86
+ sidebarExpanded: boolean;
87
+ sidebarId: string;
79
88
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
80
89
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
81
90
  export default _default;
@@ -1,4 +1,4 @@
1
- import type { Navigation } from '../../types/navigation';
1
+ import type { Navigation, SidebarVariant } from '../../types/navigation';
2
2
  type __VLS_Props = {
3
3
  /** Grouped navigation to render: an array of groups, each with a label and items. */
4
4
  navigation: Navigation;
@@ -22,6 +22,20 @@ type __VLS_Props = {
22
22
  * `false`: all groups start open and toggle independently.
23
23
  */
24
24
  autoCollapseInactiveGroups?: boolean;
25
+ /**
26
+ * Colour scheme: `'light'` (default) or `'dark'`. Colours come from the
27
+ * `--dx-sidebar-*` custom properties (see the theming guide), so either
28
+ * scheme can be rebranded in CSS.
29
+ */
30
+ variant?: SidebarVariant;
31
+ /**
32
+ * Below `sm`, show the open sidebar as a full-screen menu over the page,
33
+ * with a close button (which emits `close`), instead of a rail beside it;
34
+ * hidden there until opened. The owner must answer `close`, Escape and
35
+ * `navigate`: DXDashboard sets this and does. Off by default, so a
36
+ * standalone sidebar keeps its rail on phones.
37
+ */
38
+ phoneMenu?: boolean;
25
39
  };
26
40
  declare var __VLS_1: {
27
41
  collapsed: boolean;
@@ -42,15 +56,21 @@ type __VLS_Slots = {} & {
42
56
  footer?: (props: typeof __VLS_9) => any;
43
57
  };
44
58
  declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
59
+ close: () => any;
45
60
  toggle: () => any;
61
+ navigate: () => any;
46
62
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
63
+ onClose?: (() => any) | undefined;
47
64
  onToggle?: (() => any) | undefined;
65
+ onNavigate?: (() => any) | undefined;
48
66
  }>, {
49
67
  hidden: boolean;
50
68
  title: string;
51
69
  collapsed: boolean;
52
70
  collapsibleGroups: boolean;
53
71
  autoCollapseInactiveGroups: boolean;
72
+ variant: SidebarVariant;
73
+ phoneMenu: boolean;
54
74
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
55
75
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
56
76
  export default _default;
@@ -1,3 +1,5 @@
1
+ import { type ErrorSummarySelection } from "./DXFormErrorSummary.vue";
2
+ import { type ErrorTarget } from "../../utils/formErrorTargets";
1
3
  import type { UseFormReturn } from "../../composables/useForm";
2
4
  import type { DefineFormReturn } from "../../composables/defineForm";
3
5
  import type { FieldDefinition, FormTab, LabelCols } from "../../types";
@@ -91,16 +93,77 @@ interface Props {
91
93
  * `sm` up and stacks above the input below `sm`.
92
94
  */
93
95
  labelCols?: LabelCols;
96
+ /**
97
+ * Where to list what the last failed submit returned (`form.submitFailure`:
98
+ * the server's message and every validation message, with readable
99
+ * labels):
100
+ *
101
+ * - `"footer"` (default) — directly above the submit button, below any
102
+ * tabs, next to the control the user just pressed.
103
+ * - `"top"` — above the fields, where the form-level alert sits.
104
+ * - `"external"` — the host renders a `DXFormErrorSummary` of its own
105
+ * elsewhere (beside a submit button outside the form, as DXTable's
106
+ * modal does). DXForm renders neither a summary nor, while a failure
107
+ * is listed, the form-level alert.
108
+ * - `false` — no summary; the form-level alert behaves as before.
109
+ *
110
+ * While a summary shows a failure the form-level alert does not render,
111
+ * so a failure shows exactly one alert. Each row naming a visible field is
112
+ * a button that selects the field's tab and focuses it.
113
+ */
114
+ errorSummary?: "footer" | "top" | "external" | false;
115
+ /**
116
+ * The summary's heading when the failure has field errors (default
117
+ * "Couldn't save. Please check:"). A failure with none (a message-only
118
+ * 422, a 500) is headed by the server's message instead.
119
+ */
120
+ errorSummaryTitle?: string;
121
+ /**
122
+ * The summary's sub-heading over the rows no visible field owns (default
123
+ * "Other problems"), shown only when some rows ARE owned.
124
+ */
125
+ errorSummaryOtherTitle?: string;
126
+ /**
127
+ * After a failed submit, scroll the first field with an error into view
128
+ * (once its tab is selected and rendered). Never moves focus. On by
129
+ * default.
130
+ */
131
+ scrollToError?: boolean;
94
132
  }
95
133
  type __VLS_Props = Props;
96
134
  type __VLS_Slots = Record<string, (props: any) => any>;
135
+ /** Select the first visible tab owning a visible errored field, if any. */
97
136
  declare function goToErrorTab(): void;
137
+ /**
138
+ * Take the user to an error a summary lists: select its tab, wait for the
139
+ * pane to render, look the field up in the form (its marker, anywhere
140
+ * inside the form's element, a teleport target inside the form included),
141
+ * scroll it into view and focus its control: an editable control that can
142
+ * take focus, waiting (about a second) while none can, e.g. an async editor
143
+ * still loading or a widget rendered disabled until its data arrives;
144
+ * anything else focusable only as a fallback. A focus counts only once
145
+ * `document.activeElement` is the control. The field is looked up again by
146
+ * its key whenever its element leaves the DOM (a re-render replacing it),
147
+ * and a moment with none at all is waited out. When no marker renders
148
+ * within the field's render frames, or nothing in it takes focus, focus
149
+ * moves to the selected tab's button. Exposed so a summary rendered OUTSIDE
150
+ * the form (DXTable's modal footer) can drive it.
151
+ */
152
+ declare function focusErrorTarget(target: ErrorSummarySelection): Promise<void>;
98
153
  type __VLS_PublicProps = __VLS_Props & {
99
- /** v-model for the active tab index. */
154
+ /**
155
+ * v-model for the active tab, as an index into the VISIBLE tabs. It is a
156
+ * view of `activeTabKey` (below), which is what DXForm actually tracks.
157
+ */
100
158
  "activeTab"?: number;
101
159
  };
102
160
  declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps, {
161
+ /** Select the first visible tab owning a visible errored field, if any. */
103
162
  goToErrorTab: typeof goToErrorTab;
163
+ /** Select an error's tab, then scroll to and focus its field. */
164
+ focusErrorTarget: typeof focusErrorTarget;
165
+ /** What the last failed submit returned, resolved by this form's rules. */
166
+ errorTargets: import("vue").ComputedRef<ErrorTarget[]>;
104
167
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
105
168
  submit: () => any;
106
169
  "update:activeTab": (value: number) => any;
@@ -118,6 +181,8 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
118
181
  autoErrorTab: boolean;
119
182
  cardTabs: boolean;
120
183
  layoutThreshold: number;
184
+ errorSummary: "footer" | "top" | "external" | false;
185
+ scrollToError: boolean;
121
186
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
122
187
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
123
188
  export default _default;
@@ -0,0 +1,70 @@
1
+ import type { UseFormReturn } from "../../composables/useForm";
2
+ import type { DefineFormReturn } from "../../composables/defineForm";
3
+ import type { FieldDefinition, FormTab } from "../../types";
4
+ import { type ErrorTarget } from "../../utils/formErrorTargets";
5
+ /** What a summary row points at (emitted by `select-target`). */
6
+ export interface ErrorSummarySelection {
7
+ /** The owning field's key. */
8
+ fieldKey: string;
9
+ /** The tab holding that field, or `null` in a flat form. */
10
+ tabKey: string | null;
11
+ /** The error key as the server sent it. */
12
+ errorKey: string;
13
+ }
14
+ interface Props {
15
+ /** The form: a `useForm` return or a `defineForm` return. */
16
+ form: UseFormReturn<any> | DefineFormReturn<any>;
17
+ /** Field definitions (optional when `form` is a defineForm return). */
18
+ fields?: FieldDefinition[];
19
+ /** The form's tabs, so rows follow tab order and know their tab. */
20
+ tabs?: FormTab[];
21
+ /**
22
+ * Extra context merged under the form data when evaluating `when` and
23
+ * function labels, as on DXForm.
24
+ */
25
+ context?: Record<string, any>;
26
+ /**
27
+ * Field visibility, replacing the default (`when`/`show` against form
28
+ * data + `context`). A host form passes its own predicate so the summary
29
+ * agrees with what it renders.
30
+ */
31
+ isFieldVisible?: (field: FieldDefinition) => boolean;
32
+ /** Tab visibility, replacing the default (the tab's `when`). */
33
+ isTabVisible?: (tab: FormTab) => boolean;
34
+ /**
35
+ * A field's label, replacing the default (its `label`, resolved against
36
+ * `labelModel`: the form model, or the row for a repeater sub-field).
37
+ */
38
+ resolveLabel?: (field: FieldDefinition, labelModel: Record<string, any>) => string | null | undefined;
39
+ /**
40
+ * Rows already resolved by the host (from `resolveErrorTargets` over
41
+ * `form.submitFailure.errors`). When given, the summary lists these and
42
+ * resolves nothing itself; the message still comes from
43
+ * `form.submitFailure`, and nothing renders while that is null.
44
+ */
45
+ targets?: ErrorTarget[] | null;
46
+ /**
47
+ * The heading when the failure has at least one field error. The
48
+ * server's own message is not used then: Laravel's default 422 message
49
+ * is the first error plus "(and N more errors)", which repeats the first
50
+ * row. A failure with no field errors (a message-only 422, a 500, a
51
+ * network failure) keeps the server's message as the heading, as it is
52
+ * the only information.
53
+ */
54
+ title?: string;
55
+ /**
56
+ * The sub-heading over the rows no visible field owns, listed after the
57
+ * owned rows. Shown only when there are both kinds; when no row is owned
58
+ * they are listed under the heading alone.
59
+ */
60
+ otherTitle?: string;
61
+ }
62
+ declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
63
+ "select-target": (selection: ErrorSummarySelection) => any;
64
+ }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
65
+ "onSelect-target"?: ((selection: ErrorSummarySelection) => any) | undefined;
66
+ }>, {
67
+ title: string;
68
+ otherTitle: string;
69
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
70
+ export default _default;
@@ -15,6 +15,9 @@ interface Props {
15
15
  declare var __VLS_2: `field(${string})`, __VLS_3: {
16
16
  field: FieldDefinition;
17
17
  model: Record<string, any>;
18
+ targetAttrs: {
19
+ "data-dx-field-key": string;
20
+ };
18
21
  }, __VLS_6: `field-before(${string})`, __VLS_7: {
19
22
  field: FieldDefinition;
20
23
  model: Record<string, any>;
@@ -0,0 +1,25 @@
1
+ interface Props {
2
+ /**
3
+ * Stack when the wrapper's content width is below this many px. 576 is
4
+ * Bootstrap's `sm` breakpoint, but measured on the container.
5
+ */
6
+ stackBelow?: number;
7
+ }
8
+ declare var __VLS_1: {
9
+ stacked: boolean;
10
+ };
11
+ type __VLS_Slots = {} & {
12
+ default?: (props: typeof __VLS_1) => any;
13
+ };
14
+ declare const __VLS_component: import("vue").DefineComponent<Props, {
15
+ isStacked: import("vue").ComputedRef<boolean>;
16
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
17
+ stackBelow: number;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
20
+ export default _default;
21
+ type __VLS_WithSlots<T, S> = T & {
22
+ new (): {
23
+ $slots: S;
24
+ };
25
+ };
@@ -317,6 +317,17 @@ export interface Props<TItem = any> {
317
317
  createText?: string;
318
318
  /** Override the modal's Delete button label (default: "Delete {item}"). */
319
319
  deleteText?: string;
320
+ /**
321
+ * Heading of the modal's failed-save summary when the failure has field
322
+ * errors (default "Couldn't save. Please check:"). A failure with none
323
+ * is headed by the server's message instead.
324
+ */
325
+ errorSummaryTitle?: string;
326
+ /**
327
+ * Sub-heading over the summary rows no edit field owns (default "Other
328
+ * problems"), shown only when some rows ARE owned.
329
+ */
330
+ errorSummaryOtherTitle?: string;
320
331
  /** API endpoint pattern for updates (e.g., "/api/products/:id") */
321
332
  editUrl?: string;
322
333
  /**
@@ -416,6 +427,15 @@ export interface Props<TItem = any> {
416
427
  * doesn't *look* clickable, and a click on it can't quietly do something.
417
428
  */
418
429
  rowClickable?: (item: TItem, index: number) => boolean;
430
+ /**
431
+ * Whether clicking a row opens the edit modal (when `editFields` are set).
432
+ * Defaults to `true`. Set `false` when `editFields` exist only for the
433
+ * create modal (`openCreate()` / the create button) and a row click does
434
+ * something else, such as opening the record's own page from `row-clicked`:
435
+ * the row then emits `row-clicked` without also opening the edit modal, and
436
+ * only a `row-clicked` listener makes rows look clickable (#200).
437
+ */
438
+ editOnRowClick?: boolean;
419
439
  /** Enable client-side filtering, sorting, and pagination on items array */
420
440
  clientSide?: boolean;
421
441
  /**
@@ -46,37 +46,41 @@ interface Props {
46
46
  createText?: string;
47
47
  /** Override the Delete button label. */
48
48
  deleteText?: string;
49
+ /** The footer summary's heading for a failure with field errors. */
50
+ errorSummaryTitle?: string;
51
+ /** The footer summary's sub-heading over rows no field owns. */
52
+ errorSummaryOtherTitle?: string;
49
53
  }
50
- declare var __VLS_21: `edit-value(${string})`, __VLS_22: {
54
+ declare var __VLS_23: `edit-value(${string})`, __VLS_24: {
51
55
  item: any;
52
56
  value: any;
53
57
  update: any;
54
58
  field: any;
55
- }, __VLS_25: `edit-span(${string})`, __VLS_26: {
59
+ }, __VLS_27: `edit-span(${string})`, __VLS_28: {
56
60
  item: any;
57
61
  value: any;
58
62
  update: any;
59
63
  close: () => void;
60
- }, __VLS_29: `tab-content(${string})`, __VLS_30: {
64
+ }, __VLS_31: `tab-content(${string})`, __VLS_32: {
61
65
  item: any;
62
66
  tab: any;
63
- }, __VLS_33: `tab-before(${string})`, __VLS_34: {
67
+ }, __VLS_35: `tab-before(${string})`, __VLS_36: {
64
68
  item: any;
65
69
  tab: any;
66
- }, __VLS_37: `tab-after(${string})`, __VLS_38: {
70
+ }, __VLS_39: `tab-after(${string})`, __VLS_40: {
67
71
  item: any;
68
72
  tab: any;
69
73
  };
70
74
  type __VLS_Slots = {} & {
71
- [K in NonNullable<typeof __VLS_21>]?: (props: typeof __VLS_22) => any;
75
+ [K in NonNullable<typeof __VLS_23>]?: (props: typeof __VLS_24) => any;
72
76
  } & {
73
- [K in NonNullable<typeof __VLS_25>]?: (props: typeof __VLS_26) => any;
77
+ [K in NonNullable<typeof __VLS_27>]?: (props: typeof __VLS_28) => any;
74
78
  } & {
75
- [K in NonNullable<typeof __VLS_29>]?: (props: typeof __VLS_30) => any;
79
+ [K in NonNullable<typeof __VLS_31>]?: (props: typeof __VLS_32) => any;
76
80
  } & {
77
- [K in NonNullable<typeof __VLS_33>]?: (props: typeof __VLS_34) => any;
81
+ [K in NonNullable<typeof __VLS_35>]?: (props: typeof __VLS_36) => any;
78
82
  } & {
79
- [K in NonNullable<typeof __VLS_37>]?: (props: typeof __VLS_38) => any;
83
+ [K in NonNullable<typeof __VLS_39>]?: (props: typeof __VLS_40) => any;
80
84
  };
81
85
  declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
82
86
  cancel: () => any;
@@ -7,13 +7,26 @@ import type { FieldDefinition, FieldOption } from "../types";
7
7
  * out-of-order responses clobbering newer ones; loader failures are swallowed
8
8
  * so the last good options (or the static `field.options` fallback) stay put.
9
9
  *
10
- * Extracted verbatim from DXField's inline logic (#135) — behaviour unchanged.
10
+ * Known limit: replacing a field's `optionsLoader` at runtime does NOT restart
11
+ * the "first load" state (loaded options and `firstLoadSettled` carry over).
12
+ * Loader identity is not a stable signal: an inline loader in a computed
13
+ * `fields` array is a new function on every recompute, so treating each new
14
+ * function as a new source would blank the field and refetch on every
15
+ * unrelated edit.
16
+ *
17
+ * Extracted from DXField's inline logic (#135).
11
18
  * `getField` is an accessor (props aren't destructurable without losing
12
19
  * reactivity); `effectiveModel` is the model passed to the loader.
13
20
  */
14
21
  export interface UseAsyncOptionsReturn {
15
22
  /** Options returned by the most recent successful load, or null if none yet. */
16
23
  loadedOptions: Ref<FieldOption[] | null>;
24
+ /**
25
+ * False until the first load has SETTLED (succeeded or failed). Only that
26
+ * first load leaves a field with nothing to show; a later reload keeps the
27
+ * options it has, or the static fallback after a failure.
28
+ */
29
+ firstLoadSettled: Ref<boolean>;
17
30
  /** Trigger a load (no-op when the field has no `optionsLoader`). */
18
31
  loadOptions: () => Promise<void>;
19
32
  }
@@ -4,6 +4,18 @@ export interface ValidationErrors {
4
4
  [key: string]: string[];
5
5
  }
6
6
  export type FormError = ApiError;
7
+ /**
8
+ * What the most recent failed submit returned (#194): the server's message and
9
+ * a COPY of its validation errors, frozen at the moment the submit failed.
10
+ * The live `form.errors` empties as the user fixes fields; this does not, so a
11
+ * summary built from it keeps listing every message until the next submit.
12
+ */
13
+ export interface SubmitFailure {
14
+ /** A message fit to show a user (never empty, never a raw exception). */
15
+ message: string;
16
+ /** The validation errors the submit returned (`{}` when none). */
17
+ errors: ValidationErrors;
18
+ }
7
19
  export interface FormSubmitOptions<TPayload = unknown, TResponse = unknown> {
8
20
  onSuccess?: (data: TResponse) => void;
9
21
  onError?: (error: FormError) => void;
@@ -29,6 +41,19 @@ export interface FormState<TData extends Record<string, any>> {
29
41
  */
30
42
  wasSuccessful: boolean;
31
43
  shouldShowMessage: boolean;
44
+ /**
45
+ * What the most recent submit failed with, or `null`. Set when a submit
46
+ * fails (validation or otherwise; an aborted submit does not count),
47
+ * cleared when a submit starts and when one succeeds. Untouched by
48
+ * `clearError`, `clearErrors`, `setErrors` and field edits.
49
+ */
50
+ submitFailure: SubmitFailure | null;
51
+ /**
52
+ * How many submits have failed, counting every failure (including a
53
+ * `preserveErrors` resubmit that returned the same errors), so a watcher
54
+ * can react to "a submit just failed" even when nothing else changed.
55
+ */
56
+ failedSubmitCount: number;
32
57
  }
33
58
  export interface UseFormReturn<TData extends Record<string, any>> extends FormState<TData> {
34
59
  hasErrors: ComputedRef<boolean>;
@@ -50,4 +75,5 @@ export interface UseFormReturn<TData extends Record<string, any>> extends FormSt
50
75
  patch: <TResponse = unknown>(url: string, options?: FormSubmitOptions<TData, TResponse>) => Promise<TResponse>;
51
76
  delete: <TResponse = unknown>(url: string, options?: FormSubmitOptions<TData, TResponse>) => Promise<TResponse>;
52
77
  }
78
+ export declare function isAbortError(error: unknown): boolean;
53
79
  export declare function useForm<TData extends Record<string, any>>(initialData?: TData): UseFormReturn<TData>;