@omnitend/dashboard-for-laravel 0.42.1 → 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.
@@ -1,8 +1,8 @@
1
1
  {
2
- "generated": "2026-10-05T05:06:30.259Z",
2
+ "generated": "2026-10-05T09:32:06.242Z",
3
3
  "package": {
4
4
  "name": "@omnitend/dashboard-for-laravel",
5
- "version": "0.42.1"
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",
@@ -1,7 +1,7 @@
1
1
  # Documentation Map
2
2
 
3
3
  > Auto-generated hierarchical overview of all documentation
4
- > Last updated: 2026-10-05T05:06:30.315Z
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnitend/dashboard-for-laravel",
3
- "version": "0.42.1",
3
+ "version": "0.42.2",
4
4
  "description": "Vue 3 dashboard components for Laravel with Bootstrap Vue Next",
5
5
  "type": "module",
6
6
  "main": "./dist/dashboard-for-laravel.umd.cjs",
@@ -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
- // The value the buttons step from: the last value emitted or received.
147
- const currentValue = ref<number | null>(props.modelValue);
148
- // The shown text tracks a local ref so typing is never rewritten mid-edit
149
- // ("0." must not become "0"); it resyncs from the model while not focused.
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 && currentValue.value !== null && currentValue.value <= props.min,
172
+ () => props.min !== undefined && modelNumber.value !== null && modelNumber.value <= props.min,
163
173
  );
164
174
  const isAtMax = computed(
165
- () => props.max !== undefined && currentValue.value !== null && currentValue.value >= props.max,
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 = currentValue.value;
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 = currentValue.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
- <DXForm
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
- :key="formInstanceKey"
46
- :active-tab="activeTab"
47
- :form="form"
48
- :fields="fields"
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
- <!-- Forward DXTable's edit-value(key) → DXForm value(key) -->
60
- <template
61
- v-for="key in editValueSlotKeys"
62
- :key="`ev-${key}`"
63
- #[`value(${key})`]="sp"
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
- @slot Custom input for field `<key>` in the edit/create modal, forwarded to DXForm. Name it `edit-value(<fieldKey>)`.
67
- @binding {object} item The row being edited (null in create mode).
68
- @binding {any} value The current field value.
69
- @binding {Function} update Call with a new value to update the field.
70
- @binding {object} field The field definition.
71
- -->
72
- <slot
73
- :name="`edit-value(${key})`"
74
- :item="item"
75
- :value="sp.value"
76
- :update="sp.update"
77
- :field="sp.field"
78
- />
79
- </template>
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
- <!-- Forward edit-span(key) → span(key) -->
82
- <template
83
- v-for="key in editSpanSlotKeys"
84
- :key="`es-${key}`"
85
- #[`span(${key})`]="sp"
86
- >
87
- <!--
88
- @slot Full-width custom content for field `<key>` in the edit/create modal, forwarded to DXForm's span slot. Name it `edit-span(<fieldKey>)`.
89
- @binding {object} item The row being edited (null in create mode).
90
- @binding {any} value The current field value.
91
- @binding {Function} update Call with a new value to update the field.
92
- @binding {Function} close Call to close the edit modal.
93
- -->
94
- <slot
95
- :name="`edit-span(${key})`"
96
- :item="item"
97
- :value="sp.value"
98
- :update="sp.update"
99
- :close="close"
100
- />
101
- </template>
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
- <!-- Forward tab-content / tab-before / tab-after slots -->
104
- <template
105
- v-for="key in tabContentSlotKeys"
106
- :key="`tc-${key}`"
107
- #[`tab-content(${key})`]="sp"
108
- >
109
- <!--
110
- @slot Replaces the auto-rendered fields of edit-modal tab `<key>` with custom content. Name it `tab-content(<tabKey>)`.
111
- @binding {object} item The row being edited (null in create mode).
112
- @binding {object} tab The tab definition.
113
- -->
114
- <slot :name="`tab-content(${key})`" :item="item" :tab="sp.tab" />
115
- </template>
116
- <template
117
- v-for="key in tabBeforeSlotKeys"
118
- :key="`tb-${key}`"
119
- #[`tab-before(${key})`]="sp"
120
- >
121
- <!--
122
- @slot Custom content rendered before the fields of edit-modal tab `<key>`. Name it `tab-before(<tabKey>)`.
123
- @binding {object} item The row being edited (null in create mode).
124
- @binding {object} tab The tab definition.
125
- -->
126
- <slot :name="`tab-before(${key})`" :item="item" :tab="sp.tab" />
127
- </template>
128
- <template
129
- v-for="key in tabAfterSlotKeys"
130
- :key="`taf-${key}`"
131
- #[`tab-after(${key})`]="sp"
132
- >
133
- <!--
134
- @slot Custom content rendered after the fields of edit-modal tab `<key>`. Name it `tab-after(<tabKey>)`.
135
- @binding {object} item The row being edited (null in create mode).
136
- @binding {object} tab The tab definition.
137
- -->
138
- <slot :name="`tab-after(${key})`" :item="item" :tab="sp.tab" />
139
- </template>
140
- </DXForm>
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">