@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,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>
|