@omnitend/dashboard-for-laravel 0.42.0 → 0.42.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/DAutocomplete.vue.d.ts +2 -2
- package/dist/dashboard-for-laravel.js +4378 -4334
- package/dist/dashboard-for-laravel.js.map +1 -1
- package/dist/dashboard-for-laravel.umd.cjs +6 -6
- package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/docs/public/api-reference.json +3 -3
- package/docs/public/docs-map.md +1 -1
- package/package.json +1 -1
- package/resources/css/theme.scss +53 -7
- package/resources/js/components/base/DAutocomplete.vue +73 -2
- package/resources/js/components/extended/DXNumberStepper.vue +35 -13
- package/resources/js/components/extended/DXTableEditorModal.vue +107 -93
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated": "2026-10-
|
|
2
|
+
"generated": "2026-10-05T09:32:06.242Z",
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@omnitend/dashboard-for-laravel",
|
|
5
|
-
"version": "0.42.
|
|
5
|
+
"version": "0.42.2"
|
|
6
6
|
},
|
|
7
7
|
"components": {
|
|
8
8
|
"base": [
|
|
@@ -2806,7 +2806,7 @@
|
|
|
2806
2806
|
"name": "DXNumberStepper",
|
|
2807
2807
|
"category": "extended",
|
|
2808
2808
|
"filePath": "resources/js/components/extended/DXNumberStepper.vue",
|
|
2809
|
-
"description": "DXNumberStepper — a number input between a decrease and an increase button. `[ − ][ 12 ][ + ]` as one input group, for quantities that are usually nudged rather than typed (cases on an order line, covers on a booking). - The model is a number, or `null` when the input is empty (never `NaN`, never `\"\"`). Every step and every valid keystroke emits `update:modelValue`; debounce on the consumer's side if each change saves. - A step adds or subtracts `step` from the current value and clamps to `min`/`max`. It does not snap to a grid, so 1.05 + 0.1 is 1.15. The sum is rounded to the decimal places of the value and step, so 0.1 three times is 0.3, not 0.30000000000000004. From empty, a step goes to the in-range value nearest zero. - The button at a bound is disabled (decrease at `min`, increase at `max`). - Typing is free: the value is emitted as typed, even out of range, and is CLAMPED ON BLUR to `min`/`max` (emitting the clamped value). Half-typed input the browser cannot parse (\"-\", \"1e\") emits nothing until it parses. - ArrowUp/ArrowDown on the input go through the same step as the buttons (the browser's own spin-button stepping snaps to a grid; this does not). The native spin arrows are hidden. The buttons are `tabindex=\"-1\"`, so a list of steppers is one Tab stop each; keyboard users step with the arrows. - The buttons are square icon buttons at the input's height (never narrower than tall), soft `secondary` like other in-row actions, labelled \"Decrease\"/\"Increase\" plus the input's `aria-label` when it has one. `class` and `style` go on the group; every other attribute (`aria-label`, `name`, `id`, `placeholder`, `required`, `state`, listeners…) goes on the input. Styles live in theme.scss under `.dx-number-stepper`.",
|
|
2809
|
+
"description": "DXNumberStepper — a number input between a decrease and an increase button. `[ − ][ 12 ][ + ]` as one input group, for quantities that are usually nudged rather than typed (cases on an order line, covers on a booking). - The model is a number, or `null` when the input is empty (never `NaN`, never `\"\"`). Every step and every valid keystroke emits `update:modelValue`; debounce on the consumer's side if each change saves. - Controlled: steps, the bound checks and blur all read `modelValue`. A parent may decline an emitted value (keep 2 when 2.5 is typed): blur then shows 2, + goes to 3, and a declined step is not left on screen. - A step adds or subtracts `step` from the current value and clamps to `min`/`max`. It does not snap to a grid, so 1.05 + 0.1 is 1.15. The sum is rounded to the decimal places of the value and step, so 0.1 three times is 0.3, not 0.30000000000000004. From empty, a step goes to the in-range value nearest zero. - The button at a bound is disabled (decrease at `min`, increase at `max`). - Typing is free: the value is emitted as typed, even out of range, and is CLAMPED ON BLUR to `min`/`max` (emitting the clamped value). Half-typed input the browser cannot parse (\"-\", \"1e\") emits nothing until it parses. - ArrowUp/ArrowDown on the input go through the same step as the buttons (the browser's own spin-button stepping snaps to a grid; this does not). The native spin arrows are hidden. The buttons are `tabindex=\"-1\"`, so a list of steppers is one Tab stop each; keyboard users step with the arrows. - The buttons are square icon buttons at the input's height (never narrower than tall), soft `secondary` like other in-row actions, labelled \"Decrease\"/\"Increase\" plus the input's `aria-label` when it has one. `class` and `style` go on the group; every other attribute (`aria-label`, `name`, `id`, `placeholder`, `required`, `state`, listeners…) goes on the input. Styles live in theme.scss under `.dx-number-stepper`.",
|
|
2810
2810
|
"props": [
|
|
2811
2811
|
{
|
|
2812
2812
|
"name": "modelValue",
|
package/docs/public/docs-map.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Documentation Map
|
|
2
2
|
|
|
3
3
|
> Auto-generated hierarchical overview of all documentation
|
|
4
|
-
> Last updated: 2026-10-
|
|
4
|
+
> Last updated: 2026-10-05T09:32:06.295Z
|
|
5
5
|
|
|
6
6
|
This file provides a complete map of all available documentation for AI agents and developers.
|
|
7
7
|
|
package/package.json
CHANGED
package/resources/css/theme.scss
CHANGED
|
@@ -562,6 +562,47 @@ h6,
|
|
|
562
562
|
}
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
+
// A horizontal row whose content column holds NO form control shows a
|
|
566
|
+
// display-only value: text, a link, a badge, a flex row of text, a small
|
|
567
|
+
// table, a stack of lines. Such a row is baseline-aligned instead of
|
|
568
|
+
// top-aligned, so the first line of text in the value, whatever its shape,
|
|
569
|
+
// sits on the label's first line (the label's padding just moves the shared
|
|
570
|
+
// baseline down). No class or field flag is needed; `.dx-form-plaintext`
|
|
571
|
+
// below still works and is now redundant inside a horizontal form.
|
|
572
|
+
//
|
|
573
|
+
// Every row that holds a control keeps the top alignment above: a checkbox,
|
|
574
|
+
// switch or file input exposes a synthesized or box-edge baseline, so
|
|
575
|
+
// baseline alignment would move them. The list is what the library renders
|
|
576
|
+
// as a control plus the native ones; a `.btn` counts because it is
|
|
577
|
+
// input-height and already lines its text up with the label when
|
|
578
|
+
// top-aligned. Content with no text at all (an image, an icon) sits its
|
|
579
|
+
// bottom edge on the label's baseline, as an inline image would in a line of
|
|
580
|
+
// text. The rule needs `:has()` (Chrome 105, Safari 15.4, Firefox 121); a
|
|
581
|
+
// browser without it falls back to the top alignment.
|
|
582
|
+
$dx-display-only-row: '.dx-form--horizontal .row:has(> .col-form-label):not(:has(> .col-form-label ~ * :is(input:not([type="hidden"]), select, textarea, button, .btn, .form-control, .form-select, .form-check, .input-group, [contenteditable])))';
|
|
583
|
+
|
|
584
|
+
#{$dx-display-only-row} {
|
|
585
|
+
align-items: baseline;
|
|
586
|
+
|
|
587
|
+
// A table's baseline is its first row's, and a row's baseline is its
|
|
588
|
+
// first BASELINE-aligned cell's; with every cell `middle` (the theme's
|
|
589
|
+
// default) CSS falls back to the bottom of the cell, ~6px below the text.
|
|
590
|
+
// Baseline-align the first row's cells so the table offers its text.
|
|
591
|
+
> .col-form-label ~ * table tr:first-child > * {
|
|
592
|
+
vertical-align: baseline;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// A badge's own baseline is its smaller text's, which lands its box ~2px
|
|
596
|
+
// off the label's centre; centre it on its line instead, as
|
|
597
|
+
// `.dx-form-plaintext .badge` does below.
|
|
598
|
+
> .col-form-label ~ * .badge {
|
|
599
|
+
vertical-align: top;
|
|
600
|
+
margin-top: calc(
|
|
601
|
+
(#{math.div($input-line-height, math.div($badge-font-size, 1em)) * 1em} - 1em - #{$badge-padding-y * 2}) / 2
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
565
606
|
// A display-only value in a form row: a badge, a line of text, a short stack
|
|
566
607
|
// of lines. It has no input box, so on its own it sits at the top of the row
|
|
567
608
|
// while the label column (`.col-form-label`) is padded down to an input's text
|
|
@@ -1125,13 +1166,18 @@ fieldset:disabled .btn {
|
|
|
1125
1166
|
}
|
|
1126
1167
|
}
|
|
1127
1168
|
|
|
1128
|
-
// The
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
//
|
|
1134
|
-
|
|
1169
|
+
// The exceptions: two states that are REALLY disabled but are not unavailable
|
|
1170
|
+
// actions, so they keep their variant's own colours (by default the navy
|
|
1171
|
+
// primary fill, #151e2d / #e9f0f8) at full opacity rather than the neutral grey
|
|
1172
|
+
// above:
|
|
1173
|
+
// - a busy button (DButton `loading`, marked `aria-busy="true"`), disabled only
|
|
1174
|
+
// so it can't be pressed twice. Greying it made every save blink navy → grey
|
|
1175
|
+
// → navy, since the grey lasted exactly as long as the request;
|
|
1176
|
+
// - DXSaveButton's saved state ("✓ Saved"), a confirmation.
|
|
1177
|
+
// So saving changes the label and the disabled state but never the colour.
|
|
1178
|
+
// `.btn` + the `:is()` (its most specific argument counts) + the state outranks
|
|
1179
|
+
// the neutral rule (and `fieldset:disabled .btn`).
|
|
1180
|
+
.btn:is(.dx-save-button--saved, [aria-busy="true"]):is(:disabled, .disabled) {
|
|
1135
1181
|
--bs-btn-disabled-opacity: 1;
|
|
1136
1182
|
--bs-btn-disabled-bg: var(--bs-btn-bg);
|
|
1137
1183
|
--bs-btn-disabled-color: var(--bs-btn-color);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* searchable single-select can always reach "none" in-list (#138).
|
|
10
10
|
*/
|
|
11
11
|
<script setup lang="ts">
|
|
12
|
-
import { computed, useAttrs } from "vue";
|
|
12
|
+
import { computed, ref, useAttrs, watch } from "vue";
|
|
13
13
|
import { BAutocomplete } from "bootstrap-vue-next";
|
|
14
14
|
import type { SelectOption } from "bootstrap-vue-next";
|
|
15
15
|
|
|
@@ -127,6 +127,75 @@ const hasValue = computed(() => {
|
|
|
127
127
|
const noClearButton = computed(
|
|
128
128
|
() => attr("noClearButton", "no-clear-button") === true || !hasValue.value,
|
|
129
129
|
);
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
* Late options (0.42.1). bvn's BAutocomplete writes the input's text from the
|
|
133
|
+
* model value through reka-ui's ComboboxInput, which does so on mount and
|
|
134
|
+
* again only when the MODEL changes. A value set before its options load (a
|
|
135
|
+
* DXTable select filter seeded from the URL while the option list is still
|
|
136
|
+
* being fetched) therefore kept showing the raw value ("78") after its label
|
|
137
|
+
* ("Alcopop") arrived. bvn exposes no "resync" method, but the input's text
|
|
138
|
+
* is its `search` v-model, so this wrapper owns that model and, whenever the
|
|
139
|
+
* options change, puts the matching option's label in it.
|
|
140
|
+
*
|
|
141
|
+
* It never overwrites a search the user is typing: while the input has
|
|
142
|
+
* focus, it relabels only text that is still exactly the raw value. Multiple
|
|
143
|
+
* mode needs nothing (bvn derives the chips from the options on every
|
|
144
|
+
* render). A consumer that binds its own `search` owns the text, so it is
|
|
145
|
+
* left alone. A `:key` remount would also work but drops focus and the open
|
|
146
|
+
* menu, so the `search` model is the cleaner hook.
|
|
147
|
+
*/
|
|
148
|
+
const rootElement = ref<HTMLElement | null>(null);
|
|
149
|
+
const searchText = ref("");
|
|
150
|
+
const consumerOwnsSearch = computed(() => attr("search", "search") !== undefined);
|
|
151
|
+
const boundSearch = computed<string>(() =>
|
|
152
|
+
consumerOwnsSearch.value ? String(attr("search", "search")) : searchText.value,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const optionValueAndText = (option: unknown): { value: unknown; text: unknown } => {
|
|
156
|
+
if (option === null || typeof option !== "object") return { value: option, text: option };
|
|
157
|
+
const valueField = (attr("valueField", "value-field") as string | undefined) ?? "value";
|
|
158
|
+
const textField = (attr("textField", "text-field") as string | undefined) ?? "text";
|
|
159
|
+
const record = option as Record<string, unknown>;
|
|
160
|
+
return { value: record[valueField], text: record[textField] };
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// The same equality bvn uses to find the selected option (strict, or by key).
|
|
164
|
+
const sameValue = (optionValue: unknown, modelValue: unknown) => {
|
|
165
|
+
const by = attr("by", "by");
|
|
166
|
+
if (typeof by === "string") {
|
|
167
|
+
return (
|
|
168
|
+
(optionValue as Record<string, unknown> | null)?.[by] ===
|
|
169
|
+
(modelValue as Record<string, unknown> | null)?.[by]
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return optionValue === modelValue;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// A bare `multiple` in a template arrives as "" (it is not a declared prop).
|
|
176
|
+
const isMultiple = computed(() => {
|
|
177
|
+
const multiple = attr("multiple", "multiple");
|
|
178
|
+
return multiple !== undefined && multiple !== null && multiple !== false;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const relabelFromOptions = () => {
|
|
182
|
+
if (isMultiple.value || consumerOwnsSearch.value) return;
|
|
183
|
+
const modelValue = attr("modelValue", "model-value");
|
|
184
|
+
if (modelValue === null || modelValue === undefined || modelValue === "") return;
|
|
185
|
+
const match = computedOptions.value
|
|
186
|
+
.map(optionValueAndText)
|
|
187
|
+
.find((option) => sameValue(option.value, modelValue));
|
|
188
|
+
if (!match) return;
|
|
189
|
+
const label = String(match.text ?? "");
|
|
190
|
+
if (searchText.value === label) return;
|
|
191
|
+
const input = rootElement.value?.querySelector("input");
|
|
192
|
+
const userIsTyping =
|
|
193
|
+
input !== null && input !== undefined && document.activeElement === input;
|
|
194
|
+
if (userIsTyping && searchText.value !== String(modelValue)) return;
|
|
195
|
+
searchText.value = label;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
watch(computedOptions, relabelFromOptions);
|
|
130
199
|
</script>
|
|
131
200
|
|
|
132
201
|
<template>
|
|
@@ -138,12 +207,14 @@ const noClearButton = computed(
|
|
|
138
207
|
root, so `[data-v-x] .input-group` matches nothing in a consumer build
|
|
139
208
|
(#53). A plain element root makes it deterministic.
|
|
140
209
|
-->
|
|
141
|
-
<div class="d-autocomplete">
|
|
210
|
+
<div ref="rootElement" class="d-autocomplete">
|
|
142
211
|
<BAutocomplete
|
|
143
212
|
v-bind="$attrs"
|
|
213
|
+
:search="boundSearch"
|
|
144
214
|
:options="computedOptions"
|
|
145
215
|
:filter-function="effectiveFilterFunction"
|
|
146
216
|
:no-clear-button="noClearButton"
|
|
217
|
+
@update:search="searchText = $event"
|
|
147
218
|
>
|
|
148
219
|
<!-- Dynamically pass through all named slots with their props -->
|
|
149
220
|
<template v-for="(_, name) in $slots" :key="name" #[name]="slotProps">
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
- The model is a number, or `null` when the input is empty (never `NaN`,
|
|
8
8
|
never `""`). Every step and every valid keystroke emits `update:modelValue`;
|
|
9
9
|
debounce on the consumer's side if each change saves.
|
|
10
|
+
- Controlled: steps, the bound checks and blur all read `modelValue`. A
|
|
11
|
+
parent may decline an emitted value (keep 2 when 2.5 is typed): blur then
|
|
12
|
+
shows 2, + goes to 3, and a declined step is not left on screen.
|
|
10
13
|
- A step adds or subtracts `step` from the current value and clamps to
|
|
11
14
|
`min`/`max`. It does not snap to a grid, so 1.05 + 0.1 is 1.15. The sum is
|
|
12
15
|
rounded to the decimal places of the value and step, so 0.1 three times is
|
|
@@ -79,7 +82,7 @@
|
|
|
79
82
|
</template>
|
|
80
83
|
|
|
81
84
|
<script setup lang="ts">
|
|
82
|
-
import { computed, ref, useAttrs, watch } from "vue";
|
|
85
|
+
import { computed, nextTick, ref, useAttrs, watch } from "vue";
|
|
83
86
|
import type { StyleValue } from "vue";
|
|
84
87
|
import DInputGroup from "../base/DInputGroup.vue";
|
|
85
88
|
import DFormInput from "../base/DFormInput.vue";
|
|
@@ -143,26 +146,33 @@ const increaseAriaLabel = computed(
|
|
|
143
146
|
() => props.increaseLabel ?? (fieldLabel.value === "" ? "Increase" : `Increase ${fieldLabel.value}`),
|
|
144
147
|
);
|
|
145
148
|
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
//
|
|
149
|
+
// Controlled: steps, bounds and blur all read `modelValue`, never a value of
|
|
150
|
+
// the stepper's own. A parent may decline an emitted value (a quantity that
|
|
151
|
+
// only takes whole numbers keeps 2 when 2.5 is typed); the prop then does not
|
|
152
|
+
// change, so a private copy would silently diverge — + would step from 2.5.
|
|
153
|
+
const modelNumber = computed<number | null>(() =>
|
|
154
|
+
typeof props.modelValue === "number" && Number.isFinite(props.modelValue)
|
|
155
|
+
? props.modelValue
|
|
156
|
+
: null,
|
|
157
|
+
);
|
|
158
|
+
// The shown text is a local ref only so typing is never rewritten mid-edit
|
|
159
|
+
// ("0." must not become "0"). It resyncs from the model while not focused, on
|
|
160
|
+
// blur, and after every step (see `commit`).
|
|
150
161
|
const displayValue = ref(format(props.modelValue));
|
|
151
162
|
const isFocused = ref(false);
|
|
152
163
|
|
|
153
164
|
watch(
|
|
154
165
|
() => props.modelValue,
|
|
155
166
|
(value) => {
|
|
156
|
-
currentValue.value = value;
|
|
157
167
|
if (!isFocused.value) displayValue.value = format(value);
|
|
158
168
|
},
|
|
159
169
|
);
|
|
160
170
|
|
|
161
171
|
const isAtMin = computed(
|
|
162
|
-
() => props.min !== undefined &&
|
|
172
|
+
() => props.min !== undefined && modelNumber.value !== null && modelNumber.value <= props.min,
|
|
163
173
|
);
|
|
164
174
|
const isAtMax = computed(
|
|
165
|
-
() => props.max !== undefined &&
|
|
175
|
+
() => props.max !== undefined && modelNumber.value !== null && modelNumber.value >= props.max,
|
|
166
176
|
);
|
|
167
177
|
|
|
168
178
|
function format(value: number | null | undefined): string {
|
|
@@ -186,16 +196,30 @@ function clamp(value: number): number {
|
|
|
186
196
|
return clamped;
|
|
187
197
|
}
|
|
188
198
|
|
|
199
|
+
// A step emitted in this tick, before the parent's answer can reach the prop.
|
|
200
|
+
// Two steps in one tick (two synchronous clicks) chain from it rather than
|
|
201
|
+
// both stepping from the same stale prop; it is dropped at the next tick, when
|
|
202
|
+
// `modelValue` says what the parent made of it.
|
|
203
|
+
let unansweredStep: number | null | undefined;
|
|
204
|
+
|
|
205
|
+
// Emit a step (or a blur clamp), then show whatever the parent made of it: the
|
|
206
|
+
// new value if it accepted, its unchanged value if it declined.
|
|
189
207
|
function commit(value: number | null): void {
|
|
190
|
-
currentValue.value = value;
|
|
191
208
|
displayValue.value = format(value);
|
|
209
|
+
const isFirstInTick = unansweredStep === undefined;
|
|
210
|
+
unansweredStep = value;
|
|
192
211
|
emit("update:modelValue", value);
|
|
212
|
+
if (!isFirstInTick) return;
|
|
213
|
+
void nextTick(() => {
|
|
214
|
+
unansweredStep = undefined;
|
|
215
|
+
displayValue.value = format(props.modelValue);
|
|
216
|
+
});
|
|
193
217
|
}
|
|
194
218
|
|
|
195
219
|
function stepBy(direction: 1 | -1): void {
|
|
196
220
|
if (props.disabled) return;
|
|
197
221
|
const stepSize = Number.isFinite(props.step) && props.step > 0 ? props.step : 1;
|
|
198
|
-
const from =
|
|
222
|
+
const from = unansweredStep !== undefined ? unansweredStep : modelNumber.value;
|
|
199
223
|
if (from === null) {
|
|
200
224
|
commit(clamp(0));
|
|
201
225
|
return;
|
|
@@ -213,13 +237,11 @@ function handleInput(event: Event): void {
|
|
|
213
237
|
// A number input reports "" for unparseable text too ("-", "1e");
|
|
214
238
|
// wait for it to parse rather than emitting null mid-typing.
|
|
215
239
|
if (target.validity?.badInput) return;
|
|
216
|
-
currentValue.value = null;
|
|
217
240
|
emit("update:modelValue", null);
|
|
218
241
|
return;
|
|
219
242
|
}
|
|
220
243
|
const parsed = Number(raw);
|
|
221
244
|
if (!Number.isFinite(parsed)) return;
|
|
222
|
-
currentValue.value = parsed;
|
|
223
245
|
emit("update:modelValue", parsed);
|
|
224
246
|
}
|
|
225
247
|
|
|
@@ -227,7 +249,7 @@ function handleInput(event: Event): void {
|
|
|
227
249
|
// merged with these by Vue, so they are not re-emitted.
|
|
228
250
|
function handleBlur(): void {
|
|
229
251
|
isFocused.value = false;
|
|
230
|
-
const value =
|
|
252
|
+
const value = modelNumber.value;
|
|
231
253
|
if (value !== null && clamp(value) !== value) {
|
|
232
254
|
commit(clamp(value));
|
|
233
255
|
return;
|
|
@@ -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">
|