@ditojs/admin 3.2.7 → 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 +6011 -5826
- 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 +24 -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
|
@@ -4,7 +4,8 @@ import ResourceMixin from './ResourceMixin.js'
|
|
|
4
4
|
import SchemaParentMixin from '../mixins/SchemaParentMixin.js'
|
|
5
5
|
import { getSchemaAccessor, getStoreAccessor } from '../utils/accessor.js'
|
|
6
6
|
import { getMemberResource } from '../utils/resource.js'
|
|
7
|
-
import {
|
|
7
|
+
import { ListQuery } from '../utils/ListQuery.js'
|
|
8
|
+
import { ItemStores } from '../utils/ItemStores.js'
|
|
8
9
|
import {
|
|
9
10
|
isCompact,
|
|
10
11
|
isInlined,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
getViewPath
|
|
22
23
|
} from '../utils/schema/lookup.js'
|
|
23
24
|
import { updateOrder } from '../utils/schema/data.js'
|
|
25
|
+
import { confirmAndRemove } from '../utils/dialogs.js'
|
|
24
26
|
import {
|
|
25
27
|
isObject,
|
|
26
28
|
isString,
|
|
@@ -30,6 +32,7 @@ import {
|
|
|
30
32
|
parseDataPath,
|
|
31
33
|
normalizeDataPath
|
|
32
34
|
} from '@ditojs/utils'
|
|
35
|
+
import { markRaw } from 'vue'
|
|
33
36
|
|
|
34
37
|
// @vue/component
|
|
35
38
|
export default {
|
|
@@ -47,7 +50,16 @@ export default {
|
|
|
47
50
|
|
|
48
51
|
data() {
|
|
49
52
|
return {
|
|
50
|
-
wrappedPrimitives: null
|
|
53
|
+
wrappedPrimitives: null,
|
|
54
|
+
listQuery: markRaw(
|
|
55
|
+
new ListQuery({
|
|
56
|
+
router: this.$router,
|
|
57
|
+
getRoute: () => this.$route,
|
|
58
|
+
getSourceStore: () => this.store,
|
|
59
|
+
getDefaultQuery: () => this.defaultQuery
|
|
60
|
+
})
|
|
61
|
+
),
|
|
62
|
+
itemStores: ItemStores.getFromStore(this.store)
|
|
51
63
|
}
|
|
52
64
|
},
|
|
53
65
|
|
|
@@ -157,45 +169,28 @@ export default {
|
|
|
157
169
|
return this.routeComponent.getChildPath(this.schema.path)
|
|
158
170
|
},
|
|
159
171
|
|
|
172
|
+
// The defaults of the query, see `ListQuery`.
|
|
160
173
|
defaultQuery() {
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
return {
|
|
175
|
+
scope: this.defaultScope?.name,
|
|
176
|
+
page: this.schema.page,
|
|
177
|
+
order: this.defaultOrder
|
|
178
|
+
}
|
|
163
179
|
},
|
|
164
180
|
|
|
165
|
-
query:
|
|
166
|
-
get(
|
|
167
|
-
return
|
|
168
|
-
...this.defaultQuery,
|
|
169
|
-
...query
|
|
170
|
-
}
|
|
181
|
+
query: {
|
|
182
|
+
get() {
|
|
183
|
+
return this.listQuery.query
|
|
171
184
|
},
|
|
172
185
|
|
|
186
|
+
// Navigates to the query asynchronously, see `ListQuery.update()`. The
|
|
187
|
+
// stored query and thus the getter only change once the route did, when
|
|
188
|
+
// the `$route` watcher loads the list. Loads requested meanwhile wait
|
|
189
|
+
// for the navigation, see `requestData()`.
|
|
173
190
|
set(query) {
|
|
174
|
-
|
|
175
|
-
// ones. Use scope and page from the list schema as defaults, but allow
|
|
176
|
-
// the route query parameters to override them.
|
|
177
|
-
const {
|
|
178
|
-
scope = this.defaultScope?.name,
|
|
179
|
-
page = this.schema.page,
|
|
180
|
-
type
|
|
181
|
-
} = this.query
|
|
182
|
-
// Preserve / merge currently stored values, including any custom query
|
|
183
|
-
// parameters added by creatable.query
|
|
184
|
-
query = {
|
|
185
|
-
...this.query,
|
|
186
|
-
...(scope != null && { scope }),
|
|
187
|
-
...(page != null && { page }),
|
|
188
|
-
...(type != null && { type }),
|
|
189
|
-
...query
|
|
190
|
-
}
|
|
191
|
-
if (!equals(query, this.$route.query)) {
|
|
192
|
-
// Change the route query parameters, but don't trigger a route
|
|
193
|
-
// change, as that would cause the list to reload.
|
|
194
|
-
replaceRoute({ query })
|
|
195
|
-
}
|
|
196
|
-
return query // Let getStoreAccessor() do the actual setting
|
|
191
|
+
this.listQuery.update(query)
|
|
197
192
|
}
|
|
198
|
-
}
|
|
193
|
+
},
|
|
199
194
|
|
|
200
195
|
total: getStoreAccessor('total'),
|
|
201
196
|
|
|
@@ -355,25 +350,14 @@ export default {
|
|
|
355
350
|
// https://github.com/vuejs/vue-router/issues/3393#issuecomment-1158470149
|
|
356
351
|
flush: 'post',
|
|
357
352
|
handler(to, from) {
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
this.loadData(false)
|
|
367
|
-
} else if (
|
|
368
|
-
this.meta.reload &&
|
|
369
|
-
from.path !== to.path &&
|
|
370
|
-
from.path.startsWith(to.path)
|
|
371
|
-
) {
|
|
372
|
-
// Reload the source when navigating back to a parent-route after
|
|
373
|
-
// changing data in a child-route.
|
|
374
|
-
this.meta.reload = false
|
|
375
|
-
this.loadData(false)
|
|
376
|
-
}
|
|
353
|
+
if (
|
|
354
|
+
this.providesData &&
|
|
355
|
+
// Only the query changed, see `ListQuery`:
|
|
356
|
+
from.path === to.path &&
|
|
357
|
+
from.hash === to.hash &&
|
|
358
|
+
this.listQuery.setFromRoute(to.query)
|
|
359
|
+
) {
|
|
360
|
+
this.loadData(false)
|
|
377
361
|
}
|
|
378
362
|
}
|
|
379
363
|
},
|
|
@@ -417,10 +401,26 @@ export default {
|
|
|
417
401
|
|
|
418
402
|
methods: {
|
|
419
403
|
setupData() {
|
|
420
|
-
this.
|
|
404
|
+
this.listQuery.syncWithRoute()
|
|
421
405
|
this.ensureData()
|
|
422
406
|
},
|
|
423
407
|
|
|
408
|
+
// @override ResourceMixin.requestData()
|
|
409
|
+
requestData() {
|
|
410
|
+
// While a navigation to a changed query is pending, the stored query is
|
|
411
|
+
// stale, and the `$route` watcher loads the list with the new query once
|
|
412
|
+
// the route changed. Wait for it instead of loading the stale query, and
|
|
413
|
+
// only load if the navigation failed or was superseded.
|
|
414
|
+
const navigated = this.listQuery.whenNavigated()
|
|
415
|
+
return navigated
|
|
416
|
+
? navigated.then(hasNavigated => {
|
|
417
|
+
if (!hasNavigated && !this.$.isUnmounted) {
|
|
418
|
+
return this.requestData()
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
: ResourceMixin.methods.requestData.call(this)
|
|
422
|
+
},
|
|
423
|
+
|
|
424
424
|
// @override ResourceMixin.clearData()
|
|
425
425
|
clearData() {
|
|
426
426
|
this.total = 0
|
|
@@ -450,6 +450,10 @@ export default {
|
|
|
450
450
|
return this.value
|
|
451
451
|
},
|
|
452
452
|
|
|
453
|
+
getItemStore(item) {
|
|
454
|
+
return this.itemStores.getStore(this.getItemUid(this.schema, item))
|
|
455
|
+
},
|
|
456
|
+
|
|
453
457
|
isListResults(data) {
|
|
454
458
|
// Lists can also be loaded as `{ results, total }`, see `setData()`.
|
|
455
459
|
return (
|
|
@@ -536,57 +540,71 @@ export default {
|
|
|
536
540
|
}
|
|
537
541
|
}
|
|
538
542
|
if (removed) {
|
|
539
|
-
this.
|
|
540
|
-
|
|
543
|
+
this.itemStores.removeStore(this.getItemUid(this.schema, item))
|
|
544
|
+
// Items of lists with a resource are deleted through it, see
|
|
545
|
+
// `deleteItem()`, leaving no changes to save behind, but still
|
|
546
|
+
// changing the list.
|
|
547
|
+
if (this.isTransient) {
|
|
548
|
+
this.onChange()
|
|
549
|
+
} else {
|
|
550
|
+
this.emitChangeEvent()
|
|
551
|
+
}
|
|
541
552
|
}
|
|
542
553
|
},
|
|
543
554
|
|
|
544
|
-
deleteItem(item, index) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const
|
|
554
|
-
this.
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
555
|
+
async deleteItem(item, index) {
|
|
556
|
+
if (!item) return
|
|
557
|
+
const label = this.getItemLabel(this.schema, item, {
|
|
558
|
+
index,
|
|
559
|
+
extended: true
|
|
560
|
+
})
|
|
561
|
+
// Look up the item by identity whenever it's removed, as the data may
|
|
562
|
+
// have changed while the dialog was open or the request was pending.
|
|
563
|
+
// Object sources have no index, see `removeItem()`.
|
|
564
|
+
const getCurrentIndex = () => {
|
|
565
|
+
if (this.isObjectSource) {
|
|
566
|
+
return this.objectData === item ? null : -1
|
|
567
|
+
}
|
|
568
|
+
return this.listData.indexOf(item)
|
|
569
|
+
}
|
|
570
|
+
const removeIfPresent = () => {
|
|
571
|
+
const currentIndex = getCurrentIndex()
|
|
572
|
+
const isPresent = currentIndex !== -1
|
|
573
|
+
if (isPresent) {
|
|
574
|
+
this.removeItem(item, currentIndex)
|
|
575
|
+
}
|
|
576
|
+
return isPresent
|
|
577
|
+
}
|
|
578
|
+
await confirmAndRemove(this, {
|
|
579
|
+
label,
|
|
580
|
+
isTransient: this.isTransient,
|
|
581
|
+
remove: async () => {
|
|
582
|
+
if (this.isTransient) {
|
|
583
|
+
return removeIfPresent()
|
|
584
|
+
}
|
|
585
|
+
const currentIndex = getCurrentIndex()
|
|
586
|
+
if (currentIndex === -1) {
|
|
587
|
+
return false
|
|
588
|
+
}
|
|
574
589
|
const method = 'delete'
|
|
575
590
|
const resource = getMemberResource(
|
|
576
|
-
|
|
591
|
+
this.getItemId(this.schema, item, currentIndex),
|
|
577
592
|
this.getResource({ method })
|
|
578
593
|
)
|
|
579
|
-
if (resource) {
|
|
580
|
-
|
|
581
|
-
if (!err) {
|
|
582
|
-
this.removeItem(item, index)
|
|
583
|
-
notify()
|
|
584
|
-
}
|
|
585
|
-
this.reloadData()
|
|
586
|
-
})
|
|
594
|
+
if (!resource) {
|
|
595
|
+
return false
|
|
587
596
|
}
|
|
597
|
+
let isDeleted = false
|
|
598
|
+
await this.handleRequest({ method, resource }, err => {
|
|
599
|
+
if (!err) {
|
|
600
|
+
removeIfPresent()
|
|
601
|
+
isDeleted = true
|
|
602
|
+
}
|
|
603
|
+
this.reloadData()
|
|
604
|
+
})
|
|
605
|
+
return isDeleted
|
|
588
606
|
}
|
|
589
|
-
}
|
|
607
|
+
})
|
|
590
608
|
},
|
|
591
609
|
|
|
592
610
|
getSchemaComponent(index) {
|
|
@@ -646,10 +664,10 @@ export default {
|
|
|
646
664
|
navigateToRouteComponent(dataPath, onComplete) {
|
|
647
665
|
return new Promise((resolve, reject) => {
|
|
648
666
|
const callOnComplete = () => {
|
|
649
|
-
// Retrieve the last route
|
|
650
|
-
// we just navigated to, and pass it
|
|
651
|
-
const
|
|
652
|
-
const routeComponent = routeComponents[
|
|
667
|
+
// Retrieve the route component of the last route level, which is the
|
|
668
|
+
// component that we just navigated to, and pass it to `onComplete()`
|
|
669
|
+
const level = this.$route.matched.length - 1
|
|
670
|
+
const routeComponent = this.appState.routeComponents[level]
|
|
653
671
|
resolve(onComplete?.([routeComponent]) ?? true)
|
|
654
672
|
}
|
|
655
673
|
|
package/src/mixins/TypeMixin.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import ValueMixin from './ValueMixin.js'
|
|
2
2
|
import ContextMixin from './ContextMixin.js'
|
|
3
3
|
import ValidationMixin from './ValidationMixin.js'
|
|
4
|
+
import { getSchemaEventEntries } from '../utils/schema/events.js'
|
|
4
5
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
6
|
+
import { trackRegistration } from '../utils/registration.js'
|
|
5
7
|
import { getValidations } from '../utils/schema/validation.js'
|
|
6
8
|
import { asArray, camelize, equals } from '@ditojs/utils'
|
|
7
9
|
|
|
@@ -22,7 +24,8 @@ export default {
|
|
|
22
24
|
label: { type: String, default: null },
|
|
23
25
|
single: { type: Boolean, default: false },
|
|
24
26
|
nested: { type: Boolean, default: true },
|
|
25
|
-
|
|
27
|
+
// The id of the element that displays the errors, see `DitoContainer`.
|
|
28
|
+
errorsElementId: { type: String, default: null }
|
|
26
29
|
},
|
|
27
30
|
|
|
28
31
|
data() {
|
|
@@ -41,11 +44,6 @@ export default {
|
|
|
41
44
|
return this.schema.type
|
|
42
45
|
},
|
|
43
46
|
|
|
44
|
-
labelNode() {
|
|
45
|
-
const node = this.isMounted ? this.$el.previousElementSibling : null
|
|
46
|
-
return node?.matches('.dito-label') ? node : null
|
|
47
|
-
},
|
|
48
|
-
|
|
49
47
|
visible: getSchemaAccessor('visible', {
|
|
50
48
|
type: Boolean,
|
|
51
49
|
default() {
|
|
@@ -107,19 +105,12 @@ export default {
|
|
|
107
105
|
default: null
|
|
108
106
|
}),
|
|
109
107
|
|
|
110
|
-
inlineInfo() {
|
|
111
|
-
// When a label is present, info is shown in the label component.
|
|
112
|
-
// Otherwise, we have to show it inline.
|
|
113
|
-
return !this.label ? this.info : null
|
|
114
|
-
},
|
|
115
|
-
|
|
116
108
|
events() {
|
|
117
109
|
const events = this.getEvents()
|
|
118
|
-
// Register callbacks for all
|
|
119
|
-
// assuming they are native events
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
for (const event of Object.keys(this.schema.events || {})) {
|
|
110
|
+
// Register callbacks for all events that the schema handles and that the
|
|
111
|
+
// component doesn't handle itself, assuming they are native events, in
|
|
112
|
+
// both forms, `events: { mouseenter }` and `onMouseenter`.
|
|
113
|
+
for (const { event } of getSchemaEventEntries(this.schema)) {
|
|
123
114
|
events[`on${camelize(event, true)}`] ??= () => {
|
|
124
115
|
this.emitEvent(event)
|
|
125
116
|
}
|
|
@@ -133,7 +124,10 @@ export default {
|
|
|
133
124
|
const attributes = {
|
|
134
125
|
...this.events,
|
|
135
126
|
// Only text fields support `readonly`, disable all other components.
|
|
136
|
-
disabled: this.disabled || (this.readonly && !textField)
|
|
127
|
+
'disabled': this.disabled || (this.readonly && !textField),
|
|
128
|
+
// Link the displayed errors for assistive technology:
|
|
129
|
+
'aria-invalid': this.hasErrors || null,
|
|
130
|
+
'aria-describedby': this.hasErrors ? this.errorsElementId : null
|
|
137
131
|
}
|
|
138
132
|
|
|
139
133
|
if (nativeField) {
|
|
@@ -155,10 +149,6 @@ export default {
|
|
|
155
149
|
|
|
156
150
|
validations() {
|
|
157
151
|
return getValidations(this.schema, this.context)
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
showClearButton() {
|
|
161
|
-
return this.clearable && this.value != null
|
|
162
152
|
}
|
|
163
153
|
},
|
|
164
154
|
|
|
@@ -172,23 +162,23 @@ export default {
|
|
|
172
162
|
if (this.hasErrors && !equals(value, this.valueWithErrors)) {
|
|
173
163
|
this.clearErrors()
|
|
174
164
|
}
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
componentPath(componentPath, oldComponentPath) {
|
|
178
|
-
// Component paths change when list items move, see `_registerEntry()`.
|
|
179
|
-
this._registerWithSchemaComponent(false, oldComponentPath)
|
|
180
|
-
this._registerWithSchemaComponent(true, componentPath)
|
|
181
165
|
}
|
|
182
166
|
},
|
|
183
167
|
|
|
184
168
|
created() {
|
|
185
|
-
|
|
169
|
+
// Provide component to container for schema accessor evaluation.
|
|
170
|
+
this.$emit('update:component', this)
|
|
171
|
+
this._unregister = trackRegistration(
|
|
172
|
+
this,
|
|
173
|
+
this._registerWithSchemaComponent
|
|
174
|
+
)
|
|
186
175
|
this.setupSchemaFields()
|
|
187
176
|
this.warnAboutUncomputedValue()
|
|
188
177
|
},
|
|
189
178
|
|
|
190
179
|
unmounted() {
|
|
191
|
-
this
|
|
180
|
+
this.$emit('update:component', null)
|
|
181
|
+
this._unregister()
|
|
192
182
|
},
|
|
193
183
|
|
|
194
184
|
methods: {
|
|
@@ -198,7 +188,10 @@ export default {
|
|
|
198
188
|
warnAboutUncomputedValue() {
|
|
199
189
|
if (
|
|
200
190
|
this.schema.compute &&
|
|
201
|
-
!this.dataModel.hasComputedValueEntry(
|
|
191
|
+
!this.dataModel.hasComputedValueEntry({
|
|
192
|
+
data: this.data,
|
|
193
|
+
name: this.name
|
|
194
|
+
})
|
|
202
195
|
) {
|
|
203
196
|
console.warn(
|
|
204
197
|
`The value of the component at '${this.componentPath}' isn't ` +
|
|
@@ -207,12 +200,6 @@ export default {
|
|
|
207
200
|
}
|
|
208
201
|
},
|
|
209
202
|
|
|
210
|
-
_register(add) {
|
|
211
|
-
// Provide component to container for schema accessor evaluation.
|
|
212
|
-
this.$emit('update:component', add ? this : null)
|
|
213
|
-
this._registerWithSchemaComponent(add, this.componentPath)
|
|
214
|
-
},
|
|
215
|
-
|
|
216
203
|
_registerWithSchemaComponent(add, componentPath) {
|
|
217
204
|
// Register unnested type components separately, to prevent them from
|
|
218
205
|
// overriding parent data paths.
|
|
@@ -244,12 +231,20 @@ export default {
|
|
|
244
231
|
|
|
245
232
|
// @overridable
|
|
246
233
|
focusElement() {
|
|
247
|
-
this.
|
|
234
|
+
this._getFocusTarget('focus')?.focus?.()
|
|
248
235
|
},
|
|
249
236
|
|
|
250
237
|
// @overridable
|
|
251
238
|
blurElement() {
|
|
252
|
-
this.
|
|
239
|
+
this._getFocusTarget('blur')?.blur?.()
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
_getFocusTarget(method) {
|
|
243
|
+
// Prefer the `focus()` and `blur()` of components, e.g. `DitoInput`
|
|
244
|
+
// targets its input rather than its root element that
|
|
245
|
+
// `getFocusElement()` returns.
|
|
246
|
+
const element = asArray(this.$refs.element)[0]
|
|
247
|
+
return element?.[method] ? element : this.getFocusElement()
|
|
253
248
|
},
|
|
254
249
|
|
|
255
250
|
getFocusElement() {
|
|
@@ -298,7 +293,14 @@ export default {
|
|
|
298
293
|
|
|
299
294
|
onChange() {
|
|
300
295
|
this.markDirty()
|
|
301
|
-
this.
|
|
296
|
+
this.emitChangeEvent()
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// Emits the `change` event without marking the component dirty, e.g. for
|
|
300
|
+
// changes that are already persisted through a resource, see
|
|
301
|
+
// `SourceMixin.removeItem()`.
|
|
302
|
+
emitChangeEvent() {
|
|
303
|
+
return this.emitEvent('change', {
|
|
302
304
|
context: {
|
|
303
305
|
// Prevent endless parse recursion:
|
|
304
306
|
value: this.parsedValue !== undefined ? this.parsedValue : this.value
|
|
@@ -50,9 +50,9 @@ export default {
|
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
markTouched() {
|
|
53
|
+
// Displayed errors remain while the input is focused, e.g. after
|
|
54
|
+
// `showValidationErrors()` focused it, until new input clears them.
|
|
53
55
|
this.isTouched = true
|
|
54
|
-
// Clear currently displayed errors when focusing input.
|
|
55
|
-
this.clearErrors()
|
|
56
56
|
},
|
|
57
57
|
|
|
58
58
|
markDirty() {
|
|
@@ -74,9 +74,21 @@ export default {
|
|
|
74
74
|
this.$emit('errors', this.errors)
|
|
75
75
|
},
|
|
76
76
|
|
|
77
|
+
// Removes the last occurrence of the error that `addError()` added.
|
|
78
|
+
removeError(error) {
|
|
79
|
+
const index = this.errors?.lastIndexOf(error) ?? -1
|
|
80
|
+
if (index >= 0) {
|
|
81
|
+
this.errors.splice(index, 1)
|
|
82
|
+
if (this.errors.length === 0) {
|
|
83
|
+
this.errors = null
|
|
84
|
+
}
|
|
85
|
+
this.$emit('errors', this.errors)
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
77
89
|
showValidationErrors(errors, focus) {
|
|
78
90
|
// Convert from AJV errors objects to an array of error messages
|
|
79
|
-
this.
|
|
91
|
+
this.clearErrors()
|
|
80
92
|
if (errors.length === 0) {
|
|
81
93
|
return false
|
|
82
94
|
}
|
|
@@ -84,7 +96,7 @@ export default {
|
|
|
84
96
|
this.addError(message, true)
|
|
85
97
|
}
|
|
86
98
|
if (focus) {
|
|
87
|
-
this.
|
|
99
|
+
this.focus()
|
|
88
100
|
}
|
|
89
101
|
return true
|
|
90
102
|
},
|
|
@@ -29,6 +29,7 @@ $line-height: rounded(
|
|
|
29
29
|
$button-order-arrow-size: 4px;
|
|
30
30
|
|
|
31
31
|
// Pulldown
|
|
32
|
+
// Deprecated, remove in 4.0
|
|
32
33
|
$pulldown-radius: 0.5em;
|
|
33
34
|
$pulldown-padding-ver: 0.5em;
|
|
34
35
|
$pulldown-padding-hor: 1em;
|
|
@@ -79,6 +80,9 @@ $z-index-header: 2000;
|
|
|
79
80
|
$z-index-tippy: 750;
|
|
80
81
|
$z-index-main-buttons: 500;
|
|
81
82
|
|
|
83
|
+
// Drag overlay, see `DitoRoot` and `DitoTypeUpload`
|
|
84
|
+
$drag-overlay-duration: 0.15s;
|
|
85
|
+
|
|
82
86
|
// Patterns
|
|
83
87
|
$pattern-transparency-size: ($font-size - $border-width) * $input-height-factor;
|
|
84
88
|
$pattern-transparency: repeating-conic-gradient(
|
package/src/styles/_tippy.scss
CHANGED
|
@@ -31,15 +31,4 @@
|
|
|
31
31
|
&[data-theme~='info'] {
|
|
32
32
|
--color: #{color.adjust($color-active, $lightness: 5%)};
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
&[data-theme~='error'] {
|
|
36
|
-
--color: #{color.adjust($color-error, $lightness: 5%)};
|
|
37
|
-
|
|
38
|
-
cursor: pointer;
|
|
39
|
-
|
|
40
|
-
> .tippy-arrow {
|
|
41
|
-
transform: unset !important;
|
|
42
|
-
left: 16px !important;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
34
|
}
|
package/src/styles/style.scss
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
DitoButton
|
|
2
|
+
DitoButton(
|
|
3
3
|
:id="componentPath"
|
|
4
4
|
ref="element"
|
|
5
5
|
:type="type"
|
|
6
|
+
:verb="verb"
|
|
6
7
|
:text="text"
|
|
7
|
-
:title="
|
|
8
|
-
:
|
|
9
|
-
:class="buttonClass"
|
|
8
|
+
:title="text"
|
|
9
|
+
:aria-busy="isRunning"
|
|
10
10
|
v-bind="attributes"
|
|
11
11
|
)
|
|
12
12
|
template(#prefix)
|
|
@@ -37,7 +37,7 @@ import DitoAffixes from '../components/DitoAffixes.vue'
|
|
|
37
37
|
import { DitoButton } from '@ditojs/ui/src'
|
|
38
38
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
39
39
|
import { hasResource } from '../utils/resource.js'
|
|
40
|
-
import {
|
|
40
|
+
import { asArray } from '@ditojs/utils'
|
|
41
41
|
|
|
42
42
|
export default DitoTypeComponent.register(
|
|
43
43
|
['button', 'submit'],
|
|
@@ -60,18 +60,10 @@ export default DitoTypeComponent.register(
|
|
|
60
60
|
return this.verbs[this.name]
|
|
61
61
|
},
|
|
62
62
|
|
|
63
|
-
buttonClass() {
|
|
64
|
-
return this.verb ? `dito-button--${this.verb}` : null
|
|
65
|
-
},
|
|
66
|
-
|
|
67
63
|
text: getSchemaAccessor('text', {
|
|
68
64
|
type: String
|
|
69
65
|
}),
|
|
70
66
|
|
|
71
|
-
title() {
|
|
72
|
-
return this.text || labelize(this.verb)
|
|
73
|
-
},
|
|
74
|
-
|
|
75
67
|
prefixes() {
|
|
76
68
|
return asArray(this.schema.prefix)
|
|
77
69
|
},
|
|
@@ -98,6 +90,11 @@ export default DitoTypeComponent.register(
|
|
|
98
90
|
},
|
|
99
91
|
|
|
100
92
|
async onClick() {
|
|
93
|
+
// Prevent running the action again while it's running, e.g. submits.
|
|
94
|
+
// The button isn't disabled meanwhile, as `disabled` reflects the
|
|
95
|
+
// state of the schema, e.g. of forms that can't be submitted when
|
|
96
|
+
// they're clean, which a running action doesn't change:
|
|
97
|
+
if (this.isRunning) return
|
|
101
98
|
this.isRunning = true
|
|
102
99
|
try {
|
|
103
100
|
const res = await this.emitEvent('click', {
|
|
@@ -111,7 +108,7 @@ export default DitoTypeComponent.register(
|
|
|
111
108
|
await this.submit()
|
|
112
109
|
}
|
|
113
110
|
} catch (error) {
|
|
114
|
-
const res = await this.emitEvent('error', { error })
|
|
111
|
+
const res = await this.emitEvent('error', { context: { error } })
|
|
115
112
|
if (res === undefined) {
|
|
116
113
|
if (error instanceof AggregateError) {
|
|
117
114
|
for (const err of error.errors) {
|