@ditojs/admin 2.100.0 → 3.1.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 (81) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +3555 -2719
  3. package/dist/dito-admin.umd.js +5 -6
  4. package/package.json +84 -46
  5. package/src/DitoAdmin.js +1 -65
  6. package/src/DitoComponent.js +1 -1
  7. package/src/DitoContext.js +51 -0
  8. package/src/DitoTypeComponent.js +23 -1
  9. package/src/components/DitoAffixes.vue +6 -3
  10. package/src/components/DitoButtons.vue +14 -1
  11. package/src/components/DitoContainer.vue +19 -10
  12. package/src/components/DitoCreateButton.vue +11 -2
  13. package/src/components/DitoDialog.vue +21 -2
  14. package/src/components/DitoForm.vue +152 -84
  15. package/src/components/DitoLabel.vue +3 -2
  16. package/src/components/DitoPane.vue +55 -11
  17. package/src/components/DitoPanel.vue +34 -5
  18. package/src/components/DitoPanels.vue +2 -1
  19. package/src/components/DitoRoot.vue +6 -6
  20. package/src/components/DitoRouterView.vue +27 -0
  21. package/src/components/DitoSchema.vue +278 -145
  22. package/src/components/DitoSchemaInlined.vue +1 -1
  23. package/src/components/DitoTabs.vue +16 -18
  24. package/src/components/DitoTrail.vue +5 -4
  25. package/src/components/DitoTreeItem.vue +17 -6
  26. package/src/components/DitoView.vue +21 -3
  27. package/src/components/index.js +1 -0
  28. package/src/mixins/DitoMixin.js +53 -7
  29. package/src/mixins/ItemMixin.js +3 -6
  30. package/src/mixins/NumberMixin.js +47 -31
  31. package/src/mixins/OptionsMixin.js +132 -71
  32. package/src/mixins/ResourceMixin.js +5 -2
  33. package/src/mixins/RouteMixin.js +39 -1
  34. package/src/mixins/SortableMixin.js +0 -13
  35. package/src/mixins/SourceMixin.js +155 -96
  36. package/src/mixins/TextMixin.js +1 -1
  37. package/src/mixins/TypeMixin.js +37 -22
  38. package/src/mixins/ValidationMixin.js +9 -27
  39. package/src/mixins/ValidatorMixin.js +5 -7
  40. package/src/mixins/ValueMixin.js +5 -2
  41. package/src/types/DitoTypeButton.vue +1 -1
  42. package/src/types/DitoTypeCheckbox.vue +1 -1
  43. package/src/types/DitoTypeCheckboxes.vue +1 -1
  44. package/src/types/DitoTypeCode.vue +8 -1
  45. package/src/types/DitoTypeColor.vue +20 -34
  46. package/src/types/DitoTypeComponent.vue +1 -1
  47. package/src/types/DitoTypeComputed.vue +4 -27
  48. package/src/types/DitoTypeDate.vue +16 -5
  49. package/src/types/DitoTypeLabel.vue +1 -4
  50. package/src/types/DitoTypeList.vue +9 -5
  51. package/src/types/DitoTypeMarkup.vue +25 -13
  52. package/src/types/DitoTypeMultiselect.vue +37 -6
  53. package/src/types/DitoTypeNumber.vue +1 -1
  54. package/src/types/DitoTypeObject.vue +10 -7
  55. package/src/types/DitoTypePanel.vue +20 -6
  56. package/src/types/DitoTypeProgress.vue +1 -1
  57. package/src/types/DitoTypeRadio.vue +1 -1
  58. package/src/types/DitoTypeSection.vue +32 -4
  59. package/src/types/DitoTypeSelect.vue +1 -1
  60. package/src/types/DitoTypeSlider.vue +1 -1
  61. package/src/types/DitoTypeSwitch.vue +1 -1
  62. package/src/types/DitoTypeText.vue +13 -15
  63. package/src/types/DitoTypeTextarea.vue +1 -1
  64. package/src/types/DitoTypeTreeList.vue +5 -7
  65. package/src/types/DitoTypeUpload.vue +1 -2
  66. package/src/utils/DataSchemaResolver.js +154 -0
  67. package/src/utils/FormModel.js +615 -0
  68. package/src/utils/data.js +20 -0
  69. package/src/utils/filter.js +3 -2
  70. package/src/utils/options.js +3 -0
  71. package/src/utils/request.js +70 -0
  72. package/src/utils/schema/data.js +659 -0
  73. package/src/utils/schema/lookup.js +250 -0
  74. package/src/utils/schema/setup.js +354 -0
  75. package/src/utils/schema/structure.js +172 -0
  76. package/src/utils/schema/types.js +34 -0
  77. package/src/utils/schema/validation.js +142 -0
  78. package/src/utils/units.js +70 -3
  79. package/types/index.d.ts +174 -59
  80. package/src/mixins/DataMixin.js +0 -131
  81. package/src/utils/schema.js +0 -1121
package/types/index.d.ts CHANGED
@@ -52,6 +52,12 @@ export type RequestMethod = <T>(options: {
52
52
  headers?: Record<string, string> | null
53
53
  /** Abort signal to cancel the request. */
54
54
  signal?: AbortSignal | null
55
+ /**
56
+ * Milliseconds after which the request is aborted.
57
+ *
58
+ * @defaultValue {@link ApiConfig.timeout}
59
+ */
60
+ timeout?: number | null
55
61
  }) => Promise<RequestMethodResponse<T>>
56
62
 
57
63
  /**
@@ -170,6 +176,14 @@ export interface ApiConfig {
170
176
  */
171
177
  durationFactor: number
172
178
  }
179
+ /**
180
+ * Milliseconds after which requests are aborted, so that
181
+ * nothing waits for them forever, e.g. submitting forms for
182
+ * pending loads of options. Requests can set their own.
183
+ *
184
+ * @defaultValue `null`, no timeout
185
+ */
186
+ timeout?: number | null
173
187
  /**
174
188
  * CORS settings applied to API requests (where
175
189
  * {@link ApiConfig.isApiUrl} returns `true`).
@@ -326,9 +340,15 @@ export interface BaseSchema<$Item>
326
340
  */
327
341
  default?: OrItemAccessor<$Item>
328
342
  /**
329
- * Computes and sets the field value reactively. If
330
- * the callback returns `undefined`, the current value
331
- * is preserved.
343
+ * Computes and sets the field value reactively, also
344
+ * when the field isn't rendered, unless its `if`
345
+ * evaluates to `false`. If the callback returns
346
+ * `undefined`, the current value is preserved.
347
+ *
348
+ * As it doesn't depend on the field's component, the
349
+ * context's component properties, e.g. `component`
350
+ * and `schemaComponent`, refer to the form, view or
351
+ * dialog that holds the data, not to the field.
332
352
  */
333
353
  compute?: ItemAccessor<$Item>
334
354
  /**
@@ -367,19 +387,26 @@ export interface SchemaDitoMixin<$Item> {
367
387
  * change before value validation occurs. Rule changes do not influence how
368
388
  * the component is rendered.
369
389
  */
370
- rules?: {
371
- /** Override whether the field is required. */
372
- required?: boolean
373
- }
390
+ rules?: SchemaRules
391
+ }
392
+
393
+ /**
394
+ * Validation rules shared by all schemas, extended by the individual types.
395
+ */
396
+ export interface SchemaRules {
397
+ /** Override whether the field is required. */
398
+ required?: boolean
374
399
  }
375
400
 
376
401
  /**
377
402
  * Return false to mark event as handled and stop it from propagating to parent
378
403
  * schemas.
379
404
  */
405
+ // Handlers can be async, as events are awaited, e.g. by buttons that show
406
+ // their progress while their `click` handler runs.
380
407
  export type ItemEventHandler<$Item = any> = (
381
408
  itemParams: DitoContext<$Item>
382
- ) => void | false
409
+ ) => void | false | Promise<void | false>
383
410
 
384
411
  export type OpenEventHandler<$Item = any> = (
385
412
  itemParams: DitoContext<$Item> & { open: boolean }
@@ -565,14 +592,20 @@ export interface SchemaTypeMixin<$Item> extends SchemaFields<$Item> {
565
592
  * @see {@link SchemaFields.onInitialize} and other `on`-
566
593
  * prefixed properties for per-event documentation.
567
594
  */
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
- }
595
+ events?: BaseSchemaEvents<$Item>
596
+ }
597
+
598
+ /**
599
+ * Grouped event handlers shared by all schemas, extended by the individual
600
+ * types.
601
+ */
602
+ export interface BaseSchemaEvents<$Item> extends SchemaEvents<$Item> {
603
+ /** @see {@link BaseSchema.onFocus} */
604
+ focus?: ItemEventHandler<$Item>
605
+ /** @see {@link BaseSchema.onBlur} */
606
+ blur?: ItemEventHandler<$Item>
607
+ /** @see {@link BaseSchema.onInput} */
608
+ input?: ItemEventHandler<$Item>
576
609
  }
577
610
 
578
611
  export interface SchemaSourceMixin<$Item> {
@@ -643,9 +676,18 @@ export interface SchemaSourceMixin<$Item> {
643
676
  /** Buttons for the source. */
644
677
  buttons?: Buttons<$Item>
645
678
  /**
646
- * Whether to wrap primitive values in objects.
679
+ * The key under which primitive values are wrapped in objects, so that the
680
+ * source's forms can edit them, e.g. `'value'` for a list of strings.
647
681
  */
648
- wrapPrimitives?: boolean
682
+ wrapPrimitives?: string
683
+ /**
684
+ * Whether the forms of the source's items directly mutate the source's data
685
+ * instead of editing a copy that is applied when submitting. Forms that
686
+ * mutate data are never dirty.
687
+ *
688
+ * @defaultValue `false`
689
+ */
690
+ mutate?: boolean
649
691
  /**
650
692
  * URL path for the source items.
651
693
  */
@@ -722,7 +764,9 @@ export interface SchemaSourceMixin<$Item> {
722
764
  deletable?: OrItemAccessor<$Item, {}, boolean | { label: string }>
723
765
  /**
724
766
  * 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`.
767
+ * when the `draggable` property of the list schema is set to `true`. Items
768
+ * are numbered when the data is loaded, and when they are added, removed or
769
+ * reordered.
726
770
  */
727
771
  orderKey?: string
728
772
  /**
@@ -772,6 +816,12 @@ export type SchemaOptions<$Item, $Option = any> =
772
816
  * receives the `DitoContext` and returns an inner
773
817
  * function that is called to fetch the actual data,
774
818
  * enabling reactive dependency tracking.
819
+ *
820
+ * The options are loaded independently of the
821
+ * field's component, also for computes, so the
822
+ * context's component properties, e.g. `component`
823
+ * and `schemaComponent`, refer to the form, view or
824
+ * dialog that holds the data, not to the field.
775
825
  */
776
826
  data?: OrItemAccessor<
777
827
  $Item,
@@ -919,14 +969,15 @@ export interface SchemaNumberMixin<$Item> {
919
969
  */
920
970
  step?: OrItemAccessor<$Item, {}, number>
921
971
  /**
922
- * The amount of decimals to round to.
972
+ * The maximum amount of decimals allowed, validated on input.
923
973
  */
924
974
  decimals?: OrItemAccessor<$Item, {}, number>
925
975
  /** Validation rules for numeric constraints. */
926
- rules?: Omit<SchemaNumberMixin<$Item>, 'rules'> & {
927
- /** Restrict the value to whole numbers. */
928
- integer?: boolean
929
- }
976
+ rules?: SchemaRules &
977
+ Omit<SchemaNumberMixin<$Item>, 'rules'> & {
978
+ /** Restrict the value to whole numbers. */
979
+ integer?: boolean
980
+ }
930
981
  }
931
982
 
932
983
  export interface SchemaTextMixin<$Item> {
@@ -964,7 +1015,7 @@ export interface SchemaDataMixin<$Item> {
964
1015
  /**
965
1016
  * Data source for the component.
966
1017
  */
967
- data?: OrItemAccessor<$Item, {}, any>
1018
+ data?: OrItemAccessor<$Item, {}, Record<string, any>>
968
1019
  /**
969
1020
  * Path to retrieve data from parent context.
970
1021
  */
@@ -1128,7 +1179,7 @@ export interface InputSchema<$Item = any>
1128
1179
  | 'tel'
1129
1180
  | 'password'
1130
1181
  | 'creditcard'
1131
- rules?: {
1182
+ rules?: SchemaRules & {
1132
1183
  text?: boolean
1133
1184
  email?: boolean
1134
1185
  url?: boolean
@@ -1180,7 +1231,7 @@ export interface ButtonSchema<$Item = any>
1180
1231
  onClick?: ItemEventHandler<$Item>
1181
1232
  onSuccess?: ItemEventHandler<$Item>
1182
1233
  onError?: ErrorEventHandler<$Item>
1183
- events?: {
1234
+ events?: BaseSchemaEvents<$Item> & {
1184
1235
  click?: ItemEventHandler<$Item>
1185
1236
  success?: ItemEventHandler<$Item>
1186
1237
  error?: ErrorEventHandler<$Item>
@@ -1209,8 +1260,9 @@ export interface SwitchSchema<$Item = any> extends BaseSchema<$Item> {
1209
1260
  }
1210
1261
 
1211
1262
  export interface NumberSchema<$Item = any>
1263
+ // `rules` comes from `SchemaNumberMixin`, which extends `SchemaRules`.
1212
1264
  extends SchemaNumberMixin<$Item>,
1213
- BaseSchema<$Item>,
1265
+ Omit<BaseSchema<$Item>, 'rules'>,
1214
1266
  SchemaAffixMixin<$Item> {
1215
1267
  /**
1216
1268
  * The type of the component.
@@ -1219,8 +1271,9 @@ export interface NumberSchema<$Item = any>
1219
1271
  }
1220
1272
 
1221
1273
  export interface SliderSchema<$Item = any>
1274
+ // `rules` comes from `SchemaNumberMixin`, which extends `SchemaRules`.
1222
1275
  extends SchemaNumberMixin<$Item>,
1223
- BaseSchema<$Item> {
1276
+ Omit<BaseSchema<$Item>, 'rules'> {
1224
1277
  /**
1225
1278
  * The type of the component.
1226
1279
  */
@@ -1400,9 +1453,8 @@ export interface UploadSchema<$Item = any> extends BaseSchema<$Item> {
1400
1453
  accept?: OrArrayOf<string>
1401
1454
  /**
1402
1455
  * 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}
1456
+ * like `'200kB'`, `'1MB'`, `'3.2GB'`, etc., using base 10 (1 kB = 1000
1457
+ * bytes). IEC units like `'1MiB'` use base 2 (1 KiB = 1024 bytes).
1406
1458
  */
1407
1459
  maxSize?: string | number
1408
1460
  /**
@@ -1502,7 +1554,7 @@ type SectionContent<$Data> = {
1502
1554
  /**
1503
1555
  * A form schema for the section's content. Use this
1504
1556
  * instead of `components` to get form-level options
1505
- * like `label`, `tabs`, and `mutate`.
1557
+ * like `label` and `tabs`.
1506
1558
  */
1507
1559
  form?: ResolvableForm<$Data>
1508
1560
  /**
@@ -1679,7 +1731,9 @@ export type ColorFormat =
1679
1731
  | 'hsl'
1680
1732
  | 'hsv'
1681
1733
  export interface ColorSchema<$Item = any>
1682
- extends BaseSchema<$Item>,
1734
+ // `format` is the color format here, not the formatting function of
1735
+ // `BaseSchema`.
1736
+ extends Omit<BaseSchema<$Item>, 'format'>,
1683
1737
  SchemaAffixMixin<$Item> {
1684
1738
  /**
1685
1739
  * The type of the component.
@@ -1801,7 +1855,7 @@ export interface ListSchema<$Item = { [key: string]: any }>
1801
1855
  /**
1802
1856
  * Grouped event handlers.
1803
1857
  */
1804
- events?: {
1858
+ events?: BaseSchemaEvents<$Item> & {
1805
1859
  /**
1806
1860
  * Called when a collapsible inlined list is
1807
1861
  * toggled open or closed.
@@ -1930,7 +1984,12 @@ export type DitoContext<$Item = any> = {
1930
1984
  sourceComponent: DitoSourceInstance | null
1931
1985
  /** The currently focused option in a select. */
1932
1986
  option: any
1933
- /** All available options in a select. */
1987
+ /**
1988
+ * All available options in a select. In `compute()`, they are
1989
+ * loaded when first read, also when the field isn't rendered,
1990
+ * and are `undefined` while loading; the callback runs again
1991
+ * once they are loaded.
1992
+ */
1934
1993
  options: any
1935
1994
  /**
1936
1995
  * Whether a pulldown/select is currently open.
@@ -2062,6 +2121,14 @@ export interface DitoComponentInstanceBase<$Item = any>
2062
2121
  name: string
2063
2122
  /** Full dot-separated path to the current data. */
2064
2123
  dataPath: string
2124
+ /**
2125
+ * The path of component names through views, tabs,
2126
+ * panels and forms, with item indices from the data
2127
+ * path, e.g. `main/chapters/0/title`. Unlike
2128
+ * `dataPath`, it tells apart components that display
2129
+ * the same data. Used as the component's DOM id.
2130
+ */
2131
+ componentPath: string
2065
2132
  /** The schema definition for the current component. */
2066
2133
  schema: Component
2067
2134
  /** The component type from the schema. */
@@ -2409,6 +2476,36 @@ export interface DitoFormInstance<$Item = any>
2409
2476
  */
2410
2477
  isCreating: boolean
2411
2478
 
2479
+ /**
2480
+ * Whether the form's processed data differs from
2481
+ * the data it was loaded, saved or applied with.
2482
+ * Values that are derived when the data is set up,
2483
+ * e.g. defaults and computed values, don't count.
2484
+ * Forms with `mutate: true` aren't dirty
2485
+ * themselves, their changes make the form that
2486
+ * owns the data dirty.
2487
+ */
2488
+ isDirty: boolean
2489
+
2490
+ /**
2491
+ * Makes clean changes to the data, which don't
2492
+ * make the form dirty, while other changes still
2493
+ * do, e.g. to apply what an action on the server
2494
+ * already saved. `makeChanges()` is called
2495
+ * synchronously, the returned promise resolves
2496
+ * once the values derived from the changes are
2497
+ * clean too.
2498
+ *
2499
+ * @example
2500
+ * ```js
2501
+ * const shopOrder = await request({ method: 'post', url })
2502
+ * formComponent.applyCleanChanges(() => {
2503
+ * item.templateShopOrder = shopOrder
2504
+ * })
2505
+ * ```
2506
+ */
2507
+ applyCleanChanges(makeChanges: () => void): Promise<void>
2508
+
2412
2509
  /**
2413
2510
  * Submits the form data to the API. Returns
2414
2511
  * `true` on success, `false` if validation
@@ -2435,7 +2532,9 @@ export interface DitoFormInstance<$Item = any>
2435
2532
  close(): Promise<void>
2436
2533
 
2437
2534
  /**
2438
- * Validates all fields in the form. Optionally
2535
+ * Validates all fields in the form's data, including
2536
+ * fields that aren't rendered, e.g. in collapsed
2537
+ * sections or closed nested forms. Optionally
2439
2538
  * filter fields with a match pattern. Returns
2440
2539
  * `true` if all matched fields are valid.
2441
2540
  */
@@ -2497,8 +2596,12 @@ export interface DitoFormInstance<$Item = any>
2497
2596
  ensureData(): void
2498
2597
  /** Clears the loaded data. */
2499
2598
  clearData(): void
2500
- /** Sets the component's loaded data directly. */
2501
- setData(data: any): void
2599
+ /**
2600
+ * Sets the component's loaded data directly, and
2601
+ * returns it as the component's reactive data,
2602
+ * which can be modified to update it.
2603
+ */
2604
+ setData(data: any): any
2502
2605
  /**
2503
2606
  * Creates a new data object with default values
2504
2607
  * from the schema. Optionally sets a `type`
@@ -2576,13 +2679,17 @@ export interface DitoViewInstance<$Item = any>
2576
2679
  * a resource.
2577
2680
  */
2578
2681
  providesData: boolean
2579
- /** Sets the view's data directly. */
2580
- setData(data: any): void
2682
+ /**
2683
+ * Sets the view's data directly, and returns it
2684
+ * as the view's reactive data.
2685
+ */
2686
+ setData(data: any): any
2581
2687
 
2582
2688
  // -- Validation (ValidatorMixin) --
2583
2689
 
2584
2690
  /**
2585
- * Validates all fields in the view.
2691
+ * Validates all fields in the view's data,
2692
+ * including fields that aren't rendered.
2586
2693
  * Optionally filter fields with a match pattern.
2587
2694
  * Returns `true` if all matched fields are valid.
2588
2695
  */
@@ -2611,9 +2718,12 @@ export interface DitoViewInstance<$Item = any>
2611
2718
  export interface DitoSchemaInstance<$Item = any>
2612
2719
  extends DitoComponentInstanceBase<$Item> {
2613
2720
  /**
2614
- * Validates all fields in the schema.
2721
+ * Validates all fields in the schema's data,
2722
+ * including fields that aren't rendered, e.g. in
2723
+ * collapsed sections or closed nested forms.
2615
2724
  * Optionally filter fields with a match pattern
2616
- * (string, string[], RegExp, or function).
2725
+ * (string, string[], RegExp, or function) over
2726
+ * their data paths.
2617
2727
  * Returns `true` if all matched fields are
2618
2728
  * valid.
2619
2729
  */
@@ -2717,11 +2827,14 @@ export interface DitoSourceInstance<$Item = any>
2717
2827
  deleteItem(item: any, index: number): void
2718
2828
  /**
2719
2829
  * Navigates to a component identified by its
2720
- * data path.
2830
+ * data path. With `shouldRevealInlinedOnly`, only
2831
+ * components in inlined schemas are revealed,
2832
+ * without navigating to nested forms.
2721
2833
  */
2722
2834
  navigateToComponent(
2723
2835
  dataPath: string,
2724
- onComplete?: Function
2836
+ onComplete?: Function,
2837
+ options?: { shouldRevealInlinedOnly?: boolean }
2725
2838
  ): Promise<boolean>
2726
2839
  /**
2727
2840
  * Navigates to the route component associated
@@ -2756,8 +2869,12 @@ export interface DitoSourceInstance<$Item = any>
2756
2869
  ensureData(): void
2757
2870
  /** Clears the loaded data. */
2758
2871
  clearData(): void
2759
- /** Sets the component's loaded data directly. */
2760
- setData(data: any): void
2872
+ /**
2873
+ * Sets the component's loaded data directly, and
2874
+ * returns it as the component's reactive data,
2875
+ * which can be modified to update it.
2876
+ */
2877
+ setData(data: any): any
2761
2878
  /**
2762
2879
  * Creates a new data object with default values
2763
2880
  * from the schema. Optionally sets a `type`
@@ -2883,7 +3000,7 @@ export interface ObjectSchema<$Item = { [key: string]: any }>
2883
3000
  /**
2884
3001
  * Grouped event handlers.
2885
3002
  */
2886
- events?: {
3003
+ events?: BaseSchemaEvents<$Item> & {
2887
3004
  /**
2888
3005
  * Called when a collapsible inlined object is
2889
3006
  * toggled open or closed.
@@ -2953,8 +3070,9 @@ export interface SpacerSchema<$Item = any> extends BaseSchema<$Item> {
2953
3070
  }
2954
3071
 
2955
3072
  export interface ProgressSchema<$Item = any>
3073
+ // `rules` comes from `SchemaNumberMixin`, which extends `SchemaRules`.
2956
3074
  extends SchemaNumberMixin<$Item>,
2957
- BaseSchema<$Item> {
3075
+ Omit<BaseSchema<$Item>, 'rules'> {
2958
3076
  /**
2959
3077
  * The type of the component.
2960
3078
  */
@@ -2993,6 +3111,7 @@ type NonSectionComponent<$Item = any> =
2993
3111
  export type Component<$Item = any> =
2994
3112
  | NonSectionComponent<$Item>
2995
3113
  | SectionSchema<$Item>
3114
+ | PanelSchema<$Item>
2996
3115
 
2997
3116
  /**
2998
3117
  * Source components (list, object, tree) that contain nested
@@ -3107,11 +3226,14 @@ export type Components<$Item = any> = 0 extends 1 & $Item
3107
3226
  ? Record<string, Component>
3108
3227
  : {
3109
3228
  [K in keyof $Item]?: [$Item[K]] extends [never]
3110
- ? NonSectionComponent<$Item> | SectionSchema<$Item>
3229
+ ? // Sections and panels that share the data of their schema.
3230
+ NonSectionComponent<$Item> | SectionSchema<$Item> | PanelSchema<$Item>
3111
3231
  : NonNullable<$Item[K]> extends (infer E)[]
3112
3232
  ? E extends Record<string, any>
3113
3233
  ? NonSectionComponent<E>
3114
- : NonSectionComponent<$Item>
3234
+ : // Lists of primitive values edit them wrapped in objects under
3235
+ // the key given by `wrapPrimitives`, see `SchemaSourceMixin`.
3236
+ NonSectionComponent<Record<string, E>>
3115
3237
  : NonNullable<$Item[K]> extends Record<string, any>
3116
3238
  ?
3117
3239
  | NonOptionFieldComponent<$Item>
@@ -3147,13 +3269,6 @@ export interface Form<$Item = any> extends SchemaRoute<$Item> {
3147
3269
  * The label of the form.
3148
3270
  */
3149
3271
  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
3272
  /**
3158
3273
  * The property name used as the item's unique identifier.
3159
3274
  *
@@ -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
- }