@ditojs/admin 3.2.6 → 3.3.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 (112) hide show
  1. package/dist/dito-admin.css +1 -1
  2. package/dist/dito-admin.es.js +6012 -5827
  3. package/dist/dito-admin.umd.js +3 -7
  4. package/package.json +18 -17
  5. package/src/DitoAdmin.js +44 -24
  6. package/src/DitoContext.js +28 -2
  7. package/src/DitoTypeComponent.js +1 -0
  8. package/src/appState.js +6 -3
  9. package/src/components/DitoAccount.vue +35 -27
  10. package/src/components/DitoAffixes.vue +9 -2
  11. package/src/components/DitoClipboard.vue +59 -93
  12. package/src/components/DitoConfirmMessage.vue +18 -0
  13. package/src/components/DitoContainer.vue +56 -18
  14. package/src/components/DitoCreateButton.vue +36 -84
  15. package/src/components/DitoDialog.vue +20 -15
  16. package/src/components/DitoDragHandle.vue +44 -0
  17. package/src/components/DitoDraggable.vue +43 -16
  18. package/src/components/DitoEditButtons.vue +48 -24
  19. package/src/components/DitoErrors.vue +9 -56
  20. package/src/components/DitoForm.vue +82 -42
  21. package/src/components/DitoInputAffixes.vue +50 -0
  22. package/src/components/DitoLabel.vue +1 -15
  23. package/src/components/DitoMenu.vue +45 -52
  24. package/src/components/DitoNotifications.vue +38 -14
  25. package/src/components/DitoOptionList.vue +81 -0
  26. package/src/components/DitoOptionsEditButtons.vue +57 -0
  27. package/src/components/DitoPagination.vue +1 -8
  28. package/src/components/DitoPane.vue +51 -39
  29. package/src/components/DitoPanel.vue +12 -26
  30. package/src/components/DitoPanels.vue +11 -5
  31. package/src/components/DitoResizeHandle.vue +47 -0
  32. package/src/components/DitoRoot.vue +113 -320
  33. package/src/components/DitoSchema.vue +72 -63
  34. package/src/components/DitoSchemaInlined.vue +8 -9
  35. package/src/components/DitoScopes.vue +14 -18
  36. package/src/components/DitoSidebar.vue +4 -29
  37. package/src/components/DitoSourceItem.vue +114 -0
  38. package/src/components/DitoTableCell.vue +4 -0
  39. package/src/components/DitoTableHead.vue +15 -17
  40. package/src/components/DitoTabs.vue +25 -0
  41. package/src/components/DitoTrail.vue +25 -25
  42. package/src/components/DitoTreeItem.vue +102 -141
  43. package/src/components/DitoUploadFile.vue +5 -2
  44. package/src/components/DitoView.vue +33 -24
  45. package/src/index.js +4 -1
  46. package/src/mixins/DitoMixin.js +77 -110
  47. package/src/mixins/DomMixin.js +12 -14
  48. package/src/mixins/EmitterMixin.js +3 -2
  49. package/src/mixins/ItemMixin.js +38 -94
  50. package/src/mixins/OptionsMixin.js +38 -25
  51. package/src/mixins/ResourceMixin.js +182 -111
  52. package/src/mixins/RouteMixin.js +61 -44
  53. package/src/mixins/SchemaParentMixin.js +4 -1
  54. package/src/mixins/SourceMixin.js +118 -100
  55. package/src/mixins/TypeMixin.js +42 -40
  56. package/src/mixins/ValidationMixin.js +16 -4
  57. package/src/mixins/ValidatorMixin.js +1 -1
  58. package/src/styles/_settings.scss +4 -0
  59. package/src/styles/_tippy.scss +0 -11
  60. package/src/styles/style.scss +0 -1
  61. package/src/types/DitoTypeButton.vue +11 -14
  62. package/src/types/DitoTypeCheckboxes.vue +35 -17
  63. package/src/types/DitoTypeCode.vue +19 -41
  64. package/src/types/DitoTypeColor.vue +67 -19
  65. package/src/types/DitoTypeDate.vue +11 -14
  66. package/src/types/DitoTypeList.vue +50 -62
  67. package/src/types/DitoTypeMarkup.vue +41 -54
  68. package/src/types/DitoTypeMultiselect.vue +82 -52
  69. package/src/types/DitoTypeNumber.vue +12 -15
  70. package/src/types/DitoTypeObject.vue +4 -38
  71. package/src/types/DitoTypeRadio.vue +19 -6
  72. package/src/types/DitoTypeSection.vue +23 -9
  73. package/src/types/DitoTypeSelect.vue +39 -23
  74. package/src/types/DitoTypeSlider.vue +9 -1
  75. package/src/types/DitoTypeSwitch.vue +1 -1
  76. package/src/types/DitoTypeText.vue +11 -14
  77. package/src/types/DitoTypeTreeList.vue +46 -8
  78. package/src/types/DitoTypeUpload.vue +190 -67
  79. package/src/utils/DataModel.js +368 -146
  80. package/src/utils/FileDropTargets.js +97 -0
  81. package/src/utils/ItemStores.js +31 -0
  82. package/src/utils/ListQuery.js +115 -0
  83. package/src/utils/LoadingTracker.js +76 -0
  84. package/src/utils/SchemaGraph.js +3 -2
  85. package/src/utils/Session.js +151 -0
  86. package/src/utils/ViewRegistry.js +85 -0
  87. package/src/utils/accessor.js +12 -4
  88. package/src/utils/affixes.js +33 -0
  89. package/src/utils/changeOnceEdited.js +33 -0
  90. package/src/utils/clipboard.js +55 -0
  91. package/src/utils/data.js +10 -5
  92. package/src/utils/dialogs.js +59 -0
  93. package/src/utils/dragResize.js +61 -0
  94. package/src/utils/filter.js +36 -2
  95. package/src/utils/html.js +9 -0
  96. package/src/utils/list.js +10 -0
  97. package/src/utils/options.js +4 -0
  98. package/src/utils/registration.js +18 -0
  99. package/src/utils/request.js +108 -13
  100. package/src/utils/route.js +6 -17
  101. package/src/utils/schema/data.js +29 -2
  102. package/src/utils/schema/events.js +19 -0
  103. package/src/utils/schema/item.js +114 -0
  104. package/src/utils/schema/lookup.js +39 -14
  105. package/src/utils/schema/setup.js +5 -5
  106. package/src/utils/schema/validation.js +16 -2
  107. package/src/utils/uid.js +105 -8
  108. package/src/validators/_decimals.js +16 -5
  109. package/types/index.d.ts +168 -41
  110. package/src/mixins/LoadingMixin.js +0 -23
  111. package/src/mixins/PulldownMixin.js +0 -72
  112. package/src/styles/_pulldown.scss +0 -38
package/types/index.d.ts CHANGED
@@ -353,6 +353,10 @@ export interface BaseSchema<$Item>
353
353
  * context's component properties, e.g. `component`
354
354
  * and `schemaComponent`, refer to the form, view or
355
355
  * dialog that holds the data, not to the field.
356
+ *
357
+ * Reading `context.options` waits while the options
358
+ * load, and is `undefined` when the load fails or its
359
+ * data resolves to nothing, so use `options?.`.
356
360
  */
357
361
  compute?: ItemAccessor<$Item>
358
362
  /**
@@ -629,6 +633,9 @@ export interface SchemaSourceMixin<$Item> {
629
633
  * the default is the 'name' property of the item,
630
634
  * followed by the label of the form (plus item id)
631
635
  * and other defaults.
636
+ * Labels are HTML: Callbacks return HTML, and need to
637
+ * escape the item values that they include with
638
+ * `escapeHtml()` from `@ditojs/utils`.
632
639
  */
633
640
  itemLabel?:
634
641
  | OrItemAccessor<
@@ -850,7 +857,9 @@ export type SchemaOptions<$Item, $Option = any> =
850
857
  */
851
858
  value?: keyof $Option | ItemAccessor<$Item, { option: $Option }>
852
859
  /**
853
- * The key of the option property which should used to group the options.
860
+ * @deprecated Not supported, the options are grouped by the `groupBy`
861
+ * setting of the component's schema, see
862
+ * {@link SchemaOptionsMixin.groupBy}.
854
863
  */
855
864
  groupBy?: keyof $Option
856
865
  /**
@@ -913,7 +922,9 @@ export interface SchemaOptionsMixin<$Item, $Option = any> {
913
922
  relateBy?: string
914
923
  /**
915
924
  * The key of the option property which should be used to
916
- * group the options.
925
+ * group the options. Selects and multiselects show the groups
926
+ * in their dropdowns, radio buttons and checkboxes list each
927
+ * group in a fieldset with the group's name as its legend.
917
928
  */
918
929
  groupBy?: OrItemAccessor<$Item, {}, string>
919
930
  /**
@@ -942,7 +953,8 @@ export interface SchemaOptionsMixin<$Item, $Option = any> {
942
953
  }
943
954
  /**
944
955
  * Whether the selected option can be edited by navigating
945
- * to it.
956
+ * to its form in the view of `view`. Radio buttons and checkboxes
957
+ * show an edit button for each of their options.
946
958
  */
947
959
  editable?: OrItemAccessor<$Item, {}, boolean>
948
960
  /**
@@ -1220,7 +1232,8 @@ export interface DateSchema<$Item = any>
1220
1232
  }
1221
1233
  >
1222
1234
  /**
1223
- * @deprecated Use `formats` instead.
1235
+ * @deprecated Use `formats.date` instead. `dateFormat` is no longer read and
1236
+ * has no effect.
1224
1237
  */
1225
1238
  dateFormat?: OrItemAccessor<$Item, {}, DateFormat>
1226
1239
  }
@@ -1924,9 +1937,10 @@ export type DitoContext<$Item = any> = {
1924
1937
  */
1925
1938
  itemDataPath: string
1926
1939
  /**
1927
- * Data path of the parent item ancestor.
1940
+ * Data path of the parent item ancestor, `null` for
1941
+ * root items.
1928
1942
  */
1929
- parentItemDataPath: string
1943
+ parentItemDataPath: string | null
1930
1944
  /** Index of the closest item in its list. */
1931
1945
  itemIndex: number | null
1932
1946
  /** Index of the parent item in its list. */
@@ -1959,11 +1973,11 @@ export type DitoContext<$Item = any> = {
1959
1973
  * copy/paste operations.
1960
1974
  */
1961
1975
  clipboardItem: any
1962
- /** The currently authenticated user. */
1976
+ /** The currently authenticated user, `null` when logged out. */
1963
1977
  user: {
1964
1978
  roles?: string[]
1965
1979
  hasRole(...roles: string[]): boolean
1966
- }
1980
+ } | null
1967
1981
  /** The admin API configuration. */
1968
1982
  api: ApiConfig
1969
1983
  /** The schema definition for the current component. */
@@ -1972,8 +1986,16 @@ export type DitoContext<$Item = any> = {
1972
1986
  views: Record<string, View>
1973
1987
  /** All views flattened into a single record. */
1974
1988
  flattenedViews: Record<string, ViewSchema>
1975
- /** Display label of the current item. */
1989
+ /**
1990
+ * Display label of the current item, as HTML with
1991
+ * escaped item values.
1992
+ */
1976
1993
  itemLabel: string | null
1994
+ /**
1995
+ * The text of `itemLabel`, with its item values
1996
+ * unescaped.
1997
+ */
1998
+ itemLabelText: string | null
1977
1999
  /** Display label of the current form. */
1978
2000
  formLabel: string | null
1979
2001
  /** The current Vue component instance. */
@@ -2071,25 +2093,55 @@ export type DitoContext<$Item = any> = {
2071
2093
  }
2072
2094
  /** Returns the full URL for a given resource. */
2073
2095
  getResourceUrl(resource: Resource): string
2074
- /** Displays a notification to the user. */
2075
- notify(options: {
2076
- /** Notification style. @defaultValue `'info'` */
2077
- type?: LiteralUnion<'warning' | 'error' | 'info' | 'success'>
2078
- /** Heading text. Defaults to a label matching `type`. */
2079
- title?: string
2080
- /** Body text, rendered as HTML paragraphs. */
2081
- text: OrArrayOf<string>
2082
- /**
2083
- * The original error object. When `type` is `'error'`,
2084
- * this is logged to the console alongside the message.
2085
- */
2086
- error?: unknown
2087
- /**
2088
- * Display duration in milliseconds. When omitted, calculated
2089
- * automatically from content length. Use `0` for sticky.
2090
- */
2091
- duration?: number
2092
- }): void
2096
+ /**
2097
+ * Displays a notification to the user, with the
2098
+ * paragraphs of either `text` or `html`.
2099
+ */
2100
+ notify(
2101
+ options: {
2102
+ /** Notification style. @defaultValue `'info'` */
2103
+ type?: LiteralUnion<'warning' | 'error' | 'info' | 'success'>
2104
+ /**
2105
+ * Heading text, escaped like `text`. Defaults to
2106
+ * a label matching `type`.
2107
+ */
2108
+ title?: string
2109
+ /**
2110
+ * The original error object. When `type` is
2111
+ * `'error'`, this is logged to the console
2112
+ * alongside the message.
2113
+ */
2114
+ error?: unknown
2115
+ /**
2116
+ * Display duration in milliseconds. When omitted,
2117
+ * calculated automatically from content length.
2118
+ * Use `0` for sticky.
2119
+ */
2120
+ duration?: number
2121
+ } & (
2122
+ | {
2123
+ /**
2124
+ * Body text, escaped as plain text. Each entry
2125
+ * is a paragraph, newlines become `<br>`, and
2126
+ * `false` and `null` entries are skipped.
2127
+ */
2128
+ text: OrArrayOf<string | false | null>
2129
+ html?: never
2130
+ }
2131
+ | {
2132
+ /**
2133
+ * Body HTML from trusted sources, rendered as
2134
+ * it is. Every interpolated value must be
2135
+ * escaped with `escapeHtml()` from
2136
+ * `@ditojs/utils`. Each entry is a paragraph,
2137
+ * newlines become `<br>`, and `false` and
2138
+ * `null` entries are skipped.
2139
+ */
2140
+ html: OrArrayOf<string | false | null>
2141
+ text?: never
2142
+ }
2143
+ )
2144
+ ): void
2093
2145
  }
2094
2146
 
2095
2147
  /**
@@ -2169,16 +2221,6 @@ export interface DitoComponentInstanceBase<$Item = any>
2169
2221
  errors: string[] | null
2170
2222
  /** Whether the field has validation errors. */
2171
2223
  hasErrors: boolean
2172
- /** Whether async data is currently being loaded. */
2173
- isLoading: boolean
2174
- /**
2175
- * Sets the loading state. Optionally propagates
2176
- * to the root or view component.
2177
- */
2178
- setLoading(
2179
- isLoading: boolean,
2180
- options?: { updateRoot?: boolean; updateView?: boolean }
2181
- ): void
2182
2224
  /**
2183
2225
  * Whether the component works with data not yet
2184
2226
  * persisted to the server.
@@ -2256,7 +2298,7 @@ export interface DitoComponentInstanceBase<$Item = any>
2256
2298
  /** The parent form component. */
2257
2299
  parentFormComponent: DitoComponentInstanceBase | null
2258
2300
  /** The root component instance. */
2259
- rootComponent: DitoComponentInstanceBase | null
2301
+ rootComponent: DitoRootInstance | null
2260
2302
  /** The nearest ancestor tab component. */
2261
2303
  tabComponent: DitoComponentInstanceBase | null
2262
2304
  /** The parent route component. */
@@ -2485,8 +2527,63 @@ export interface EmitterMixin {
2485
2527
  ): this
2486
2528
  }
2487
2529
 
2530
+ /**
2531
+ * Counts the pending operations of a scope, e.g. the
2532
+ * requests of a resource component, of all components
2533
+ * in a view, or of the whole admin, and forwards them
2534
+ * to the tracker of the enclosing scope.
2535
+ */
2536
+ export interface LoadingTracker {
2537
+ /** The tracker of the enclosing scope, if any. */
2538
+ readonly parent: LoadingTracker | null
2539
+ /** The tracker of the outermost scope. */
2540
+ readonly root: LoadingTracker
2541
+ /** Whether any operation within the scope is pending. */
2542
+ readonly isLoading: boolean
2543
+ /**
2544
+ * Begins an operation in this scope and its enclosing
2545
+ * ones, and returns the function that ends it.
2546
+ */
2547
+ begin(): () => void
2548
+ /**
2549
+ * Tracks the operation that `callback()` performs until
2550
+ * its returned promise settles, and returns its result.
2551
+ */
2552
+ track<T>(callback: () => T | Promise<T>): Promise<T>
2553
+ }
2554
+
2555
+ /**
2556
+ * The members of components that load and submit their
2557
+ * data through API resources (ResourceMixin): forms and
2558
+ * sources.
2559
+ */
2560
+ export interface DitoResourceInstance {
2561
+ /**
2562
+ * Whether any request or other operation within this
2563
+ * component's scope is pending, as tracked by its
2564
+ * `LoadingTracker`.
2565
+ */
2566
+ readonly isLoading: boolean
2567
+ /** Tracks the pending requests of the component. */
2568
+ readonly loadingTracker: LoadingTracker
2569
+ /**
2570
+ * Begins or ends a loading operation of the component.
2571
+ * The options are ignored, since the operations are
2572
+ * always forwarded to the enclosing view and the root.
2573
+ *
2574
+ * @deprecated Use `loadingTracker.begin()` and call the
2575
+ * function that it returns, or
2576
+ * `loadingTracker.track(callback)` instead.
2577
+ */
2578
+ setLoading(
2579
+ isLoading: boolean,
2580
+ options?: { updateRoot?: boolean; updateView?: boolean }
2581
+ ): void
2582
+ }
2583
+
2488
2584
  export interface DitoFormInstance<$Item = any>
2489
- extends DitoComponentInstanceBase<$Item> {
2585
+ extends DitoComponentInstanceBase<$Item>,
2586
+ DitoResourceInstance {
2490
2587
  /**
2491
2588
  * Whether this form is creating a new item
2492
2589
  * (`true`) or editing an existing one (`false`).
@@ -2645,10 +2742,38 @@ export interface DitoFormInstance<$Item = any>
2645
2742
  getChildPath(path: string): string
2646
2743
  }
2647
2744
 
2745
+ /** The root component of the admin (DitoRoot). */
2746
+ export interface DitoRootInstance extends DitoComponentInstanceBase {
2747
+ /**
2748
+ * Whether any request or other operation within the
2749
+ * admin is pending, as tracked by its `LoadingTracker`.
2750
+ */
2751
+ readonly isLoading: boolean
2752
+ /** Tracks the pending requests of the whole admin. */
2753
+ readonly loadingTracker: LoadingTracker
2754
+ }
2755
+
2648
2756
  export interface DitoViewInstance<$Item = any>
2649
2757
  extends DitoComponentInstanceBase<$Item> {
2650
2758
  /** Always `true` for view components. */
2651
2759
  isView: true
2760
+ /**
2761
+ * Whether any request or other operation within the
2762
+ * view, e.g. of its components, is pending, as
2763
+ * tracked by its `LoadingTracker`. The view is
2764
+ * disabled while it is loading.
2765
+ */
2766
+ readonly isLoading: boolean
2767
+ /** Tracks the pending requests of the view's components. */
2768
+ readonly loadingTracker: LoadingTracker
2769
+ /**
2770
+ * Begins or ends a loading operation of the view.
2771
+ *
2772
+ * @deprecated Use `loadingTracker.begin()` and call the
2773
+ * function that it returns, or
2774
+ * `loadingTracker.track(callback)` instead.
2775
+ */
2776
+ setLoading(isLoading: boolean): void
2652
2777
 
2653
2778
  // -- Route (RouteMixin) --
2654
2779
 
@@ -2767,7 +2892,8 @@ export interface DitoSchemaInstance<$Item = any>
2767
2892
  }
2768
2893
 
2769
2894
  export interface DitoSourceInstance<$Item = any>
2770
- extends DitoComponentInstanceBase<$Item> {
2895
+ extends DitoComponentInstanceBase<$Item>,
2896
+ DitoResourceInstance {
2771
2897
  // -- Data access (SourceMixin) --
2772
2898
 
2773
2899
  /** The list data array (getter/setter). */
@@ -3465,6 +3591,7 @@ export type SchemaByType<$Item = any> = {
3465
3591
  'url': InputSchema<$Item>
3466
3592
  'label': LabelSchema<$Item>
3467
3593
  'section': SectionSchema<$Item>
3594
+ 'panel': PanelSchema<$Item>
3468
3595
  'hidden': HiddenSchema<$Item>
3469
3596
  'unknown': never
3470
3597
  }
@@ -1,23 +0,0 @@
1
- // @vue/component
2
- export default {
3
- data() {
4
- return {
5
- isLoading: false
6
- }
7
- },
8
-
9
- methods: {
10
- setLoading(isLoading, { updateRoot = false, updateView = false } = {}) {
11
- if (!this.isLoading ^ !isLoading) {
12
- // Boolean xor
13
- this.isLoading = !!isLoading
14
- if (updateRoot) {
15
- this.rootComponent.registerLoading(isLoading)
16
- }
17
- if (updateView) {
18
- this.viewComponent.setLoading(this.isLoading)
19
- }
20
- }
21
- }
22
- }
23
- }
@@ -1,72 +0,0 @@
1
- import DomMixin from './DomMixin.js'
2
-
3
- // @vue/component
4
- export default {
5
- mixins: [DomMixin],
6
-
7
- data() {
8
- return {
9
- pulldown: {
10
- open: false,
11
- startTime: 0,
12
- checkTime: true,
13
- events: {
14
- mousedown: () => {
15
- this.setPulldownOpen(false)
16
- this.pulldown.handlers.remove()
17
- },
18
-
19
- mouseup: () => {
20
- if (this.onPulldownMouseUp()) {
21
- this.pulldown.handlers.remove()
22
- }
23
- }
24
- },
25
- handlers: null
26
- }
27
- }
28
- },
29
-
30
- computed: {
31
- pulldownTriggerAttributes() {
32
- return {
33
- 'aria-haspopup': 'menu',
34
- 'aria-expanded': this.pulldown.open
35
- }
36
- }
37
- },
38
-
39
- methods: {
40
- onPulldownMouseDown(value = null) {
41
- if (value === null) {
42
- this.setPulldownOpen(true)
43
- this.checkTime = true
44
- } else {
45
- this.checkTime = false
46
- }
47
- },
48
-
49
- onPulldownMouseUp(value = null) {
50
- const { startTime } = this.pulldown
51
- if (!this.checkTime || startTime && (Date.now() - startTime > 250)) {
52
- this.setPulldownOpen(false)
53
- if (value !== null) {
54
- this.onPulldownSelect(value)
55
- }
56
- return true
57
- }
58
- },
59
-
60
- onPulldownSelect(/* value */) {
61
- // NOTE: To be overridden.
62
- },
63
-
64
- setPulldownOpen(open) {
65
- this.pulldown.open = open
66
- this.pulldown.startTime = open ? Date.now() : 0
67
- if (open) {
68
- this.pulldown.handlers = this.domOn(document, this.pulldown.events)
69
- }
70
- }
71
- }
72
- }
@@ -1,38 +0,0 @@
1
- @import '../styles/_imports';
2
-
3
- .dito-pulldown {
4
- $self: &;
5
-
6
- display: none;
7
- position: absolute;
8
- top: 0;
9
- z-index: $z-index-popup;
10
- border-radius: $pulldown-radius;
11
- box-shadow: $shadow-window;
12
- overflow: hidden;
13
-
14
- &--open {
15
- display: block;
16
- }
17
-
18
- li {
19
- @include user-select(none);
20
- }
21
-
22
- &__item {
23
- display: block;
24
- padding: $pulldown-padding;
25
- line-height: 1;
26
- background: $button-color;
27
-
28
- &--disabled {
29
- color: $color-disabled;
30
- cursor: default;
31
- }
32
-
33
- &:not(&--disabled):hover {
34
- background: $color-active;
35
- color: $color-white;
36
- }
37
- }
38
- }