@ditojs/admin 2.100.0 → 3.0.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.
Files changed (84) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +3421 -2662
  3. package/dist/dito-admin.umd.js +6 -6
  4. package/package.json +81 -45
  5. package/src/DitoComponent.js +1 -1
  6. package/src/DitoContext.js +51 -0
  7. package/src/DitoTypeComponent.js +6 -1
  8. package/src/components/DitoAffixes.vue +6 -3
  9. package/src/components/DitoContainer.vue +19 -10
  10. package/src/components/DitoCreateButton.vue +11 -2
  11. package/src/components/DitoDialog.vue +21 -2
  12. package/src/components/DitoForm.vue +156 -83
  13. package/src/components/DitoLabel.vue +3 -2
  14. package/src/components/DitoPane.vue +55 -11
  15. package/src/components/DitoPanel.vue +33 -5
  16. package/src/components/DitoPanels.vue +2 -1
  17. package/src/components/DitoRoot.vue +13 -6
  18. package/src/components/DitoSchema.vue +278 -145
  19. package/src/components/DitoSchemaInlined.vue +1 -1
  20. package/src/components/DitoTabs.vue +16 -18
  21. package/src/components/DitoTrail.vue +5 -4
  22. package/src/components/DitoTreeItem.vue +17 -6
  23. package/src/components/DitoView.vue +25 -2
  24. package/src/mixins/DitoMixin.js +42 -7
  25. package/src/mixins/ItemMixin.js +3 -6
  26. package/src/mixins/NumberMixin.js +47 -31
  27. package/src/mixins/OptionsMixin.js +122 -71
  28. package/src/mixins/ResourceMixin.js +5 -2
  29. package/src/mixins/RouteMixin.js +39 -1
  30. package/src/mixins/SortableMixin.js +0 -13
  31. package/src/mixins/SourceMixin.js +142 -95
  32. package/src/mixins/TextMixin.js +1 -1
  33. package/src/mixins/TypeMixin.js +37 -22
  34. package/src/mixins/ValidationMixin.js +9 -27
  35. package/src/mixins/ValidatorMixin.js +5 -7
  36. package/src/mixins/ValueMixin.js +5 -2
  37. package/src/types/DitoTypeButton.vue +1 -1
  38. package/src/types/DitoTypeCheckbox.vue +1 -1
  39. package/src/types/DitoTypeCheckboxes.vue +1 -1
  40. package/src/types/DitoTypeCode.vue +8 -1
  41. package/src/types/DitoTypeColor.vue +20 -34
  42. package/src/types/DitoTypeComponent.vue +1 -1
  43. package/src/types/DitoTypeComputed.vue +4 -27
  44. package/src/types/DitoTypeDate.vue +16 -5
  45. package/src/types/DitoTypeLabel.vue +1 -4
  46. package/src/types/DitoTypeList.vue +9 -5
  47. package/src/types/DitoTypeMarkup.vue +25 -13
  48. package/src/types/DitoTypeMultiselect.vue +26 -6
  49. package/src/types/DitoTypeNumber.vue +1 -1
  50. package/src/types/DitoTypeObject.vue +10 -7
  51. package/src/types/DitoTypePanel.vue +19 -6
  52. package/src/types/DitoTypeProgress.vue +1 -1
  53. package/src/types/DitoTypeRadio.vue +1 -1
  54. package/src/types/DitoTypeSection.vue +32 -4
  55. package/src/types/DitoTypeSelect.vue +1 -1
  56. package/src/types/DitoTypeSlider.vue +1 -1
  57. package/src/types/DitoTypeSwitch.vue +1 -1
  58. package/src/types/DitoTypeText.vue +13 -15
  59. package/src/types/DitoTypeTextarea.vue +1 -1
  60. package/src/types/DitoTypeTreeList.vue +12 -7
  61. package/src/types/DitoTypeUpload.vue +1 -2
  62. package/src/utils/DataSchemaResolver.js +141 -0
  63. package/src/utils/DataSchemaResolver.test.js +132 -0
  64. package/src/utils/FormModel.js +556 -0
  65. package/src/utils/FormModel.test.js +808 -0
  66. package/src/utils/data.js +20 -0
  67. package/src/utils/data.test.js +28 -0
  68. package/src/utils/filter.js +3 -2
  69. package/src/utils/options.js +3 -0
  70. package/src/utils/schema/data.js +617 -0
  71. package/src/utils/schema/data.test.js +471 -0
  72. package/src/utils/schema/lookup.js +266 -0
  73. package/src/utils/schema/lookup.test.js +61 -0
  74. package/src/utils/schema/setup.js +354 -0
  75. package/src/utils/schema/structure.js +172 -0
  76. package/src/utils/schema/structure.test.js +57 -0
  77. package/src/utils/schema/types.js +34 -0
  78. package/src/utils/schema/validation.js +142 -0
  79. package/src/utils/schema/validation.test.js +395 -0
  80. package/src/utils/units.js +70 -3
  81. package/src/utils/units.test.js +71 -0
  82. package/types/index.d.ts +149 -59
  83. package/src/mixins/DataMixin.js +0 -131
  84. package/src/utils/schema.js +0 -1121
package/types/index.d.ts CHANGED
@@ -326,9 +326,10 @@ export interface BaseSchema<$Item>
326
326
  */
327
327
  default?: OrItemAccessor<$Item>
328
328
  /**
329
- * Computes and sets the field value reactively. If
330
- * the callback returns `undefined`, the current value
331
- * is preserved.
329
+ * Computes and sets the field value reactively, also
330
+ * when the field isn't rendered, unless its `if`
331
+ * evaluates to `false`. If the callback returns
332
+ * `undefined`, the current value is preserved.
332
333
  */
333
334
  compute?: ItemAccessor<$Item>
334
335
  /**
@@ -367,19 +368,26 @@ export interface SchemaDitoMixin<$Item> {
367
368
  * change before value validation occurs. Rule changes do not influence how
368
369
  * the component is rendered.
369
370
  */
370
- rules?: {
371
- /** Override whether the field is required. */
372
- required?: boolean
373
- }
371
+ rules?: SchemaRules
372
+ }
373
+
374
+ /**
375
+ * Validation rules shared by all schemas, extended by the individual types.
376
+ */
377
+ export interface SchemaRules {
378
+ /** Override whether the field is required. */
379
+ required?: boolean
374
380
  }
375
381
 
376
382
  /**
377
383
  * Return false to mark event as handled and stop it from propagating to parent
378
384
  * schemas.
379
385
  */
386
+ // Handlers can be async, as events are awaited, e.g. by buttons that show
387
+ // their progress while their `click` handler runs.
380
388
  export type ItemEventHandler<$Item = any> = (
381
389
  itemParams: DitoContext<$Item>
382
- ) => void | false
390
+ ) => void | false | Promise<void | false>
383
391
 
384
392
  export type OpenEventHandler<$Item = any> = (
385
393
  itemParams: DitoContext<$Item> & { open: boolean }
@@ -565,14 +573,20 @@ export interface SchemaTypeMixin<$Item> extends SchemaFields<$Item> {
565
573
  * @see {@link SchemaFields.onInitialize} and other `on`-
566
574
  * prefixed properties for per-event documentation.
567
575
  */
568
- events?: SchemaEvents<$Item> & {
569
- /** @see {@link BaseSchema.onFocus} */
570
- focus?: ItemEventHandler<$Item>
571
- /** @see {@link BaseSchema.onBlur} */
572
- blur?: ItemEventHandler<$Item>
573
- /** @see {@link BaseSchema.onInput} */
574
- input?: ItemEventHandler<$Item>
575
- }
576
+ events?: BaseSchemaEvents<$Item>
577
+ }
578
+
579
+ /**
580
+ * Grouped event handlers shared by all schemas, extended by the individual
581
+ * types.
582
+ */
583
+ export interface BaseSchemaEvents<$Item> extends SchemaEvents<$Item> {
584
+ /** @see {@link BaseSchema.onFocus} */
585
+ focus?: ItemEventHandler<$Item>
586
+ /** @see {@link BaseSchema.onBlur} */
587
+ blur?: ItemEventHandler<$Item>
588
+ /** @see {@link BaseSchema.onInput} */
589
+ input?: ItemEventHandler<$Item>
576
590
  }
577
591
 
578
592
  export interface SchemaSourceMixin<$Item> {
@@ -643,9 +657,18 @@ export interface SchemaSourceMixin<$Item> {
643
657
  /** Buttons for the source. */
644
658
  buttons?: Buttons<$Item>
645
659
  /**
646
- * Whether to wrap primitive values in objects.
660
+ * The key under which primitive values are wrapped in objects, so that the
661
+ * source's forms can edit them, e.g. `'value'` for a list of strings.
662
+ */
663
+ wrapPrimitives?: string
664
+ /**
665
+ * Whether the forms of the source's items directly mutate the source's data
666
+ * instead of editing a copy that is applied when submitting. Forms that
667
+ * mutate data are never dirty.
668
+ *
669
+ * @defaultValue `false`
647
670
  */
648
- wrapPrimitives?: boolean
671
+ mutate?: boolean
649
672
  /**
650
673
  * URL path for the source items.
651
674
  */
@@ -722,7 +745,9 @@ export interface SchemaSourceMixin<$Item> {
722
745
  deletable?: OrItemAccessor<$Item, {}, boolean | { label: string }>
723
746
  /**
724
747
  * The column used for the order resulting from dragging around list entries
725
- * when the `draggable` property of the list schema is set to `true`.
748
+ * when the `draggable` property of the list schema is set to `true`. Items
749
+ * are numbered when the data is loaded, and when they are added, removed or
750
+ * reordered.
726
751
  */
727
752
  orderKey?: string
728
753
  /**
@@ -919,14 +944,15 @@ export interface SchemaNumberMixin<$Item> {
919
944
  */
920
945
  step?: OrItemAccessor<$Item, {}, number>
921
946
  /**
922
- * The amount of decimals to round to.
947
+ * The maximum amount of decimals allowed, validated on input.
923
948
  */
924
949
  decimals?: OrItemAccessor<$Item, {}, number>
925
950
  /** Validation rules for numeric constraints. */
926
- rules?: Omit<SchemaNumberMixin<$Item>, 'rules'> & {
927
- /** Restrict the value to whole numbers. */
928
- integer?: boolean
929
- }
951
+ rules?: SchemaRules &
952
+ Omit<SchemaNumberMixin<$Item>, 'rules'> & {
953
+ /** Restrict the value to whole numbers. */
954
+ integer?: boolean
955
+ }
930
956
  }
931
957
 
932
958
  export interface SchemaTextMixin<$Item> {
@@ -964,7 +990,7 @@ export interface SchemaDataMixin<$Item> {
964
990
  /**
965
991
  * Data source for the component.
966
992
  */
967
- data?: OrItemAccessor<$Item, {}, any>
993
+ data?: OrItemAccessor<$Item, {}, Record<string, any>>
968
994
  /**
969
995
  * Path to retrieve data from parent context.
970
996
  */
@@ -1128,7 +1154,7 @@ export interface InputSchema<$Item = any>
1128
1154
  | 'tel'
1129
1155
  | 'password'
1130
1156
  | 'creditcard'
1131
- rules?: {
1157
+ rules?: SchemaRules & {
1132
1158
  text?: boolean
1133
1159
  email?: boolean
1134
1160
  url?: boolean
@@ -1180,7 +1206,7 @@ export interface ButtonSchema<$Item = any>
1180
1206
  onClick?: ItemEventHandler<$Item>
1181
1207
  onSuccess?: ItemEventHandler<$Item>
1182
1208
  onError?: ErrorEventHandler<$Item>
1183
- events?: {
1209
+ events?: BaseSchemaEvents<$Item> & {
1184
1210
  click?: ItemEventHandler<$Item>
1185
1211
  success?: ItemEventHandler<$Item>
1186
1212
  error?: ErrorEventHandler<$Item>
@@ -1209,8 +1235,9 @@ export interface SwitchSchema<$Item = any> extends BaseSchema<$Item> {
1209
1235
  }
1210
1236
 
1211
1237
  export interface NumberSchema<$Item = any>
1238
+ // `rules` comes from `SchemaNumberMixin`, which extends `SchemaRules`.
1212
1239
  extends SchemaNumberMixin<$Item>,
1213
- BaseSchema<$Item>,
1240
+ Omit<BaseSchema<$Item>, 'rules'>,
1214
1241
  SchemaAffixMixin<$Item> {
1215
1242
  /**
1216
1243
  * The type of the component.
@@ -1219,8 +1246,9 @@ export interface NumberSchema<$Item = any>
1219
1246
  }
1220
1247
 
1221
1248
  export interface SliderSchema<$Item = any>
1249
+ // `rules` comes from `SchemaNumberMixin`, which extends `SchemaRules`.
1222
1250
  extends SchemaNumberMixin<$Item>,
1223
- BaseSchema<$Item> {
1251
+ Omit<BaseSchema<$Item>, 'rules'> {
1224
1252
  /**
1225
1253
  * The type of the component.
1226
1254
  */
@@ -1400,9 +1428,8 @@ export interface UploadSchema<$Item = any> extends BaseSchema<$Item> {
1400
1428
  accept?: OrArrayOf<string>
1401
1429
  /**
1402
1430
  * The maximum size of the file expressed as number of bytes or as a string
1403
- * like `'200kb'`, `'1mb'`, `'3.2gb'`, etc.
1404
- *
1405
- * @see {@link https://github.com/patrickkettner/filesize-parser/blob/master/test.js String Examples}
1431
+ * like `'200kB'`, `'1MB'`, `'3.2GB'`, etc., using base 10 (1 kB = 1000
1432
+ * bytes). IEC units like `'1MiB'` use base 2 (1 KiB = 1024 bytes).
1406
1433
  */
1407
1434
  maxSize?: string | number
1408
1435
  /**
@@ -1502,7 +1529,7 @@ type SectionContent<$Data> = {
1502
1529
  /**
1503
1530
  * A form schema for the section's content. Use this
1504
1531
  * instead of `components` to get form-level options
1505
- * like `label`, `tabs`, and `mutate`.
1532
+ * like `label` and `tabs`.
1506
1533
  */
1507
1534
  form?: ResolvableForm<$Data>
1508
1535
  /**
@@ -1679,7 +1706,9 @@ export type ColorFormat =
1679
1706
  | 'hsl'
1680
1707
  | 'hsv'
1681
1708
  export interface ColorSchema<$Item = any>
1682
- extends BaseSchema<$Item>,
1709
+ // `format` is the color format here, not the formatting function of
1710
+ // `BaseSchema`.
1711
+ extends Omit<BaseSchema<$Item>, 'format'>,
1683
1712
  SchemaAffixMixin<$Item> {
1684
1713
  /**
1685
1714
  * The type of the component.
@@ -1801,7 +1830,7 @@ export interface ListSchema<$Item = { [key: string]: any }>
1801
1830
  /**
1802
1831
  * Grouped event handlers.
1803
1832
  */
1804
- events?: {
1833
+ events?: BaseSchemaEvents<$Item> & {
1805
1834
  /**
1806
1835
  * Called when a collapsible inlined list is
1807
1836
  * toggled open or closed.
@@ -1930,7 +1959,12 @@ export type DitoContext<$Item = any> = {
1930
1959
  sourceComponent: DitoSourceInstance | null
1931
1960
  /** The currently focused option in a select. */
1932
1961
  option: any
1933
- /** All available options in a select. */
1962
+ /**
1963
+ * All available options in a select. In `compute()`, they are
1964
+ * loaded when first read, also when the field isn't rendered,
1965
+ * and are `undefined` while loading; the callback runs again
1966
+ * once they are loaded.
1967
+ */
1934
1968
  options: any
1935
1969
  /**
1936
1970
  * Whether a pulldown/select is currently open.
@@ -2062,6 +2096,14 @@ export interface DitoComponentInstanceBase<$Item = any>
2062
2096
  name: string
2063
2097
  /** Full dot-separated path to the current data. */
2064
2098
  dataPath: string
2099
+ /**
2100
+ * The path of component names through views, tabs,
2101
+ * panels and forms, with item indices from the data
2102
+ * path, e.g. `main/chapters/0/title`. Unlike
2103
+ * `dataPath`, it tells apart components that display
2104
+ * the same data. Used as the component's DOM id.
2105
+ */
2106
+ componentPath: string
2065
2107
  /** The schema definition for the current component. */
2066
2108
  schema: Component
2067
2109
  /** The component type from the schema. */
@@ -2409,6 +2451,36 @@ export interface DitoFormInstance<$Item = any>
2409
2451
  */
2410
2452
  isCreating: boolean
2411
2453
 
2454
+ /**
2455
+ * Whether the form's processed data differs from
2456
+ * the data it was loaded, saved or applied with.
2457
+ * Values that are derived when the data is set up,
2458
+ * e.g. defaults and computed values, don't count.
2459
+ * Forms with `mutate: true` aren't dirty
2460
+ * themselves, their changes make the form that
2461
+ * owns the data dirty.
2462
+ */
2463
+ isDirty: boolean
2464
+
2465
+ /**
2466
+ * Makes clean changes to the data, which don't
2467
+ * make the form dirty, while other changes still
2468
+ * do, e.g. to apply what an action on the server
2469
+ * already saved. `makeChanges()` is called
2470
+ * synchronously, the returned promise resolves
2471
+ * once the values derived from the changes are
2472
+ * clean too.
2473
+ *
2474
+ * @example
2475
+ * ```js
2476
+ * const shopOrder = await request({ method: 'post', url })
2477
+ * formComponent.applyCleanChanges(() => {
2478
+ * item.templateShopOrder = shopOrder
2479
+ * })
2480
+ * ```
2481
+ */
2482
+ applyCleanChanges(makeChanges: () => void): Promise<void>
2483
+
2412
2484
  /**
2413
2485
  * Submits the form data to the API. Returns
2414
2486
  * `true` on success, `false` if validation
@@ -2435,7 +2507,9 @@ export interface DitoFormInstance<$Item = any>
2435
2507
  close(): Promise<void>
2436
2508
 
2437
2509
  /**
2438
- * Validates all fields in the form. Optionally
2510
+ * Validates all fields in the form's data, including
2511
+ * fields that aren't rendered, e.g. in collapsed
2512
+ * sections or closed nested forms. Optionally
2439
2513
  * filter fields with a match pattern. Returns
2440
2514
  * `true` if all matched fields are valid.
2441
2515
  */
@@ -2497,8 +2571,12 @@ export interface DitoFormInstance<$Item = any>
2497
2571
  ensureData(): void
2498
2572
  /** Clears the loaded data. */
2499
2573
  clearData(): void
2500
- /** Sets the component's loaded data directly. */
2501
- setData(data: any): void
2574
+ /**
2575
+ * Sets the component's loaded data directly, and
2576
+ * returns it as the component's reactive data,
2577
+ * which can be modified to update it.
2578
+ */
2579
+ setData(data: any): any
2502
2580
  /**
2503
2581
  * Creates a new data object with default values
2504
2582
  * from the schema. Optionally sets a `type`
@@ -2576,13 +2654,17 @@ export interface DitoViewInstance<$Item = any>
2576
2654
  * a resource.
2577
2655
  */
2578
2656
  providesData: boolean
2579
- /** Sets the view's data directly. */
2580
- setData(data: any): void
2657
+ /**
2658
+ * Sets the view's data directly, and returns it
2659
+ * as the view's reactive data.
2660
+ */
2661
+ setData(data: any): any
2581
2662
 
2582
2663
  // -- Validation (ValidatorMixin) --
2583
2664
 
2584
2665
  /**
2585
- * Validates all fields in the view.
2666
+ * Validates all fields in the view's data,
2667
+ * including fields that aren't rendered.
2586
2668
  * Optionally filter fields with a match pattern.
2587
2669
  * Returns `true` if all matched fields are valid.
2588
2670
  */
@@ -2611,9 +2693,12 @@ export interface DitoViewInstance<$Item = any>
2611
2693
  export interface DitoSchemaInstance<$Item = any>
2612
2694
  extends DitoComponentInstanceBase<$Item> {
2613
2695
  /**
2614
- * Validates all fields in the schema.
2696
+ * Validates all fields in the schema's data,
2697
+ * including fields that aren't rendered, e.g. in
2698
+ * collapsed sections or closed nested forms.
2615
2699
  * Optionally filter fields with a match pattern
2616
- * (string, string[], RegExp, or function).
2700
+ * (string, string[], RegExp, or function) over
2701
+ * their data paths.
2617
2702
  * Returns `true` if all matched fields are
2618
2703
  * valid.
2619
2704
  */
@@ -2717,11 +2802,14 @@ export interface DitoSourceInstance<$Item = any>
2717
2802
  deleteItem(item: any, index: number): void
2718
2803
  /**
2719
2804
  * Navigates to a component identified by its
2720
- * data path.
2805
+ * data path. With `shouldRevealInlinedOnly`, only
2806
+ * components in inlined schemas are revealed,
2807
+ * without navigating to nested forms.
2721
2808
  */
2722
2809
  navigateToComponent(
2723
2810
  dataPath: string,
2724
- onComplete?: Function
2811
+ onComplete?: Function,
2812
+ options?: { shouldRevealInlinedOnly?: boolean }
2725
2813
  ): Promise<boolean>
2726
2814
  /**
2727
2815
  * Navigates to the route component associated
@@ -2756,8 +2844,12 @@ export interface DitoSourceInstance<$Item = any>
2756
2844
  ensureData(): void
2757
2845
  /** Clears the loaded data. */
2758
2846
  clearData(): void
2759
- /** Sets the component's loaded data directly. */
2760
- setData(data: any): void
2847
+ /**
2848
+ * Sets the component's loaded data directly, and
2849
+ * returns it as the component's reactive data,
2850
+ * which can be modified to update it.
2851
+ */
2852
+ setData(data: any): any
2761
2853
  /**
2762
2854
  * Creates a new data object with default values
2763
2855
  * from the schema. Optionally sets a `type`
@@ -2883,7 +2975,7 @@ export interface ObjectSchema<$Item = { [key: string]: any }>
2883
2975
  /**
2884
2976
  * Grouped event handlers.
2885
2977
  */
2886
- events?: {
2978
+ events?: BaseSchemaEvents<$Item> & {
2887
2979
  /**
2888
2980
  * Called when a collapsible inlined object is
2889
2981
  * toggled open or closed.
@@ -2953,8 +3045,9 @@ export interface SpacerSchema<$Item = any> extends BaseSchema<$Item> {
2953
3045
  }
2954
3046
 
2955
3047
  export interface ProgressSchema<$Item = any>
3048
+ // `rules` comes from `SchemaNumberMixin`, which extends `SchemaRules`.
2956
3049
  extends SchemaNumberMixin<$Item>,
2957
- BaseSchema<$Item> {
3050
+ Omit<BaseSchema<$Item>, 'rules'> {
2958
3051
  /**
2959
3052
  * The type of the component.
2960
3053
  */
@@ -2993,6 +3086,7 @@ type NonSectionComponent<$Item = any> =
2993
3086
  export type Component<$Item = any> =
2994
3087
  | NonSectionComponent<$Item>
2995
3088
  | SectionSchema<$Item>
3089
+ | PanelSchema<$Item>
2996
3090
 
2997
3091
  /**
2998
3092
  * Source components (list, object, tree) that contain nested
@@ -3107,11 +3201,14 @@ export type Components<$Item = any> = 0 extends 1 & $Item
3107
3201
  ? Record<string, Component>
3108
3202
  : {
3109
3203
  [K in keyof $Item]?: [$Item[K]] extends [never]
3110
- ? NonSectionComponent<$Item> | SectionSchema<$Item>
3204
+ ? // Sections and panels that share the data of their schema.
3205
+ NonSectionComponent<$Item> | SectionSchema<$Item> | PanelSchema<$Item>
3111
3206
  : NonNullable<$Item[K]> extends (infer E)[]
3112
3207
  ? E extends Record<string, any>
3113
3208
  ? NonSectionComponent<E>
3114
- : NonSectionComponent<$Item>
3209
+ : // Lists of primitive values edit them wrapped in objects under
3210
+ // the key given by `wrapPrimitives`, see `SchemaSourceMixin`.
3211
+ NonSectionComponent<Record<string, E>>
3115
3212
  : NonNullable<$Item[K]> extends Record<string, any>
3116
3213
  ?
3117
3214
  | NonOptionFieldComponent<$Item>
@@ -3147,13 +3244,6 @@ export interface Form<$Item = any> extends SchemaRoute<$Item> {
3147
3244
  * The label of the form.
3148
3245
  */
3149
3246
  label?: OrItemAccessor<$Item, {}, string | boolean>
3150
- /**
3151
- * Whether the form directly mutates the parent data instead
3152
- * of working on a copy.
3153
- *
3154
- * @defaultValue `false`
3155
- */
3156
- mutate?: boolean
3157
3247
  /**
3158
3248
  * The property name used as the item's unique identifier.
3159
3249
  *
@@ -1,131 +0,0 @@
1
- import {
2
- isObject,
3
- isFunction,
4
- isPromise,
5
- normalizeDataPath,
6
- getValueAtDataPath
7
- } from '@ditojs/utils'
8
- import { markRaw, ref } from 'vue'
9
- import LoadingMixin from './LoadingMixin.js'
10
-
11
- // @vue/component
12
- export default {
13
- mixins: [LoadingMixin],
14
-
15
- data() {
16
- return {
17
- isLoading: false,
18
- asyncDataEntries: markRaw({})
19
- }
20
- },
21
-
22
- methods: {
23
- handleDataSchema(schema, name, loadingOptions) {
24
- if (!isObject(schema)) {
25
- schema = { data: schema }
26
- }
27
- let { data = undefined, dataPath = null } = schema
28
- // Create a reactive entry for the async data, if it doesn't exist yet.
29
- // NOTE: `markRaw()` is used to avoid reactivity on `asyncDataEntries`
30
- // itself, as reactivity is only desired on `reactiveVersion`, which is
31
- // used to trigger controlled reevaluation of the computed getter.
32
- const asyncEntry = (this.asyncDataEntries[name] ??= {
33
- reactiveVersion: ref(1),
34
- dependencyFunction: null,
35
- resolvedData: undefined,
36
- resolving: false,
37
- resolved: false
38
- })
39
- // If the data callback provided a dependency function when it was called,
40
- // cal it in every call of `handleDataSchema()` to force Vue to keep track
41
- // of the async dependencies. Also access `reactiveVersion.value` right
42
- // away, to ensure that the reactive property is tracked as a dependency:
43
- asyncEntry.reactiveVersion.value &&
44
- asyncEntry.dependencyFunction?.(this.context)
45
-
46
- if (asyncEntry.resolved) {
47
- // If the data was resolved already, return it and clear the resolved
48
- // value. This works because Vue caches the result of computed getters
49
- // and only reevaluates if one of the dependencies changed. This is to
50
- // ensure that a cached value here doesn't block / override
51
- // reevaluation when a dependency changes:
52
- const { resolvedData } = asyncEntry
53
- asyncEntry.resolvedData = undefined
54
- asyncEntry.resolved = false
55
- return resolvedData
56
- }
57
- // Avoid calling the data function twice:
58
- if (asyncEntry.resolving) {
59
- data = null
60
- } else if (data) {
61
- if (isFunction(data)) {
62
- const result = data(this.context)
63
- // If the result of the data function is another function, then the
64
- // first data function is there to track dependencies and the real
65
- // data loading happens in the function that it returned. Keep track
66
- // it in `dependencyFunction` so it can be called on each call of
67
- // `handleDataSchema()` to keep the dependencies intact, and call
68
- // the function that it returned once to get the actual data:
69
- if (isFunction(result)) {
70
- asyncEntry.dependencyFunction = data
71
- data = result(this.context)
72
- } else {
73
- data = result
74
- }
75
- }
76
- // NOTE: If the data is not a promise, it is resolved already.
77
- if (isPromise(data)) {
78
- // If the data is asynchronous, it can't be returned straight away.
79
- // But we can cheat using computed properties and `resolvedData`,
80
- // which is going to receive the loaded data asynchronously,
81
- // triggering a recompute of the computed property that calls
82
- // `handleDataSchema()`.
83
- asyncEntry.resolving = true
84
- this.resolveData(data, loadingOptions)
85
- .then(data => {
86
- asyncEntry.resolvedData = data
87
- asyncEntry.resolving = false
88
- asyncEntry.resolved = true
89
- // Trigger reevaluation of the computed getter by increasing the
90
- // `reactiveVersion` value.
91
- asyncEntry.reactiveVersion.value++
92
- })
93
- .catch(error => {
94
- console.error(error)
95
- asyncEntry.resolving = false
96
- })
97
- // Clear data until promise is resolved and `resolvedData` is set
98
- data = null
99
- }
100
- } else if (dataPath) {
101
- data = getValueAtDataPath(
102
- this.rootData,
103
- normalizeDataPath(`${this.dataPath}/${dataPath}`)
104
- )
105
- }
106
- return data
107
- },
108
-
109
- async resolveData(load, loadingOptions = {}) {
110
- // Use a timeout to allow already resolved promises to return data without
111
- // showing a loading indicator.
112
- let clearLoading = false
113
- const timer = setTimeout(() => {
114
- this.setLoading(true, loadingOptions)
115
- clearLoading = true
116
- }, 0)
117
- let data = null
118
- try {
119
- data = await (isFunction(load) ? load() : load)
120
- } catch (error) {
121
- this.addError(error.message || error)
122
- }
123
- if (clearLoading) {
124
- this.setLoading(false, loadingOptions)
125
- } else {
126
- clearTimeout(timer)
127
- }
128
- return data
129
- }
130
- }
131
- }