@omnitend/dashboard-for-laravel 0.41.0 → 0.41.2
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/components/base/DButton.vue.d.ts +9 -0
- package/dist/components/base/DModal.vue.d.ts +26 -2
- package/dist/components/extended/DXLoading.vue.d.ts +9 -0
- package/dist/components/extended/DXModalActions.vue.d.ts +12 -0
- package/dist/components/extended/DXSwitch.vue.d.ts +1 -1
- package/dist/components/extended/DXTable.vue.d.ts +4 -0
- package/dist/dashboard-for-laravel.js +3066 -2998
- 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 +3 -0
- package/dist/style.css +2 -2
- package/docs/public/api-reference.json +80 -8
- 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 +44 -39
- package/resources/js/components/base/DAutocomplete.vue +3 -3
- package/resources/js/components/base/DButton.vue +39 -5
- package/resources/js/components/base/DModal.vue +13 -2
- package/resources/js/components/extended/DXLoading.vue +47 -0
- package/resources/js/components/extended/DXModalActions.vue +13 -0
- package/resources/js/components/extended/DXSwitch.vue +16 -10
- package/resources/js/components/extended/DXTable.vue +97 -95
- package/resources/js/components/extended/DXTablePagination.vue +1 -1
- package/resources/js/index.ts +3 -0
|
@@ -72,8 +72,7 @@
|
|
|
72
72
|
supplied their own `table-colgroup` (forwarded generically) —
|
|
73
73
|
so a table with no widths gets no colgroup and renders exactly
|
|
74
74
|
as before. `<col>` widths are authoritative under
|
|
75
|
-
`table-layout: fixed`,
|
|
76
|
-
is the width-determining first row. -->
|
|
75
|
+
`table-layout: fixed`, regardless of any consumer header banners. -->
|
|
77
76
|
<template
|
|
78
77
|
v-if="fieldsHaveWidths && !$slots['table-colgroup']"
|
|
79
78
|
#table-colgroup
|
|
@@ -85,99 +84,100 @@
|
|
|
85
84
|
/>
|
|
86
85
|
</template>
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
consumer's own thead-top content is COMPOSED above it rather
|
|
90
|
-
than being dropped: a grouped column-header banner or a pinned
|
|
91
|
-
totals row sits above the headers where it belongs (#120). -->
|
|
92
|
-
<template v-if="hasFilters || $slots['thead-top']" #thead-top="theadScope">
|
|
87
|
+
<template v-if="$slots['thead-top']" #thead-top="theadScope">
|
|
93
88
|
<!--
|
|
94
|
-
@slot
|
|
89
|
+
@slot Rows above the column headers, such as grouped-column banners or pinned totals. Give it `<tr>`s.
|
|
95
90
|
@binding {object} columns The number of columns in the table.
|
|
96
91
|
@binding {object} fields The table's fields.
|
|
97
92
|
-->
|
|
98
93
|
<slot name="thead-top" v-bind="theadScope" />
|
|
94
|
+
</template>
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
96
|
+
<!-- The table supplies the below-header row and one cell per field. -->
|
|
97
|
+
<template v-if="hasFilters || $slots['thead-sub']" #thead-sub="{ field, ...filterScope }">
|
|
98
|
+
<div v-if="hasFilters" class="dx-table-filter-cell">
|
|
99
|
+
<!-- Text Filter -->
|
|
100
|
+
<DFormInput
|
|
101
|
+
v-if="field.filter === 'text'"
|
|
102
|
+
:model-value="effectiveFilters[filterKeyFor(field)] || ''"
|
|
103
|
+
:placeholder="field.filterPlaceholder || `Search ${field.label || field.key}...`"
|
|
104
|
+
size="sm"
|
|
105
|
+
@update:model-value="handleFilterChange(filterKeyFor(field), $event as string)"
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<!-- Multi-value Select Filter (#51): several values at once; the filters entry is an array -->
|
|
109
|
+
<DAutocomplete
|
|
110
|
+
v-else-if="field.filter === 'select' && field.filterMultiple"
|
|
111
|
+
:model-value="multiFilterValueFor(field)"
|
|
112
|
+
:options="getFieldFilterOptions(field)"
|
|
113
|
+
:placeholder="field.filterPlaceholder || filterAllLabelFor(field)"
|
|
114
|
+
size="sm"
|
|
115
|
+
multiple
|
|
116
|
+
open-on-focus
|
|
117
|
+
@update:model-value="handleMultiSelectFilterChange(field, $event)"
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
<!-- Select Filter: typeahead — browse the full list on focus, or type to narrow; clear (✕) resets to "no filter" -->
|
|
121
|
+
<DAutocomplete
|
|
122
|
+
v-else-if="field.filter === 'select'"
|
|
123
|
+
:model-value="(effectiveFilters[filterKeyFor(field)] as string) || ''"
|
|
124
|
+
:options="getFieldFilterOptions(field)"
|
|
125
|
+
:placeholder="field.filterPlaceholder || filterAllLabelFor(field)"
|
|
126
|
+
size="sm"
|
|
127
|
+
open-on-focus
|
|
128
|
+
@update:model-value="handleSelectFilterChange(field, $event)"
|
|
129
|
+
/>
|
|
130
|
+
|
|
131
|
+
<!-- Native Select Filter (S3b): a plain <select> for consumers who
|
|
132
|
+
want OS-native behaviour (full-height menu, native keyboard/scroll)
|
|
133
|
+
instead of the DAutocomplete typeahead. Shares the select filter's
|
|
134
|
+
option list (getFieldFilterOptions) and change handler
|
|
135
|
+
(handleSelectFilterChange) EXACTLY — so it behaves identically to
|
|
136
|
+
filter:"select" in every way but the control. Single-select only:
|
|
137
|
+
`filterMultiple` is ignored here. The model maps "no filter" to the
|
|
138
|
+
FILTER_ALL_VALUE sentinel (see nativeSelectFilterValue) so the "All …"
|
|
139
|
+
option shows selected in a native control. -->
|
|
140
|
+
<DFormSelect
|
|
141
|
+
v-else-if="field.filter === 'select-native'"
|
|
142
|
+
:model-value="nativeSelectFilterValue(field)"
|
|
143
|
+
:options="getFieldFilterOptions(field) as unknown as Record<string, unknown>[]"
|
|
144
|
+
size="sm"
|
|
145
|
+
@update:model-value="handleSelectFilterChange(field, $event)"
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
<!-- Number Filter -->
|
|
149
|
+
<DFormInput
|
|
150
|
+
v-else-if="field.filter === 'number'"
|
|
151
|
+
:model-value="effectiveFilters[filterKeyFor(field)] || ''"
|
|
152
|
+
:placeholder="field.filterPlaceholder || `Filter ${field.label || field.key}...`"
|
|
153
|
+
type="number"
|
|
154
|
+
size="sm"
|
|
155
|
+
@update:model-value="handleFilterChange(filterKeyFor(field), $event as string)"
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
<!-- Date Filter -->
|
|
159
|
+
<DFormInput
|
|
160
|
+
v-else-if="field.filter === 'date'"
|
|
161
|
+
:model-value="effectiveFilters[filterKeyFor(field)] || ''"
|
|
162
|
+
type="date"
|
|
163
|
+
size="sm"
|
|
164
|
+
@update:model-value="handleFilterChange(filterKeyFor(field), $event as string)"
|
|
165
|
+
/>
|
|
169
166
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
<!-- No filter for this column -->
|
|
168
|
+
<div v-else></div>
|
|
169
|
+
</div>
|
|
170
|
+
<slot name="thead-sub" v-bind="{ ...filterScope, field }" />
|
|
174
171
|
</template>
|
|
175
172
|
|
|
176
173
|
<!-- Custom headers for all fields -->
|
|
177
174
|
<template v-for="field in fields" :key="`head-${field.key}`" #[`head(${field.key})`]="{ label }">
|
|
178
175
|
<div class="d-flex align-items-center justify-content-between gap-2">
|
|
179
176
|
<div class="flex-grow-1">
|
|
180
|
-
<div class="fw-semibold">
|
|
177
|
+
<div class="fw-semibold">
|
|
178
|
+
<DXFieldLabel v-if="field.info" :label="headerLabel(field, label)" :info="field.info" />
|
|
179
|
+
<template v-else>{{ headerLabel(field, label) }}</template>
|
|
180
|
+
</div>
|
|
181
181
|
<small v-if="field.hint" class="text-muted d-block" style="font-weight: normal;">{{ field.hint }}</small>
|
|
182
182
|
</div>
|
|
183
183
|
|
|
@@ -329,6 +329,7 @@ import DTable from "../base/DTable.vue";
|
|
|
329
329
|
import DFormInput from "../base/DFormInput.vue";
|
|
330
330
|
import DFormSelect from "../base/DFormSelect.vue";
|
|
331
331
|
import DAutocomplete from "../base/DAutocomplete.vue";
|
|
332
|
+
import DXFieldLabel from "./DXFieldLabel.vue";
|
|
332
333
|
import DButton from "../base/DButton.vue";
|
|
333
334
|
import DXTablePagination from "./DXTablePagination.vue";
|
|
334
335
|
import DXTableEditorModal from "./DXTableEditorModal.vue";
|
|
@@ -407,6 +408,8 @@ export interface TableField {
|
|
|
407
408
|
filterAllText?: string;
|
|
408
409
|
sortable?: boolean;
|
|
409
410
|
hint?: string;
|
|
411
|
+
/** Help revealed from a focusable info icon beside the column label. */
|
|
412
|
+
info?: string;
|
|
410
413
|
/**
|
|
411
414
|
* The inline filter control for this column, or `false`/omit for none. See
|
|
412
415
|
* `FilterType`. `'select'` (typeahead) and `'select-native'` (plain
|
|
@@ -1318,9 +1321,8 @@ const dottedCellFields = (slots: Record<string, unknown>): TableField[] =>
|
|
|
1318
1321
|
/*
|
|
1319
1322
|
* Per-column widths (#156). A field's `width`/`minWidth` is applied through a
|
|
1320
1323
|
* `<colgroup>` `<col>`, NOT the header `<th>`: with `table-layout: fixed` the
|
|
1321
|
-
* column widths are taken from the FIRST row of cells,
|
|
1322
|
-
*
|
|
1323
|
-
* would be ignored whenever filters are present. A `<col>` width is
|
|
1324
|
+
* column widths are taken from the FIRST row of cells, which may be a
|
|
1325
|
+
* consumer banner rather than the column labels. A `<col>` width is
|
|
1324
1326
|
* authoritative regardless of row order, in both fixed and auto layout.
|
|
1325
1327
|
*/
|
|
1326
1328
|
const sizeToCss = (value: string | number): string =>
|
|
@@ -1771,6 +1773,7 @@ watch(clientSideLastPage, (lastPage) => {
|
|
|
1771
1773
|
// Client-side paginated items (final output)
|
|
1772
1774
|
const clientSidePaginatedItems = computed<T[]>(() => {
|
|
1773
1775
|
if (!isClientSideMode.value) return [];
|
|
1776
|
+
if (!props.showPagination) return clientSideSortedItems.value;
|
|
1774
1777
|
|
|
1775
1778
|
const perPage = effectivePerPage.value;
|
|
1776
1779
|
const start = (clientSidePage.value - 1) * perPage;
|
|
@@ -1787,19 +1790,19 @@ const clientSidePagination = computed<PaginationData>(() => {
|
|
|
1787
1790
|
const lastPage = clientSideLastPage.value;
|
|
1788
1791
|
|
|
1789
1792
|
// Same clamped page the slice uses — see `clientSidePage` (#118).
|
|
1790
|
-
const validPage = clientSidePage.value;
|
|
1793
|
+
const validPage = props.showPagination ? clientSidePage.value : 1;
|
|
1791
1794
|
|
|
1792
|
-
const from = total > 0 ? (validPage - 1) * perPage + 1 : 0;
|
|
1793
|
-
const to = Math.min(validPage * perPage, total);
|
|
1795
|
+
const from = total > 0 ? (props.showPagination ? (validPage - 1) * perPage + 1 : 1) : 0;
|
|
1796
|
+
const to = props.showPagination ? Math.min(validPage * perPage, total) : total;
|
|
1794
1797
|
|
|
1795
1798
|
return {
|
|
1796
1799
|
current_page: validPage,
|
|
1797
|
-
per_page: perPage,
|
|
1800
|
+
per_page: props.showPagination ? perPage : Math.max(1, total),
|
|
1798
1801
|
total,
|
|
1799
1802
|
total_unfiltered: totalUnfiltered !== total ? totalUnfiltered : undefined,
|
|
1800
1803
|
from,
|
|
1801
1804
|
to,
|
|
1802
|
-
last_page: lastPage,
|
|
1805
|
+
last_page: props.showPagination ? lastPage : 1,
|
|
1803
1806
|
};
|
|
1804
1807
|
});
|
|
1805
1808
|
|
|
@@ -1826,7 +1829,7 @@ const handleClientSidePageChange = (page: number) => {
|
|
|
1826
1829
|
// Computed: determine if per-page selector should be shown
|
|
1827
1830
|
// Hide it when total items is less than the smallest page size option
|
|
1828
1831
|
const shouldShowPerPageSelector = computed(() => {
|
|
1829
|
-
if (!props.showPerPageSelector) return false;
|
|
1832
|
+
if (!props.showPagination || !props.showPerPageSelector) return false;
|
|
1830
1833
|
|
|
1831
1834
|
const smallestOption = Math.min(...props.perPageOptions);
|
|
1832
1835
|
const total = isClientSideMode.value
|
|
@@ -2262,7 +2265,7 @@ const refresh = () => {
|
|
|
2262
2265
|
*
|
|
2263
2266
|
* - `head(<key>)` — DXTable draws its own column headers (sort indicators,
|
|
2264
2267
|
* field hints). Forwarding a consumer's would silently drop those.
|
|
2265
|
-
* - `thead-top`
|
|
2268
|
+
* - `thead-top` / `thead-sub` — composed with the below-header filter cells.
|
|
2266
2269
|
*
|
|
2267
2270
|
* `head-end(<key>)` is DXTable's OWN additive header slot (#99) and is likewise
|
|
2268
2271
|
* not forwarded: the inner table has no such slot, and the prefixes below are
|
|
@@ -2312,11 +2315,10 @@ const isTableSlot = (name: string) =>
|
|
|
2312
2315
|
* invisible (#114). Called from the template, it re-evaluates every render.
|
|
2313
2316
|
*
|
|
2314
2317
|
* It also has to EXCLUDE the slots DXTable renders itself: declaring `#thead-top`
|
|
2315
|
-
*
|
|
2316
|
-
* template, silently dropping the filter row and the consumer's banner (#120).
|
|
2318
|
+
* or `#thead-sub` here would override the composed header/filter slots.
|
|
2317
2319
|
*/
|
|
2318
2320
|
const forwardableSlotNames = (slots: Record<string, unknown>): string[] =>
|
|
2319
|
-
Object.keys(slots).filter((name) => isTableSlot(name));
|
|
2321
|
+
Object.keys(slots).filter((name) => name !== 'thead-sub' && isTableSlot(name));
|
|
2320
2322
|
|
|
2321
2323
|
const tableSlotSignature = (slots: Record<string, unknown>): string => {
|
|
2322
2324
|
// DXTable's OWN dotted-cell slots count too (#121). bvn captures its cell
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component
|
|
3
|
-
|
|
3
|
+
Pagination + per-page footer shared by `DXTable` and standalone tables. Renders the pager, the
|
|
4
4
|
per-page selector, and the "X to Y out of Z items" info line (with the
|
|
5
5
|
"Filtered from N" note when a column filter is active).
|
|
6
6
|
|
package/resources/js/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ export { default as DXDashboard } from "./components/extended/DXDashboard.vue";
|
|
|
9
9
|
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
|
+
export { default as DXModalActions } from "./components/extended/DXModalActions.vue";
|
|
13
|
+
export { default as DXLoading } from "./components/extended/DXLoading.vue";
|
|
12
14
|
export { default as DXSwitch } from "./components/extended/DXSwitch.vue";
|
|
13
15
|
export { default as DXCurrencyInput } from "./components/extended/DXCurrencyInput.vue";
|
|
14
16
|
export { default as DXStatCard } from "./components/extended/DXStatCard.vue";
|
|
@@ -23,6 +25,7 @@ export { default as DXRepeater } from "./components/extended/DXRepeater.vue";
|
|
|
23
25
|
*/
|
|
24
26
|
export { default as DXBasicForm } from "./components/extended/DXBasicForm.vue";
|
|
25
27
|
export { default as DXTable } from "./components/extended/DXTable.vue";
|
|
28
|
+
export { default as DXTablePagination } from "./components/extended/DXTablePagination.vue";
|
|
26
29
|
export { default as DXDashboardSidebar } from "./components/extended/DXDashboardSidebar.vue";
|
|
27
30
|
export { default as DXDashboardNavbar } from "./components/extended/DXDashboardNavbar.vue";
|
|
28
31
|
export { default as DXUserAvatar } from "./components/extended/DXUserAvatar.vue";
|