@omnitend/dashboard-for-laravel 0.22.0 → 0.24.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/DXDashboard.vue.d.ts +12 -1
- package/dist/components/extended/DXDashboardNavbar.vue.d.ts +7 -2
- package/dist/components/extended/DXField.vue.d.ts +4 -4
- package/dist/components/extended/DXTable.vue.d.ts +55 -2
- package/dist/components/extended/DXUserAvatar.vue.d.ts +40 -0
- package/dist/dashboard-for-laravel.js +9578 -9410
- package/dist/dashboard-for-laravel.js.map +1 -1
- package/dist/dashboard-for-laravel.umd.cjs +8 -8
- package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +44 -0
- package/docs/public/api-reference.json +120 -6
- package/docs/public/docs-map.md +3 -2
- package/docs/public/llms.txt +3 -2
- package/package.json +1 -1
- package/resources/js/components/extended/DXDashboard.vue +42 -4
- package/resources/js/components/extended/DXDashboardNavbar.vue +5 -18
- package/resources/js/components/extended/DXField.vue +153 -26
- package/resources/js/components/extended/DXTable.vue +516 -193
- package/resources/js/components/extended/DXUserAvatar.vue +104 -0
- package/resources/js/index.ts +1 -0
- package/resources/js/types/index.ts +48 -0
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
<DFormCheckbox
|
|
62
62
|
v-else
|
|
63
63
|
v-model="fieldValue"
|
|
64
|
-
:disabled="isDisabled || isReadonly"
|
|
65
|
-
v-bind="
|
|
64
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
65
|
+
v-bind="controlPropsWithGuards"
|
|
66
66
|
>
|
|
67
67
|
<DXFieldLabel :label="resolvedLabel" class="visually-hidden" />
|
|
68
68
|
</DFormCheckbox>
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
<DFormCheckbox
|
|
98
98
|
v-else
|
|
99
99
|
v-model="fieldValue"
|
|
100
|
-
:disabled="isDisabled || isReadonly"
|
|
101
|
-
v-bind="
|
|
100
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
101
|
+
v-bind="controlPropsWithGuards"
|
|
102
102
|
>
|
|
103
103
|
<DXFieldLabel :label="resolvedLabel" :info="resolvedInfo" />
|
|
104
104
|
</DFormCheckbox>
|
|
@@ -159,8 +159,8 @@
|
|
|
159
159
|
<DXSwitch
|
|
160
160
|
v-else
|
|
161
161
|
v-model="switchModel"
|
|
162
|
-
:disabled="isDisabled || isReadonly"
|
|
163
|
-
v-bind="
|
|
162
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
163
|
+
v-bind="controlPropsWithGuards"
|
|
164
164
|
>
|
|
165
165
|
<DXFieldLabel
|
|
166
166
|
:label="switchText"
|
|
@@ -201,8 +201,8 @@
|
|
|
201
201
|
<DXSwitch
|
|
202
202
|
v-else
|
|
203
203
|
v-model="switchModel"
|
|
204
|
-
:disabled="isDisabled || isReadonly"
|
|
205
|
-
v-bind="
|
|
204
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
205
|
+
v-bind="controlPropsWithGuards"
|
|
206
206
|
>
|
|
207
207
|
<DXFieldLabel :label="switchText" :info="resolvedInfo" />
|
|
208
208
|
</DXSwitch>
|
|
@@ -306,8 +306,8 @@
|
|
|
306
306
|
v-model="fieldValue"
|
|
307
307
|
:field="field"
|
|
308
308
|
:model="model"
|
|
309
|
-
:disabled="isDisabled || isReadonly"
|
|
310
|
-
v-bind="
|
|
309
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
310
|
+
v-bind="controlPropsWithGuards"
|
|
311
311
|
/>
|
|
312
312
|
|
|
313
313
|
<!-- Textarea. Error clearing happens in the v-model setter. -->
|
|
@@ -319,10 +319,35 @@
|
|
|
319
319
|
:rows="field.rows || 3"
|
|
320
320
|
:state="fieldState"
|
|
321
321
|
:disabled="isDisabled"
|
|
322
|
-
:readonly="isReadonly"
|
|
323
|
-
|
|
322
|
+
:readonly="isReadonly || isPlaintext"
|
|
323
|
+
:plaintext="isPlaintext"
|
|
324
|
+
v-bind="inputPropsWithGuards"
|
|
324
325
|
/>
|
|
325
326
|
|
|
327
|
+
<!-- Searchable select: type to filter a long option list, while the
|
|
328
|
+
model keeps the option's VALUE (an id), not the typed text. The
|
|
329
|
+
`autocomplete` type below models the text, so it can't back a
|
|
330
|
+
foreign key. -->
|
|
331
|
+
<template v-else-if="field.type === 'select' && field.searchable">
|
|
332
|
+
<DAutocomplete
|
|
333
|
+
v-if="searchableOptionsReady"
|
|
334
|
+
v-model="fieldValue"
|
|
335
|
+
:options="resolvedOptions"
|
|
336
|
+
:placeholder="field.placeholder"
|
|
337
|
+
:required="field.required"
|
|
338
|
+
:state="fieldState"
|
|
339
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
340
|
+
open-on-focus
|
|
341
|
+
v-bind="controlPropsWithGuards"
|
|
342
|
+
/>
|
|
343
|
+
<!-- BAutocomplete resolves the input's display text from the model
|
|
344
|
+
AT MOUNT and doesn't re-derive it when `options` arrive later,
|
|
345
|
+
so an async list would leave the raw id showing ("51" instead
|
|
346
|
+
of "Waitrose") — and a long list is exactly the case that loads
|
|
347
|
+
async. Hold the control until the options are in. -->
|
|
348
|
+
<DFormInput v-else placeholder="Loading…" disabled />
|
|
349
|
+
</template>
|
|
350
|
+
|
|
326
351
|
<!-- Select (sync or async options) -->
|
|
327
352
|
<DFormSelect
|
|
328
353
|
v-else-if="field.type === 'select'"
|
|
@@ -330,8 +355,8 @@
|
|
|
330
355
|
:required="field.required"
|
|
331
356
|
:options="resolvedOptions"
|
|
332
357
|
:state="fieldState"
|
|
333
|
-
:disabled="isDisabled || isReadonly"
|
|
334
|
-
v-bind="
|
|
358
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
359
|
+
v-bind="controlPropsWithGuards"
|
|
335
360
|
/>
|
|
336
361
|
|
|
337
362
|
<!-- Radio group -->
|
|
@@ -341,8 +366,8 @@
|
|
|
341
366
|
:options="resolvedOptions"
|
|
342
367
|
:required="field.required"
|
|
343
368
|
:state="fieldState"
|
|
344
|
-
:disabled="isDisabled || isReadonly"
|
|
345
|
-
v-bind="
|
|
369
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
370
|
+
v-bind="controlPropsWithGuards"
|
|
346
371
|
/>
|
|
347
372
|
|
|
348
373
|
<!-- File / image upload -->
|
|
@@ -352,8 +377,8 @@
|
|
|
352
377
|
:accept="field.accept || (field.type === 'image' ? 'image/*' : undefined)"
|
|
353
378
|
:required="field.required"
|
|
354
379
|
:state="fieldState"
|
|
355
|
-
:disabled="isDisabled || isReadonly"
|
|
356
|
-
v-bind="
|
|
380
|
+
:disabled="isDisabled || isReadonly || isPlaintext"
|
|
381
|
+
v-bind="controlPropsWithGuards"
|
|
357
382
|
@change="handleFileChange"
|
|
358
383
|
/>
|
|
359
384
|
<img
|
|
@@ -384,8 +409,9 @@
|
|
|
384
409
|
:max="field.max"
|
|
385
410
|
:state="fieldState"
|
|
386
411
|
:disabled="isDisabled"
|
|
387
|
-
:readonly="isReadonly"
|
|
388
|
-
|
|
412
|
+
:readonly="isReadonly || isPlaintext"
|
|
413
|
+
:plaintext="isPlaintext"
|
|
414
|
+
v-bind="inputPropsWithGuards"
|
|
389
415
|
@input="handleCurrencyInput"
|
|
390
416
|
@focus="handleCurrencyFocus"
|
|
391
417
|
@blur="handleCurrencyBlur"
|
|
@@ -401,8 +427,9 @@
|
|
|
401
427
|
:max="field.max"
|
|
402
428
|
:state="fieldState"
|
|
403
429
|
:disabled="isDisabled"
|
|
404
|
-
:readonly="isReadonly"
|
|
405
|
-
|
|
430
|
+
:readonly="isReadonly || isPlaintext"
|
|
431
|
+
:plaintext="isPlaintext"
|
|
432
|
+
v-bind="inputPropsWithGuards"
|
|
406
433
|
/>
|
|
407
434
|
|
|
408
435
|
<template v-if="field.type === 'percentage'" #append>
|
|
@@ -422,9 +449,10 @@
|
|
|
422
449
|
:placeholder="field.placeholder"
|
|
423
450
|
:state="fieldState"
|
|
424
451
|
:disabled="isDisabled"
|
|
425
|
-
:readonly="isReadonly"
|
|
452
|
+
:readonly="isReadonly || isPlaintext"
|
|
453
|
+
:plaintext="isPlaintext"
|
|
426
454
|
autocomplete="off"
|
|
427
|
-
v-bind="
|
|
455
|
+
v-bind="inputPropsWithGuards"
|
|
428
456
|
/>
|
|
429
457
|
<datalist :id="datalistId">
|
|
430
458
|
<option
|
|
@@ -437,6 +465,31 @@
|
|
|
437
465
|
</datalist>
|
|
438
466
|
</template>
|
|
439
467
|
|
|
468
|
+
<!-- Password with a reveal toggle (the default for `type: "password"`).
|
|
469
|
+
Typing a long password blind is the one place the eye really earns
|
|
470
|
+
its keep, so it's opt-out (`revealable: false`) rather than opt-in. -->
|
|
471
|
+
<DInputGroup v-else-if="isRevealablePassword">
|
|
472
|
+
<DFormInput
|
|
473
|
+
v-model="textFieldModel"
|
|
474
|
+
:required="field.required"
|
|
475
|
+
:placeholder="field.placeholder"
|
|
476
|
+
:state="fieldState"
|
|
477
|
+
:disabled="isDisabled"
|
|
478
|
+
:readonly="isReadonly"
|
|
479
|
+
v-bind="passwordInputProps"
|
|
480
|
+
/>
|
|
481
|
+
<template #append>
|
|
482
|
+
<DButton
|
|
483
|
+
variant="outline-secondary"
|
|
484
|
+
:icon="passwordRevealed ? 'eye-slash' : 'eye'"
|
|
485
|
+
:aria-label="passwordRevealed ? 'Hide password' : 'Show password'"
|
|
486
|
+
:aria-pressed="passwordRevealed"
|
|
487
|
+
:disabled="isDisabled"
|
|
488
|
+
@click="passwordRevealed = !passwordRevealed"
|
|
489
|
+
/>
|
|
490
|
+
</template>
|
|
491
|
+
</DInputGroup>
|
|
492
|
+
|
|
440
493
|
<!-- Text-based inputs (text/email/password/number/url/tel/date/time/datetime) -->
|
|
441
494
|
<DFormInput
|
|
442
495
|
v-else
|
|
@@ -449,8 +502,9 @@
|
|
|
449
502
|
:max="field.max"
|
|
450
503
|
:state="fieldState"
|
|
451
504
|
:disabled="isDisabled"
|
|
452
|
-
:readonly="isReadonly"
|
|
453
|
-
|
|
505
|
+
:readonly="isReadonly || isPlaintext"
|
|
506
|
+
:plaintext="isPlaintext"
|
|
507
|
+
v-bind="inputPropsWithGuards"
|
|
454
508
|
/>
|
|
455
509
|
|
|
456
510
|
<!-- Validation error -->
|
|
@@ -505,6 +559,8 @@ import DXSwitch from "./DXSwitch.vue";
|
|
|
505
559
|
import DFormInvalidFeedback from "../base/DFormInvalidFeedback.vue";
|
|
506
560
|
import DFormText from "../base/DFormText.vue";
|
|
507
561
|
import DInputGroup from "../base/DInputGroup.vue";
|
|
562
|
+
import DAutocomplete from "../base/DAutocomplete.vue";
|
|
563
|
+
import DButton from "../base/DButton.vue";
|
|
508
564
|
import DXFieldLabel from "./DXFieldLabel.vue";
|
|
509
565
|
import type { UseFormReturn } from "../../composables/useForm";
|
|
510
566
|
import type { FieldDefinition, FieldOption, FieldType, LabelCols } from "../../types";
|
|
@@ -880,6 +936,77 @@ const isDisabled = computed(() => {
|
|
|
880
936
|
|
|
881
937
|
const isReadonly = computed(() => resolveMaybe(props.field.readonly) ?? false);
|
|
882
938
|
|
|
939
|
+
// Display-only: the value as static text rather than a control. bvn's
|
|
940
|
+
// `plaintext` only swaps the class, so pair it with `readonly` at every call
|
|
941
|
+
// site — otherwise the "static text" is still editable.
|
|
942
|
+
const isPlaintext = computed(() => resolveMaybe(props.field.plaintext) ?? false);
|
|
943
|
+
|
|
944
|
+
const passwordRevealed = ref(false);
|
|
945
|
+
|
|
946
|
+
// See the template: the searchable select can't render before its options
|
|
947
|
+
// resolve, or it shows the raw id instead of the option's label.
|
|
948
|
+
const searchableOptionsReady = computed(
|
|
949
|
+
() => !props.field.optionsLoader || loadedOptions.value !== null,
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
const isRevealablePassword = computed(
|
|
953
|
+
() =>
|
|
954
|
+
props.field.type === "password" &&
|
|
955
|
+
(props.field.revealable ?? true) &&
|
|
956
|
+
!isPlaintext.value,
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
// DXTable reuses ONE DXField instance per field key across modal opens (the
|
|
960
|
+
// v-for is keyed on `field.key`, which is stable across rows) and swaps in a
|
|
961
|
+
// fresh form per record. Without this, revealing row A's password leaves row
|
|
962
|
+
// B's — and the create form's — showing in clear text.
|
|
963
|
+
watch(
|
|
964
|
+
() => [props.form, props.field.key],
|
|
965
|
+
() => {
|
|
966
|
+
passwordRevealed.value = false;
|
|
967
|
+
},
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
/*
|
|
971
|
+
* `inputProps` is an escape hatch that deliberately wins over the field's own
|
|
972
|
+
* bindings — it is spread last. But it must not win over the guarantees the
|
|
973
|
+
* field makes, or those guarantees are only documentation: a `plaintext` field
|
|
974
|
+
* that `inputProps: { readonly: false }` re-opens for editing isn't read-only,
|
|
975
|
+
* and a password whose `type` `inputProps` sets to "text" is unmasked under a
|
|
976
|
+
* toggle still reading "Show password". So the guards are re-applied ON TOP of
|
|
977
|
+
* inputProps. Everything else in inputProps still passes through, and a field
|
|
978
|
+
* that isn't plaintext/readonly is untouched — `inputProps: { readonly: true }`
|
|
979
|
+
* still works as before.
|
|
980
|
+
*/
|
|
981
|
+
|
|
982
|
+
// For controls with a native readonly state (the text family + textarea).
|
|
983
|
+
const inputPropsWithGuards = computed(() => {
|
|
984
|
+
const merged: Record<string, any> = { ...props.field.inputProps };
|
|
985
|
+
if (isPlaintext.value) {
|
|
986
|
+
merged.plaintext = true;
|
|
987
|
+
merged.readonly = true;
|
|
988
|
+
} else if (isReadonly.value) {
|
|
989
|
+
merged.readonly = true;
|
|
990
|
+
}
|
|
991
|
+
return merged;
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// For controls with NO native readonly state (select/radio/checkbox/switch/
|
|
995
|
+
// file/component), where read-only is expressed as `disabled`.
|
|
996
|
+
const controlPropsWithGuards = computed(() => {
|
|
997
|
+
const merged: Record<string, any> = { ...props.field.inputProps };
|
|
998
|
+
if (isDisabled.value || isReadonly.value || isPlaintext.value) {
|
|
999
|
+
merged.disabled = true;
|
|
1000
|
+
}
|
|
1001
|
+
return merged;
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
// The reveal toggle owns the password input's `type`.
|
|
1005
|
+
const passwordInputProps = computed(() => ({
|
|
1006
|
+
...inputPropsWithGuards.value,
|
|
1007
|
+
type: passwordRevealed.value ? "text" : "password",
|
|
1008
|
+
}));
|
|
1009
|
+
|
|
883
1010
|
const inputType = computed<string>(() => {
|
|
884
1011
|
const type: FieldType = props.field.type;
|
|
885
1012
|
if (type === "datetime") return "datetime-local";
|