@radio-garden/ditojs-admin 2.85.2-0.5067ad799

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 (153) hide show
  1. package/README.md +180 -0
  2. package/dist/dito-admin.css +1 -0
  3. package/dist/dito-admin.es.js +12106 -0
  4. package/dist/dito-admin.umd.js +7 -0
  5. package/package.json +96 -0
  6. package/src/DitoAdmin.js +293 -0
  7. package/src/DitoComponent.js +34 -0
  8. package/src/DitoContext.js +318 -0
  9. package/src/DitoTypeComponent.js +42 -0
  10. package/src/DitoUser.js +12 -0
  11. package/src/appState.js +12 -0
  12. package/src/components/DitoAccount.vue +60 -0
  13. package/src/components/DitoAffix.vue +68 -0
  14. package/src/components/DitoAffixes.vue +200 -0
  15. package/src/components/DitoButtons.vue +80 -0
  16. package/src/components/DitoClipboard.vue +186 -0
  17. package/src/components/DitoContainer.vue +374 -0
  18. package/src/components/DitoCreateButton.vue +146 -0
  19. package/src/components/DitoDialog.vue +242 -0
  20. package/src/components/DitoDraggable.vue +117 -0
  21. package/src/components/DitoEditButtons.vue +135 -0
  22. package/src/components/DitoErrors.vue +83 -0
  23. package/src/components/DitoForm.vue +521 -0
  24. package/src/components/DitoFormInner.vue +26 -0
  25. package/src/components/DitoFormNested.vue +17 -0
  26. package/src/components/DitoHeader.vue +84 -0
  27. package/src/components/DitoLabel.vue +200 -0
  28. package/src/components/DitoMenu.vue +186 -0
  29. package/src/components/DitoNavigation.vue +40 -0
  30. package/src/components/DitoNotifications.vue +170 -0
  31. package/src/components/DitoPagination.vue +42 -0
  32. package/src/components/DitoPane.vue +334 -0
  33. package/src/components/DitoPanel.vue +256 -0
  34. package/src/components/DitoPanels.vue +61 -0
  35. package/src/components/DitoRoot.vue +524 -0
  36. package/src/components/DitoSchema.vue +846 -0
  37. package/src/components/DitoSchemaInlined.vue +97 -0
  38. package/src/components/DitoScopes.vue +76 -0
  39. package/src/components/DitoSidebar.vue +50 -0
  40. package/src/components/DitoSpinner.vue +95 -0
  41. package/src/components/DitoTableCell.vue +64 -0
  42. package/src/components/DitoTableHead.vue +121 -0
  43. package/src/components/DitoTabs.vue +103 -0
  44. package/src/components/DitoTrail.vue +124 -0
  45. package/src/components/DitoTreeItem.vue +420 -0
  46. package/src/components/DitoUploadFile.vue +199 -0
  47. package/src/components/DitoVNode.vue +14 -0
  48. package/src/components/DitoView.vue +143 -0
  49. package/src/components/index.js +42 -0
  50. package/src/directives/resize.js +83 -0
  51. package/src/index.js +1 -0
  52. package/src/mixins/ContextMixin.js +68 -0
  53. package/src/mixins/DataMixin.js +131 -0
  54. package/src/mixins/DitoMixin.js +591 -0
  55. package/src/mixins/DomMixin.js +29 -0
  56. package/src/mixins/EmitterMixin.js +158 -0
  57. package/src/mixins/ItemMixin.js +144 -0
  58. package/src/mixins/LoadingMixin.js +23 -0
  59. package/src/mixins/NumberMixin.js +118 -0
  60. package/src/mixins/OptionsMixin.js +304 -0
  61. package/src/mixins/PulldownMixin.js +63 -0
  62. package/src/mixins/ResourceMixin.js +398 -0
  63. package/src/mixins/RouteMixin.js +190 -0
  64. package/src/mixins/SchemaParentMixin.js +33 -0
  65. package/src/mixins/SortableMixin.js +49 -0
  66. package/src/mixins/SourceMixin.js +734 -0
  67. package/src/mixins/TextMixin.js +26 -0
  68. package/src/mixins/TypeMixin.js +280 -0
  69. package/src/mixins/ValidationMixin.js +119 -0
  70. package/src/mixins/ValidatorMixin.js +57 -0
  71. package/src/mixins/ValueMixin.js +31 -0
  72. package/src/styles/_base.scss +17 -0
  73. package/src/styles/_button.scss +191 -0
  74. package/src/styles/_imports.scss +3 -0
  75. package/src/styles/_info.scss +19 -0
  76. package/src/styles/_layout.scss +19 -0
  77. package/src/styles/_pulldown.scss +38 -0
  78. package/src/styles/_scroll.scss +13 -0
  79. package/src/styles/_settings.scss +88 -0
  80. package/src/styles/_table.scss +223 -0
  81. package/src/styles/_tippy.scss +45 -0
  82. package/src/styles/style.scss +9 -0
  83. package/src/types/DitoTypeButton.vue +143 -0
  84. package/src/types/DitoTypeCheckbox.vue +27 -0
  85. package/src/types/DitoTypeCheckboxes.vue +65 -0
  86. package/src/types/DitoTypeCode.vue +199 -0
  87. package/src/types/DitoTypeColor.vue +272 -0
  88. package/src/types/DitoTypeComponent.vue +31 -0
  89. package/src/types/DitoTypeComputed.vue +50 -0
  90. package/src/types/DitoTypeDate.vue +99 -0
  91. package/src/types/DitoTypeLabel.vue +23 -0
  92. package/src/types/DitoTypeList.vue +364 -0
  93. package/src/types/DitoTypeMarkup.vue +700 -0
  94. package/src/types/DitoTypeMultiselect.vue +522 -0
  95. package/src/types/DitoTypeNumber.vue +66 -0
  96. package/src/types/DitoTypeObject.vue +136 -0
  97. package/src/types/DitoTypePanel.vue +18 -0
  98. package/src/types/DitoTypeProgress.vue +40 -0
  99. package/src/types/DitoTypeRadio.vue +45 -0
  100. package/src/types/DitoTypeSection.vue +80 -0
  101. package/src/types/DitoTypeSelect.vue +133 -0
  102. package/src/types/DitoTypeSlider.vue +66 -0
  103. package/src/types/DitoTypeSpacer.vue +11 -0
  104. package/src/types/DitoTypeSwitch.vue +40 -0
  105. package/src/types/DitoTypeText.vue +101 -0
  106. package/src/types/DitoTypeTextarea.vue +48 -0
  107. package/src/types/DitoTypeTreeList.vue +193 -0
  108. package/src/types/DitoTypeUpload.vue +503 -0
  109. package/src/types/index.js +30 -0
  110. package/src/utils/SchemaGraph.js +147 -0
  111. package/src/utils/accessor.js +75 -0
  112. package/src/utils/agent.js +47 -0
  113. package/src/utils/data.js +92 -0
  114. package/src/utils/filter.js +266 -0
  115. package/src/utils/math.js +14 -0
  116. package/src/utils/options.js +48 -0
  117. package/src/utils/path.js +5 -0
  118. package/src/utils/resource.js +44 -0
  119. package/src/utils/route.js +53 -0
  120. package/src/utils/schema.js +1121 -0
  121. package/src/utils/type.js +81 -0
  122. package/src/utils/uid.js +15 -0
  123. package/src/utils/units.js +5 -0
  124. package/src/validators/_creditcard.js +6 -0
  125. package/src/validators/_decimals.js +11 -0
  126. package/src/validators/_domain.js +6 -0
  127. package/src/validators/_email.js +6 -0
  128. package/src/validators/_hostname.js +6 -0
  129. package/src/validators/_integer.js +6 -0
  130. package/src/validators/_max.js +6 -0
  131. package/src/validators/_min.js +6 -0
  132. package/src/validators/_password.js +5 -0
  133. package/src/validators/_range.js +6 -0
  134. package/src/validators/_required.js +9 -0
  135. package/src/validators/_url.js +6 -0
  136. package/src/validators/index.js +12 -0
  137. package/src/verbs.js +17 -0
  138. package/types/index.d.ts +3298 -0
  139. package/types/tests/admin.test-d.ts +27 -0
  140. package/types/tests/component-buttons.test-d.ts +44 -0
  141. package/types/tests/component-list.test-d.ts +159 -0
  142. package/types/tests/component-misc.test-d.ts +137 -0
  143. package/types/tests/component-object.test-d.ts +69 -0
  144. package/types/tests/component-section.test-d.ts +174 -0
  145. package/types/tests/component-select.test-d.ts +107 -0
  146. package/types/tests/components.test-d.ts +81 -0
  147. package/types/tests/context.test-d.ts +31 -0
  148. package/types/tests/fixtures.ts +24 -0
  149. package/types/tests/form.test-d.ts +109 -0
  150. package/types/tests/instance.test-d.ts +20 -0
  151. package/types/tests/schema-features.test-d.ts +402 -0
  152. package/types/tests/variance.test-d.ts +125 -0
  153. package/types/tests/view.test-d.ts +146 -0
@@ -0,0 +1,199 @@
1
+ <template lang="pug">
2
+ .dito-upload-file
3
+ .dito-thumbnail(
4
+ v-if="thumbnail"
5
+ :class="`dito-thumbnail--${thumbnail}`"
6
+ )
7
+ .dito-thumbnail__inner
8
+ img(
9
+ v-if="source"
10
+ :src="source"
11
+ crossorigin="anonymous"
12
+ )
13
+ .dito-thumbnail__type(
14
+ v-else
15
+ )
16
+ span {{ type }}
17
+ span {{ file.name }}
18
+ </template>
19
+
20
+ <script>
21
+ import DitoComponent from '../DitoComponent.js'
22
+
23
+ // @vue/component
24
+ export default DitoComponent.component('DitoUploadFile', {
25
+ props: {
26
+ file: { type: Object, required: true },
27
+ thumbnail: { type: String, default: null },
28
+ thumbnailUrl: { type: String, default: null }
29
+ },
30
+
31
+ data() {
32
+ return {
33
+ uploadUrl: null
34
+ }
35
+ },
36
+
37
+ computed: {
38
+ type() {
39
+ return (
40
+ TYPES[this.file.type] ||
41
+ this.file.name.split('.').pop().toUpperCase()
42
+ )
43
+ },
44
+
45
+ source() {
46
+ return this.uploadUrl || this.thumbnailUrl
47
+ }
48
+ },
49
+
50
+ watch: {
51
+ 'file.upload.file': {
52
+ immediate: true,
53
+ handler(file) {
54
+ if (this.thumbnail && file?.type.startsWith('image/')) {
55
+ const reader = new FileReader()
56
+ reader.onload = () => {
57
+ this.uploadUrl = reader.result
58
+ }
59
+ reader.readAsDataURL(file)
60
+ } else {
61
+ this.uploadUrl = null
62
+ }
63
+ }
64
+ }
65
+ }
66
+ })
67
+
68
+ const TYPES = {
69
+ 'text/plain': 'TXT',
70
+ 'text/html': 'HTML',
71
+ 'text/css': 'CSS',
72
+ 'text/javascript': 'JS',
73
+ 'image/jpeg': 'JPG',
74
+ 'image/png': 'PNG',
75
+ 'image/gif': 'GIF',
76
+ 'image/svg+xml': 'SVG',
77
+ 'movie/mp4': 'MP4',
78
+ 'audio/mpeg': 'MP3',
79
+ 'application/json': 'JSON',
80
+ 'application/xml': 'XML',
81
+ 'application/pdf': 'PDF',
82
+ 'application/zip': 'ZIP'
83
+ }
84
+ </script>
85
+
86
+ <style lang="scss">
87
+ @use 'sass:math';
88
+ @import '../styles/_imports';
89
+
90
+ .dito-upload-file {
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: flex-start;
94
+ }
95
+
96
+ .dito-thumbnail {
97
+ $self: &;
98
+
99
+ // Small size by default
100
+ --max-size: #{1 * $input-height};
101
+ --corner-size: calc(var(--max-size) / 5);
102
+ --shadow-size: 1px;
103
+ --min-size: calc(2 * var(--corner-size));
104
+ --margin: 0em;
105
+ --drop-shadow: drop-shadow(
106
+ 0 calc(var(--shadow-size) * 0.75) var(--shadow-size)
107
+ #{rgba($color-black, 0.4)}
108
+ );
109
+
110
+ position: relative;
111
+ margin: var(--margin);
112
+ margin-right: 0.5em;
113
+ filter: var(--drop-shadow);
114
+
115
+ &--small {
116
+ --max-size: #{1 * $input-height};
117
+ --margin: 0em;
118
+ --shadow-size: 1px;
119
+ }
120
+
121
+ &--medium {
122
+ --max-size: #{2 * $input-height};
123
+ --margin: 0.25em;
124
+ --shadow-size: 1.5px;
125
+ }
126
+
127
+ &--large {
128
+ --max-size: #{4 * $input-height};
129
+ --margin: 0.5em;
130
+ --shadow-size: 2.5px;
131
+ }
132
+
133
+ &__inner {
134
+ background: #ffffff;
135
+ clip-path: polygon(
136
+ 0 0,
137
+ calc(100% - var(--corner-size)) 0,
138
+ 100% var(--corner-size),
139
+ 100% 100%,
140
+ 0 100%
141
+ );
142
+
143
+ &::after {
144
+ content: '';
145
+ position: absolute;
146
+ top: 0;
147
+ right: 0;
148
+ width: var(--corner-size);
149
+ height: var(--corner-size);
150
+ background: linear-gradient(45deg, #ffffff, #eeeeee 40%, #dddddd 50%);
151
+ filter: var(--drop-shadow);
152
+ }
153
+ }
154
+
155
+ &__type {
156
+ --font-size: var(--corner-size);
157
+
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ min-width: var(--min-size);
162
+ min-height: var(--max-size);
163
+ aspect-ratio: 3 / 4;
164
+
165
+ span {
166
+ --color: #{$color-grey};
167
+
168
+ font-size: min(var(--font-size), #{1.25 * $font-size});
169
+ color: var(--color);
170
+
171
+ #{$self}:not(#{$self}--small) & {
172
+ padding: 0 calc(var(--font-size) / 4);
173
+ border-radius: calc(var(--font-size) / 4);
174
+ background: var(--color);
175
+ color: #ffffff;
176
+ }
177
+
178
+ #{$self}--medium & {
179
+ --color: #{$color-light};
180
+ }
181
+
182
+ #{$self}--large & {
183
+ --color: #{$color-lighter};
184
+ }
185
+ }
186
+ }
187
+
188
+ img {
189
+ display: block;
190
+ // SVG images need 100% settings to scale into the container.
191
+ width: 100%;
192
+ height: 100%;
193
+ min-width: var(--min-size);
194
+ min-height: var(--min-size);
195
+ max-width: var(--max-size);
196
+ max-height: var(--max-size);
197
+ }
198
+ }
199
+ </style>
@@ -0,0 +1,14 @@
1
+ <script>
2
+ import DitoComponent from '../DitoComponent.js'
3
+
4
+ // @vue/component
5
+ function DitoVNode({ vnode }) {
6
+ return vnode
7
+ }
8
+
9
+ DitoVNode.props = {
10
+ vnode: { type: Object, required: true }
11
+ }
12
+
13
+ export default DitoComponent.component('DitoVnode', DitoVNode)
14
+ </script>
@@ -0,0 +1,143 @@
1
+ <template lang="pug">
2
+ template(
3
+ v-if="user && shouldRenderSchema(viewSchema)"
4
+ )
5
+ //- Only render DitoView when it is active, otherwise a normal router-view
6
+ //- instead, to nest further route components.
7
+ //- NOTE: This is different from the handling in DitoForm, where `v-show` is
8
+ //- used to always render forms even when other nested forms are present.
9
+ RouterView(
10
+ v-if="!isLastRoute"
11
+ :key="name"
12
+ )
13
+ .dito-view.dito-scroll-parent(
14
+ v-else
15
+ :data-resource="sourceSchema.path"
16
+ )
17
+ DitoSchema(
18
+ :key="name"
19
+ :schema="viewSchema"
20
+ :data="data"
21
+ :meta="meta"
22
+ :store="getChildStore(name)"
23
+ padding="root"
24
+ :disabled="isLoading"
25
+ scrollable
26
+ single
27
+ )
28
+ </template>
29
+
30
+ <script>
31
+ import DitoComponent from '../DitoComponent.js'
32
+ import RouteMixin from '../mixins/RouteMixin.js'
33
+ import {
34
+ isSingleComponentView,
35
+ someNestedSchemaComponent
36
+ } from '../utils/schema.js'
37
+ import { hasResource } from '../utils/resource.js'
38
+
39
+ // @vue/component
40
+ export default DitoComponent.component('DitoView', {
41
+ mixins: [RouteMixin],
42
+
43
+ provide() {
44
+ // Redirect $sourceComponent and $resourceComponent to the main component:
45
+ return {
46
+ $sourceComponent: () => this.mainComponent?.sourceComponent || null,
47
+ $resourceComponent: () => this.mainComponent?.resourceComponent || null
48
+ }
49
+ },
50
+
51
+ data() {
52
+ return {
53
+ // Updated from LoadingMixin through `setLoading(isLoading)`:
54
+ isLoading: false,
55
+ // NOTE: Data is shared across all views because the router recycles the
56
+ // DitoView component.
57
+ data: {}
58
+ }
59
+ },
60
+
61
+ computed: {
62
+ schema() {
63
+ return this.meta.schema ?? {}
64
+ },
65
+
66
+ name() {
67
+ return this.schema.name
68
+ },
69
+
70
+ isView() {
71
+ return true
72
+ },
73
+
74
+ isSingleComponentView() {
75
+ return isSingleComponentView(this.schema)
76
+ },
77
+
78
+ mainComponent() {
79
+ return this.mainSchemaComponent?.getComponentByDataPath(this.name)
80
+ },
81
+
82
+ viewSchema() {
83
+ const { component, ...schema } = this.schema
84
+ // Translate single-component schemas into multi-component schemas,
85
+ // so they can be rendered directly through DitoSchema also:
86
+ return this.isSingleComponentView
87
+ ? {
88
+ ...schema,
89
+ components: {
90
+ [schema.name]: {
91
+ name: schema.name,
92
+ label: false,
93
+ ...component
94
+ }
95
+ }
96
+ }
97
+ : schema
98
+ },
99
+
100
+ providesData() {
101
+ return someNestedSchemaComponent(this.viewSchema, hasResource)
102
+ }
103
+ },
104
+
105
+ watch: {
106
+ $route: {
107
+ // https://github.com/vuejs/vue-router/issues/3393#issuecomment-1158470149
108
+ flush: 'post',
109
+ handler(to, from) {
110
+ // See if the route changes completely, and clear the data if it does.
111
+ if (this.isFullRouteChange(to, from)) {
112
+ this.isLoading = false
113
+ this.data = {}
114
+ }
115
+ }
116
+ }
117
+ },
118
+
119
+ mounted() {
120
+ // Prevent bypassing of if-condition by direct URL access.
121
+ if (!this.shouldRenderSchema(this.viewSchema)) {
122
+ this.$router.replace({ path: '/' })
123
+ }
124
+ },
125
+
126
+ methods: {
127
+ setData(data) {
128
+ this.data = data
129
+ },
130
+
131
+ getChildPath(path) {
132
+ // Lists inside single-component views use the view's path for sub-paths:
133
+ return this.isSingleComponentView
134
+ ? this.path
135
+ : `${this.path}/${path}`
136
+ },
137
+
138
+ setLoading(isLoading) {
139
+ this.isLoading = !!isLoading
140
+ }
141
+ }
142
+ })
143
+ </script>
@@ -0,0 +1,42 @@
1
+ // NOTE: index.js exports nothing, but Dito.js components will be registered in
2
+ // DitoComponent and can be rendered through their tag-names.
3
+ // NOTE: Sequence is meaningful for reasons of CSS declaration sequence. The
4
+ // convention is in order of encountered hierarchy in the DOM.
5
+
6
+ export { default as DitoRoot } from './DitoRoot.vue'
7
+ export { default as DitoMenu } from './DitoMenu.vue'
8
+ export { default as DitoTrail } from './DitoTrail.vue'
9
+ export { default as DitoHeader } from './DitoHeader.vue'
10
+ export { default as DitoNavigation } from './DitoNavigation.vue'
11
+ export { default as DitoNotifications } from './DitoNotifications.vue'
12
+ export { default as DitoSidebar } from './DitoSidebar.vue'
13
+ export { default as DitoAccount } from './DitoAccount.vue'
14
+ export { default as DitoDialog } from './DitoDialog.vue'
15
+ export { default as DitoLabel } from './DitoLabel.vue'
16
+ export { default as DitoSchema } from './DitoSchema.vue'
17
+ export { default as DitoSchemaInlined } from './DitoSchemaInlined.vue'
18
+ export { default as DitoPane } from './DitoPane.vue'
19
+ export { default as DitoContainer } from './DitoContainer.vue'
20
+ export { default as DitoTabs } from './DitoTabs.vue'
21
+ export { default as DitoPanel } from './DitoPanel.vue'
22
+ export { default as DitoPanels } from './DitoPanels.vue'
23
+ export { default as DitoButtons } from './DitoButtons.vue'
24
+ export { default as DitoEditButtons } from './DitoEditButtons.vue'
25
+ export { default as DitoCreateButton } from './DitoCreateButton.vue'
26
+ export { default as DitoClipboard } from './DitoClipboard.vue'
27
+ export { default as DitoView } from './DitoView.vue'
28
+ export { default as DitoForm } from './DitoForm.vue'
29
+ export { default as DitoFormInner } from './DitoFormInner.vue'
30
+ export { default as DitoFormNested } from './DitoFormNested.vue'
31
+ export { default as DitoErrors } from './DitoErrors.vue'
32
+ export { default as DitoScopes } from './DitoScopes.vue'
33
+ export { default as DitoPagination } from './DitoPagination.vue'
34
+ export { default as DitoTreeItem } from './DitoTreeItem.vue'
35
+ export { default as DitoTableHead } from './DitoTableHead.vue'
36
+ export { default as DitoTableCell } from './DitoTableCell.vue'
37
+ export { default as DitoUploadFile } from './DitoUploadFile.vue'
38
+ export { default as DitoDraggable } from './DitoDraggable.vue'
39
+ export { default as DitoSpinner } from './DitoSpinner.vue'
40
+ export { default as DitoAffix } from './DitoAffix.vue'
41
+ export { default as DitoAffixes } from './DitoAffixes.vue'
42
+ export { default as DitoVNode } from './DitoVNode.vue'
@@ -0,0 +1,83 @@
1
+ import { asArray } from '@ditojs/utils'
2
+
3
+ export default {
4
+ mounted(node, binding) {
5
+ observeResize(node, binding.value, binding.arg)
6
+ },
7
+
8
+ unmounted(node, binding) {
9
+ unobserveResize(node, binding.value, binding.arg)
10
+ }
11
+ }
12
+
13
+ export function observeResize(node, handler, options) {
14
+ Observer.getObserver(options).observe(node, handler)
15
+ }
16
+
17
+ export function unobserveResize(node, handler, options) {
18
+ Observer.getObserver(options).unobserve(node, handler)
19
+ }
20
+
21
+ export const isResizeSupported = typeof ResizeObserver !== 'undefined'
22
+
23
+ const observers = {}
24
+
25
+ class Observer {
26
+ constructor(key, options) {
27
+ this.key = key
28
+ this.options = options
29
+ this.observer = isResizeSupported
30
+ ? new ResizeObserver(entries => this.handle(entries))
31
+ : null
32
+ this.handlersByNode = new WeakMap()
33
+ this.nodeCount = 0
34
+ }
35
+
36
+ observe(node, handler) {
37
+ let handlers = this.handlersByNode.get(node)
38
+ if (!handlers) {
39
+ handlers = new Set()
40
+ this.handlersByNode.set(node, handlers)
41
+ this.observer?.observe(node, this.options)
42
+ this.nodeCount++
43
+ }
44
+ handlers.add(handler)
45
+ }
46
+
47
+ unobserve(node, handler) {
48
+ const handlers = this.handlersByNode.get(node)
49
+ if (handlers?.delete(handler) && handlers.size === 0) {
50
+ this.handlersByNode.delete(node)
51
+ this.observer?.unobserve(node)
52
+ if (--this.nodeCount === 0) {
53
+ delete observers[this.key]
54
+ }
55
+ }
56
+ }
57
+
58
+ handle(entries) {
59
+ for (const entry of entries) {
60
+ const handlers = this.handlersByNode.get(entry.target)
61
+ if (handlers) {
62
+ const event = {
63
+ target: entry.target,
64
+ contentRect: entry.contentRect,
65
+ // Use `asArray` since Firefox before v92 returns these as objects:
66
+ borderBoxSize: asArray(entry.borderBoxSize),
67
+ contentBoxSize: asArray(entry.contentBoxSize),
68
+ devicePixelContentBoxSize: asArray(entry.devicePixelContentBoxSize)
69
+ }
70
+ for (const handler of handlers) {
71
+ handler(event)
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ static getObserver({ box = 'content-box' } = {}) {
78
+ const options = { box }
79
+ const key = JSON.stringify(options)
80
+ observers[key] ||= new Observer(key, options)
81
+ return observers[key]
82
+ }
83
+ }
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export { default } from './DitoAdmin.js'
@@ -0,0 +1,68 @@
1
+ import DitoContext from '../DitoContext.js'
2
+ import { getItem, getParentItem } from '../utils/data.js'
3
+
4
+ // @vue/component
5
+ export default {
6
+ computed: {
7
+ context() {
8
+ return new DitoContext(this, { nested: this.nested })
9
+ },
10
+
11
+ // The following computed properties are similar to `DitoContext`
12
+ // properties, so that we can access these on `this` as well:
13
+ // NOTE: While internally, we speak of `data`, in the API surface the
14
+ // term `item` is used for the data that relates to editing objects.
15
+ // NOTE: This should always return the same as:
16
+ // return getItem(this.rootData, this.dataPath, false)
17
+ parentData() {
18
+ const data = getParentItem(this.rootData, this.dataPath, this.nested)
19
+ return data !== this.data ? data : null
20
+ },
21
+
22
+ // @overridable
23
+ processedData() {
24
+ return getProcessedParentData(this, this.dataPath, this.nested)
25
+ },
26
+
27
+ processedRootData() {
28
+ return getProcessedParentData(this, '', false)
29
+ },
30
+
31
+ item() {
32
+ return this.data
33
+ },
34
+
35
+ parentItem() {
36
+ return this.parentData
37
+ },
38
+
39
+ rootItem() {
40
+ return this.rootData
41
+ },
42
+
43
+ processedItem() {
44
+ return this.processedData
45
+ },
46
+
47
+ processedRootItem() {
48
+ return this.processedRootData
49
+ }
50
+ }
51
+ }
52
+
53
+ function getProcessedParentData(component, dataPath, nested = false) {
54
+ // We can only get the processed data through the schemaComponent, but
55
+ // that's not necessarily the item represented by this component.
56
+ // Solution: Find the relative path and the processed sub-item from there:
57
+ let { schemaComponent } = component
58
+ // Find the schema component that contains the desired data-path:
59
+ while (schemaComponent.dataPath.length > dataPath.length) {
60
+ schemaComponent = schemaComponent.parentSchemaComponent
61
+ }
62
+ return getItem(
63
+ schemaComponent.processedData,
64
+ // Get the dataPath relative to the schemaComponent's data:
65
+ dataPath.slice(schemaComponent.dataPath.length),
66
+ nested
67
+ )
68
+ }
@@ -0,0 +1,131 @@
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
+ }