@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,80 @@
1
+ <template lang="pug">
2
+ .dito-buttons(
3
+ v-if="buttonSchemas || hasSlotContent($slots.default)"
4
+ )
5
+ template(
6
+ v-for="(buttonSchema, buttonDataPath) in buttonSchemas"
7
+ )
8
+ DitoContainer(
9
+ v-if="shouldRenderSchema(buttonSchema)"
10
+ :key="buttonDataPath"
11
+ :schema="buttonSchema"
12
+ :dataPath="buttonDataPath"
13
+ :data="data"
14
+ :meta="meta"
15
+ :nested="nested"
16
+ :store="getChildStore(buttonSchema.name)"
17
+ :disabled="disabled"
18
+ )
19
+ template(
20
+ v-for="vnode of $slots.default?.()"
21
+ )
22
+ //- Render each node in the default slot through `dito-vnode`,
23
+ //- so it can be wrapped in a `.dito-container` class.
24
+ .dito-container(
25
+ v-if="hasVNodeContent(vnode)"
26
+ )
27
+ DitoVnode(:vnode="vnode")
28
+ </template>
29
+
30
+ <script>
31
+ import DitoComponent from '../DitoComponent.js'
32
+ import ContextMixin from '../mixins/ContextMixin.js'
33
+ import { appendDataPath } from '../utils/data.js'
34
+ import { hasSlotContent, hasVNodeContent } from '@ditojs/ui/src'
35
+
36
+ // @vue/component
37
+ export default DitoComponent.component('DitoButtons', {
38
+ mixins: [ContextMixin],
39
+
40
+ provide: {
41
+ $tabComponent: () => null
42
+ },
43
+
44
+ props: {
45
+ buttons: { type: Object, default: null },
46
+ dataPath: { type: String, default: '' },
47
+ data: { type: [Object, Array], default: null },
48
+ meta: { type: Object, default: () => ({}) },
49
+ store: { type: Object, default: () => ({}) },
50
+ nested: { type: Boolean, default: true },
51
+ disabled: { type: Boolean, default: false }
52
+ },
53
+
54
+ computed: {
55
+ buttonSchemas() {
56
+ // Compute a buttons list which has the dataPath baked into its keys.
57
+ const { dataPath, buttons } = this
58
+ return buttons
59
+ ? Object.values(buttons).reduce((schemas, button) => {
60
+ schemas[appendDataPath(dataPath, button.name)] = button
61
+ return schemas
62
+ }, {})
63
+ : null
64
+ }
65
+ },
66
+
67
+ methods: {
68
+ hasSlotContent,
69
+ hasVNodeContent
70
+ }
71
+ })
72
+ </script>
73
+
74
+ <style lang="scss">
75
+ .dito-buttons {
76
+ > .dito-container {
77
+ padding: 0;
78
+ }
79
+ }
80
+ </style>
@@ -0,0 +1,186 @@
1
+ <template lang="pug">
2
+ .dito-clipboard.dito-buttons.dito-buttons--round(
3
+ v-if="clipboard"
4
+ )
5
+ button.dito-button.dito-button--copy(
6
+ ref="copyData"
7
+ type="button"
8
+ title="Copy Data"
9
+ :disabled="!copyEnabled"
10
+ @click="onCopy"
11
+ )
12
+ button.dito-button.dito-button--paste(
13
+ type="button"
14
+ title="Paste Data"
15
+ :disabled="!pasteEnabled"
16
+ @click="onPaste"
17
+ )
18
+ </template>
19
+
20
+ <script>
21
+ import { isObject, clone, deindent } from '@ditojs/utils'
22
+ import DitoComponent from '../DitoComponent.js'
23
+ import DomMixin from '../mixins/DomMixin.js'
24
+ import DitoContext from '../DitoContext.js'
25
+
26
+ // @vue/component
27
+ export default DitoComponent.component('DitoClipboard', {
28
+ mixins: [DomMixin],
29
+
30
+ props: {
31
+ clipboard: { type: [Boolean, Object], required: true },
32
+ schema: { type: Object, required: true }
33
+ },
34
+
35
+ data() {
36
+ return {
37
+ copyEnabled: false,
38
+ pasteEnabled: false
39
+ }
40
+ },
41
+
42
+ computed: {
43
+ clipboardOptions() {
44
+ return isObject(this.clipboard) ? this.clipboard : {}
45
+ },
46
+
47
+ copyData() {
48
+ const { copy } = this.clipboardOptions
49
+ return copy
50
+ ? clipboardData =>
51
+ copy.call(this, new DitoContext(this, { clipboardData }))
52
+ : clipboardData => clone(clipboardData)
53
+ },
54
+
55
+ pasteData() {
56
+ const { paste } = this.clipboardOptions
57
+ return paste
58
+ ? clipboardData =>
59
+ paste.call(this, new DitoContext(this, { clipboardData }))
60
+ : clipboardData => clipboardData
61
+ }
62
+ },
63
+
64
+ watch: {
65
+ // Check right away also in case there's already data (e.g. create form):
66
+ 'parentComponent.hasData': {
67
+ immediate: true,
68
+ handler: 'updateCopy'
69
+ },
70
+
71
+ 'appState.clipboardData': {
72
+ immediate: true,
73
+ handler: 'updatePaste'
74
+ }
75
+ },
76
+
77
+ mounted() {
78
+ // Check clipboard content whenever something gets copied or the window gets
79
+ // (re)activated, as those are the moments when the clipboard can change:
80
+ this.domOn(document, { copy: this.updatePaste })
81
+ this.domOn(window, { focus: this.updatePaste })
82
+ },
83
+
84
+ methods: {
85
+ checkClipboardData(clipboardData) {
86
+ const { $schema, ...data } = clipboardData || {}
87
+ return $schema === this.schema.name ? data : null
88
+ },
89
+
90
+ async getClipboardData(report) {
91
+ // Use the internal clipboard as fallback.
92
+ let { clipboardData } = this.appState
93
+ try {
94
+ const json = await navigator.clipboard?.readText?.()
95
+ if (json) {
96
+ clipboardData = JSON.parse(json)
97
+ }
98
+ } catch (err) {
99
+ if (report) {
100
+ console.error(err, err.name, err.message)
101
+ if (err.name === 'SyntaxError') {
102
+ alert(deindent`
103
+ The data in the clipboard appears to be malformed:
104
+ ${err.message}
105
+ `)
106
+ }
107
+ }
108
+ }
109
+ return this.checkClipboardData(clipboardData)
110
+ },
111
+
112
+ updateCopy() {
113
+ this.copyEnabled = this.parentComponent.hasData
114
+ },
115
+
116
+ async updatePaste() {
117
+ this.pasteEnabled = !!this.checkClipboardData(this.appState.clipboardData)
118
+ if (!this.pasteEnabled && this.appState.agent.chrome) {
119
+ // See if the clipboard content is valid JSON data that is compatible
120
+ // with the current target schema, and only then activate the pasting:
121
+ const data = await this.getClipboardData(false) // Don't report
122
+ this.pasteEnabled = !!data
123
+ }
124
+ },
125
+
126
+ async onCopy() {
127
+ let data = this.parentComponent.clipboardData
128
+ try {
129
+ if (data) {
130
+ data = {
131
+ $schema: this.schema.name,
132
+ ...this.copyData(data)
133
+ }
134
+ }
135
+ // Keep an internal clipboard as fallback.
136
+ this.appState.clipboardData = data
137
+ this.pasteEnabled = true
138
+ try {
139
+ const json = JSON.stringify(data, null, 2)
140
+ await navigator.clipboard?.writeText?.(json)
141
+ } catch (err) {
142
+ console.error(err, err.name, err.message)
143
+ }
144
+ } catch (error) {
145
+ console.error(error)
146
+ alert(error.message)
147
+ }
148
+ },
149
+
150
+ async onPaste() {
151
+ let data = await this.getClipboardData(true) // Report
152
+ try {
153
+ data = data && this.pasteData(data)
154
+ if (data) {
155
+ this.parentComponent.clipboardData = data
156
+ }
157
+ } catch (error) {
158
+ console.error(error)
159
+ alert(error.message)
160
+ }
161
+ }
162
+ }
163
+ })
164
+ </script>
165
+
166
+ <style lang="scss">
167
+ @import '../styles/_imports';
168
+
169
+ .dito-clipboard {
170
+ display: flex;
171
+
172
+ .dito-schema & {
173
+ // Push clipboard to the right in the flex layout, see:
174
+ // https://codepen.io/tholex/pen/hveBx/
175
+ margin-left: auto;
176
+ }
177
+
178
+ .dito-header & {
179
+ margin-left: 0;
180
+
181
+ .dito-button {
182
+ margin: 0 0 $tab-margin $tab-margin;
183
+ }
184
+ }
185
+ }
186
+ </style>
@@ -0,0 +1,374 @@
1
+ <template lang="pug">
2
+ .dito-container(
3
+ v-show="componentVisible"
4
+ :class="containerClasses"
5
+ :style="containerStyles"
6
+ )
7
+ Teleport(
8
+ v-if="isMounted && panelEntries.length > 0"
9
+ to=".dito-sidebar__teleport"
10
+ )
11
+ DitoPanels(
12
+ :panels="panelEntries"
13
+ :data="data"
14
+ :meta="meta"
15
+ :store="store"
16
+ :disabled="disabled"
17
+ )
18
+ DitoLabel(
19
+ v-if="hasLabel"
20
+ :class="labelClasses"
21
+ :dataPath="labelDataPath"
22
+ :label="label"
23
+ :info="info"
24
+ )
25
+ component.dito-component(
26
+ v-if="!(hasLabel && isLabel)"
27
+ :is="typeComponent"
28
+ ref="component"
29
+ :class="componentClasses"
30
+ :schema="schema"
31
+ :dataPath="dataPath"
32
+ :data="data"
33
+ :meta="meta"
34
+ :store="store"
35
+ :width="width"
36
+ :label="label"
37
+ :single="single"
38
+ :nested="nested"
39
+ :accumulatedBasis="combinedBasis"
40
+ @errors="onErrors"
41
+ @update:component="value => (component = value)"
42
+ )
43
+ DitoErrors(:errors="errors")
44
+ </template>
45
+
46
+ <script>
47
+ import { isString, isNumber } from '@ditojs/utils'
48
+ import DitoComponent from '../DitoComponent.js'
49
+ import ValueMixin from '../mixins/ValueMixin.js'
50
+ import ContextMixin from '../mixins/ContextMixin.js'
51
+ import DitoContext from '../DitoContext.js'
52
+ import { getSchemaAccessor } from '../utils/accessor.js'
53
+ import {
54
+ getAllPanelEntries,
55
+ getTypeComponent,
56
+ hasLabel,
57
+ omitSpacing
58
+ } from '../utils/schema.js'
59
+ import { parseFraction } from '../utils/math.js'
60
+
61
+ // @vue/component
62
+ export default DitoComponent.component('DitoContainer', {
63
+ mixins: [ValueMixin, ContextMixin],
64
+ props: {
65
+ schema: { type: Object, required: true },
66
+ dataPath: { type: String, default: '' },
67
+ data: { type: [Object, Array], required: true },
68
+ meta: { type: Object, required: true },
69
+ store: { type: Object, required: true },
70
+ single: { type: Boolean, default: false },
71
+ nested: { type: Boolean, default: true },
72
+ disabled: { type: Boolean, required: true },
73
+ compact: { type: Boolean, default: false },
74
+ generateLabels: { type: Boolean, default: false },
75
+ verticalLabels: { type: Boolean, default: false },
76
+ accumulatedBasis: { type: Number, default: null }
77
+ },
78
+
79
+ data() {
80
+ return {
81
+ errors: null,
82
+ // The nested type component instance, for context-based schema accessor
83
+ // evaluation.
84
+ component: null
85
+ }
86
+ },
87
+
88
+ computed: {
89
+ context() {
90
+ return new DitoContext(
91
+ // When available, use the type component for context-based schema
92
+ // accessors, but fall back to container.
93
+ // TODO: Consider architectural inversion to eliminate timing issues:
94
+ // - Type components render DitoContainer at their root
95
+ // - Pass type component content through container's default slot
96
+ // - DitoPane/DitoButtons render type components directly
97
+ // - Eliminates need for component instance synchronization
98
+ // - Provides true synchronous access to component context
99
+ this.component ?? this,
100
+ { nested: this.nested }
101
+ )
102
+ },
103
+
104
+ name() {
105
+ return this.schema.name
106
+ },
107
+
108
+ type() {
109
+ return this.schema.type
110
+ },
111
+
112
+ typeComponent() {
113
+ return getTypeComponent(this.type)
114
+ },
115
+
116
+ isLabel() {
117
+ return this.type === 'label'
118
+ },
119
+
120
+ hasLabel() {
121
+ return hasLabel(this.schema, this.generateLabels)
122
+ },
123
+
124
+ label() {
125
+ return this.hasLabel ? this.getLabel(this.schema) : null
126
+ },
127
+
128
+ labelDataPath() {
129
+ // Unnested types don't have a dataPath for themselves, don't use it:
130
+ return this.nested ? this.dataPath : null
131
+ },
132
+
133
+ info: getSchemaAccessor('info', {
134
+ type: String,
135
+ default: null
136
+ }),
137
+
138
+ width: getSchemaAccessor('width', {
139
+ type: [String, Number],
140
+ default() {
141
+ return this.typeComponent?.defaultWidth
142
+ },
143
+ get(width) {
144
+ // Use 100% == 1.0 as default width when nothing is set:
145
+ return width === undefined
146
+ ? 1.0
147
+ : isString(width)
148
+ ? width.match(/^\s*[<>]?\s*(.*)$/)[1] // Remove width operator
149
+ : width
150
+ }
151
+ }),
152
+
153
+ widthOperator: getSchemaAccessor('width', {
154
+ type: String,
155
+ get(width) {
156
+ return isString(width)
157
+ ? width.match(/^\s*([<>]?)/)[1] || null
158
+ : null
159
+ }
160
+ }),
161
+
162
+ componentVisible: getSchemaAccessor('visible', {
163
+ type: Boolean,
164
+ default() {
165
+ return this.typeComponent?.defaultVisible
166
+ }
167
+ }),
168
+
169
+ componentDisabled: getSchemaAccessor('disabled', {
170
+ type: Boolean,
171
+ default: false,
172
+ get(disabled) {
173
+ return disabled || this.disabled
174
+ }
175
+ }),
176
+
177
+ flexGrow() {
178
+ // Interpret '>50%' as '50%, flex-grow: 1`
179
+ return (
180
+ this.widthOperator === '>' ||
181
+ this.width === 'fill'
182
+ )
183
+ },
184
+
185
+ flexShrink() {
186
+ // Interpret '<50%' as '50%, flex-shrink: 1`
187
+ return this.widthOperator === '<'
188
+ },
189
+
190
+ flexBasis() {
191
+ const width = this.width
192
+ // 'auto' = no fitting:
193
+ return [null, 'auto', 'fill'].includes(width)
194
+ ? 'auto'
195
+ : /%$/.test(width)
196
+ ? parseFloat(width) / 100 // percentage -> fraction
197
+ : /[a-z]/.test(width)
198
+ ? width // native units
199
+ : parseFraction(width) // fraction
200
+ },
201
+
202
+ combinedBasis() {
203
+ const { accumulatedBasis, flexBasis } = this
204
+ return isNumber(accumulatedBasis) && isNumber(flexBasis)
205
+ ? accumulatedBasis * flexBasis
206
+ : null
207
+ },
208
+
209
+ containerClasses() {
210
+ const { class: classes } = this.schema
211
+ const prefix = 'dito-container'
212
+ return {
213
+ [`${prefix}--disabled`]: this.componentDisabled,
214
+ [`${prefix}--has-errors`]: !!this.errors,
215
+ [`${prefix}--single`]: this.single,
216
+ [`${prefix}--compact`]: this.compact,
217
+ [`${prefix}--label-vertical`]: this.verticalLabels,
218
+ [`${prefix}--omit-spacing`]: omitSpacing(this.schema),
219
+ ...(isString(classes) ? { [classes]: true } : classes)
220
+ }
221
+ },
222
+
223
+ containerStyles() {
224
+ const { flexBasis, combinedBasis } = this
225
+ return {
226
+ '--grow': this.flexGrow ? 1 : 0,
227
+ '--shrink': this.flexShrink ? 1 : 0,
228
+ '--basis': isNumber(flexBasis) ? `${flexBasis * 100}%` : flexBasis,
229
+ '--basis-mobile':
230
+ isNumber(combinedBasis) && combinedBasis <= 0.25
231
+ ? `${flexBasis * 200}%`
232
+ : null
233
+ }
234
+ },
235
+
236
+ componentClasses() {
237
+ return {
238
+ 'dito-component--single': this.single,
239
+ ...this.getLayoutClasses('dito-component')
240
+ }
241
+ },
242
+
243
+ labelClasses() {
244
+ return {
245
+ 'dito-label--visible': this.isLabel,
246
+ ...this.getLayoutClasses('dito-label')
247
+ }
248
+ },
249
+
250
+ panelEntries() {
251
+ return getAllPanelEntries(
252
+ this.api,
253
+ this.schema,
254
+ this.dataPath,
255
+ this.$refs.component,
256
+ this.tabComponent
257
+ )
258
+ }
259
+ },
260
+
261
+ methods: {
262
+ getLayoutClasses(prefix) {
263
+ return {
264
+ [`${prefix}--fill`]: this.width === 'fill' || this.flexBasis !== 'auto',
265
+ [`${prefix}--grow`]: this.flexGrow,
266
+ [`${prefix}--shrink`]: this.flexShrink
267
+ }
268
+ },
269
+
270
+ onErrors(errors) {
271
+ this.errors = errors
272
+ }
273
+ }
274
+ })
275
+ </script>
276
+
277
+ <style lang="scss">
278
+ @import '../styles/_imports';
279
+
280
+ .dito-container {
281
+ --grow: 0;
282
+ --shrink: 1;
283
+ --basis: auto;
284
+
285
+ position: relative;
286
+ display: flex;
287
+ flex: var(--grow) var(--shrink) var(--basis);
288
+ flex-flow: column;
289
+ align-items: flex-start;
290
+ box-sizing: border-box;
291
+ // To prevent list tables from blowing out of their flex box containers.
292
+ max-width: 100%;
293
+ // Cannot use margin here as it needs to be part of box-sizing for
294
+ // percentages in flex-basis to work.
295
+ padding: var(--container-padding);
296
+
297
+ > .dito-label:not(.dito-label--visible):only-child {
298
+ // Used e.g. when sources hide themselves due to maxDepth, but the label
299
+ // is rendered above it.
300
+ display: none;
301
+ }
302
+
303
+ &:empty {
304
+ padding: 0;
305
+ }
306
+
307
+ .dito-pane > & {
308
+ .dito-page--width-80 & {
309
+ flex-grow: 1;
310
+ flex-basis: var(--basis-mobile, var(--basis));
311
+ // DEBUG: background: yellow;
312
+ }
313
+
314
+ .dito-page--width-60 & {
315
+ flex-basis: calc(2 * var(--basis));
316
+ // DEBUG: background: orange;
317
+ }
318
+
319
+ .dito-sidebar--width-99 & {
320
+ flex-grow: 1;
321
+ // DEBUG: background: yellow;
322
+ }
323
+
324
+ .dito-sidebar--width-60 & {
325
+ flex-basis: calc(2 * var(--basis));
326
+ // DEBUG: background: orange;
327
+ }
328
+ }
329
+
330
+ &--single {
331
+ height: 100%; // So that list buttons can be sticky at the bottom;
332
+ }
333
+
334
+ &--label-vertical {
335
+ // For plain components without labels in rows with other components that
336
+ // have labels, add some spacing to the top to align with the other
337
+ // components (e.g. buttons):
338
+ > .dito-component:first-child:not(.dito-section, .dito-list, .dito-object) {
339
+ margin-top: $input-height;
340
+ }
341
+ }
342
+
343
+ &--compact:not(&--label-vertical) {
344
+ // Display labels in compact schema as inline-blocks, to allow compact
345
+ // layouts with `width: 'auto'` elements:
346
+ display: flex;
347
+ flex-flow: row wrap;
348
+ align-items: center;
349
+ }
350
+
351
+ &--omit-spacing {
352
+ padding: 0;
353
+
354
+ > .dito-label {
355
+ margin: $form-spacing-half $form-spacing-half 0;
356
+ }
357
+ }
358
+ }
359
+
360
+ .dito-component {
361
+ position: relative;
362
+
363
+ &--fill {
364
+ width: 100%;
365
+
366
+ &.dito-checkbox,
367
+ &.dito-radio-button {
368
+ // WebKit doesn't like changed width on checkboxes and radios, override:
369
+ display: inline-block;
370
+ width: auto;
371
+ }
372
+ }
373
+ }
374
+ </style>