@omnitend/dashboard-for-laravel 0.16.2 → 0.16.3

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.
@@ -75,6 +75,14 @@ export interface FieldDefinition {
75
75
  max?: number | string;
76
76
  /** Symbol shown for `currency` fields (default: the locale's, "£"). */
77
77
  currencySymbol?: string;
78
+ /**
79
+ * For `currency` fields: minor-unit decimal places the *display* value is
80
+ * formatted to on blur and on initial seed (default: 2, e.g. GBP/EUR/USD —
81
+ * pass 0 for a currency with no minor unit, e.g. JPY). The model stays a
82
+ * plain number throughout; only the input's shown text is padded (`3.8` →
83
+ * `3.80`). Typing is never reformatted mid-edit.
84
+ */
85
+ decimals?: number;
78
86
  /**
79
87
  * For `percentage` fields: treat the underlying model value as a 0–1
80
88
  * fraction while showing/editing it as a 0–100 percentage. The model keeps
@@ -1,8 +1,8 @@
1
1
  {
2
- "generated": "2026-07-06T07:46:59.742Z",
2
+ "generated": "2026-07-06T19:02:59.128Z",
3
3
  "package": {
4
4
  "name": "@omnitend/dashboard-for-laravel",
5
- "version": "0.16.1"
5
+ "version": "0.16.2"
6
6
  },
7
7
  "components": {
8
8
  "base": [
@@ -1,7 +1,7 @@
1
1
  # Documentation Map
2
2
 
3
3
  > Auto-generated hierarchical overview of all documentation
4
- > Last updated: 2026-07-06T07:46:59.797Z
4
+ > Last updated: 2026-07-06T19:02:59.193Z
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.16.2",
3
+ "version": "0.16.3",
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",
@@ -152,6 +152,25 @@ $dashboard-navbar-height: 64px;
152
152
 
153
153
  @import "bootstrap-vue-next/dist/bootstrap-vue-next.css";
154
154
 
155
+ // ----------------------------------------------------------------------------
156
+ // Forms
157
+ // ----------------------------------------------------------------------------
158
+ // Bootstrap's compiled `.form-control`/`.form-select` height isn't exposed as
159
+ // a CSS custom property, so a component that needs to match input height
160
+ // exactly (e.g. DXField's `switch` box, which isn't itself a `.form-control`)
161
+ // would otherwise have to hardcode the same padding-y/line-height/border-width
162
+ // formula a second time — and silently drift from this theme if those
163
+ // variables ever change here. Compute it once, from the real theme variables,
164
+ // and expose it as a var so such components can reference the single source
165
+ // of truth instead of duplicating the formula.
166
+ :root {
167
+ // $input-border-width resolves to a CSS var() (Bootstrap 5.3+), which Sass
168
+ // can't multiply directly — fold the *2 into the calc() itself instead.
169
+ --dx-input-height: calc(
170
+ #{$input-line-height * 1em} + #{$input-padding-y * 2} + (2 * #{$input-border-width})
171
+ );
172
+ }
173
+
155
174
  // ----------------------------------------------------------------------------
156
175
  // Dashboard Custom Styles
157
176
  // ----------------------------------------------------------------------------
@@ -247,12 +247,34 @@
247
247
  <template v-if="field.type === 'currency'" #prepend>
248
248
  <span class="input-group-text">{{ field.currencySymbol || "£" }}</span>
249
249
  </template>
250
+
251
+ <!-- Currency: the displayed text is padded to the minor-unit
252
+ precision on blur/seed (e.g. `3.8` -> `3.80`); the model stays
253
+ a plain number and typing is never reformatted mid-edit. -->
254
+ <DFormInput
255
+ v-if="field.type === 'currency'"
256
+ :model-value="currencyDisplayValue"
257
+ type="number"
258
+ :required="field.required"
259
+ :placeholder="field.placeholder"
260
+ :step="field.step ?? '0.01'"
261
+ :min="field.min"
262
+ :max="field.max"
263
+ :state="fieldState"
264
+ :disabled="isDisabled"
265
+ :readonly="isReadonly"
266
+ v-bind="field.inputProps"
267
+ @input="handleCurrencyInput"
268
+ @focus="handleCurrencyFocus"
269
+ @blur="handleCurrencyBlur"
270
+ />
250
271
  <DFormInput
272
+ v-else
251
273
  v-model="numericInputValue"
252
274
  type="number"
253
275
  :required="field.required"
254
276
  :placeholder="field.placeholder"
255
- :step="field.step ?? (field.type === 'currency' ? '0.01' : undefined)"
277
+ :step="field.step"
256
278
  :min="field.min"
257
279
  :max="field.max"
258
280
  :state="fieldState"
@@ -260,6 +282,7 @@
260
282
  :readonly="isReadonly"
261
283
  v-bind="field.inputProps"
262
284
  />
285
+
263
286
  <template v-if="field.type === 'percentage'" #append>
264
287
  <span class="input-group-text">%</span>
265
288
  </template>
@@ -478,6 +501,50 @@ function setValue(value: any): void {
478
501
  props.form.clearError(errorKey.value);
479
502
  }
480
503
 
504
+ // ————————————————— currency display formatting (#69)
505
+
506
+ // The model stays a plain number throughout; only the input's *displayed*
507
+ // text is padded to the minor-unit precision (`3.8` -> `3.80`), on blur and on
508
+ // initial seed. Reformatting reactively on every keystroke (rather than on
509
+ // blur) would fight the user mid-edit — e.g. rounding "3." to "3.00" before
510
+ // they've typed the fractional digits — so a local ref tracks the shown text
511
+ // independently of the numeric model, and is only resynced from the model
512
+ // while the input isn't focused.
513
+ const currencyDecimals = computed(() => props.field.decimals ?? 2);
514
+ const isCurrencyFocused = ref(false);
515
+ const currencyDisplayValue = ref("");
516
+
517
+ function formatCurrency(value: any): string {
518
+ if (value === null || value === undefined || value === "") return "";
519
+ const num = Number(value);
520
+ if (!Number.isFinite(num)) return String(value);
521
+ return num.toFixed(currencyDecimals.value);
522
+ }
523
+
524
+ watch(
525
+ () => fieldValue.value,
526
+ (value) => {
527
+ if (isCurrencyFocused.value) return;
528
+ currencyDisplayValue.value = formatCurrency(value);
529
+ },
530
+ { immediate: true },
531
+ );
532
+
533
+ function handleCurrencyFocus(): void {
534
+ isCurrencyFocused.value = true;
535
+ }
536
+
537
+ function handleCurrencyInput(event: Event): void {
538
+ const raw = (event.target as HTMLInputElement).value;
539
+ currencyDisplayValue.value = raw;
540
+ setValue(raw);
541
+ }
542
+
543
+ function handleCurrencyBlur(): void {
544
+ isCurrencyFocused.value = false;
545
+ currencyDisplayValue.value = formatCurrency(fieldValue.value);
546
+ }
547
+
481
548
  const fieldState = computed(() => props.form.getState(errorKey.value));
482
549
 
483
550
  const resolvedLabel = computed(
@@ -623,17 +690,23 @@ onBeforeUnmount(() => {
623
690
  border-radius: var(--bs-border-radius);
624
691
  }
625
692
 
626
- /* Switch field: a full-width "filled box" toggle, the same height as a select
627
- or text input, that colour-codes its state — a neutral box when off, a filled
628
- success green when on. The label sits on the left and the toggle on the right.
629
- Renders the underlying `.form-check` (from the switch checkbox) as the box. */
693
+ /* Switch field: a compact "filled box" toggle, sized to its content — like the
694
+ plain `checkbox` field type, it doesn't stretch to fill the form row — but
695
+ matching standard input height so it lines up with sibling text/select
696
+ fields. Colour-codes its state: a neutral box when off, a filled success
697
+ green when on. The label sits on the left and the toggle on the right.
698
+ Renders the underlying `.form-check` (from the switch checkbox) as the box.
699
+ `justify-content: space-between` on a full-width box used to leave a wide
700
+ unclickable gap between the label and the toggle (only the tiny toggle
701
+ itself was a real click target); shrinking the box to fit its content
702
+ removes that dead zone entirely. */
630
703
  .dx-switch :deep(.form-check) {
631
- display: flex;
704
+ display: inline-flex;
632
705
  align-items: center;
633
- justify-content: space-between;
634
706
  gap: 0.75rem;
635
- width: 100%;
636
- min-height: calc(1.5em + 0.75rem + 2px); /* match .form-control height */
707
+ width: fit-content;
708
+ max-width: 100%; /* wrap/ellipsize rather than overflow a narrow field */
709
+ min-height: var(--dx-input-height, calc(1.5em + 0.75rem + 2px)); /* match .form-control height */
637
710
  margin: 0;
638
711
  /* Override Bootstrap `.form-switch`'s 2.5em left padding for the floated input. */
639
712
  padding: 0.375rem 0.75rem;
@@ -114,6 +114,15 @@ export interface FieldDefinition {
114
114
  /** Symbol shown for `currency` fields (default: the locale's, "£"). */
115
115
  currencySymbol?: string;
116
116
 
117
+ /**
118
+ * For `currency` fields: minor-unit decimal places the *display* value is
119
+ * formatted to on blur and on initial seed (default: 2, e.g. GBP/EUR/USD —
120
+ * pass 0 for a currency with no minor unit, e.g. JPY). The model stays a
121
+ * plain number throughout; only the input's shown text is padded (`3.8` →
122
+ * `3.80`). Typing is never reformatted mid-edit.
123
+ */
124
+ decimals?: number;
125
+
117
126
  /**
118
127
  * For `percentage` fields: treat the underlying model value as a 0–1
119
128
  * fraction while showing/editing it as a 0–100 percentage. The model keeps