@ditojs/admin 3.2.6 → 3.3.0
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/dist/dito-admin.css +1 -1
- package/dist/dito-admin.es.js +6012 -5827
- package/dist/dito-admin.umd.js +3 -7
- package/package.json +18 -17
- package/src/DitoAdmin.js +44 -24
- package/src/DitoContext.js +28 -2
- package/src/DitoTypeComponent.js +1 -0
- package/src/appState.js +6 -3
- package/src/components/DitoAccount.vue +35 -27
- package/src/components/DitoAffixes.vue +9 -2
- package/src/components/DitoClipboard.vue +59 -93
- package/src/components/DitoConfirmMessage.vue +18 -0
- package/src/components/DitoContainer.vue +56 -18
- package/src/components/DitoCreateButton.vue +36 -84
- package/src/components/DitoDialog.vue +20 -15
- package/src/components/DitoDragHandle.vue +44 -0
- package/src/components/DitoDraggable.vue +43 -16
- package/src/components/DitoEditButtons.vue +48 -24
- package/src/components/DitoErrors.vue +9 -56
- package/src/components/DitoForm.vue +82 -42
- package/src/components/DitoInputAffixes.vue +50 -0
- package/src/components/DitoLabel.vue +1 -15
- package/src/components/DitoMenu.vue +45 -52
- package/src/components/DitoNotifications.vue +38 -14
- package/src/components/DitoOptionList.vue +81 -0
- package/src/components/DitoOptionsEditButtons.vue +57 -0
- package/src/components/DitoPagination.vue +1 -8
- package/src/components/DitoPane.vue +51 -39
- package/src/components/DitoPanel.vue +12 -26
- package/src/components/DitoPanels.vue +11 -5
- package/src/components/DitoResizeHandle.vue +47 -0
- package/src/components/DitoRoot.vue +113 -320
- package/src/components/DitoSchema.vue +72 -63
- package/src/components/DitoSchemaInlined.vue +8 -9
- package/src/components/DitoScopes.vue +14 -18
- package/src/components/DitoSidebar.vue +4 -29
- package/src/components/DitoSourceItem.vue +114 -0
- package/src/components/DitoTableCell.vue +4 -0
- package/src/components/DitoTableHead.vue +15 -17
- package/src/components/DitoTabs.vue +25 -0
- package/src/components/DitoTrail.vue +25 -25
- package/src/components/DitoTreeItem.vue +102 -141
- package/src/components/DitoUploadFile.vue +5 -2
- package/src/components/DitoView.vue +33 -24
- package/src/index.js +4 -1
- package/src/mixins/DitoMixin.js +77 -110
- package/src/mixins/DomMixin.js +12 -14
- package/src/mixins/EmitterMixin.js +3 -2
- package/src/mixins/ItemMixin.js +38 -94
- package/src/mixins/OptionsMixin.js +38 -25
- package/src/mixins/ResourceMixin.js +182 -111
- package/src/mixins/RouteMixin.js +61 -44
- package/src/mixins/SchemaParentMixin.js +4 -1
- package/src/mixins/SourceMixin.js +118 -100
- package/src/mixins/TypeMixin.js +42 -40
- package/src/mixins/ValidationMixin.js +16 -4
- package/src/mixins/ValidatorMixin.js +1 -1
- package/src/styles/_settings.scss +4 -0
- package/src/styles/_tippy.scss +0 -11
- package/src/styles/style.scss +0 -1
- package/src/types/DitoTypeButton.vue +11 -14
- package/src/types/DitoTypeCheckboxes.vue +35 -17
- package/src/types/DitoTypeCode.vue +19 -41
- package/src/types/DitoTypeColor.vue +67 -19
- package/src/types/DitoTypeDate.vue +11 -14
- package/src/types/DitoTypeList.vue +50 -62
- package/src/types/DitoTypeMarkup.vue +41 -54
- package/src/types/DitoTypeMultiselect.vue +82 -52
- package/src/types/DitoTypeNumber.vue +12 -15
- package/src/types/DitoTypeObject.vue +4 -38
- package/src/types/DitoTypeRadio.vue +19 -6
- package/src/types/DitoTypeSection.vue +23 -9
- package/src/types/DitoTypeSelect.vue +39 -23
- package/src/types/DitoTypeSlider.vue +9 -1
- package/src/types/DitoTypeSwitch.vue +1 -1
- package/src/types/DitoTypeText.vue +11 -14
- package/src/types/DitoTypeTreeList.vue +46 -8
- package/src/types/DitoTypeUpload.vue +190 -67
- package/src/utils/DataModel.js +368 -146
- package/src/utils/FileDropTargets.js +97 -0
- package/src/utils/ItemStores.js +31 -0
- package/src/utils/ListQuery.js +115 -0
- package/src/utils/LoadingTracker.js +76 -0
- package/src/utils/SchemaGraph.js +3 -2
- package/src/utils/Session.js +151 -0
- package/src/utils/ViewRegistry.js +85 -0
- package/src/utils/accessor.js +12 -4
- package/src/utils/affixes.js +33 -0
- package/src/utils/changeOnceEdited.js +33 -0
- package/src/utils/clipboard.js +55 -0
- package/src/utils/data.js +10 -5
- package/src/utils/dialogs.js +59 -0
- package/src/utils/dragResize.js +61 -0
- package/src/utils/filter.js +36 -2
- package/src/utils/html.js +9 -0
- package/src/utils/list.js +10 -0
- package/src/utils/options.js +4 -0
- package/src/utils/registration.js +18 -0
- package/src/utils/request.js +108 -13
- package/src/utils/route.js +6 -17
- package/src/utils/schema/data.js +29 -2
- package/src/utils/schema/events.js +19 -0
- package/src/utils/schema/item.js +114 -0
- package/src/utils/schema/lookup.js +39 -14
- package/src/utils/schema/setup.js +5 -5
- package/src/utils/schema/validation.js +16 -2
- package/src/utils/uid.js +105 -8
- package/src/validators/_decimals.js +16 -5
- package/types/index.d.ts +168 -41
- package/src/mixins/LoadingMixin.js +0 -23
- package/src/mixins/PulldownMixin.js +0 -72
- package/src/styles/_pulldown.scss +0 -38
|
@@ -18,8 +18,8 @@ slot(name="prepend")
|
|
|
18
18
|
)
|
|
19
19
|
Teleport(
|
|
20
20
|
v-if="hasHeader"
|
|
21
|
-
|
|
22
|
-
:disabled="!
|
|
21
|
+
to=".dito-header__teleport"
|
|
22
|
+
:disabled="!isTopLevelSchema"
|
|
23
23
|
)
|
|
24
24
|
.dito-schema-header(
|
|
25
25
|
v-if="active"
|
|
@@ -38,13 +38,17 @@ slot(name="prepend")
|
|
|
38
38
|
)
|
|
39
39
|
DitoTabs(
|
|
40
40
|
v-if="opened"
|
|
41
|
-
|
|
41
|
+
:modelValue="selectedTab"
|
|
42
42
|
:tabs="renderedTabs"
|
|
43
|
+
@update:modelValue="selectTab"
|
|
43
44
|
)
|
|
44
45
|
DitoClipboard(
|
|
45
46
|
v-if="clipboard"
|
|
46
47
|
:clipboard="clipboard"
|
|
47
48
|
:schema="schema"
|
|
49
|
+
:hasData="hasData"
|
|
50
|
+
:getData="getDataForClipboard"
|
|
51
|
+
@paste="setData"
|
|
48
52
|
)
|
|
49
53
|
slot(name="edit-buttons")
|
|
50
54
|
TransitionHeight(:enabled="inlined")
|
|
@@ -65,7 +69,7 @@ slot(name="prepend")
|
|
|
65
69
|
//- of their components). `navigateToComponent()` then needs to
|
|
66
70
|
//- select the tab that displays the component.
|
|
67
71
|
DitoPane.dito-pane__tab(
|
|
68
|
-
v-show="
|
|
72
|
+
v-show="isTabSelected(tab)"
|
|
69
73
|
ref="tabs"
|
|
70
74
|
:tab="tab"
|
|
71
75
|
:schema="tabSchema"
|
|
@@ -78,7 +82,6 @@ slot(name="prepend")
|
|
|
78
82
|
:disabled="disabled"
|
|
79
83
|
:compact="compact"
|
|
80
84
|
:generateLabels="generateLabels"
|
|
81
|
-
:accumulatedBasis="accumulatedBasis"
|
|
82
85
|
)
|
|
83
86
|
DitoPane.dito-pane__main(
|
|
84
87
|
v-if="hasMainPane"
|
|
@@ -93,7 +96,6 @@ slot(name="prepend")
|
|
|
93
96
|
:disabled="disabled"
|
|
94
97
|
:compact="compact"
|
|
95
98
|
:generateLabels="generateLabels"
|
|
96
|
-
:accumulatedBasis="accumulatedBasis"
|
|
97
99
|
)
|
|
98
100
|
slot(
|
|
99
101
|
v-if="!inlined && isPopulated"
|
|
@@ -118,13 +120,14 @@ import {
|
|
|
118
120
|
} from '@ditojs/utils'
|
|
119
121
|
import { TransitionHeight } from '@ditojs/ui/src'
|
|
120
122
|
import DitoComponent from '../DitoComponent.js'
|
|
123
|
+
import DitoContext from '../DitoContext.js'
|
|
121
124
|
import ContextMixin from '../mixins/ContextMixin.js'
|
|
122
|
-
import ItemMixin from '../mixins/ItemMixin.js'
|
|
123
125
|
import { appendDataPath } from '../utils/data.js'
|
|
124
126
|
import { isPanel } from '../utils/schema/structure.js'
|
|
125
127
|
import {
|
|
126
128
|
getNamedSchemas,
|
|
127
129
|
getPanelEntries,
|
|
130
|
+
getItemFormSchema,
|
|
128
131
|
isEmptySchema
|
|
129
132
|
} from '../utils/schema/lookup.js'
|
|
130
133
|
import { initializeData, processData } from '../utils/schema/data.js'
|
|
@@ -136,13 +139,17 @@ import { getSchemaAccessor, getStoreAccessor } from '../utils/accessor.js'
|
|
|
136
139
|
|
|
137
140
|
// @vue/component
|
|
138
141
|
export default DitoComponent.component('DitoSchema', {
|
|
139
|
-
mixins: [ContextMixin
|
|
142
|
+
mixins: [ContextMixin],
|
|
140
143
|
components: { TransitionHeight },
|
|
141
144
|
inheritAttrs: false,
|
|
142
145
|
|
|
143
146
|
provide() {
|
|
144
147
|
return {
|
|
145
|
-
$schemaComponent: () => this
|
|
148
|
+
$schemaComponent: () => this,
|
|
149
|
+
// Inlined schemas share the width of their container, see
|
|
150
|
+
// `DitoContainer.combinedBasis`, while all others take the full width,
|
|
151
|
+
// e.g. panels in the sidebar.
|
|
152
|
+
...(!this.inlined && { $accumulatedBasis: () => 1 })
|
|
146
153
|
}
|
|
147
154
|
},
|
|
148
155
|
|
|
@@ -171,21 +178,25 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
171
178
|
collapsible: { type: Boolean, default: false },
|
|
172
179
|
scrollable: { type: Boolean, default: false },
|
|
173
180
|
hasOwnData: { type: Boolean, default: false },
|
|
174
|
-
generateLabels: { type: Boolean, default: false }
|
|
175
|
-
labelNode: { type: HTMLElement, default: null },
|
|
176
|
-
accumulatedBasis: { type: Number, default: 1 }
|
|
181
|
+
generateLabels: { type: Boolean, default: false }
|
|
177
182
|
},
|
|
178
183
|
|
|
179
184
|
data() {
|
|
180
185
|
const { data } = this.schema
|
|
181
186
|
return {
|
|
182
|
-
// Allow schema to provide more data through `schema.data`, vue-style
|
|
187
|
+
// Allow schema to provide more data through `schema.data`, vue-style.
|
|
188
|
+
// `data()` runs before the computed properties are set up, so create the
|
|
189
|
+
// context here rather than using the `context` computed property:
|
|
183
190
|
...(
|
|
184
191
|
data && isFunction(data)
|
|
185
|
-
? data(this
|
|
192
|
+
? data(new DitoContext(this, { nested: false }))
|
|
186
193
|
: data
|
|
187
194
|
),
|
|
188
195
|
selectedTab: null,
|
|
196
|
+
// Whether the selected tab still follows `defaultTab`, until the route
|
|
197
|
+
// or the user select a tab, or until the data that `defaultTab()` and
|
|
198
|
+
// the tabs' `if` are evaluated with is there, see `selectDefaultTab()`.
|
|
199
|
+
shouldFollowDefaultTab: true,
|
|
189
200
|
componentsRegistry: {},
|
|
190
201
|
unnestedComponentsRegistry: {},
|
|
191
202
|
panesRegistry: {},
|
|
@@ -213,10 +224,13 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
213
224
|
},
|
|
214
225
|
|
|
215
226
|
panelEntries() {
|
|
227
|
+
// Panels of schemas inlined in tabs are only shown with their tab, as
|
|
228
|
+
// the panels of components in tabs, see `DitoContainer.panelEntries`.
|
|
216
229
|
return getPanelEntries(
|
|
217
230
|
this.schema.panels,
|
|
218
231
|
this.dataPath,
|
|
219
|
-
this.componentPath
|
|
232
|
+
this.componentPath,
|
|
233
|
+
this.tabComponent?.panelTab ?? null
|
|
220
234
|
)
|
|
221
235
|
},
|
|
222
236
|
|
|
@@ -262,34 +276,14 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
262
276
|
return this.hasLabel || this.hasTabs || !!this.clipboard
|
|
263
277
|
},
|
|
264
278
|
|
|
265
|
-
headerTeleport() {
|
|
266
|
-
return this.isTopLevelSchema
|
|
267
|
-
? '.dito-header__teleport'
|
|
268
|
-
: this.labelNode
|
|
269
|
-
},
|
|
270
|
-
|
|
271
279
|
// @override
|
|
272
280
|
processedData() {
|
|
273
281
|
return this.processData({ target: 'server', schemaOnly: true })
|
|
274
282
|
},
|
|
275
283
|
|
|
276
|
-
clipboardData: {
|
|
277
|
-
get() {
|
|
278
|
-
return this.processData({ target: 'clipboard', schemaOnly: true })
|
|
279
|
-
},
|
|
280
|
-
|
|
281
|
-
set(data) {
|
|
282
|
-
this.setData(data)
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
clipboardItem() {
|
|
287
|
-
return this.clipboardData
|
|
288
|
-
},
|
|
289
|
-
|
|
290
284
|
formLabel() {
|
|
291
285
|
return this.getLabel(
|
|
292
|
-
|
|
286
|
+
getItemFormSchema(this.sourceSchema, this.data, this.context)
|
|
293
287
|
)
|
|
294
288
|
},
|
|
295
289
|
|
|
@@ -396,15 +390,22 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
396
390
|
// https://github.com/vuejs/vue-router/issues/3393#issuecomment-1158470149
|
|
397
391
|
flush: 'post',
|
|
398
392
|
handler(routeTab) {
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
this.selectedTab = routeTab
|
|
393
|
+
// Changes of the hash to the selected tab are the route following the
|
|
394
|
+
// selection, see `selectedTab()`, not a selection by the route.
|
|
395
|
+
if (this.hasTopLevelTabs && routeTab !== this.selectedTab) {
|
|
396
|
+
this.selectTab(routeTab)
|
|
404
397
|
}
|
|
405
398
|
}
|
|
406
399
|
},
|
|
407
400
|
|
|
401
|
+
hasData(hasData) {
|
|
402
|
+
// `defaultTab()` and the tabs' `if` may depend on data that is only
|
|
403
|
+
// available after mounting, e.g. the item of a form once it's loaded.
|
|
404
|
+
if (hasData && this.shouldFollowDefaultTab) {
|
|
405
|
+
this.selectDefaultTab()
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
|
|
408
409
|
selectedTab(newTab, oldTab) {
|
|
409
410
|
if (this.scrollable) {
|
|
410
411
|
const { content } = this.$refs
|
|
@@ -424,9 +425,6 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
424
425
|
hash: tab ? `#${tab}` : null
|
|
425
426
|
})
|
|
426
427
|
}
|
|
427
|
-
if (this.hasErrors) {
|
|
428
|
-
this.repositionErrors()
|
|
429
|
-
}
|
|
430
428
|
}
|
|
431
429
|
},
|
|
432
430
|
|
|
@@ -438,7 +436,11 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
438
436
|
},
|
|
439
437
|
|
|
440
438
|
mounted() {
|
|
441
|
-
|
|
439
|
+
if (this.routeTab) {
|
|
440
|
+
this.selectTab(this.routeTab)
|
|
441
|
+
} else {
|
|
442
|
+
this.selectDefaultTab()
|
|
443
|
+
}
|
|
442
444
|
},
|
|
443
445
|
|
|
444
446
|
unmounted() {
|
|
@@ -505,6 +507,23 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
505
507
|
return this.isPopulated && this.components.every(callback)
|
|
506
508
|
},
|
|
507
509
|
|
|
510
|
+
// Selects the tab chosen by the route or the user.
|
|
511
|
+
selectTab(tab) {
|
|
512
|
+
this.selectedTab = tab
|
|
513
|
+
this.shouldFollowDefaultTab = false
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
// Selects `defaultTab`, and keeps following it until there is data, but
|
|
517
|
+
// not after that, so that editing the data doesn't switch the tab.
|
|
518
|
+
selectDefaultTab() {
|
|
519
|
+
this.selectedTab = this.defaultTab
|
|
520
|
+
this.shouldFollowDefaultTab = !this.hasData
|
|
521
|
+
},
|
|
522
|
+
|
|
523
|
+
isTabSelected(tab) {
|
|
524
|
+
return this.selectedTab === tab
|
|
525
|
+
},
|
|
526
|
+
|
|
508
527
|
onOpen(open) {
|
|
509
528
|
this.emitEvent('open', { context: { open } })
|
|
510
529
|
this.opened = open
|
|
@@ -526,17 +545,6 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
526
545
|
}
|
|
527
546
|
},
|
|
528
547
|
|
|
529
|
-
repositionErrors() {
|
|
530
|
-
// Fire a fake scroll event to force the repositioning of error tooltips,
|
|
531
|
-
// as otherwise they sometimes don't show up in the right place initially
|
|
532
|
-
// when changing tabs.
|
|
533
|
-
const scrollContainer = this.$refs.content.closest('.dito-scroll')
|
|
534
|
-
const dispatch = () => scrollContainer.dispatchEvent(new Event('scroll'))
|
|
535
|
-
dispatch()
|
|
536
|
-
// This is required to handle `&--label-vertical` based layout changes.
|
|
537
|
-
setTimeout(dispatch, 0)
|
|
538
|
-
},
|
|
539
|
-
|
|
540
548
|
focus() {
|
|
541
549
|
this.opened = true
|
|
542
550
|
return this.parentSchemaComponent?.focus()
|
|
@@ -797,6 +805,12 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
797
805
|
return { localData, foreignData }
|
|
798
806
|
},
|
|
799
807
|
|
|
808
|
+
// Called by `DitoClipboard` only when copying, as processing all data on
|
|
809
|
+
// each change would be wasteful.
|
|
810
|
+
getDataForClipboard() {
|
|
811
|
+
return this.processData({ target: 'clipboard', schemaOnly: true })
|
|
812
|
+
},
|
|
813
|
+
|
|
800
814
|
processData({ target = 'clipboard', schemaOnly = true } = {}) {
|
|
801
815
|
return processData(
|
|
802
816
|
this.dataSchema,
|
|
@@ -856,8 +870,8 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
856
870
|
|
|
857
871
|
// Registries are keyed by component path. Component paths change when list
|
|
858
872
|
// items move, and the entries then register again under their new path,
|
|
859
|
-
// see `
|
|
860
|
-
_registerEntry(registry, entry, add, componentPath
|
|
873
|
+
// see `trackRegistration()`.
|
|
874
|
+
_registerEntry(registry, entry, add, componentPath) {
|
|
861
875
|
if (add) {
|
|
862
876
|
registry[componentPath] = entry
|
|
863
877
|
} else if (registry[componentPath] === entry) {
|
|
@@ -966,11 +980,6 @@ function compareByDocumentPosition(component1, component2) {
|
|
|
966
980
|
align-items: flex-end;
|
|
967
981
|
}
|
|
968
982
|
|
|
969
|
-
.dito-label & {
|
|
970
|
-
// When teleported into container label.
|
|
971
|
-
flex: 1;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
983
|
> .dito-label {
|
|
975
984
|
margin-bottom: 0;
|
|
976
985
|
}
|
|
@@ -14,15 +14,16 @@ DitoSchema.dito-schema-inlined(
|
|
|
14
14
|
:collapsed="collapsed"
|
|
15
15
|
:collapsible="collapsible"
|
|
16
16
|
:generateLabels="!isCompact"
|
|
17
|
-
:labelNode="labelNode"
|
|
18
|
-
:accumulatedBasis="accumulatedBasis"
|
|
19
17
|
)
|
|
20
18
|
//- Render dito-edit-buttons for inlined schemas separately from all
|
|
21
19
|
//- others in `TypeList` as a scope, for better handling of layout.
|
|
20
|
+
//- `sourceSchema` is the schema of the list or object that holds the item,
|
|
21
|
+
//- passed through `meta.schema`, while `schema` is the item's form.
|
|
22
22
|
template(#edit-buttons)
|
|
23
23
|
DitoEditButtons(
|
|
24
24
|
v-if="deletable || draggable || editable || insertIndex != null"
|
|
25
|
-
:schema="
|
|
25
|
+
:schema="sourceSchema"
|
|
26
|
+
:formSchema="schema"
|
|
26
27
|
:dataPath="dataPath"
|
|
27
28
|
:data="data"
|
|
28
29
|
:meta="meta"
|
|
@@ -36,6 +37,7 @@ DitoSchema.dito-schema-inlined(
|
|
|
36
37
|
:createPath="createPath"
|
|
37
38
|
:insertIndex="insertIndex"
|
|
38
39
|
@delete="$emit('delete')"
|
|
40
|
+
@move="delta => $emit('move', delta)"
|
|
39
41
|
)
|
|
40
42
|
</template>
|
|
41
43
|
|
|
@@ -44,7 +46,7 @@ import DitoComponent from '../DitoComponent.js'
|
|
|
44
46
|
import { isCompact } from '../utils/schema/structure.js'
|
|
45
47
|
// @vue/component
|
|
46
48
|
export default DitoComponent.component('DitoSchemaInlined', {
|
|
47
|
-
emits: ['delete'],
|
|
49
|
+
emits: ['delete', 'move'],
|
|
48
50
|
|
|
49
51
|
props: {
|
|
50
52
|
schema: { type: Object, required: true },
|
|
@@ -63,9 +65,7 @@ export default DitoComponent.component('DitoSchemaInlined', {
|
|
|
63
65
|
deletable: { type: Boolean, default: false },
|
|
64
66
|
editPath: { type: String, default: null },
|
|
65
67
|
createPath: { type: String, default: null },
|
|
66
|
-
insertIndex: { type: Number, default: null }
|
|
67
|
-
labelNode: { type: HTMLElement, default: null },
|
|
68
|
-
accumulatedBasis: { type: Number, default: null }
|
|
68
|
+
insertIndex: { type: Number, default: null }
|
|
69
69
|
},
|
|
70
70
|
|
|
71
71
|
computed: {
|
|
@@ -85,8 +85,7 @@ export default DitoComponent.component('DitoSchemaInlined', {
|
|
|
85
85
|
|
|
86
86
|
.dito-schema-inlined {
|
|
87
87
|
// Use grid layout for two reasons: For `TransitionHeight` to work smoothly,
|
|
88
|
-
// and to align the header above the content
|
|
89
|
-
// outside of the schema.
|
|
88
|
+
// and to align the header above the content.
|
|
90
89
|
display: grid;
|
|
91
90
|
grid-template-rows: min-content;
|
|
92
91
|
grid-template-columns: 100%;
|
|
@@ -1,38 +1,34 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-scopes
|
|
3
|
-
|
|
3
|
+
DitoButton(
|
|
4
4
|
v-for="(scope, key) in scopes"
|
|
5
5
|
:key="key"
|
|
6
|
-
|
|
7
|
-
:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type="button"
|
|
12
|
-
:class="{ 'dito-button--selected': scope.name === query.scope }"
|
|
13
|
-
:aria-pressed="scope.name === query.scope"
|
|
14
|
-
:title="scope.hint || getLabel(scope)"
|
|
15
|
-
@click="navigate"
|
|
16
|
-
) {{ getLabel(scope) }}
|
|
6
|
+
:class="{ 'dito-button--selected': scope.name === query.scope }"
|
|
7
|
+
:aria-pressed="scope.name === query.scope"
|
|
8
|
+
:title="scope.hint || getLabel(scope)"
|
|
9
|
+
@click="selectScope(scope)"
|
|
10
|
+
) {{ getLabel(scope) }}
|
|
17
11
|
</template>
|
|
18
12
|
|
|
19
13
|
<script>
|
|
20
14
|
import DitoComponent from '../DitoComponent.js'
|
|
15
|
+
import { DitoButton } from '@ditojs/ui/src'
|
|
21
16
|
|
|
22
17
|
// @vue/component
|
|
23
18
|
export default DitoComponent.component('DitoScopes', {
|
|
19
|
+
components: { DitoButton },
|
|
20
|
+
|
|
24
21
|
props: {
|
|
25
22
|
query: { type: Object, required: true },
|
|
26
23
|
scopes: { type: Object, required: true }
|
|
27
24
|
},
|
|
28
25
|
|
|
29
26
|
methods: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return this.getQueryLink(query)
|
|
27
|
+
selectScope(scope) {
|
|
28
|
+
this.sourceComponent.listQuery.update(
|
|
29
|
+
{ scope: scope.name },
|
|
30
|
+
{ resetPage: true }
|
|
31
|
+
)
|
|
36
32
|
}
|
|
37
33
|
}
|
|
38
34
|
})
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
aside.dito-sidebar.dito-scroll-parent
|
|
3
|
-
v-resize="onResizeSidebar"
|
|
4
|
-
:class="classes"
|
|
5
|
-
)
|
|
2
|
+
aside.dito-sidebar.dito-scroll-parent
|
|
6
3
|
nav.dito-header
|
|
7
4
|
slot
|
|
8
5
|
.dito-sidebar__teleport.dito-scroll
|
|
@@ -12,37 +9,15 @@ aside.dito-sidebar.dito-scroll-parent(
|
|
|
12
9
|
import DitoComponent from '../DitoComponent.js'
|
|
13
10
|
|
|
14
11
|
// @vue/component
|
|
15
|
-
export default DitoComponent.component('DitoSidebar', {
|
|
16
|
-
data() {
|
|
17
|
-
return {
|
|
18
|
-
sidebarWidth: 0
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
computed: {
|
|
23
|
-
classes() {
|
|
24
|
-
const prefix = 'dito-sidebar'
|
|
25
|
-
// NOTE: Keep synced with $sidebar-max-width in SCSS:
|
|
26
|
-
const sidebarWidth = 360
|
|
27
|
-
return {
|
|
28
|
-
[`${prefix}--width-99`]: this.sidebarWidth < sidebarWidth,
|
|
29
|
-
[`${prefix}--width-60`]: this.sidebarWidth <= sidebarWidth * 0.6
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
methods: {
|
|
35
|
-
onResizeSidebar({ contentRect: { width } }) {
|
|
36
|
-
this.sidebarWidth = width
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
})
|
|
12
|
+
export default DitoComponent.component('DitoSidebar', {})
|
|
40
13
|
</script>
|
|
41
14
|
|
|
42
15
|
<style lang="scss">
|
|
43
16
|
@import '../styles/_imports';
|
|
44
17
|
|
|
45
18
|
.dito-sidebar {
|
|
19
|
+
// For the layout of `DitoContainer` in narrow sidebars:
|
|
20
|
+
container: dito-sidebar / inline-size;
|
|
46
21
|
flex: 0 1 $sidebar-max-width;
|
|
47
22
|
max-width: $sidebar-max-width;
|
|
48
23
|
min-width: $sidebar-min-width;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
//- Renders an item of a list or object source: Inlined items render their
|
|
3
|
+
//- form, others the source's `component`, `render()` or the item's label.
|
|
4
|
+
DitoSchemaInlined(
|
|
5
|
+
v-if="isInlined"
|
|
6
|
+
:label="inlinedLabel"
|
|
7
|
+
:schema="formSchema"
|
|
8
|
+
:dataPath="dataPath"
|
|
9
|
+
:data="data"
|
|
10
|
+
:meta="meta"
|
|
11
|
+
:store="store"
|
|
12
|
+
:disabled="disabled"
|
|
13
|
+
:collapsed="collapsed"
|
|
14
|
+
:collapsible="collapsible"
|
|
15
|
+
:deletable="deletable"
|
|
16
|
+
:draggable="draggable"
|
|
17
|
+
:editable="editable"
|
|
18
|
+
:editPath="editPath"
|
|
19
|
+
:createPath="createPath"
|
|
20
|
+
:insertIndex="insertIndex"
|
|
21
|
+
@delete="$emit('delete')"
|
|
22
|
+
@move="delta => $emit('move', delta)"
|
|
23
|
+
)
|
|
24
|
+
component(
|
|
25
|
+
v-else-if="schema.component"
|
|
26
|
+
:is="schema.component"
|
|
27
|
+
:dataPath="dataPath"
|
|
28
|
+
:data="data"
|
|
29
|
+
:nested="false"
|
|
30
|
+
)
|
|
31
|
+
span(
|
|
32
|
+
v-else-if="sourceComponent.render"
|
|
33
|
+
v-html="renderedItem"
|
|
34
|
+
)
|
|
35
|
+
span(
|
|
36
|
+
v-else
|
|
37
|
+
v-html="itemLabel"
|
|
38
|
+
)
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script>
|
|
42
|
+
import DitoComponent from '../DitoComponent.js'
|
|
43
|
+
import DitoContext from '../DitoContext.js'
|
|
44
|
+
import { isInlined, isListSource } from '../utils/schema/structure.js'
|
|
45
|
+
import { getItemFormSchema } from '../utils/schema/lookup.js'
|
|
46
|
+
|
|
47
|
+
// @vue/component
|
|
48
|
+
export default DitoComponent.component('DitoSourceItem', {
|
|
49
|
+
emits: ['delete', 'move'],
|
|
50
|
+
|
|
51
|
+
props: {
|
|
52
|
+
// The schema of the list or object source that holds the item.
|
|
53
|
+
schema: { type: Object, required: true },
|
|
54
|
+
dataPath: { type: String, required: true },
|
|
55
|
+
data: { type: Object, required: true },
|
|
56
|
+
// The index of the item in a list source, `null` in object sources.
|
|
57
|
+
index: { type: Number, default: null },
|
|
58
|
+
meta: { type: Object, required: true },
|
|
59
|
+
store: { type: Object, required: true },
|
|
60
|
+
disabled: { type: Boolean, required: true },
|
|
61
|
+
// The remaining props only apply to inlined items, see `DitoSchemaInlined`:
|
|
62
|
+
collapsed: { type: Boolean, default: false },
|
|
63
|
+
collapsible: { type: Boolean, default: false },
|
|
64
|
+
draggable: { type: Boolean, default: false },
|
|
65
|
+
editable: { type: Boolean, default: false },
|
|
66
|
+
deletable: { type: Boolean, default: false },
|
|
67
|
+
editPath: { type: String, default: null },
|
|
68
|
+
createPath: { type: String, default: null },
|
|
69
|
+
insertIndex: { type: Number, default: null }
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
computed: {
|
|
73
|
+
isInlined() {
|
|
74
|
+
return isInlined(this.schema)
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
formSchema() {
|
|
78
|
+
return getItemFormSchema(
|
|
79
|
+
this.schema,
|
|
80
|
+
this.data,
|
|
81
|
+
this.sourceComponent.context
|
|
82
|
+
)
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
inlinedLabel() {
|
|
86
|
+
// List items always show their label, objects only when collapsible.
|
|
87
|
+
return isListSource(this.schema) || this.collapsible
|
|
88
|
+
? this.sourceComponent.getItemLabel(this.schema, this.data, {
|
|
89
|
+
index: this.index,
|
|
90
|
+
asObject: true
|
|
91
|
+
})
|
|
92
|
+
: null
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
itemLabel() {
|
|
96
|
+
return this.sourceComponent.getItemLabel(this.schema, this.data, {
|
|
97
|
+
index: this.index
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
renderedItem() {
|
|
102
|
+
const { sourceComponent } = this
|
|
103
|
+
return sourceComponent.render(
|
|
104
|
+
new DitoContext(sourceComponent, {
|
|
105
|
+
data: this.data,
|
|
106
|
+
value: this.data,
|
|
107
|
+
index: this.index,
|
|
108
|
+
dataPath: this.dataPath
|
|
109
|
+
})
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
</script>
|
|
@@ -33,8 +33,12 @@ export default DitoComponent.component('DitoTableCell', {
|
|
|
33
33
|
mixins: [ContextMixin],
|
|
34
34
|
|
|
35
35
|
props: {
|
|
36
|
+
// The schema of the cell, e.g. a column of a list or a property of a tree
|
|
37
|
+
// item, whose `name` is the key of its value in `data`.
|
|
36
38
|
cell: { type: Object, required: true },
|
|
39
|
+
// The schema of the list or tree that holds the item.
|
|
37
40
|
schema: { type: Object, required: true },
|
|
41
|
+
// The data path and data of the item, see `renderCell()`.
|
|
38
42
|
dataPath: { type: String, required: true },
|
|
39
43
|
data: { type: Object, required: true },
|
|
40
44
|
meta: { type: Object, required: true },
|
|
@@ -8,21 +8,15 @@ thead.dito-table-head
|
|
|
8
8
|
v-if="shouldRenderSchema(column)"
|
|
9
9
|
scope="col"
|
|
10
10
|
:class="getColumnClass(column)"
|
|
11
|
+
:aria-sort="column.sortable ? getSortState(column) : null"
|
|
11
12
|
)
|
|
12
|
-
|
|
13
|
+
DitoButton(
|
|
13
14
|
v-if="column.sortable"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
custom
|
|
15
|
+
:class="getSortClass(column)"
|
|
16
|
+
@click="sortByColumn(column)"
|
|
17
17
|
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:class="getSortClass(column)"
|
|
21
|
-
:aria-sort="getSortState(column)"
|
|
22
|
-
@click="navigate"
|
|
23
|
-
)
|
|
24
|
-
.dito-button__order-arrows
|
|
25
|
-
span {{ getLabel(column) }}
|
|
18
|
+
.dito-button__order-arrows
|
|
19
|
+
span {{ getLabel(column) }}
|
|
26
20
|
span(
|
|
27
21
|
v-else
|
|
28
22
|
) {{ getLabel(column) }}
|
|
@@ -35,10 +29,13 @@ thead.dito-table-head
|
|
|
35
29
|
|
|
36
30
|
<script>
|
|
37
31
|
import DitoComponent from '../DitoComponent.js'
|
|
32
|
+
import { DitoButton } from '@ditojs/ui/src'
|
|
38
33
|
import { hyphenate } from '@ditojs/utils'
|
|
39
34
|
|
|
40
35
|
// @vue/component
|
|
41
36
|
export default DitoComponent.component('DitoTableHead', {
|
|
37
|
+
components: { DitoButton },
|
|
38
|
+
|
|
42
39
|
props: {
|
|
43
40
|
query: { type: Object, required: true },
|
|
44
41
|
columns: { type: Object, required: true },
|
|
@@ -74,16 +71,17 @@ export default DitoComponent.component('DitoTableHead', {
|
|
|
74
71
|
)
|
|
75
72
|
},
|
|
76
73
|
|
|
77
|
-
|
|
74
|
+
sortByColumn(column) {
|
|
78
75
|
// Toggle order if the same column is clicked again.
|
|
79
76
|
const order =
|
|
80
77
|
this.sort.name === column.name && this.sort.order === 'asc'
|
|
81
78
|
? 'desc'
|
|
82
79
|
: 'asc'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
order: `${column.name} ${order}`
|
|
86
|
-
|
|
80
|
+
// Sorting resets the page, as the sorted list has other pages.
|
|
81
|
+
this.sourceComponent.listQuery.update(
|
|
82
|
+
{ order: `${column.name} ${order}` },
|
|
83
|
+
{ resetPage: true }
|
|
84
|
+
)
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
})
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
:tabindex="modelValue === key ? 0 : -1"
|
|
11
11
|
:class="{ 'dito-tabs__link--active': modelValue === key }"
|
|
12
12
|
@click="$emit('update:modelValue', key)"
|
|
13
|
+
@keydown="onKeyDown($event, key)"
|
|
13
14
|
) {{ getLabel(tabSchema, key) }}
|
|
14
15
|
</template>
|
|
15
16
|
|
|
@@ -33,6 +34,30 @@ export default DitoComponent.component('DitoTabs', {
|
|
|
33
34
|
|
|
34
35
|
getTabPanelId(key) {
|
|
35
36
|
return `${this.schemaComponent.componentPath}-tabpanel-${key}`
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Supports the keyboard interaction of tabs with manual activation: The
|
|
40
|
+
// arrow keys, Home and End move the focus to other tabs, Enter and Space
|
|
41
|
+
// select the focused tab, so that moving through the tabs doesn't switch
|
|
42
|
+
// their panels and the route hash on each key press.
|
|
43
|
+
onKeyDown(event, key) {
|
|
44
|
+
const keys = Object.keys(this.tabs)
|
|
45
|
+
const index = keys.indexOf(key)
|
|
46
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
47
|
+
event.preventDefault()
|
|
48
|
+
this.$emit('update:modelValue', key)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
const focusIndex = {
|
|
52
|
+
ArrowLeft: (index - 1 + keys.length) % keys.length,
|
|
53
|
+
ArrowRight: (index + 1) % keys.length,
|
|
54
|
+
Home: 0,
|
|
55
|
+
End: keys.length - 1
|
|
56
|
+
}[event.key]
|
|
57
|
+
if (focusIndex !== undefined) {
|
|
58
|
+
event.preventDefault()
|
|
59
|
+
document.getElementById(this.getTabId(keys[focusIndex]))?.focus()
|
|
60
|
+
}
|
|
36
61
|
}
|
|
37
62
|
}
|
|
38
63
|
})
|