@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
|
@@ -10,6 +10,7 @@ import { reactive, computed } from 'vue'
|
|
|
10
10
|
import { getTypeOptions } from './types.js'
|
|
11
11
|
import {
|
|
12
12
|
findNestedSchemaComponent,
|
|
13
|
+
isMenu,
|
|
13
14
|
isSchema,
|
|
14
15
|
isSingleComponentView
|
|
15
16
|
} from './structure.js'
|
|
@@ -72,6 +73,14 @@ export function getViewPath(schema, context) {
|
|
|
72
73
|
return null
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// Returns the path that links to the view or menu `schema`, displayed at
|
|
77
|
+
// `path`: Menus link to their first item.
|
|
78
|
+
export function getViewLinkPath(schema, path = schema.fullPath) {
|
|
79
|
+
return isMenu(schema)
|
|
80
|
+
? Object.values(schema.items)[0].fullPath
|
|
81
|
+
: path
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
export function getViewEditPath(schema, id, context) {
|
|
76
85
|
const path = getViewPath(schema, context)
|
|
77
86
|
return path ? `${path}/${id}` : null
|
|
@@ -125,14 +134,19 @@ function getFormWithCreatedComponents(form, context) {
|
|
|
125
134
|
formsByItem = new WeakMap()
|
|
126
135
|
formsByComponentsCallback.set(form.components, formsByItem)
|
|
127
136
|
}
|
|
128
|
-
let
|
|
129
|
-
if (!
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
let entry = formsByItem.get(item)
|
|
138
|
+
if (!entry) {
|
|
139
|
+
entry = { context }
|
|
140
|
+
entry.formWithCreatedComponents = computed(() =>
|
|
141
|
+
createFormWithComponents(form, entry.context)
|
|
132
142
|
)
|
|
133
|
-
formsByItem.set(item,
|
|
143
|
+
formsByItem.set(item, entry)
|
|
134
144
|
}
|
|
135
|
-
|
|
145
|
+
// Recreate the components with the latest context rather than the one of
|
|
146
|
+
// the first caller, whose component may have been unmounted since. Changes
|
|
147
|
+
// of the context alone don't recreate them, see above.
|
|
148
|
+
entry.context = context
|
|
149
|
+
return entry.formWithCreatedComponents.value
|
|
136
150
|
}
|
|
137
151
|
|
|
138
152
|
function createFormWithComponents(form, context) {
|
|
@@ -214,16 +228,27 @@ export function getButtonSchemas(buttons) {
|
|
|
214
228
|
}
|
|
215
229
|
|
|
216
230
|
// Returns the entry of the panel described by `schema`, which `DitoPanels`
|
|
217
|
-
// displays at `dataPath` and `componentPath`.
|
|
218
|
-
|
|
219
|
-
|
|
231
|
+
// displays at `dataPath` and `componentPath`. Panels of components in tabs are
|
|
232
|
+
// only displayed while their tab is selected: `tab` describes that tab as
|
|
233
|
+
// `{ tabName, isTabSelected }`, where `isTabSelected()` tells if it currently
|
|
234
|
+
// is, see `DitoPane.panelTab`.
|
|
235
|
+
function getPanelEntry(schema, dataPath, componentPath, tab) {
|
|
236
|
+
return schema
|
|
237
|
+
? {
|
|
238
|
+
schema,
|
|
239
|
+
dataPath,
|
|
240
|
+
componentPath,
|
|
241
|
+
tabName: tab?.tabName ?? null,
|
|
242
|
+
isTabSelected: tab?.isTabSelected ?? null
|
|
243
|
+
}
|
|
244
|
+
: null
|
|
220
245
|
}
|
|
221
246
|
|
|
222
247
|
export function getPanelEntries(
|
|
223
248
|
panelSchemas,
|
|
224
249
|
dataPath,
|
|
225
250
|
componentPath,
|
|
226
|
-
|
|
251
|
+
tab = null,
|
|
227
252
|
panelEntries = []
|
|
228
253
|
) {
|
|
229
254
|
if (panelSchemas) {
|
|
@@ -232,7 +257,7 @@ export function getPanelEntries(
|
|
|
232
257
|
schema,
|
|
233
258
|
appendDataPath(dataPath, key),
|
|
234
259
|
appendDataPath(componentPath, key),
|
|
235
|
-
|
|
260
|
+
tab
|
|
236
261
|
)
|
|
237
262
|
if (entry) {
|
|
238
263
|
panelEntries.push(entry)
|
|
@@ -256,7 +281,7 @@ export function getAllPanelEntries(
|
|
|
256
281
|
dataPathComponentPath,
|
|
257
282
|
schemaComponentPath,
|
|
258
283
|
component = null,
|
|
259
|
-
|
|
284
|
+
tab = null
|
|
260
285
|
) {
|
|
261
286
|
const panelSchema = getTypeOptions(schema)?.getPanelSchema?.(
|
|
262
287
|
api,
|
|
@@ -270,7 +295,7 @@ export function getAllPanelEntries(
|
|
|
270
295
|
panelSchema,
|
|
271
296
|
appendDataPath(dataPath, panelSchema.name),
|
|
272
297
|
appendDataPath(dataPathComponentPath, panelSchema.name),
|
|
273
|
-
|
|
298
|
+
tab
|
|
274
299
|
)
|
|
275
300
|
]
|
|
276
301
|
: []
|
|
@@ -280,7 +305,7 @@ export function getAllPanelEntries(
|
|
|
280
305
|
schema?.panels,
|
|
281
306
|
dataPath,
|
|
282
307
|
schemaComponentPath,
|
|
283
|
-
|
|
308
|
+
tab,
|
|
284
309
|
panelEntries
|
|
285
310
|
)
|
|
286
311
|
return panelEntries
|
|
@@ -97,13 +97,13 @@ export async function resolveSchemas(
|
|
|
97
97
|
export async function resolveViews(unresolvedViews) {
|
|
98
98
|
return resolveSchemas(unresolvedViews, async (schema, unwrapModule) => {
|
|
99
99
|
schema = await resolveSchema(schema, unwrapModule)
|
|
100
|
-
if (
|
|
101
|
-
// Generate a name for sub-menus from their label if it's missing.
|
|
102
|
-
// NOTE: This is never actually referenced from anywhere, but they need
|
|
103
|
-
// a name by which they're stored in the parent object.
|
|
100
|
+
if (isMenu(schema)) {
|
|
104
101
|
schema = {
|
|
105
102
|
...schema,
|
|
106
|
-
name
|
|
103
|
+
// Generate a name for sub-menus from their label if it's missing.
|
|
104
|
+
// NOTE: This is never actually referenced from anywhere, but they need
|
|
105
|
+
// a name by which they're stored in the parent object.
|
|
106
|
+
name: schema.name || camelize(schema.label),
|
|
107
107
|
items: await resolveSchemas(schema.items)
|
|
108
108
|
}
|
|
109
109
|
}
|
|
@@ -40,8 +40,7 @@ export function getValidations(schema, context) {
|
|
|
40
40
|
export function getValueValidationErrors(value, validations) {
|
|
41
41
|
const errors = []
|
|
42
42
|
for (const [name, setting] of Object.entries(validations)) {
|
|
43
|
-
|
|
44
|
-
const validator = validators[name]
|
|
43
|
+
const validator = getValidator(name)
|
|
45
44
|
if (
|
|
46
45
|
validator &&
|
|
47
46
|
// Only apply 'required' validator to empty values.
|
|
@@ -61,6 +60,21 @@ export function getValueValidationErrors(value, validations) {
|
|
|
61
60
|
return errors
|
|
62
61
|
}
|
|
63
62
|
|
|
63
|
+
// The names of unknown validators that were warned about, see `getValidator()`:
|
|
64
|
+
const warnedUnknownValidatorNames = new Set()
|
|
65
|
+
|
|
66
|
+
// Returns the validator of the name, or `null` with a warning, once per name,
|
|
67
|
+
// e.g. for misspelled `schema.rules`.
|
|
68
|
+
function getValidator(name) {
|
|
69
|
+
// eslint-disable-next-line import/namespace
|
|
70
|
+
const validator = validators[name] ?? null
|
|
71
|
+
if (!validator && !warnedUnknownValidatorNames.has(name)) {
|
|
72
|
+
warnedUnknownValidatorNames.add(name)
|
|
73
|
+
console.warn(`Unknown validator '${name}' is ignored.`)
|
|
74
|
+
}
|
|
75
|
+
return validator
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
/**
|
|
65
79
|
* Validates `data` along the components of `schema`, including tabs, panels
|
|
66
80
|
* and the nested forms of list and object items, skipping components whose
|
package/src/utils/uid.js
CHANGED
|
@@ -1,15 +1,112 @@
|
|
|
1
1
|
import { toRaw } from 'vue'
|
|
2
|
+
import { isArray, isPlainObject, isString } from '@ditojs/utils'
|
|
3
|
+
import { isTemporaryId } from './data.js'
|
|
2
4
|
|
|
3
|
-
const
|
|
5
|
+
const uidsByItem = new WeakMap()
|
|
4
6
|
|
|
5
|
-
// Generated
|
|
6
|
-
|
|
7
|
+
// Generated uids have their own prefix, so that they can't be mistaken for
|
|
8
|
+
// temporary ids, see `setTemporaryId()`.
|
|
9
|
+
const generatedUidPrefix = 'uid-'
|
|
10
|
+
let lastGeneratedUid = 0
|
|
11
|
+
|
|
12
|
+
// Generates and remembers unique ids per passed object using a weak map.
|
|
7
13
|
export function getUid(item, getItemId = null) {
|
|
8
14
|
const raw = toRaw(item)
|
|
9
|
-
let
|
|
10
|
-
if (!
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
let uid = uidsByItem.get(raw)
|
|
16
|
+
if (!uid && item) {
|
|
17
|
+
uid = getItemId?.(item) || `${generatedUidPrefix}${++lastGeneratedUid}`
|
|
18
|
+
uidsByItem.set(raw, uid)
|
|
19
|
+
}
|
|
20
|
+
return uid
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Transfers the uids of new objects in `source`, which are generated or
|
|
24
|
+
// temporary ids, to the corresponding objects in `target` that don't have uids
|
|
25
|
+
// yet, e.g. when the saved data replaces the data that was edited. This way,
|
|
26
|
+
// the components and stores that are keyed by the uids of new items are kept,
|
|
27
|
+
// even though the items only have their ids once they are saved.
|
|
28
|
+
// Objects are matched by their keys. Array items are matched by their `id`
|
|
29
|
+
// properties, see `transferArrayUids()`. The items of lists with a custom
|
|
30
|
+
// `idKey` have no `id`, so they count as new items and are matched in order.
|
|
31
|
+
export function transferUids(source, target) {
|
|
32
|
+
const rawSource = toRaw(source)
|
|
33
|
+
const rawTarget = toRaw(target)
|
|
34
|
+
if (
|
|
35
|
+
rawSource !== rawTarget &&
|
|
36
|
+
isTransferable(rawSource) &&
|
|
37
|
+
isTransferable(rawTarget)
|
|
38
|
+
) {
|
|
39
|
+
const uid = uidsByItem.get(rawSource)
|
|
40
|
+
if (isNewItemUid(uid) && !uidsByItem.has(rawTarget)) {
|
|
41
|
+
uidsByItem.set(rawTarget, uid)
|
|
42
|
+
}
|
|
43
|
+
if (isArray(rawSource) && isArray(rawTarget)) {
|
|
44
|
+
transferArrayUids(rawSource, rawTarget)
|
|
45
|
+
} else {
|
|
46
|
+
for (const key of Object.keys(rawSource)) {
|
|
47
|
+
if (key in rawTarget) {
|
|
48
|
+
transferUids(rawSource[key], rawTarget[key])
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Matches the items of two arrays by their ids rather than their positions, as
|
|
56
|
+
// the saved items may be ordered differently than the edited ones: Each saved
|
|
57
|
+
// item in `source` is matched with the item in `target` that has the same id.
|
|
58
|
+
// The new items in `source`, which have no id or a temporary one, can only be
|
|
59
|
+
// matched with the items in `target` that have none of the saved ids, in order,
|
|
60
|
+
// and only if their counts are equal. Otherwise, they are left unmatched rather
|
|
61
|
+
// than risking to give an item the uid, component and store of another.
|
|
62
|
+
function transferArrayUids(source, target) {
|
|
63
|
+
const sourceItems = source.filter(isTransferable)
|
|
64
|
+
const targetItems = target.filter(isTransferable)
|
|
65
|
+
const targetItemsById = new Map()
|
|
66
|
+
for (const item of targetItems) {
|
|
67
|
+
const id = getSavedItemId(item)
|
|
68
|
+
if (id !== undefined) {
|
|
69
|
+
targetItemsById.set(id, item)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const savedIds = new Set()
|
|
73
|
+
const newSourceItems = []
|
|
74
|
+
for (const item of sourceItems) {
|
|
75
|
+
const id = getSavedItemId(item)
|
|
76
|
+
if (id !== undefined) {
|
|
77
|
+
savedIds.add(id)
|
|
78
|
+
const targetItem = targetItemsById.get(id)
|
|
79
|
+
if (targetItem) {
|
|
80
|
+
transferUids(item, targetItem)
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
newSourceItems.push(item)
|
|
84
|
+
}
|
|
13
85
|
}
|
|
14
|
-
|
|
86
|
+
const newTargetItems = targetItems.filter(
|
|
87
|
+
item => !savedIds.has(getSavedItemId(item))
|
|
88
|
+
)
|
|
89
|
+
if (newSourceItems.length === newTargetItems.length) {
|
|
90
|
+
newSourceItems.forEach((item, index) => {
|
|
91
|
+
transferUids(item, newTargetItems[index])
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Returns the id of a saved item as a string, so that numeric ids match their
|
|
97
|
+
// string representations, or `undefined` for new items.
|
|
98
|
+
function getSavedItemId(item) {
|
|
99
|
+
const { id } = item
|
|
100
|
+
return id != null && !isTemporaryId(id) ? String(id) : undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isTransferable(value) {
|
|
104
|
+
return isArray(value) || isPlainObject(value)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isNewItemUid(uid) {
|
|
108
|
+
return (
|
|
109
|
+
isString(uid) &&
|
|
110
|
+
(uid.startsWith(generatedUidPrefix) || isTemporaryId(uid))
|
|
111
|
+
)
|
|
15
112
|
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
|
+
function hasUnlimitedDecimals(decimals) {
|
|
2
|
+
return decimals == null || decimals === '*'
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
export const decimals = {
|
|
2
6
|
validate: (value, decimals) => {
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
// `decimals: 0` allows no decimal point at all, only integers:
|
|
8
|
+
const fraction = hasUnlimitedDecimals(decimals)
|
|
9
|
+
? '(\\.\\d+)?'
|
|
10
|
+
: decimals > 0
|
|
11
|
+
? `(\\.\\d{1,${decimals}})?`
|
|
12
|
+
: ''
|
|
13
|
+
return new RegExp(`^[-+]?\\d*${fraction}$`).test(value)
|
|
5
14
|
},
|
|
6
15
|
|
|
7
16
|
message: (value, decimals) =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
hasUnlimitedDecimals(decimals)
|
|
18
|
+
? 'must be numeric and may contain decimal points'
|
|
19
|
+
: decimals > 0
|
|
20
|
+
? `must be numeric and may contain ${decimals} decimal points`
|
|
21
|
+
: 'must be numeric and may not contain decimal points'
|
|
11
22
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -353,6 +353,10 @@ export interface BaseSchema<$Item>
|
|
|
353
353
|
* context's component properties, e.g. `component`
|
|
354
354
|
* and `schemaComponent`, refer to the form, view or
|
|
355
355
|
* dialog that holds the data, not to the field.
|
|
356
|
+
*
|
|
357
|
+
* Reading `context.options` waits while the options
|
|
358
|
+
* load, and is `undefined` when the load fails or its
|
|
359
|
+
* data resolves to nothing, so use `options?.`.
|
|
356
360
|
*/
|
|
357
361
|
compute?: ItemAccessor<$Item>
|
|
358
362
|
/**
|
|
@@ -629,6 +633,9 @@ export interface SchemaSourceMixin<$Item> {
|
|
|
629
633
|
* the default is the 'name' property of the item,
|
|
630
634
|
* followed by the label of the form (plus item id)
|
|
631
635
|
* and other defaults.
|
|
636
|
+
* Labels are HTML: Callbacks return HTML, and need to
|
|
637
|
+
* escape the item values that they include with
|
|
638
|
+
* `escapeHtml()` from `@ditojs/utils`.
|
|
632
639
|
*/
|
|
633
640
|
itemLabel?:
|
|
634
641
|
| OrItemAccessor<
|
|
@@ -850,7 +857,9 @@ export type SchemaOptions<$Item, $Option = any> =
|
|
|
850
857
|
*/
|
|
851
858
|
value?: keyof $Option | ItemAccessor<$Item, { option: $Option }>
|
|
852
859
|
/**
|
|
853
|
-
*
|
|
860
|
+
* @deprecated Not supported, the options are grouped by the `groupBy`
|
|
861
|
+
* setting of the component's schema, see
|
|
862
|
+
* {@link SchemaOptionsMixin.groupBy}.
|
|
854
863
|
*/
|
|
855
864
|
groupBy?: keyof $Option
|
|
856
865
|
/**
|
|
@@ -913,7 +922,9 @@ export interface SchemaOptionsMixin<$Item, $Option = any> {
|
|
|
913
922
|
relateBy?: string
|
|
914
923
|
/**
|
|
915
924
|
* The key of the option property which should be used to
|
|
916
|
-
* group the options.
|
|
925
|
+
* group the options. Selects and multiselects show the groups
|
|
926
|
+
* in their dropdowns, radio buttons and checkboxes list each
|
|
927
|
+
* group in a fieldset with the group's name as its legend.
|
|
917
928
|
*/
|
|
918
929
|
groupBy?: OrItemAccessor<$Item, {}, string>
|
|
919
930
|
/**
|
|
@@ -942,7 +953,8 @@ export interface SchemaOptionsMixin<$Item, $Option = any> {
|
|
|
942
953
|
}
|
|
943
954
|
/**
|
|
944
955
|
* Whether the selected option can be edited by navigating
|
|
945
|
-
* to
|
|
956
|
+
* to its form in the view of `view`. Radio buttons and checkboxes
|
|
957
|
+
* show an edit button for each of their options.
|
|
946
958
|
*/
|
|
947
959
|
editable?: OrItemAccessor<$Item, {}, boolean>
|
|
948
960
|
/**
|
|
@@ -1220,7 +1232,8 @@ export interface DateSchema<$Item = any>
|
|
|
1220
1232
|
}
|
|
1221
1233
|
>
|
|
1222
1234
|
/**
|
|
1223
|
-
* @deprecated Use `formats` instead.
|
|
1235
|
+
* @deprecated Use `formats.date` instead. `dateFormat` is no longer read and
|
|
1236
|
+
* has no effect.
|
|
1224
1237
|
*/
|
|
1225
1238
|
dateFormat?: OrItemAccessor<$Item, {}, DateFormat>
|
|
1226
1239
|
}
|
|
@@ -1924,9 +1937,10 @@ export type DitoContext<$Item = any> = {
|
|
|
1924
1937
|
*/
|
|
1925
1938
|
itemDataPath: string
|
|
1926
1939
|
/**
|
|
1927
|
-
* Data path of the parent item ancestor
|
|
1940
|
+
* Data path of the parent item ancestor, `null` for
|
|
1941
|
+
* root items.
|
|
1928
1942
|
*/
|
|
1929
|
-
parentItemDataPath: string
|
|
1943
|
+
parentItemDataPath: string | null
|
|
1930
1944
|
/** Index of the closest item in its list. */
|
|
1931
1945
|
itemIndex: number | null
|
|
1932
1946
|
/** Index of the parent item in its list. */
|
|
@@ -1959,11 +1973,11 @@ export type DitoContext<$Item = any> = {
|
|
|
1959
1973
|
* copy/paste operations.
|
|
1960
1974
|
*/
|
|
1961
1975
|
clipboardItem: any
|
|
1962
|
-
/** The currently authenticated user. */
|
|
1976
|
+
/** The currently authenticated user, `null` when logged out. */
|
|
1963
1977
|
user: {
|
|
1964
1978
|
roles?: string[]
|
|
1965
1979
|
hasRole(...roles: string[]): boolean
|
|
1966
|
-
}
|
|
1980
|
+
} | null
|
|
1967
1981
|
/** The admin API configuration. */
|
|
1968
1982
|
api: ApiConfig
|
|
1969
1983
|
/** The schema definition for the current component. */
|
|
@@ -1972,8 +1986,16 @@ export type DitoContext<$Item = any> = {
|
|
|
1972
1986
|
views: Record<string, View>
|
|
1973
1987
|
/** All views flattened into a single record. */
|
|
1974
1988
|
flattenedViews: Record<string, ViewSchema>
|
|
1975
|
-
/**
|
|
1989
|
+
/**
|
|
1990
|
+
* Display label of the current item, as HTML with
|
|
1991
|
+
* escaped item values.
|
|
1992
|
+
*/
|
|
1976
1993
|
itemLabel: string | null
|
|
1994
|
+
/**
|
|
1995
|
+
* The text of `itemLabel`, with its item values
|
|
1996
|
+
* unescaped.
|
|
1997
|
+
*/
|
|
1998
|
+
itemLabelText: string | null
|
|
1977
1999
|
/** Display label of the current form. */
|
|
1978
2000
|
formLabel: string | null
|
|
1979
2001
|
/** The current Vue component instance. */
|
|
@@ -2071,25 +2093,55 @@ export type DitoContext<$Item = any> = {
|
|
|
2071
2093
|
}
|
|
2072
2094
|
/** Returns the full URL for a given resource. */
|
|
2073
2095
|
getResourceUrl(resource: Resource): string
|
|
2074
|
-
/**
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2096
|
+
/**
|
|
2097
|
+
* Displays a notification to the user, with the
|
|
2098
|
+
* paragraphs of either `text` or `html`.
|
|
2099
|
+
*/
|
|
2100
|
+
notify(
|
|
2101
|
+
options: {
|
|
2102
|
+
/** Notification style. @defaultValue `'info'` */
|
|
2103
|
+
type?: LiteralUnion<'warning' | 'error' | 'info' | 'success'>
|
|
2104
|
+
/**
|
|
2105
|
+
* Heading text, escaped like `text`. Defaults to
|
|
2106
|
+
* a label matching `type`.
|
|
2107
|
+
*/
|
|
2108
|
+
title?: string
|
|
2109
|
+
/**
|
|
2110
|
+
* The original error object. When `type` is
|
|
2111
|
+
* `'error'`, this is logged to the console
|
|
2112
|
+
* alongside the message.
|
|
2113
|
+
*/
|
|
2114
|
+
error?: unknown
|
|
2115
|
+
/**
|
|
2116
|
+
* Display duration in milliseconds. When omitted,
|
|
2117
|
+
* calculated automatically from content length.
|
|
2118
|
+
* Use `0` for sticky.
|
|
2119
|
+
*/
|
|
2120
|
+
duration?: number
|
|
2121
|
+
} & (
|
|
2122
|
+
| {
|
|
2123
|
+
/**
|
|
2124
|
+
* Body text, escaped as plain text. Each entry
|
|
2125
|
+
* is a paragraph, newlines become `<br>`, and
|
|
2126
|
+
* `false` and `null` entries are skipped.
|
|
2127
|
+
*/
|
|
2128
|
+
text: OrArrayOf<string | false | null>
|
|
2129
|
+
html?: never
|
|
2130
|
+
}
|
|
2131
|
+
| {
|
|
2132
|
+
/**
|
|
2133
|
+
* Body HTML from trusted sources, rendered as
|
|
2134
|
+
* it is. Every interpolated value must be
|
|
2135
|
+
* escaped with `escapeHtml()` from
|
|
2136
|
+
* `@ditojs/utils`. Each entry is a paragraph,
|
|
2137
|
+
* newlines become `<br>`, and `false` and
|
|
2138
|
+
* `null` entries are skipped.
|
|
2139
|
+
*/
|
|
2140
|
+
html: OrArrayOf<string | false | null>
|
|
2141
|
+
text?: never
|
|
2142
|
+
}
|
|
2143
|
+
)
|
|
2144
|
+
): void
|
|
2093
2145
|
}
|
|
2094
2146
|
|
|
2095
2147
|
/**
|
|
@@ -2169,16 +2221,6 @@ export interface DitoComponentInstanceBase<$Item = any>
|
|
|
2169
2221
|
errors: string[] | null
|
|
2170
2222
|
/** Whether the field has validation errors. */
|
|
2171
2223
|
hasErrors: boolean
|
|
2172
|
-
/** Whether async data is currently being loaded. */
|
|
2173
|
-
isLoading: boolean
|
|
2174
|
-
/**
|
|
2175
|
-
* Sets the loading state. Optionally propagates
|
|
2176
|
-
* to the root or view component.
|
|
2177
|
-
*/
|
|
2178
|
-
setLoading(
|
|
2179
|
-
isLoading: boolean,
|
|
2180
|
-
options?: { updateRoot?: boolean; updateView?: boolean }
|
|
2181
|
-
): void
|
|
2182
2224
|
/**
|
|
2183
2225
|
* Whether the component works with data not yet
|
|
2184
2226
|
* persisted to the server.
|
|
@@ -2256,7 +2298,7 @@ export interface DitoComponentInstanceBase<$Item = any>
|
|
|
2256
2298
|
/** The parent form component. */
|
|
2257
2299
|
parentFormComponent: DitoComponentInstanceBase | null
|
|
2258
2300
|
/** The root component instance. */
|
|
2259
|
-
rootComponent:
|
|
2301
|
+
rootComponent: DitoRootInstance | null
|
|
2260
2302
|
/** The nearest ancestor tab component. */
|
|
2261
2303
|
tabComponent: DitoComponentInstanceBase | null
|
|
2262
2304
|
/** The parent route component. */
|
|
@@ -2485,8 +2527,63 @@ export interface EmitterMixin {
|
|
|
2485
2527
|
): this
|
|
2486
2528
|
}
|
|
2487
2529
|
|
|
2530
|
+
/**
|
|
2531
|
+
* Counts the pending operations of a scope, e.g. the
|
|
2532
|
+
* requests of a resource component, of all components
|
|
2533
|
+
* in a view, or of the whole admin, and forwards them
|
|
2534
|
+
* to the tracker of the enclosing scope.
|
|
2535
|
+
*/
|
|
2536
|
+
export interface LoadingTracker {
|
|
2537
|
+
/** The tracker of the enclosing scope, if any. */
|
|
2538
|
+
readonly parent: LoadingTracker | null
|
|
2539
|
+
/** The tracker of the outermost scope. */
|
|
2540
|
+
readonly root: LoadingTracker
|
|
2541
|
+
/** Whether any operation within the scope is pending. */
|
|
2542
|
+
readonly isLoading: boolean
|
|
2543
|
+
/**
|
|
2544
|
+
* Begins an operation in this scope and its enclosing
|
|
2545
|
+
* ones, and returns the function that ends it.
|
|
2546
|
+
*/
|
|
2547
|
+
begin(): () => void
|
|
2548
|
+
/**
|
|
2549
|
+
* Tracks the operation that `callback()` performs until
|
|
2550
|
+
* its returned promise settles, and returns its result.
|
|
2551
|
+
*/
|
|
2552
|
+
track<T>(callback: () => T | Promise<T>): Promise<T>
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
/**
|
|
2556
|
+
* The members of components that load and submit their
|
|
2557
|
+
* data through API resources (ResourceMixin): forms and
|
|
2558
|
+
* sources.
|
|
2559
|
+
*/
|
|
2560
|
+
export interface DitoResourceInstance {
|
|
2561
|
+
/**
|
|
2562
|
+
* Whether any request or other operation within this
|
|
2563
|
+
* component's scope is pending, as tracked by its
|
|
2564
|
+
* `LoadingTracker`.
|
|
2565
|
+
*/
|
|
2566
|
+
readonly isLoading: boolean
|
|
2567
|
+
/** Tracks the pending requests of the component. */
|
|
2568
|
+
readonly loadingTracker: LoadingTracker
|
|
2569
|
+
/**
|
|
2570
|
+
* Begins or ends a loading operation of the component.
|
|
2571
|
+
* The options are ignored, since the operations are
|
|
2572
|
+
* always forwarded to the enclosing view and the root.
|
|
2573
|
+
*
|
|
2574
|
+
* @deprecated Use `loadingTracker.begin()` and call the
|
|
2575
|
+
* function that it returns, or
|
|
2576
|
+
* `loadingTracker.track(callback)` instead.
|
|
2577
|
+
*/
|
|
2578
|
+
setLoading(
|
|
2579
|
+
isLoading: boolean,
|
|
2580
|
+
options?: { updateRoot?: boolean; updateView?: boolean }
|
|
2581
|
+
): void
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2488
2584
|
export interface DitoFormInstance<$Item = any>
|
|
2489
|
-
extends DitoComponentInstanceBase<$Item
|
|
2585
|
+
extends DitoComponentInstanceBase<$Item>,
|
|
2586
|
+
DitoResourceInstance {
|
|
2490
2587
|
/**
|
|
2491
2588
|
* Whether this form is creating a new item
|
|
2492
2589
|
* (`true`) or editing an existing one (`false`).
|
|
@@ -2645,10 +2742,38 @@ export interface DitoFormInstance<$Item = any>
|
|
|
2645
2742
|
getChildPath(path: string): string
|
|
2646
2743
|
}
|
|
2647
2744
|
|
|
2745
|
+
/** The root component of the admin (DitoRoot). */
|
|
2746
|
+
export interface DitoRootInstance extends DitoComponentInstanceBase {
|
|
2747
|
+
/**
|
|
2748
|
+
* Whether any request or other operation within the
|
|
2749
|
+
* admin is pending, as tracked by its `LoadingTracker`.
|
|
2750
|
+
*/
|
|
2751
|
+
readonly isLoading: boolean
|
|
2752
|
+
/** Tracks the pending requests of the whole admin. */
|
|
2753
|
+
readonly loadingTracker: LoadingTracker
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2648
2756
|
export interface DitoViewInstance<$Item = any>
|
|
2649
2757
|
extends DitoComponentInstanceBase<$Item> {
|
|
2650
2758
|
/** Always `true` for view components. */
|
|
2651
2759
|
isView: true
|
|
2760
|
+
/**
|
|
2761
|
+
* Whether any request or other operation within the
|
|
2762
|
+
* view, e.g. of its components, is pending, as
|
|
2763
|
+
* tracked by its `LoadingTracker`. The view is
|
|
2764
|
+
* disabled while it is loading.
|
|
2765
|
+
*/
|
|
2766
|
+
readonly isLoading: boolean
|
|
2767
|
+
/** Tracks the pending requests of the view's components. */
|
|
2768
|
+
readonly loadingTracker: LoadingTracker
|
|
2769
|
+
/**
|
|
2770
|
+
* Begins or ends a loading operation of the view.
|
|
2771
|
+
*
|
|
2772
|
+
* @deprecated Use `loadingTracker.begin()` and call the
|
|
2773
|
+
* function that it returns, or
|
|
2774
|
+
* `loadingTracker.track(callback)` instead.
|
|
2775
|
+
*/
|
|
2776
|
+
setLoading(isLoading: boolean): void
|
|
2652
2777
|
|
|
2653
2778
|
// -- Route (RouteMixin) --
|
|
2654
2779
|
|
|
@@ -2767,7 +2892,8 @@ export interface DitoSchemaInstance<$Item = any>
|
|
|
2767
2892
|
}
|
|
2768
2893
|
|
|
2769
2894
|
export interface DitoSourceInstance<$Item = any>
|
|
2770
|
-
extends DitoComponentInstanceBase<$Item
|
|
2895
|
+
extends DitoComponentInstanceBase<$Item>,
|
|
2896
|
+
DitoResourceInstance {
|
|
2771
2897
|
// -- Data access (SourceMixin) --
|
|
2772
2898
|
|
|
2773
2899
|
/** The list data array (getter/setter). */
|
|
@@ -3465,6 +3591,7 @@ export type SchemaByType<$Item = any> = {
|
|
|
3465
3591
|
'url': InputSchema<$Item>
|
|
3466
3592
|
'label': LabelSchema<$Item>
|
|
3467
3593
|
'section': SectionSchema<$Item>
|
|
3594
|
+
'panel': PanelSchema<$Item>
|
|
3468
3595
|
'hidden': HiddenSchema<$Item>
|
|
3469
3596
|
'unknown': never
|
|
3470
3597
|
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// @vue/component
|
|
2
|
-
export default {
|
|
3
|
-
data() {
|
|
4
|
-
return {
|
|
5
|
-
isLoading: false
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
methods: {
|
|
10
|
-
setLoading(isLoading, { updateRoot = false, updateView = false } = {}) {
|
|
11
|
-
if (!this.isLoading ^ !isLoading) {
|
|
12
|
-
// Boolean xor
|
|
13
|
-
this.isLoading = !!isLoading
|
|
14
|
-
if (updateRoot) {
|
|
15
|
-
this.rootComponent.registerLoading(isLoading)
|
|
16
|
-
}
|
|
17
|
-
if (updateView) {
|
|
18
|
-
this.viewComponent.setLoading(this.isLoading)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|