@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.
- package/README.md +180 -0
- package/dist/dito-admin.css +1 -0
- package/dist/dito-admin.es.js +12106 -0
- package/dist/dito-admin.umd.js +7 -0
- package/package.json +96 -0
- package/src/DitoAdmin.js +293 -0
- package/src/DitoComponent.js +34 -0
- package/src/DitoContext.js +318 -0
- package/src/DitoTypeComponent.js +42 -0
- package/src/DitoUser.js +12 -0
- package/src/appState.js +12 -0
- package/src/components/DitoAccount.vue +60 -0
- package/src/components/DitoAffix.vue +68 -0
- package/src/components/DitoAffixes.vue +200 -0
- package/src/components/DitoButtons.vue +80 -0
- package/src/components/DitoClipboard.vue +186 -0
- package/src/components/DitoContainer.vue +374 -0
- package/src/components/DitoCreateButton.vue +146 -0
- package/src/components/DitoDialog.vue +242 -0
- package/src/components/DitoDraggable.vue +117 -0
- package/src/components/DitoEditButtons.vue +135 -0
- package/src/components/DitoErrors.vue +83 -0
- package/src/components/DitoForm.vue +521 -0
- package/src/components/DitoFormInner.vue +26 -0
- package/src/components/DitoFormNested.vue +17 -0
- package/src/components/DitoHeader.vue +84 -0
- package/src/components/DitoLabel.vue +200 -0
- package/src/components/DitoMenu.vue +186 -0
- package/src/components/DitoNavigation.vue +40 -0
- package/src/components/DitoNotifications.vue +170 -0
- package/src/components/DitoPagination.vue +42 -0
- package/src/components/DitoPane.vue +334 -0
- package/src/components/DitoPanel.vue +256 -0
- package/src/components/DitoPanels.vue +61 -0
- package/src/components/DitoRoot.vue +524 -0
- package/src/components/DitoSchema.vue +846 -0
- package/src/components/DitoSchemaInlined.vue +97 -0
- package/src/components/DitoScopes.vue +76 -0
- package/src/components/DitoSidebar.vue +50 -0
- package/src/components/DitoSpinner.vue +95 -0
- package/src/components/DitoTableCell.vue +64 -0
- package/src/components/DitoTableHead.vue +121 -0
- package/src/components/DitoTabs.vue +103 -0
- package/src/components/DitoTrail.vue +124 -0
- package/src/components/DitoTreeItem.vue +420 -0
- package/src/components/DitoUploadFile.vue +199 -0
- package/src/components/DitoVNode.vue +14 -0
- package/src/components/DitoView.vue +143 -0
- package/src/components/index.js +42 -0
- package/src/directives/resize.js +83 -0
- package/src/index.js +1 -0
- package/src/mixins/ContextMixin.js +68 -0
- package/src/mixins/DataMixin.js +131 -0
- package/src/mixins/DitoMixin.js +591 -0
- package/src/mixins/DomMixin.js +29 -0
- package/src/mixins/EmitterMixin.js +158 -0
- package/src/mixins/ItemMixin.js +144 -0
- package/src/mixins/LoadingMixin.js +23 -0
- package/src/mixins/NumberMixin.js +118 -0
- package/src/mixins/OptionsMixin.js +304 -0
- package/src/mixins/PulldownMixin.js +63 -0
- package/src/mixins/ResourceMixin.js +398 -0
- package/src/mixins/RouteMixin.js +190 -0
- package/src/mixins/SchemaParentMixin.js +33 -0
- package/src/mixins/SortableMixin.js +49 -0
- package/src/mixins/SourceMixin.js +734 -0
- package/src/mixins/TextMixin.js +26 -0
- package/src/mixins/TypeMixin.js +280 -0
- package/src/mixins/ValidationMixin.js +119 -0
- package/src/mixins/ValidatorMixin.js +57 -0
- package/src/mixins/ValueMixin.js +31 -0
- package/src/styles/_base.scss +17 -0
- package/src/styles/_button.scss +191 -0
- package/src/styles/_imports.scss +3 -0
- package/src/styles/_info.scss +19 -0
- package/src/styles/_layout.scss +19 -0
- package/src/styles/_pulldown.scss +38 -0
- package/src/styles/_scroll.scss +13 -0
- package/src/styles/_settings.scss +88 -0
- package/src/styles/_table.scss +223 -0
- package/src/styles/_tippy.scss +45 -0
- package/src/styles/style.scss +9 -0
- package/src/types/DitoTypeButton.vue +143 -0
- package/src/types/DitoTypeCheckbox.vue +27 -0
- package/src/types/DitoTypeCheckboxes.vue +65 -0
- package/src/types/DitoTypeCode.vue +199 -0
- package/src/types/DitoTypeColor.vue +272 -0
- package/src/types/DitoTypeComponent.vue +31 -0
- package/src/types/DitoTypeComputed.vue +50 -0
- package/src/types/DitoTypeDate.vue +99 -0
- package/src/types/DitoTypeLabel.vue +23 -0
- package/src/types/DitoTypeList.vue +364 -0
- package/src/types/DitoTypeMarkup.vue +700 -0
- package/src/types/DitoTypeMultiselect.vue +522 -0
- package/src/types/DitoTypeNumber.vue +66 -0
- package/src/types/DitoTypeObject.vue +136 -0
- package/src/types/DitoTypePanel.vue +18 -0
- package/src/types/DitoTypeProgress.vue +40 -0
- package/src/types/DitoTypeRadio.vue +45 -0
- package/src/types/DitoTypeSection.vue +80 -0
- package/src/types/DitoTypeSelect.vue +133 -0
- package/src/types/DitoTypeSlider.vue +66 -0
- package/src/types/DitoTypeSpacer.vue +11 -0
- package/src/types/DitoTypeSwitch.vue +40 -0
- package/src/types/DitoTypeText.vue +101 -0
- package/src/types/DitoTypeTextarea.vue +48 -0
- package/src/types/DitoTypeTreeList.vue +193 -0
- package/src/types/DitoTypeUpload.vue +503 -0
- package/src/types/index.js +30 -0
- package/src/utils/SchemaGraph.js +147 -0
- package/src/utils/accessor.js +75 -0
- package/src/utils/agent.js +47 -0
- package/src/utils/data.js +92 -0
- package/src/utils/filter.js +266 -0
- package/src/utils/math.js +14 -0
- package/src/utils/options.js +48 -0
- package/src/utils/path.js +5 -0
- package/src/utils/resource.js +44 -0
- package/src/utils/route.js +53 -0
- package/src/utils/schema.js +1121 -0
- package/src/utils/type.js +81 -0
- package/src/utils/uid.js +15 -0
- package/src/utils/units.js +5 -0
- package/src/validators/_creditcard.js +6 -0
- package/src/validators/_decimals.js +11 -0
- package/src/validators/_domain.js +6 -0
- package/src/validators/_email.js +6 -0
- package/src/validators/_hostname.js +6 -0
- package/src/validators/_integer.js +6 -0
- package/src/validators/_max.js +6 -0
- package/src/validators/_min.js +6 -0
- package/src/validators/_password.js +5 -0
- package/src/validators/_range.js +6 -0
- package/src/validators/_required.js +9 -0
- package/src/validators/_url.js +6 -0
- package/src/validators/index.js +12 -0
- package/src/verbs.js +17 -0
- package/types/index.d.ts +3298 -0
- package/types/tests/admin.test-d.ts +27 -0
- package/types/tests/component-buttons.test-d.ts +44 -0
- package/types/tests/component-list.test-d.ts +159 -0
- package/types/tests/component-misc.test-d.ts +137 -0
- package/types/tests/component-object.test-d.ts +69 -0
- package/types/tests/component-section.test-d.ts +174 -0
- package/types/tests/component-select.test-d.ts +107 -0
- package/types/tests/components.test-d.ts +81 -0
- package/types/tests/context.test-d.ts +31 -0
- package/types/tests/fixtures.ts +24 -0
- package/types/tests/form.test-d.ts +109 -0
- package/types/tests/instance.test-d.ts +20 -0
- package/types/tests/schema-features.test-d.ts +402 -0
- package/types/tests/variance.test-d.ts +125 -0
- 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>
|