@omnitend/dashboard-for-laravel 0.41.2 → 0.42.1
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/README.md +2 -1
- package/dist/assets/MavenPro-Bold-35b16f06.woff2 +0 -0
- package/dist/assets/MavenPro-Medium-febc8674.woff2 +0 -0
- package/dist/assets/MavenPro-Regular-aee3df9e.woff2 +0 -0
- package/dist/assets/MavenPro-SemiBold-c555dbfa.woff2 +0 -0
- package/dist/assets/Poppins-Medium-5800fa57.woff2 +0 -0
- package/dist/assets/Poppins-SemiBold-94a36267.woff2 +0 -0
- package/dist/charts.js +148 -130
- 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/DAutocomplete.vue.d.ts +2 -2
- package/dist/components/charts/chartTheme.d.ts +14 -2
- package/dist/components/extended/DXField.vue.d.ts +16 -5
- package/dist/components/extended/DXForm.vue.d.ts +25 -9
- package/dist/components/extended/DXNumberStepper.vue.d.ts +33 -0
- package/dist/components/extended/DXSaveButton.vue.d.ts +38 -0
- package/dist/components/extended/DXTable.vue.d.ts +6 -2
- package/dist/composables/useForm.d.ts +7 -0
- package/dist/dashboard-for-laravel.js +9317 -9052
- 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 +2 -0
- package/dist/style.css +2 -2
- package/dist/types/index.d.ts +20 -3
- package/docs/public/api-reference.json +165 -14
- package/docs/public/docs-map.md +4 -2
- package/docs/public/llms.txt +4 -2
- package/package.json +1 -1
- package/resources/css/theme.scss +558 -89
- package/resources/fonts/maven-pro/MavenPro-Bold.woff2 +0 -0
- package/resources/fonts/maven-pro/MavenPro-Medium.woff2 +0 -0
- package/resources/fonts/maven-pro/MavenPro-Regular.woff2 +0 -0
- package/resources/fonts/maven-pro/MavenPro-SemiBold.woff2 +0 -0
- package/resources/fonts/maven-pro/OFL.txt +93 -0
- package/resources/fonts/poppins/OFL.txt +93 -0
- package/resources/fonts/poppins/Poppins-Medium.woff2 +0 -0
- package/resources/fonts/poppins/Poppins-SemiBold.woff2 +0 -0
- package/resources/js/components/base/DAutocomplete.vue +73 -2
- package/resources/js/components/charts/DXBarChart.vue +2 -1
- package/resources/js/components/charts/DXDoughnutChart.vue +2 -1
- package/resources/js/components/charts/DXLineChart.vue +2 -1
- package/resources/js/components/charts/chartTheme.ts +68 -21
- package/resources/js/components/extended/DXDashboard.vue +4 -2
- package/resources/js/components/extended/DXDashboardSidebar.vue +52 -20
- package/resources/js/components/extended/DXField.vue +59 -28
- package/resources/js/components/extended/DXForm.vue +122 -21
- package/resources/js/components/extended/DXNumberStepper.vue +243 -0
- package/resources/js/components/extended/DXSaveButton.vue +82 -0
- package/resources/js/components/extended/DXTable.vue +44 -15
- package/resources/js/composables/useForm.ts +11 -0
- package/resources/js/composables/useToast.ts +1 -1
- package/resources/js/index.ts +2 -0
- package/resources/js/types/index.ts +21 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
DXSaveButton — a save button that confirms the save in place.
|
|
3
|
+
|
|
4
|
+
Three states, driven by two booleans the consumer owns:
|
|
5
|
+
|
|
6
|
+
- idle (`saving` and `saved` both false) — the normal Save button.
|
|
7
|
+
- saving (`saving`) — DButton's busy state: disabled at once, spinner after
|
|
8
|
+
the anti-flash delay, `savingText` if given.
|
|
9
|
+
- saved (`saved` and not `saving`) — "✓ Saved" (tick icon) and genuinely
|
|
10
|
+
`disabled`, until the consumer clears `saved` (typically on the next edit
|
|
11
|
+
to the form). The colour does not change: the button keeps its own
|
|
12
|
+
variant (navy primary by default) at full strength, neither the neutral
|
|
13
|
+
grey of a disabled button nor a faded one. Only the label and the
|
|
14
|
+
disabled state say it is saved.
|
|
15
|
+
|
|
16
|
+
`saving` wins over `saved`, so re-saving from the saved state shows busy.
|
|
17
|
+
The label change is announced: the button itself is a polite live region,
|
|
18
|
+
and while it is `aria-busy` (saving) announcements wait until it settles,
|
|
19
|
+
so a screen reader hears "Saved" once the save lands.
|
|
20
|
+
|
|
21
|
+
Every other attribute (type, block, size, class, listeners…) passes through
|
|
22
|
+
to DButton. DXForm uses this for its submit button.
|
|
23
|
+
-->
|
|
24
|
+
<template>
|
|
25
|
+
<DButton
|
|
26
|
+
v-bind="$attrs"
|
|
27
|
+
:variant="variant"
|
|
28
|
+
:icon="isShowingSaved ? 'check-lg' : icon"
|
|
29
|
+
:loading="saving"
|
|
30
|
+
:loading-text="savingText"
|
|
31
|
+
:disabled="disabled || isShowingSaved"
|
|
32
|
+
:class="{ 'dx-save-button--saved': isShowingSaved }"
|
|
33
|
+
aria-live="polite"
|
|
34
|
+
>
|
|
35
|
+
<template v-if="isShowingSaved">{{ savedText }}</template>
|
|
36
|
+
<slot v-else>Save</slot>
|
|
37
|
+
</DButton>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { computed } from "vue";
|
|
42
|
+
import type { ButtonVariant } from "bootstrap-vue-next";
|
|
43
|
+
import DButton from "../base/DButton.vue";
|
|
44
|
+
|
|
45
|
+
defineOptions({ inheritAttrs: false });
|
|
46
|
+
|
|
47
|
+
interface Props {
|
|
48
|
+
/** A save is in flight: busy/spinner state (DButton's `loading`). */
|
|
49
|
+
saving?: boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The form's current contents have been saved: show `savedText` with a
|
|
53
|
+
* tick and disable the button. Clear it when the form changes.
|
|
54
|
+
*/
|
|
55
|
+
saved?: boolean;
|
|
56
|
+
|
|
57
|
+
/** Label for the saved state. @default 'Saved' */
|
|
58
|
+
savedText?: string;
|
|
59
|
+
|
|
60
|
+
/** Label shown beside the spinner while saving (DButton's `loadingText`). */
|
|
61
|
+
savingText?: string;
|
|
62
|
+
|
|
63
|
+
/** Variant in every state; the saved state keeps it. @default 'primary' */
|
|
64
|
+
variant?: ButtonVariant | null;
|
|
65
|
+
|
|
66
|
+
/** Leading icon when not saved. The saved state shows `check-lg`. */
|
|
67
|
+
icon?: string;
|
|
68
|
+
|
|
69
|
+
/** Disable the button regardless of state. */
|
|
70
|
+
disabled?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
74
|
+
saving: false,
|
|
75
|
+
saved: false,
|
|
76
|
+
savedText: "Saved",
|
|
77
|
+
variant: "primary",
|
|
78
|
+
disabled: false,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const isShowingSaved = computed(() => props.saved && !props.saving);
|
|
82
|
+
</script>
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
<DFormInput
|
|
101
101
|
v-if="field.filter === 'text'"
|
|
102
102
|
:model-value="effectiveFilters[filterKeyFor(field)] || ''"
|
|
103
|
-
:placeholder="field.filterPlaceholder ||
|
|
103
|
+
:placeholder="field.filterPlaceholder || `${field.label || field.key}…`"
|
|
104
104
|
size="sm"
|
|
105
105
|
@update:model-value="handleFilterChange(filterKeyFor(field), $event as string)"
|
|
106
106
|
/>
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
<DFormInput
|
|
150
150
|
v-else-if="field.filter === 'number'"
|
|
151
151
|
:model-value="effectiveFilters[filterKeyFor(field)] || ''"
|
|
152
|
-
:placeholder="field.filterPlaceholder ||
|
|
152
|
+
:placeholder="field.filterPlaceholder || `${field.label || field.key}…`"
|
|
153
153
|
type="number"
|
|
154
154
|
size="sm"
|
|
155
155
|
@update:model-value="handleFilterChange(filterKeyFor(field), $event as string)"
|
|
@@ -577,7 +577,10 @@ export interface Props<TItem = any> {
|
|
|
577
577
|
/** Error message */
|
|
578
578
|
error?: string | null;
|
|
579
579
|
|
|
580
|
-
/**
|
|
580
|
+
/**
|
|
581
|
+
* Pagination data (Inertia mode). Omit it on a plain `items` table and the
|
|
582
|
+
* footer counts the rows given as one page.
|
|
583
|
+
*/
|
|
581
584
|
pagination?: PaginationData;
|
|
582
585
|
|
|
583
586
|
/** Show pagination controls */
|
|
@@ -685,7 +688,8 @@ export interface Props<TItem = any> {
|
|
|
685
688
|
/**
|
|
686
689
|
* Label column width for the edit modal's horizontal layout, forwarded to
|
|
687
690
|
* `DXForm`'s `labelCols` (a single width or a per-breakpoint object). Only
|
|
688
|
-
* meaningful when `editLayout` is "horizontal".
|
|
691
|
+
* meaningful when `editLayout` is "horizontal". Omitted: DXForm's default
|
|
692
|
+
* 45% label column.
|
|
689
693
|
*/
|
|
690
694
|
editLabelCols?: LabelCols;
|
|
691
695
|
|
|
@@ -989,8 +993,11 @@ if (
|
|
|
989
993
|
console.warn('[DXTable] clientSide mode ignores provider, apiUrl and inertiaUrl props. Data is processed locally from items.');
|
|
990
994
|
}
|
|
991
995
|
|
|
992
|
-
//
|
|
993
|
-
|
|
996
|
+
// Effective busy state. Provider mode: `busy` (two-way with BTable's own
|
|
997
|
+
// loading). Items and client-side modes: `busy`, the documented prop, or the
|
|
998
|
+
// deprecated `loading`; these modes used to read only `loading`, so a page
|
|
999
|
+
// passing `:busy` showed the empty text while its rows were still loading.
|
|
1000
|
+
const effectiveBusy = computed(() => isProviderMode.value ? props.busy : props.busy || props.loading);
|
|
994
1001
|
|
|
995
1002
|
/*
|
|
996
1003
|
* `perPage`, `sortBy` and `filters` are dual-purpose: with a `v-model` they are
|
|
@@ -2023,9 +2030,10 @@ const handleApiPageChange = (page: number) => {
|
|
|
2023
2030
|
* `currentPage` and `perPage`; `busy` is the raw prop (two-way with BTable's
|
|
2024
2031
|
* provider loading via `@update:busy`).
|
|
2025
2032
|
* - client-side: rows are pre-sliced by `clientSidePaginatedItems`, and local
|
|
2026
|
-
* sorting is off (we sort in `clientSideSortedItems`)
|
|
2033
|
+
* sorting is off (we sort in `clientSideSortedItems`); `busy` is
|
|
2034
|
+
* `effectiveBusy`.
|
|
2027
2035
|
* - inertia: server-paginated `items`, local sorting off, `busy` is
|
|
2028
|
-
* `effectiveBusy` (the `loading`
|
|
2036
|
+
* `effectiveBusy` (`busy` or the deprecated `loading` in this mode).
|
|
2029
2037
|
*
|
|
2030
2038
|
* A key absent from the returned object is simply not bound, which is what the
|
|
2031
2039
|
* original blocks did (e.g. non-provider modes never set `provider`/`currentPage`,
|
|
@@ -2041,7 +2049,7 @@ const tableModeBindings = computed<Record<string, unknown>>(() => {
|
|
|
2041
2049
|
};
|
|
2042
2050
|
}
|
|
2043
2051
|
if (isClientSideMode.value) {
|
|
2044
|
-
return { items: clientSidePaginatedItems.value, noLocalSorting: true };
|
|
2052
|
+
return { items: clientSidePaginatedItems.value, noLocalSorting: true, busy: effectiveBusy.value };
|
|
2045
2053
|
}
|
|
2046
2054
|
// inertia
|
|
2047
2055
|
return { items: resolvedItems.value, noLocalSorting: true, busy: effectiveBusy.value };
|
|
@@ -2050,13 +2058,32 @@ const tableModeBindings = computed<Record<string, unknown>>(() => {
|
|
|
2050
2058
|
/*
|
|
2051
2059
|
* The pagination metadata the single footer renders, by mode (#123). Null means
|
|
2052
2060
|
* no footer: only provider mode has that case (a custom provider with no
|
|
2053
|
-
* `pagination` prop). Client-side always
|
|
2054
|
-
*
|
|
2055
|
-
*
|
|
2061
|
+
* `pagination` prop). Client-side and inertia always render a footer.
|
|
2062
|
+
*
|
|
2063
|
+
* Inertia mode is also what a plain `:items` table with no `pagination` lands
|
|
2064
|
+
* in. Its footer read the `withDefaults` placeholder (`total: 0`), so two
|
|
2065
|
+
* rendered rows sat above "0 items.". With no `pagination` passed, the rows
|
|
2066
|
+
* given ARE the whole set: describe them as one page.
|
|
2056
2067
|
*/
|
|
2068
|
+
const unpaginatedItemsPagination = computed<PaginationData>(() => {
|
|
2069
|
+
const itemCount = resolvedItems.value?.length ?? 0;
|
|
2070
|
+
return {
|
|
2071
|
+
current_page: 1,
|
|
2072
|
+
// One page holding every row, so the pager (shown only when
|
|
2073
|
+
// total > per_page) never appears. Floor of 1 keeps the page
|
|
2074
|
+
// maths away from a zero divisor.
|
|
2075
|
+
per_page: Math.max(itemCount, 1),
|
|
2076
|
+
total: itemCount,
|
|
2077
|
+
from: itemCount > 0 ? 1 : 0,
|
|
2078
|
+
to: itemCount,
|
|
2079
|
+
};
|
|
2080
|
+
});
|
|
2081
|
+
|
|
2057
2082
|
const activePagination = computed<PaginationData | null>(() => {
|
|
2058
2083
|
if (isClientSideMode.value) return clientSidePagination.value;
|
|
2059
|
-
if (isInertiaMode.value)
|
|
2084
|
+
if (isInertiaMode.value) {
|
|
2085
|
+
return paginationWasProvided ? props.pagination : unpaginatedItemsPagination.value;
|
|
2086
|
+
}
|
|
2060
2087
|
if (isProviderMode.value) return providerPagination.value;
|
|
2061
2088
|
return null;
|
|
2062
2089
|
});
|
|
@@ -2347,7 +2374,9 @@ const tableSlotSignature = (slots: Record<string, unknown>): string => {
|
|
|
2347
2374
|
// three data modes from one place.
|
|
2348
2375
|
const tablePassthroughProps = computed(() => ({
|
|
2349
2376
|
footClone: props.footClone,
|
|
2350
|
-
|
|
2377
|
+
// No empty text while rows are loading: "No … found" during a load reads as
|
|
2378
|
+
// "there is no data". Provider mode is unchanged by this.
|
|
2379
|
+
showEmpty: props.showEmpty && (isProviderMode.value || effectiveBusy.value === false),
|
|
2351
2380
|
emptyText:
|
|
2352
2381
|
props.emptyText ??
|
|
2353
2382
|
(hasActiveFilters.value
|
|
@@ -2457,7 +2486,7 @@ defineExpose({
|
|
|
2457
2486
|
}
|
|
2458
2487
|
|
|
2459
2488
|
/* Muted header titles (#157): in a data table the CONTENT is what matters —
|
|
2460
|
-
near-black bold headers compete with it. Grey (
|
|
2489
|
+
near-black bold headers compete with it. Grey (weight 500) keeps the
|
|
2461
2490
|
structure without the shout. Consumers re-louden via the token. */
|
|
2462
2491
|
:deep(thead th) {
|
|
2463
2492
|
color: var(--dx-table-header-color, var(--bs-secondary-color));
|
|
@@ -31,6 +31,13 @@ export interface FormState<TData extends Record<string, any>> {
|
|
|
31
31
|
message: string;
|
|
32
32
|
touched: Record<string, boolean>;
|
|
33
33
|
recentlySuccessful: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the most recent submission succeeded. Unlike
|
|
36
|
+
* `recentlySuccessful` (which clears itself after 1.5s) this stays true
|
|
37
|
+
* until the next submission starts, and is false after a failure — the
|
|
38
|
+
* Inertia `wasSuccessful` semantics. DXForm's saved state reads it.
|
|
39
|
+
*/
|
|
40
|
+
wasSuccessful: boolean;
|
|
34
41
|
shouldShowMessage: boolean;
|
|
35
42
|
}
|
|
36
43
|
|
|
@@ -172,6 +179,7 @@ export function useForm<TData extends Record<string, any>>(
|
|
|
172
179
|
message: "",
|
|
173
180
|
touched: {},
|
|
174
181
|
recentlySuccessful: false,
|
|
182
|
+
wasSuccessful: false,
|
|
175
183
|
shouldShowMessage: false,
|
|
176
184
|
});
|
|
177
185
|
|
|
@@ -263,6 +271,7 @@ export function useForm<TData extends Record<string, any>>(
|
|
|
263
271
|
): Promise<TResponse> => {
|
|
264
272
|
inFlightCount += 1;
|
|
265
273
|
state.processing = true;
|
|
274
|
+
state.wasSuccessful = false;
|
|
266
275
|
if (!options.preserveErrors) clearErrors();
|
|
267
276
|
|
|
268
277
|
const payloadRaw = (
|
|
@@ -326,6 +335,7 @@ export function useForm<TData extends Record<string, any>>(
|
|
|
326
335
|
});
|
|
327
336
|
|
|
328
337
|
state.recentlySuccessful = true;
|
|
338
|
+
state.wasSuccessful = true;
|
|
329
339
|
if (successTimer !== null) clearTimeout(successTimer);
|
|
330
340
|
successTimer = setTimeout(() => {
|
|
331
341
|
state.recentlySuccessful = false;
|
|
@@ -338,6 +348,7 @@ export function useForm<TData extends Record<string, any>>(
|
|
|
338
348
|
}
|
|
339
349
|
return data;
|
|
340
350
|
} catch (err) {
|
|
351
|
+
state.wasSuccessful = false;
|
|
341
352
|
const { errors, message } = errorsFromLaravel(err);
|
|
342
353
|
setErrors(errors);
|
|
343
354
|
setMessage(message);
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
// class, driving Bootstrap's own `--bs-toast-*` variables cleanly. An
|
|
14
14
|
// unrecognised variant (e.g. "primary", "dark", or none) falls through to
|
|
15
15
|
// BVN's default `.text-bg-*` styling unchanged.
|
|
16
|
-
const THEMED_TOAST_VARIANTS = new Set(["success", "danger", "warning", "info"]);
|
|
16
|
+
const THEMED_TOAST_VARIANTS = new Set(["success", "danger", "warning", "info", "pending"]);
|
|
17
17
|
|
|
18
18
|
function themeToastParam(param: Record<string, unknown>): Record<string, unknown> {
|
|
19
19
|
const variant = param.variant as string | null | undefined;
|
package/resources/js/index.ts
CHANGED
|
@@ -10,9 +10,11 @@ export { default as DXForm } from "./components/extended/DXForm.vue";
|
|
|
10
10
|
export { default as DXField } from "./components/extended/DXField.vue";
|
|
11
11
|
export { default as DXFieldLabel } from "./components/extended/DXFieldLabel.vue";
|
|
12
12
|
export { default as DXModalActions } from "./components/extended/DXModalActions.vue";
|
|
13
|
+
export { default as DXSaveButton } from "./components/extended/DXSaveButton.vue";
|
|
13
14
|
export { default as DXLoading } from "./components/extended/DXLoading.vue";
|
|
14
15
|
export { default as DXSwitch } from "./components/extended/DXSwitch.vue";
|
|
15
16
|
export { default as DXCurrencyInput } from "./components/extended/DXCurrencyInput.vue";
|
|
17
|
+
export { default as DXNumberStepper } from "./components/extended/DXNumberStepper.vue";
|
|
16
18
|
export { default as DXStatCard } from "./components/extended/DXStatCard.vue";
|
|
17
19
|
// Chart components moved to the `@omnitend/dashboard-for-laravel/charts` entry
|
|
18
20
|
// (#142) so chart.js / vue-chartjs stay genuinely optional peers — the main
|
|
@@ -73,8 +73,9 @@ export type OptionsLoader = (model: any) => Promise<FieldOption[]>;
|
|
|
73
73
|
* Deliberately excludes `boolean` (which BFormGroup's own `labelCols` prop
|
|
74
74
|
* accepts): Vue's type-based `defineProps` auto-defaults an omitted prop to
|
|
75
75
|
* `false` (not `undefined`) whenever its declared type includes `boolean`,
|
|
76
|
-
* which would silently break the `field.labelCols ?? formLabelCols
|
|
77
|
-
*
|
|
76
|
+
* which would silently break the `field.labelCols ?? formLabelCols` fallback
|
|
77
|
+
* chain used to resolve this value (`undefined` at the end of it means the
|
|
78
|
+
* default 45% label column, `--dx-form-label-width`).
|
|
78
79
|
*/
|
|
79
80
|
export type LabelCols =
|
|
80
81
|
| number
|
|
@@ -241,7 +242,8 @@ export interface FieldDefinition {
|
|
|
241
242
|
|
|
242
243
|
/**
|
|
243
244
|
* Per-field override of DXForm's `labelCols` prop, for horizontal
|
|
244
|
-
* layout. Falls back to the form-level `labelCols` when omitted
|
|
245
|
+
* layout. Falls back to the form-level `labelCols` when omitted, and to
|
|
246
|
+
* the default 45% label column (`--dx-form-label-width`) when neither is set.
|
|
245
247
|
*/
|
|
246
248
|
labelCols?: LabelCols;
|
|
247
249
|
|
|
@@ -636,3 +638,19 @@ export interface FormTab {
|
|
|
636
638
|
/** Lazily mount tab content until first activated */
|
|
637
639
|
lazy?: boolean;
|
|
638
640
|
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* The theme's extra colour variant, `pending`: waiting, and the next move is
|
|
644
|
+
* not yours ("awaiting payment", "sent to printer, not yet confirmed").
|
|
645
|
+
* theme.scss paints it everywhere a stock variant is painted (badge, soft and
|
|
646
|
+
* outline button, alert, text, subtle utilities, table row, list-group item,
|
|
647
|
+
* toast). Registering it on bootstrap-vue-next's `BaseColorVariant` adds it to
|
|
648
|
+
* every variant union derived from that interface (`ColorVariant`,
|
|
649
|
+
* `ButtonVariant` incl. `outline-pending`, the `-subtle` and `-emphasis`
|
|
650
|
+
* forms), so `<DBadge variant="pending">` type-checks.
|
|
651
|
+
*/
|
|
652
|
+
declare module "bootstrap-vue-next" {
|
|
653
|
+
interface BaseColorVariant {
|
|
654
|
+
pending: unknown;
|
|
655
|
+
}
|
|
656
|
+
}
|