@omnitend/dashboard-for-laravel 0.42.1 → 0.43.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/components/extended/DXCurrencyInput.vue.d.ts +2 -2
- package/dist/components/extended/DXDashboard.vue.d.ts +14 -6
- package/dist/components/extended/DXDashboardNavbar.vue.d.ts +12 -3
- package/dist/components/extended/DXDashboardSidebar.vue.d.ts +21 -1
- package/dist/components/extended/DXStackingTable.vue.d.ts +25 -0
- package/dist/dashboard-for-laravel.js +10345 -10102
- 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 -1
- package/dist/style.css +1 -1
- package/dist/types/navigation.d.ts +8 -0
- package/dist/utils/pageScrollLock.d.ts +9 -0
- package/docs/public/api-reference.json +82 -8
- package/docs/public/docs-map.md +3 -2
- package/docs/public/llms.txt +3 -2
- package/package.json +1 -1
- package/resources/css/theme.scss +452 -36
- package/resources/js/components/base/DModal.vue +20 -2
- package/resources/js/components/extended/DXCurrencyInput.vue +3 -1
- package/resources/js/components/extended/DXDashboard.vue +236 -43
- package/resources/js/components/extended/DXDashboardNavbar.vue +23 -13
- package/resources/js/components/extended/DXDashboardSidebar.vue +103 -29
- package/resources/js/components/extended/DXNumberStepper.vue +35 -13
- package/resources/js/components/extended/DXStackingTable.vue +264 -0
- package/resources/js/components/extended/DXTableEditorModal.vue +107 -93
- package/resources/js/composables/useContainerWidth.ts +12 -3
- package/resources/js/index.ts +2 -0
- package/resources/js/types/navigation.ts +9 -0
- package/resources/js/utils/pageScrollLock.ts +27 -0
|
@@ -40,104 +40,118 @@
|
|
|
40
40
|
<!-- Tabs stay bare here: the modal body already provides the panel
|
|
41
41
|
boundary, so DXForm's default card-panel-around-tabs (#159) would be
|
|
42
42
|
redundant nested chrome inside the modal. -->
|
|
43
|
-
|
|
43
|
+
<!-- Not editable while the full record is loading (showUrl): the
|
|
44
|
+
response reseeds the form, so anything typed before it lands would
|
|
45
|
+
be silently replaced. `disabled` greys and blocks the native
|
|
46
|
+
controls (Bootstrap styles :disabled); `inert` also blocks focus,
|
|
47
|
+
clicks and keystrokes for custom slot controls that are not form
|
|
48
|
+
elements (a contenteditable, an editor widget). Save and Delete are
|
|
49
|
+
disabled for the same window in the footer. -->
|
|
50
|
+
<fieldset
|
|
44
51
|
v-if="form"
|
|
45
|
-
|
|
46
|
-
:
|
|
47
|
-
:
|
|
48
|
-
:
|
|
49
|
-
:tabs="tabs"
|
|
50
|
-
:layout="layout"
|
|
51
|
-
:label-cols="labelCols"
|
|
52
|
-
:card="card"
|
|
53
|
-
:card-tabs="false"
|
|
54
|
-
:context="item ?? undefined"
|
|
55
|
-
:show-submit="false"
|
|
56
|
-
@update:active-tab="emit('update:activeTab', $event)"
|
|
57
|
-
@submit="emit('save')"
|
|
52
|
+
class="dx-edit-fieldset"
|
|
53
|
+
:disabled="loading"
|
|
54
|
+
:inert="loading"
|
|
55
|
+
:aria-busy="loading ? 'true' : undefined"
|
|
58
56
|
>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
:
|
|
63
|
-
|
|
57
|
+
<DXForm
|
|
58
|
+
:key="formInstanceKey"
|
|
59
|
+
:active-tab="activeTab"
|
|
60
|
+
:form="form"
|
|
61
|
+
:fields="fields"
|
|
62
|
+
:tabs="tabs"
|
|
63
|
+
:layout="layout"
|
|
64
|
+
:label-cols="labelCols"
|
|
65
|
+
:card="card"
|
|
66
|
+
:card-tabs="false"
|
|
67
|
+
:context="item ?? undefined"
|
|
68
|
+
:show-submit="false"
|
|
69
|
+
@update:active-tab="emit('update:activeTab', $event)"
|
|
70
|
+
@submit="emit('save')"
|
|
64
71
|
>
|
|
65
|
-
<!--
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
<!-- Forward DXTable's edit-value(key) → DXForm value(key) -->
|
|
73
|
+
<template
|
|
74
|
+
v-for="key in editValueSlotKeys"
|
|
75
|
+
:key="`ev-${key}`"
|
|
76
|
+
#[`value(${key})`]="sp"
|
|
77
|
+
>
|
|
78
|
+
<!--
|
|
79
|
+
@slot Custom input for field `<key>` in the edit/create modal, forwarded to DXForm. Name it `edit-value(<fieldKey>)`.
|
|
80
|
+
@binding {object} item The row being edited (null in create mode).
|
|
81
|
+
@binding {any} value The current field value.
|
|
82
|
+
@binding {Function} update Call with a new value to update the field.
|
|
83
|
+
@binding {object} field The field definition.
|
|
84
|
+
-->
|
|
85
|
+
<slot
|
|
86
|
+
:name="`edit-value(${key})`"
|
|
87
|
+
:item="item"
|
|
88
|
+
:value="sp.value"
|
|
89
|
+
:update="sp.update"
|
|
90
|
+
:field="sp.field"
|
|
91
|
+
/>
|
|
92
|
+
</template>
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
<!-- Forward edit-span(key) → span(key) -->
|
|
95
|
+
<template
|
|
96
|
+
v-for="key in editSpanSlotKeys"
|
|
97
|
+
:key="`es-${key}`"
|
|
98
|
+
#[`span(${key})`]="sp"
|
|
99
|
+
>
|
|
100
|
+
<!--
|
|
101
|
+
@slot Full-width custom content for field `<key>` in the edit/create modal, forwarded to DXForm's span slot. Name it `edit-span(<fieldKey>)`.
|
|
102
|
+
@binding {object} item The row being edited (null in create mode).
|
|
103
|
+
@binding {any} value The current field value.
|
|
104
|
+
@binding {Function} update Call with a new value to update the field.
|
|
105
|
+
@binding {Function} close Call to close the edit modal.
|
|
106
|
+
-->
|
|
107
|
+
<slot
|
|
108
|
+
:name="`edit-span(${key})`"
|
|
109
|
+
:item="item"
|
|
110
|
+
:value="sp.value"
|
|
111
|
+
:update="sp.update"
|
|
112
|
+
:close="close"
|
|
113
|
+
/>
|
|
114
|
+
</template>
|
|
102
115
|
|
|
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
|
-
|
|
116
|
+
<!-- Forward tab-content / tab-before / tab-after slots -->
|
|
117
|
+
<template
|
|
118
|
+
v-for="key in tabContentSlotKeys"
|
|
119
|
+
:key="`tc-${key}`"
|
|
120
|
+
#[`tab-content(${key})`]="sp"
|
|
121
|
+
>
|
|
122
|
+
<!--
|
|
123
|
+
@slot Replaces the auto-rendered fields of edit-modal tab `<key>` with custom content. Name it `tab-content(<tabKey>)`.
|
|
124
|
+
@binding {object} item The row being edited (null in create mode).
|
|
125
|
+
@binding {object} tab The tab definition.
|
|
126
|
+
-->
|
|
127
|
+
<slot :name="`tab-content(${key})`" :item="item" :tab="sp.tab" />
|
|
128
|
+
</template>
|
|
129
|
+
<template
|
|
130
|
+
v-for="key in tabBeforeSlotKeys"
|
|
131
|
+
:key="`tb-${key}`"
|
|
132
|
+
#[`tab-before(${key})`]="sp"
|
|
133
|
+
>
|
|
134
|
+
<!--
|
|
135
|
+
@slot Custom content rendered before the fields of edit-modal tab `<key>`. Name it `tab-before(<tabKey>)`.
|
|
136
|
+
@binding {object} item The row being edited (null in create mode).
|
|
137
|
+
@binding {object} tab The tab definition.
|
|
138
|
+
-->
|
|
139
|
+
<slot :name="`tab-before(${key})`" :item="item" :tab="sp.tab" />
|
|
140
|
+
</template>
|
|
141
|
+
<template
|
|
142
|
+
v-for="key in tabAfterSlotKeys"
|
|
143
|
+
:key="`taf-${key}`"
|
|
144
|
+
#[`tab-after(${key})`]="sp"
|
|
145
|
+
>
|
|
146
|
+
<!--
|
|
147
|
+
@slot Custom content rendered after the fields of edit-modal tab `<key>`. Name it `tab-after(<tabKey>)`.
|
|
148
|
+
@binding {object} item The row being edited (null in create mode).
|
|
149
|
+
@binding {object} tab The tab definition.
|
|
150
|
+
-->
|
|
151
|
+
<slot :name="`tab-after(${key})`" :item="item" :tab="sp.tab" />
|
|
152
|
+
</template>
|
|
153
|
+
</DXForm>
|
|
154
|
+
</fieldset>
|
|
141
155
|
|
|
142
156
|
<template #footer>
|
|
143
157
|
<div class="d-flex justify-content-between w-100">
|
|
@@ -270,10 +270,17 @@ export function useContainerWidth(
|
|
|
270
270
|
// express without mutating during evaluation. With `hysteresis: 0` it is
|
|
271
271
|
// exactly `width < threshold`. The watcher is `sync` so `isBelow` is never
|
|
272
272
|
// observed one tick behind `width`.
|
|
273
|
+
//
|
|
274
|
+
// The band only applies to a state that came from a MEASUREMENT. Before
|
|
275
|
+
// the first one, `isBelow` is the `initialWidth` guess (true by default),
|
|
276
|
+
// and holding that guess through the band would leave a container that
|
|
277
|
+
// measures just above the threshold (say 590 against 576 + 24) on the
|
|
278
|
+
// narrow layout until it next grows: it never crossed anything.
|
|
273
279
|
const initialThreshold = resolveThreshold();
|
|
274
280
|
const isBelow = ref<boolean>(
|
|
275
281
|
initialThreshold === undefined ? false : startWidth < initialThreshold,
|
|
276
282
|
);
|
|
283
|
+
let isBelowCameFromMeasurement = false;
|
|
277
284
|
watch(
|
|
278
285
|
[width, () => resolveThreshold()],
|
|
279
286
|
([currentWidth, currentThreshold]) => {
|
|
@@ -281,9 +288,11 @@ export function useContainerWidth(
|
|
|
281
288
|
isBelow.value = false;
|
|
282
289
|
return;
|
|
283
290
|
}
|
|
284
|
-
isBelow.value =
|
|
285
|
-
|
|
286
|
-
|
|
291
|
+
isBelow.value =
|
|
292
|
+
isBelow.value && isBelowCameFromMeasurement
|
|
293
|
+
? currentWidth < currentThreshold + hysteresis
|
|
294
|
+
: currentWidth < currentThreshold;
|
|
295
|
+
if (hasMeasured.value) isBelowCameFromMeasurement = true;
|
|
287
296
|
},
|
|
288
297
|
{ flush: "sync" },
|
|
289
298
|
);
|
package/resources/js/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { default as DXStatCard } from "./components/extended/DXStatCard.vue";
|
|
|
20
20
|
// (#142) so chart.js / vue-chartjs stay genuinely optional peers — the main
|
|
21
21
|
// entry no longer references them. See resources/js/charts.ts.
|
|
22
22
|
export { default as DXRepeater } from "./components/extended/DXRepeater.vue";
|
|
23
|
+
export { default as DXStackingTable } from "./components/extended/DXStackingTable.vue";
|
|
23
24
|
/**
|
|
24
25
|
* @deprecated Use `DXForm`. `DXBasicForm` is a thin wrapper around `DXForm`
|
|
25
26
|
* (a flat form is just `DXForm` without a `tabs` prop) that logs a one-time
|
|
@@ -202,4 +203,5 @@ export type {
|
|
|
202
203
|
Navigation,
|
|
203
204
|
NavbarSearchAlign,
|
|
204
205
|
NavbarActionsOnMobile,
|
|
206
|
+
SidebarVariant,
|
|
205
207
|
} from "./types/navigation";
|
|
@@ -38,6 +38,15 @@ export type Navigation = NavigationGroup[];
|
|
|
38
38
|
*/
|
|
39
39
|
export type NavbarSearchAlign = "start" | "center";
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Colour scheme of the dashboard sidebar: `"light"` (the default: a white pane,
|
|
43
|
+
* tinted group headers, a green active item, Poppins) or `"dark"` (the navy
|
|
44
|
+
* rail dfl shipped before). Every colour comes from a `--dx-sidebar-*` custom
|
|
45
|
+
* property, so either can be rebranded in CSS. Shared by DXDashboard (which
|
|
46
|
+
* forwards it as `sidebarVariant`) and DXDashboardSidebar (`variant`).
|
|
47
|
+
*/
|
|
48
|
+
export type SidebarVariant = "light" | "dark";
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* What the navbar actions slot does below the `md` breakpoint: `"wrap"` moves
|
|
43
52
|
* it to its own full-width row (the bar grows), `"hide"` removes it entirely
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The class on <html> that stops the page scrolling behind DXDashboard's open
|
|
3
|
+
* phone menu (styled in theme.scss, below `sm` only).
|
|
4
|
+
*/
|
|
5
|
+
export const PAGE_SCROLL_LOCK_CLASS = 'dx-dashboard-menu-open';
|
|
6
|
+
|
|
7
|
+
// Every owner currently holding the lock. Several dashboards can share a page,
|
|
8
|
+
// so one releasing (closing, unmounting, a <KeepAlive> deactivation) must not
|
|
9
|
+
// unlock the page while another's menu is still open.
|
|
10
|
+
const lockOwners = new Set<object>();
|
|
11
|
+
|
|
12
|
+
const syncRootClass = (): void => {
|
|
13
|
+
if (typeof document === 'undefined') return;
|
|
14
|
+
document.documentElement.classList.toggle(PAGE_SCROLL_LOCK_CLASS, lockOwners.size > 0);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Hold the page scroll lock for `owner`. Idempotent. */
|
|
18
|
+
export const acquirePageScrollLock = (owner: object): void => {
|
|
19
|
+
lockOwners.add(owner);
|
|
20
|
+
syncRootClass();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Release `owner`'s hold; the page unlocks once no owner holds it. Idempotent. */
|
|
24
|
+
export const releasePageScrollLock = (owner: object): void => {
|
|
25
|
+
if (!lockOwners.delete(owner)) return;
|
|
26
|
+
syncRootClass();
|
|
27
|
+
};
|