@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,334 @@
1
+ <!-- eslint-disable vue/no-template-shadow -->
2
+ <template lang="pug">
3
+ .dito-pane(
4
+ v-if="isPopulated && componentSchemas.length > 0"
5
+ v-resize="onResizePane"
6
+ :class="classes"
7
+ )
8
+ template(
9
+ v-for=`{
10
+ schema,
11
+ dataPath,
12
+ nestedDataPath,
13
+ nested,
14
+ store
15
+ }, index in componentSchemas`
16
+ )
17
+ //- Use <span> for .dito-pane__break so we can use the
18
+ //- `.dito-container:first-of-type` selector.
19
+ span.dito-pane__break(
20
+ v-if="['before', 'both'].includes(schema.break)"
21
+ )
22
+ DitoContainer(
23
+ v-if="shouldRenderSchema(schema)"
24
+ ref="containers"
25
+ :key="nestedDataPath"
26
+ :data-index="index"
27
+ :schema="schema"
28
+ :dataPath="dataPath"
29
+ :data="data"
30
+ :meta="meta"
31
+ :store="store"
32
+ :single="isSingleComponent"
33
+ :nested="nested"
34
+ :disabled="disabled"
35
+ :compact="compact"
36
+ :generateLabels="generateLabels"
37
+ :verticalLabels="isInLabeledRow(index)"
38
+ :accumulatedBasis="accumulatedBasis"
39
+ )
40
+ span.dito-pane__break(
41
+ v-if="['after', 'both'].includes(schema.break)"
42
+ )
43
+ </template>
44
+
45
+ <script>
46
+ import DitoComponent from '../DitoComponent.js'
47
+ import ContextMixin from '../mixins/ContextMixin.js'
48
+ import { appendDataPath } from '../utils/data.js'
49
+ import { isNested } from '../utils/schema.js'
50
+
51
+ // @vue/component
52
+ export default DitoComponent.component('DitoPane', {
53
+ mixins: [ContextMixin],
54
+
55
+ provide() {
56
+ return {
57
+ $tabComponent: () => this.tabComponent
58
+ }
59
+ },
60
+
61
+ props: {
62
+ schema: { type: Object, required: true },
63
+ dataPath: { type: String, default: '' },
64
+ data: { type: Object, default: null },
65
+ meta: { type: Object, required: true },
66
+ store: { type: Object, required: true },
67
+ tab: { type: String, default: null },
68
+ single: { type: Boolean, default: false },
69
+ padding: { type: String, default: null },
70
+ disabled: { type: Boolean, default: false },
71
+ compact: { type: Boolean, default: false },
72
+ generateLabels: { type: Boolean, default: false },
73
+ accumulatedBasis: { type: Number, default: null }
74
+ },
75
+
76
+ data() {
77
+ return {
78
+ positions: []
79
+ }
80
+ },
81
+
82
+ computed: {
83
+ nested() {
84
+ // For `ContextMixin`:
85
+ return false
86
+ },
87
+
88
+ classes() {
89
+ const prefix = 'dito-pane'
90
+ return {
91
+ [`${prefix}--single`]: this.isSingleComponent,
92
+ [`${prefix}--padding-${this.padding}`]: !!this.padding
93
+ }
94
+ },
95
+
96
+ tabComponent() {
97
+ return this.tab ? this : this.$tabComponent()
98
+ },
99
+
100
+ componentSchemas() {
101
+ // Compute a components list which has the dataPath baked into its keys
102
+ // and adds the key as the name to each component, used for labels, etc.
103
+ // NOTE: schema can be null while multi-form lists load their data,
104
+ // because only the available data will determine the type of form.
105
+ // When editing primitive values through a form, do not append 'name' to
106
+ // the component's dataPath so it can be mapped to from validation errors.
107
+ // NOTE: Not all schemas / components have a sourceSchema, e.g. dialogs.
108
+ const wrapPrimitives = this.sourceSchema?.wrapPrimitives
109
+ return Object.entries(this.schema?.components || {}).map(
110
+ ([name, schema]) => {
111
+ // Always add name to component schema, but clone it first to avoid
112
+ // mutating the original schema potentially used in multiple places.
113
+ schema = { ...schema, name }
114
+ // Share dataPath and store with parent if not nested:
115
+ const nested = isNested(schema)
116
+ const nestedDataPath = appendDataPath(this.dataPath, name)
117
+ return {
118
+ schema,
119
+ dataPath:
120
+ nested && !wrapPrimitives
121
+ ? nestedDataPath
122
+ : this.dataPath,
123
+ nestedDataPath,
124
+ nested,
125
+ store: this.getChildStore(name)
126
+ }
127
+ }
128
+ )
129
+ },
130
+
131
+ isSingleComponent() {
132
+ return this.single && this.componentSchemas.length === 1
133
+ },
134
+
135
+ verticalLabelsByIndices() {
136
+ const { positions } = this
137
+
138
+ const isLastInRow = index => (
139
+ positions[index] && (
140
+ index === positions.length - 1 ||
141
+ findNextPosition(index).top > positions[index].top
142
+ )
143
+ )
144
+
145
+ const findNextPosition = index => {
146
+ for (let i = index + 1; i < positions.length; i++) {
147
+ if (positions[i]) return positions[i]
148
+ }
149
+ return 0
150
+ }
151
+
152
+ const rows = []
153
+ let row = []
154
+ for (let index = 0; index < positions.length; index++) {
155
+ row.push(index)
156
+ if (isLastInRow(index)) {
157
+ rows.push(row)
158
+ row = []
159
+ }
160
+ }
161
+ if (row.length > 0) {
162
+ rows.push(row)
163
+ }
164
+
165
+ const verticalLabelsByIndices = []
166
+
167
+ for (const row of rows) {
168
+ let hasLabelsInRow = false
169
+ for (const index of row) {
170
+ const position = this.positions[index]
171
+ if (
172
+ position?.height > 2 && (
173
+ position.node.matches(':has(> .dito-label)') ||
174
+ position.node
175
+ .closest('.dito-container')
176
+ .matches('.dito-container--label-vertical')
177
+ )
178
+ ) {
179
+ // TODO: Handle nested schemas, e.g. 'section' or 'object' and
180
+ // detect labels there too.
181
+ hasLabelsInRow = true
182
+ break
183
+ }
184
+ }
185
+ for (const index of row) {
186
+ verticalLabelsByIndices[index] = hasLabelsInRow
187
+ }
188
+ }
189
+
190
+ return verticalLabelsByIndices
191
+ }
192
+ },
193
+
194
+ watch: {
195
+ 'componentSchemas.length'(length) {
196
+ this.positions.length = length
197
+ }
198
+ },
199
+
200
+ created() {
201
+ this._register(true)
202
+ },
203
+
204
+ unmounted() {
205
+ this._register(false)
206
+ },
207
+
208
+ methods: {
209
+ _register(add) {
210
+ this.schemaComponent._registerPane(this, add)
211
+ },
212
+
213
+ focus() {
214
+ if (this.tab) {
215
+ return this.$router.push({ hash: `#${this.tab}` })
216
+ }
217
+ },
218
+
219
+ onResizePane() {
220
+ this.$nextTick(() => {
221
+ for (const container of this.$refs.containers) {
222
+ const node = container.$el
223
+ const index = +node.dataset.index
224
+ const bounds = node.getBoundingClientRect()
225
+ const style = getComputedStyle(node)
226
+ const padding = parseFloat(style.padding)
227
+ const fontSize = parseFloat(style.fontSize)
228
+ const height = bounds.height - 2 * padding
229
+ this.positions[index] =
230
+ height <= 0
231
+ ? null
232
+ : {
233
+ top: bounds.y,
234
+ height: height / fontSize,
235
+ node
236
+ }
237
+ }
238
+ })
239
+ },
240
+
241
+ isInLabeledRow(index) {
242
+ return !!this.verticalLabelsByIndices[index]
243
+ }
244
+ }
245
+ })
246
+ </script>
247
+
248
+ <style lang="scss">
249
+ @import '../styles/_imports';
250
+
251
+ .dito-pane {
252
+ $self: &;
253
+ $root-padding: $content-padding - $form-spacing-half;
254
+
255
+ --pane-padding: 0px;
256
+ --container-padding: #{$form-spacing-half};
257
+
258
+ display: flex;
259
+ position: relative;
260
+ flex-flow: row wrap;
261
+ align-items: flex-start;
262
+ align-content: flex-start;
263
+ // Remove the padding added by `.dito-container` inside `.dito-pane`:
264
+ margin: -$form-spacing-half;
265
+ padding: var(--pane-padding);
266
+ // Use `flex: 0%` for all `.dito-pane` except `.dito-pane__main`,
267
+ // so that the `.dito-buttons--main` can be moved all the way to the bottom.
268
+ flex: 0%;
269
+
270
+ &__main {
271
+ flex: 100%;
272
+ }
273
+
274
+ &--padding-root {
275
+ // A root-level pane inside a scroll view. Clear negative margin from above.
276
+ margin: 0;
277
+ // Move the negative margin used to remove the padding added by
278
+ // `.dito-container` inside `.dito-pane` to the padding:
279
+ --pane-padding: #{$root-padding};
280
+
281
+ &#{$self}--single {
282
+ --pane-padding: #{$content-padding};
283
+ --container-padding: 0px;
284
+ }
285
+
286
+ &:has(> .dito-container--label-vertical:first-of-type) {
287
+ // Reduce top spacing when the first row has labels.
288
+ margin-top: -$form-spacing-half;
289
+ }
290
+
291
+ // Display a ruler between tabbed components and towards the .dito-buttons
292
+ &__tab + &__main {
293
+ &::before {
294
+ // Use a pseudo element to display a ruler with proper margins
295
+ display: block;
296
+ content: '';
297
+ width: 100%;
298
+ border-bottom: $border-style;
299
+ // Shift ruler up by $root-padding to exclude removed $form-spacing-half
300
+ margin: (-$root-padding) $form-spacing-half $root-padding;
301
+ padding: $form-spacing-half;
302
+ }
303
+ }
304
+
305
+ &__main + .dito-buttons--main {
306
+ // Needed forms with sticky main buttons.
307
+ margin: $content-padding;
308
+ margin-bottom: 0;
309
+ }
310
+ }
311
+
312
+ &--padding-inlined {
313
+ --pane-padding: 0px;
314
+ }
315
+
316
+ &--padding-nested {
317
+ --pane-padding: #{$form-spacing};
318
+
319
+ &:has(> .dito-container--label-vertical:first-of-type) {
320
+ // Reduce top spacing when the first row has labels.
321
+ padding-top: $form-spacing-half;
322
+ }
323
+ }
324
+
325
+ &__break {
326
+ // `&_break` is rendered as <span> so we can use the
327
+ // `.dito-container:first-of-type` selector to match the first container
328
+ // even if it has a break before it.
329
+ display: block;
330
+ flex: 100%;
331
+ height: 0;
332
+ }
333
+ }
334
+ </style>
@@ -0,0 +1,256 @@
1
+ <template lang="pug">
2
+ //- Only show panels in tabs when the tabs are also visible.
3
+ component.dito-panel(
4
+ v-if="shouldRenderSchema(panelSchema)"
5
+ v-show="visible && (!panelTabComponent || panelTabComponent.visible)"
6
+ :is="panelTag"
7
+ @submit.prevent
8
+ )
9
+ DitoSchema.dito-panel__schema(
10
+ :schema="panelSchema"
11
+ :dataSchema="panelDataSchema"
12
+ :dataPath="panelDataPath"
13
+ :data="panelData"
14
+ :meta="meta"
15
+ :store="store"
16
+ padding="nested"
17
+ :disabled="disabled"
18
+ :hasOwnData="hasOwnData"
19
+ generateLabels
20
+ )
21
+ template(#prepend)
22
+ h2.dito-panel__header(:class="{ 'dito-panel__header--sticky': sticky }")
23
+ span {{ getLabel(schema) }}
24
+ DitoButtons.dito-buttons--small(
25
+ :buttons="panelButtonSchemas"
26
+ :dataPath="panelDataPath"
27
+ :data="panelData"
28
+ :meta="meta"
29
+ :store="store"
30
+ :disabled="disabled"
31
+ )
32
+ template(#buttons)
33
+ DitoButtons(
34
+ :buttons="buttonSchemas"
35
+ :dataPath="panelDataPath"
36
+ :data="panelData"
37
+ :meta="meta"
38
+ :store="store"
39
+ :disabled="disabled"
40
+ )
41
+ </template>
42
+
43
+ <script>
44
+ import { isFunction } from '@ditojs/utils'
45
+ import DitoComponent from '../DitoComponent.js'
46
+ import ContextMixin from '../mixins/ContextMixin.js'
47
+ import ValidatorMixin from '../mixins/ValidatorMixin.js'
48
+ import { getButtonSchemas } from '../utils/schema.js'
49
+ import { getSchemaAccessor } from '../utils/accessor.js'
50
+
51
+ // @vue/component
52
+ export default DitoComponent.component('DitoPanel', {
53
+ mixins: [ContextMixin, ValidatorMixin],
54
+
55
+ provide() {
56
+ return {
57
+ $panelComponent: () => this,
58
+ $tabComponent: () => this.panelTabComponent
59
+ }
60
+ },
61
+
62
+ props: {
63
+ schema: { type: Object, required: true },
64
+ dataPath: { type: String, required: true },
65
+ data: { type: Object, required: true },
66
+ meta: { type: Object, required: true },
67
+ store: { type: Object, required: true },
68
+ disabled: { type: Boolean, required: true },
69
+ panelTabComponent: { type: Object, default: null }
70
+ },
71
+
72
+ data() {
73
+ return {
74
+ ownData: null
75
+ }
76
+ },
77
+
78
+ computed: {
79
+ nested() {
80
+ // For `ContextMixin`:
81
+ return true
82
+ },
83
+
84
+ panelComponent() {
85
+ return this
86
+ },
87
+
88
+ tabComponent() {
89
+ return this.panelTabComponent
90
+ },
91
+
92
+ buttonSchemas() {
93
+ return getButtonSchemas(this.schema.buttons)
94
+ },
95
+
96
+ panelButtonSchemas() {
97
+ return getButtonSchemas(this.schema.panelButtons)
98
+ },
99
+
100
+ hasOwnData() {
101
+ return !!this.ownData
102
+ },
103
+
104
+ panelData() {
105
+ return this.ownData || this.data
106
+ },
107
+
108
+ panelSchema() {
109
+ if (this.hasOwnData) {
110
+ return this.schema
111
+ } else {
112
+ // Remove `data` from the schema, so that DitoSchema isn't using it to
113
+ // produce its own data. See $filters panel for more details on data.
114
+ const { data, ...schema } = this.schema
115
+ return schema
116
+ }
117
+ },
118
+
119
+ panelTag() {
120
+ // Panels that provide their own data need their own form.
121
+ return this.hasOwnData ? 'form' : 'div'
122
+ },
123
+
124
+ panelDataSchema() {
125
+ return this.hasOwnData ? this.schema : this.schemaComponent.schema
126
+ },
127
+
128
+ panelDataPath() {
129
+ // If the panel provides its own data, then it needs to prefix all
130
+ // components with its data-path, but if it shares data with the schema
131
+ // component, then it should share the data-path name space too.
132
+ return this.hasOwnData ? this.dataPath : this.schemaComponent.dataPath
133
+ },
134
+
135
+ visible: getSchemaAccessor('visible', {
136
+ type: Boolean,
137
+ default: true
138
+ }),
139
+
140
+ sticky: getSchemaAccessor('sticky', {
141
+ type: Boolean,
142
+ default: false
143
+ })
144
+ },
145
+
146
+ created() {
147
+ this._register(true)
148
+ // NOTE: This is not the same as `schema.data` handling in DitoSchema,
149
+ // where the data is added to the actual component.
150
+ const { data } = this.schema
151
+ if (data) {
152
+ this.ownData = isFunction(data)
153
+ ? data(this.context)
154
+ : data
155
+ }
156
+ },
157
+
158
+ unmounted() {
159
+ this._register(false)
160
+ },
161
+
162
+ methods: {
163
+ _register(add) {
164
+ // Register the panels so that other components can find them by their
165
+ // data-path, e.g. in TypeList.onFilterErrors()
166
+ this.schemaComponent._registerPanel(this, add)
167
+ }
168
+ }
169
+ })
170
+ </script>
171
+
172
+ <style lang="scss">
173
+ @import '../styles/_imports';
174
+
175
+ .dito-panel {
176
+ &:not(:last-child) {
177
+ margin-bottom: $content-padding;
178
+ }
179
+
180
+ &__header {
181
+ display: block;
182
+ position: relative;
183
+ box-sizing: border-box;
184
+ padding: $input-padding;
185
+ background: $button-color;
186
+ border: $border-style;
187
+ border-top-left-radius: $border-radius;
188
+ border-top-right-radius: $border-radius;
189
+
190
+ &--sticky {
191
+ $margin: $input-height-factor * $line-height * $font-size-small +
192
+ $form-spacing;
193
+
194
+ position: sticky;
195
+ top: $content-padding;
196
+ margin-bottom: $margin;
197
+ z-index: 1;
198
+
199
+ & + * {
200
+ margin-top: -$margin;
201
+ }
202
+
203
+ &::before {
204
+ content: '';
205
+ display: block;
206
+ position: absolute;
207
+ background: $content-color-background;
208
+ left: 0;
209
+ right: 0;
210
+ height: $content-padding;
211
+ top: -$content-padding;
212
+ margin: -$border-width;
213
+ }
214
+ }
215
+
216
+ .dito-buttons {
217
+ position: absolute;
218
+ right: $input-padding-ver;
219
+ top: 50%;
220
+ transform: translateY(-50%);
221
+ }
222
+ }
223
+
224
+ &__schema {
225
+ font-size: $font-size-small;
226
+ background: $content-color-background;
227
+ border: $border-style;
228
+ border-top: 0;
229
+ border-bottom-left-radius: $border-radius;
230
+ border-bottom-right-radius: $border-radius;
231
+
232
+ > .dito-schema-content {
233
+ .dito-object {
234
+ border: none;
235
+ padding: 0;
236
+ }
237
+
238
+ > .dito-buttons {
239
+ --button-margin: #{$form-spacing};
240
+
241
+ padding: $form-spacing;
242
+
243
+ .dito-container {
244
+ padding: 0;
245
+ }
246
+ }
247
+ }
248
+
249
+ .dito-label {
250
+ label {
251
+ font-weight: normal;
252
+ }
253
+ }
254
+ }
255
+ }
256
+ </style>
@@ -0,0 +1,61 @@
1
+ <template lang="pug">
2
+ .dito-panels(
3
+ v-if="panels.length > 0"
4
+ )
5
+ template(
6
+ v-for="{ schema, dataPath, tabComponent } in panels"
7
+ )
8
+ DitoPanel(
9
+ v-if="shouldRenderSchema(schema)"
10
+ :key="getPanelKey(dataPath, tabComponent)"
11
+ :schema="schema"
12
+ :dataPath="dataPath"
13
+ :data="data"
14
+ :meta="meta"
15
+ :store="getChildStore(schema.name)"
16
+ :disabled="schema.disabled ?? disabled"
17
+ :panelTabComponent="tabComponent"
18
+ )
19
+ </template>
20
+
21
+ <script>
22
+ import DitoComponent from '../DitoComponent.js'
23
+ import ContextMixin from '../mixins/ContextMixin.js'
24
+
25
+ // @vue/component
26
+ export default DitoComponent.component('DitoPanels', {
27
+ mixins: [ContextMixin],
28
+
29
+ props: {
30
+ panels: { type: Array, default: null },
31
+ data: { type: Object, required: true },
32
+ meta: { type: Object, required: true },
33
+ store: { type: Object, required: true },
34
+ disabled: { type: Boolean, required: true }
35
+ },
36
+
37
+ computed: {
38
+ nested() {
39
+ // For `ContextMixin`:
40
+ return false
41
+ }
42
+ },
43
+
44
+ methods: {
45
+ getPanelKey(dataPath, tabComponent) {
46
+ // Allow separate tabs to use panels of the same name, by
47
+ // prefixing their key with the tab name.
48
+ return tabComponent ? `${tabComponent.tab}_${dataPath}` : dataPath
49
+ }
50
+ }
51
+ })
52
+ </script>
53
+
54
+ <style lang="scss">
55
+ @import '../styles/_imports';
56
+
57
+ .dito-panels {
58
+ margin: $content-padding;
59
+ margin-left: $form-spacing;
60
+ }
61
+ </style>