@ditojs/admin 2.4.0 → 2.4.2
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.es.js +2043 -1811
- package/dist/dito-admin.umd.js +5 -4
- package/dist/style.css +1 -1
- package/package.json +5 -5
- package/src/components/DitoContainer.vue +1 -6
- package/src/components/DitoErrors.vue +52 -4
- package/src/components/DitoHeader.vue +1 -1
- package/src/components/DitoPane.vue +1 -1
- package/src/components/DitoRoot.vue +24 -6
- package/src/components/DitoSchema.vue +22 -3
- package/src/components/DitoUploadFile.vue +198 -0
- package/src/components/index.js +1 -0
- package/src/mixins/DitoMixin.js +24 -5
- package/src/mixins/TypeMixin.js +21 -13
- package/src/mixins/ValidationMixin.js +9 -1
- package/src/mixins/ValidatorMixin.js +1 -5
- package/src/styles/_info.scss +0 -16
- package/src/styles/_tippy.scss +39 -0
- package/src/styles/style.scss +2 -1
- package/src/types/DitoTypeButton.vue +1 -0
- package/src/types/DitoTypeCheckbox.vue +1 -0
- package/src/types/DitoTypeComputed.vue +1 -0
- package/src/types/DitoTypeList.vue +2 -2
- package/src/types/DitoTypeMarkup.vue +6 -3
- package/src/types/DitoTypeNumber.vue +1 -0
- package/src/types/DitoTypeProgress.vue +1 -0
- package/src/types/DitoTypeSwitch.vue +1 -0
- package/src/types/DitoTypeText.vue +1 -0
- package/src/types/DitoTypeTextarea.vue +1 -0
- package/src/types/DitoTypeUpload.vue +196 -77
- package/src/utils/schema.js +1 -1
- package/src/utils/uid.js +7 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/admin",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/master/packages/admin",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"not ie_mob > 0"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ditojs/ui": "^2.4.
|
|
37
|
-
"@ditojs/utils": "^2.4.
|
|
36
|
+
"@ditojs/ui": "^2.4.1",
|
|
37
|
+
"@ditojs/utils": "^2.4.1",
|
|
38
38
|
"@kyvg/vue3-notification": "^2.9.0",
|
|
39
39
|
"@lk77/vue3-color": "^3.0.6",
|
|
40
40
|
"@tiptap/core": "^2.0.3",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"vue-upload-component": "^3.1.8"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@ditojs/build": "^2.4.
|
|
77
|
+
"@ditojs/build": "^2.4.1",
|
|
78
78
|
"@vitejs/plugin-vue": "^4.1.0",
|
|
79
79
|
"@vue/compiler-sfc": "^3.2.47",
|
|
80
80
|
"pug": "^3.0.2",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"vite": "^4.3.1"
|
|
84
84
|
},
|
|
85
85
|
"types": "types",
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "3041bd1b47272ac99276fbeca8152cbfb1d75b77",
|
|
87
87
|
"scripts": {
|
|
88
88
|
"build": "vite build",
|
|
89
89
|
"watch": "yarn build --mode 'development' --watch",
|
|
@@ -216,6 +216,7 @@ export default DitoComponent.component('DitoContainer', {
|
|
|
216
216
|
.dito-container {
|
|
217
217
|
$self: &;
|
|
218
218
|
|
|
219
|
+
position: relative;
|
|
219
220
|
display: flex;
|
|
220
221
|
flex-flow: column;
|
|
221
222
|
align-items: flex-start;
|
|
@@ -260,12 +261,6 @@ export default DitoComponent.component('DitoContainer', {
|
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
263
|
|
|
263
|
-
&--drop-target {
|
|
264
|
-
background: $content-color-background;
|
|
265
|
-
border-radius: $border-radius;
|
|
266
|
-
z-index: $drag-overlay-z-index + 1;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
264
|
&--omit-padding {
|
|
270
265
|
padding: 0;
|
|
271
266
|
|
|
@@ -5,17 +5,66 @@
|
|
|
5
5
|
ul
|
|
6
6
|
li(
|
|
7
7
|
v-for="error of errors"
|
|
8
|
-
)
|
|
9
|
-
| {{ error }}
|
|
8
|
+
) {{ error }}
|
|
10
9
|
</template>
|
|
11
10
|
|
|
12
11
|
<script>
|
|
12
|
+
import tippy from 'tippy.js'
|
|
13
13
|
import DitoComponent from '../DitoComponent.js'
|
|
14
|
+
import { markRaw } from 'vue'
|
|
14
15
|
|
|
15
16
|
// @vue/component
|
|
16
17
|
export default DitoComponent.component('DitoErrors', {
|
|
17
18
|
props: {
|
|
18
19
|
errors: { type: Array, default: null }
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
data() {
|
|
23
|
+
return {
|
|
24
|
+
tip: null
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
watch: {
|
|
29
|
+
errors() {
|
|
30
|
+
this.$nextTick(this.updateErrors)
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
unmounted() {
|
|
35
|
+
this.tip?.destroy()
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
methods: {
|
|
39
|
+
updateErrors() {
|
|
40
|
+
let { tip } = this
|
|
41
|
+
tip?.hide()
|
|
42
|
+
if (this.errors) {
|
|
43
|
+
tip = this.tip ??= markRaw(tippy(this.$el.closest('.dito-container')))
|
|
44
|
+
tip.setProps({
|
|
45
|
+
content: this.errors.join('\n'),
|
|
46
|
+
theme: 'error',
|
|
47
|
+
trigger: 'manual',
|
|
48
|
+
appendTo: 'parent',
|
|
49
|
+
placement: 'bottom-start',
|
|
50
|
+
animation: 'shift-away-subtle',
|
|
51
|
+
popperOptions: {
|
|
52
|
+
modifiers: [
|
|
53
|
+
{
|
|
54
|
+
name: 'flip',
|
|
55
|
+
enabled: false
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
interactive: true,
|
|
60
|
+
hideOnClick: false,
|
|
61
|
+
offset: [3, 3], // 1/2 form-spacing
|
|
62
|
+
zIndex: 1
|
|
63
|
+
})
|
|
64
|
+
tip.popper.addEventListener('mousedown', () => tip.hide())
|
|
65
|
+
tip.show()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
19
68
|
}
|
|
20
69
|
})
|
|
21
70
|
</script>
|
|
@@ -25,10 +74,9 @@ export default DitoComponent.component('DitoErrors', {
|
|
|
25
74
|
|
|
26
75
|
.dito-errors {
|
|
27
76
|
position: absolute;
|
|
28
|
-
|
|
77
|
+
opacity: 0;
|
|
29
78
|
|
|
30
79
|
ul {
|
|
31
|
-
margin-top: 1px;
|
|
32
80
|
color: $color-error;
|
|
33
81
|
}
|
|
34
82
|
}
|
|
@@ -151,10 +151,9 @@ export default DitoComponent.component('DitoRoot', {
|
|
|
151
151
|
|
|
152
152
|
const toggleDropTargetClass = enabled => {
|
|
153
153
|
for (const upload of uploads) {
|
|
154
|
-
upload
|
|
155
|
-
'dito-container
|
|
156
|
-
enabled
|
|
157
|
-
)
|
|
154
|
+
upload
|
|
155
|
+
.closest('.dito-container')
|
|
156
|
+
.classList.toggle('dito-drop-target', enabled)
|
|
158
157
|
}
|
|
159
158
|
if (!enabled) {
|
|
160
159
|
uploads = []
|
|
@@ -166,7 +165,7 @@ export default DitoComponent.component('DitoRoot', {
|
|
|
166
165
|
if (enabled) {
|
|
167
166
|
toggleDropTargetClass(true)
|
|
168
167
|
} else {
|
|
169
|
-
setTimeout(() => toggleDropTargetClass(false),
|
|
168
|
+
setTimeout(() => toggleDropTargetClass(false), 150)
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
171
|
|
|
@@ -486,14 +485,33 @@ function addRoutes(router, routes) {
|
|
|
486
485
|
backdrop-filter: blur(8px);
|
|
487
486
|
}
|
|
488
487
|
|
|
488
|
+
.dito-drop-target {
|
|
489
|
+
--shadow-alpha: 0.25;
|
|
490
|
+
|
|
491
|
+
background: $content-color-background;
|
|
492
|
+
border-radius: $border-radius;
|
|
493
|
+
z-index: $drag-overlay-z-index + 1;
|
|
494
|
+
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, var(--shadow-alpha)));
|
|
495
|
+
}
|
|
496
|
+
|
|
489
497
|
.dito-drag-enter-active,
|
|
490
498
|
.dito-drag-leave-active {
|
|
491
|
-
|
|
499
|
+
$duration: 0.15s;
|
|
500
|
+
|
|
501
|
+
transition: opacity $duration, backdrop-filter $duration;
|
|
502
|
+
|
|
503
|
+
~ * .dito-drop-target {
|
|
504
|
+
transition: filter $duration;
|
|
505
|
+
}
|
|
492
506
|
}
|
|
493
507
|
|
|
494
508
|
.dito-drag-enter-from,
|
|
495
509
|
.dito-drag-leave-to {
|
|
496
510
|
opacity: 0;
|
|
497
511
|
backdrop-filter: blur(0);
|
|
512
|
+
|
|
513
|
+
~ * .dito-drop-target {
|
|
514
|
+
--shadow-alpha: 0;
|
|
515
|
+
}
|
|
498
516
|
}
|
|
499
517
|
</style>
|
|
@@ -3,7 +3,10 @@ slot(name="before")
|
|
|
3
3
|
.dito-schema(
|
|
4
4
|
v-bind="$attrs"
|
|
5
5
|
)
|
|
6
|
-
.dito-schema-content(
|
|
6
|
+
.dito-schema-content(
|
|
7
|
+
ref="content"
|
|
8
|
+
:class="{ 'dito-scroll': scrollable }"
|
|
9
|
+
)
|
|
7
10
|
Teleport(
|
|
8
11
|
to=".dito-header__menu"
|
|
9
12
|
:disabled="!headerInMenu"
|
|
@@ -280,6 +283,10 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
280
283
|
return this.everyComponent(it => it.isValidated)
|
|
281
284
|
},
|
|
282
285
|
|
|
286
|
+
hasErrors() {
|
|
287
|
+
return this.someComponent(it => it.hasErrors)
|
|
288
|
+
},
|
|
289
|
+
|
|
283
290
|
hasData() {
|
|
284
291
|
return !!this.data
|
|
285
292
|
},
|
|
@@ -334,6 +341,9 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
334
341
|
handler(hash) {
|
|
335
342
|
if (this.hasTabs) {
|
|
336
343
|
this.currentTab = hash?.slice(1) || null
|
|
344
|
+
if (this.hasErrors) {
|
|
345
|
+
this.repositionErrors()
|
|
346
|
+
}
|
|
337
347
|
}
|
|
338
348
|
}
|
|
339
349
|
},
|
|
@@ -453,9 +463,18 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
453
463
|
}
|
|
454
464
|
},
|
|
455
465
|
|
|
466
|
+
repositionErrors() {
|
|
467
|
+
// Force repositioning of error tooltips, as otherwise they
|
|
468
|
+
// sometimes don't show up in the right place initially when
|
|
469
|
+
// changing tabs
|
|
470
|
+
const scrollParent = this.$refs.content.closest('.dito-scroll')
|
|
471
|
+
scrollParent.scrollTop++
|
|
472
|
+
scrollParent.scrollTop--
|
|
473
|
+
},
|
|
474
|
+
|
|
456
475
|
focus() {
|
|
457
|
-
this.parentSchemaComponent?.focus()
|
|
458
476
|
this.opened = true
|
|
477
|
+
return this.parentSchemaComponent?.focus()
|
|
459
478
|
},
|
|
460
479
|
|
|
461
480
|
validateAll(match, notify = true) {
|
|
@@ -485,7 +504,7 @@ export default DitoComponent.component('DitoSchema', {
|
|
|
485
504
|
if (!component.validate(notify)) {
|
|
486
505
|
// Focus first error field
|
|
487
506
|
if (notify && first) {
|
|
488
|
-
component.
|
|
507
|
+
component.scrollIntoView()
|
|
489
508
|
}
|
|
490
509
|
first = false
|
|
491
510
|
isValid = false
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.dito-upload-file
|
|
3
|
+
.dito-thumbnail(
|
|
4
|
+
v-if="thumbnail"
|
|
5
|
+
:class="`dito-thumbnail--${thumbnail}`"
|
|
6
|
+
)
|
|
7
|
+
.dito-thumbnail__inner
|
|
8
|
+
img(
|
|
9
|
+
v-if="source"
|
|
10
|
+
:src="source"
|
|
11
|
+
crossorigin="anonymous"
|
|
12
|
+
)
|
|
13
|
+
.dito-thumbnail__type(
|
|
14
|
+
v-else
|
|
15
|
+
)
|
|
16
|
+
span {{ type }}
|
|
17
|
+
span {{ file.name }}
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
import DitoComponent from '../DitoComponent.js'
|
|
22
|
+
|
|
23
|
+
// @vue/component
|
|
24
|
+
export default DitoComponent.component('DitoUploadFile', {
|
|
25
|
+
props: {
|
|
26
|
+
file: { type: Object, required: true },
|
|
27
|
+
thumbnail: { type: String, default: null },
|
|
28
|
+
thumbnailUrl: { type: String, default: null }
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
data() {
|
|
32
|
+
return {
|
|
33
|
+
uploadUrl: null
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
computed: {
|
|
38
|
+
type() {
|
|
39
|
+
return (
|
|
40
|
+
TYPES[this.file.type] ||
|
|
41
|
+
this.file.name.split('.').pop().toUpperCase()
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
source() {
|
|
46
|
+
return this.uploadUrl || this.thumbnailUrl
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
watch: {
|
|
51
|
+
'file.upload.file': {
|
|
52
|
+
immediate: true,
|
|
53
|
+
handler(file) {
|
|
54
|
+
if (file && this.thumbnail) {
|
|
55
|
+
const reader = new FileReader()
|
|
56
|
+
reader.onload = () => {
|
|
57
|
+
this.uploadUrl = reader.result
|
|
58
|
+
}
|
|
59
|
+
reader.readAsDataURL(file)
|
|
60
|
+
} else {
|
|
61
|
+
this.uploadUrl = null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const TYPES = {
|
|
69
|
+
'text/plain': 'TXT',
|
|
70
|
+
'text/html': 'HTML',
|
|
71
|
+
'text/css': 'CSS',
|
|
72
|
+
'text/javascript': 'JS',
|
|
73
|
+
'image/jpeg': 'JPG',
|
|
74
|
+
'image/png': 'PNG',
|
|
75
|
+
'image/gif': 'GIF',
|
|
76
|
+
'image/svg+xml': 'SVG',
|
|
77
|
+
'movie/mp4': 'MP4',
|
|
78
|
+
'audio/mpeg': 'MP3',
|
|
79
|
+
'application/json': 'JSON',
|
|
80
|
+
'application/xml': 'XML',
|
|
81
|
+
'application/pdf': 'PDF',
|
|
82
|
+
'application/zip': 'ZIP'
|
|
83
|
+
}
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<style lang="scss">
|
|
87
|
+
@use 'sass:math';
|
|
88
|
+
@import '../styles/_imports';
|
|
89
|
+
|
|
90
|
+
.dito-upload-file {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: flex-start;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dito-thumbnail {
|
|
97
|
+
$self: &;
|
|
98
|
+
|
|
99
|
+
// Small size by default
|
|
100
|
+
--max-size: #{1 * $input-height};
|
|
101
|
+
--corner-size: calc(var(--max-size) / 5);
|
|
102
|
+
--shadow-size: 1px;
|
|
103
|
+
--min-size: calc(2 * var(--corner-size));
|
|
104
|
+
--margin: 0em;
|
|
105
|
+
--drop-shadow: drop-shadow(
|
|
106
|
+
0 calc(var(--shadow-size) * 0.75) var(--shadow-size) #{rgba(
|
|
107
|
+
$color-black,
|
|
108
|
+
0.4
|
|
109
|
+
)}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
position: relative;
|
|
113
|
+
margin: var(--margin);
|
|
114
|
+
margin-right: 0.5em;
|
|
115
|
+
filter: var(--drop-shadow);
|
|
116
|
+
|
|
117
|
+
&--small {
|
|
118
|
+
--max-size: #{1 * $input-height};
|
|
119
|
+
--margin: 0em;
|
|
120
|
+
--shadow-size: 1px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&--medium {
|
|
124
|
+
--max-size: #{2 * $input-height};
|
|
125
|
+
--margin: 0.25em;
|
|
126
|
+
--shadow-size: 1.5px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
&--large {
|
|
130
|
+
--max-size: #{4 * $input-height};
|
|
131
|
+
--margin: 0.5em;
|
|
132
|
+
--shadow-size: 2.5px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&__inner {
|
|
136
|
+
background: #ffffff;
|
|
137
|
+
clip-path: polygon(
|
|
138
|
+
0 0,
|
|
139
|
+
calc(100% - var(--corner-size)) 0,
|
|
140
|
+
100% var(--corner-size),
|
|
141
|
+
100% 100%,
|
|
142
|
+
0 100%
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
&::after {
|
|
146
|
+
content: '';
|
|
147
|
+
position: absolute;
|
|
148
|
+
top: 0;
|
|
149
|
+
right: 0;
|
|
150
|
+
width: var(--corner-size);
|
|
151
|
+
height: var(--corner-size);
|
|
152
|
+
background: linear-gradient(45deg, #ffffff, #eeeeee 40%, #dddddd 50%);
|
|
153
|
+
filter: var(--drop-shadow);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
&__type {
|
|
158
|
+
--font-size: var(--corner-size);
|
|
159
|
+
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
min-width: var(--min-size);
|
|
164
|
+
min-height: var(--max-size);
|
|
165
|
+
aspect-ratio: 3 / 4;
|
|
166
|
+
|
|
167
|
+
span {
|
|
168
|
+
--color: #{$color-grey};
|
|
169
|
+
|
|
170
|
+
font-size: min(var(--font-size), #{1.25 * $font-size});
|
|
171
|
+
color: var(--color);
|
|
172
|
+
|
|
173
|
+
#{$self}:not(#{$self}--small) & {
|
|
174
|
+
padding: 0 calc(var(--font-size) / 4);
|
|
175
|
+
border-radius: calc(var(--font-size) / 4);
|
|
176
|
+
background: var(--color);
|
|
177
|
+
color: #ffffff;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#{$self}--medium & {
|
|
181
|
+
--color: #{$color-light};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#{$self}--large & {
|
|
185
|
+
--color: #{$color-lighter};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
img {
|
|
191
|
+
display: block;
|
|
192
|
+
min-width: var(--min-size);
|
|
193
|
+
min-height: var(--min-size);
|
|
194
|
+
max-width: var(--max-size);
|
|
195
|
+
max-height: var(--max-size);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
</style>
|
package/src/components/index.js
CHANGED
|
@@ -31,5 +31,6 @@ export { default as DitoPagination } from './DitoPagination.vue'
|
|
|
31
31
|
export { default as DitoTreeItem } from './DitoTreeItem.vue'
|
|
32
32
|
export { default as DitoTableHead } from './DitoTableHead.vue'
|
|
33
33
|
export { default as DitoTableCell } from './DitoTableCell.vue'
|
|
34
|
+
export { default as DitoUploadFile } from './DitoUploadFile.vue'
|
|
34
35
|
export { default as DitoDraggable } from './DitoDraggable.vue'
|
|
35
36
|
export { default as DitoVNode } from './DitoVNode.vue'
|
package/src/mixins/DitoMixin.js
CHANGED
|
@@ -192,19 +192,36 @@ export default {
|
|
|
192
192
|
return value
|
|
193
193
|
},
|
|
194
194
|
|
|
195
|
-
getChildStore(key) {
|
|
196
|
-
|
|
195
|
+
getChildStore(key, index) {
|
|
196
|
+
// When storing, temporary ids change to permanent ones and thus the key
|
|
197
|
+
// can change, so we need to store the index as well, to be able to find
|
|
198
|
+
// the store again after the item was saved.
|
|
199
|
+
const store = (
|
|
200
|
+
this.getStore(key) ||
|
|
201
|
+
index != null && this.getStore(index) ||
|
|
202
|
+
this.setStore(key, reactive({}))
|
|
203
|
+
)
|
|
204
|
+
if (index != null) {
|
|
205
|
+
this.setStore(index, store)
|
|
206
|
+
}
|
|
207
|
+
return store
|
|
197
208
|
},
|
|
198
209
|
|
|
199
210
|
getSchemaValue(
|
|
200
211
|
keyOrDataPath,
|
|
201
|
-
{
|
|
212
|
+
{
|
|
213
|
+
type,
|
|
214
|
+
default: def,
|
|
215
|
+
schema = this.schema,
|
|
216
|
+
context = this.context,
|
|
217
|
+
callback = true
|
|
218
|
+
} = {}
|
|
202
219
|
) {
|
|
203
220
|
return getSchemaValue(keyOrDataPath, {
|
|
204
221
|
type,
|
|
205
222
|
schema,
|
|
223
|
+
context,
|
|
206
224
|
callback,
|
|
207
|
-
context: this.context,
|
|
208
225
|
default: isFunction(def) ? () => def.call(this) : def
|
|
209
226
|
})
|
|
210
227
|
},
|
|
@@ -365,7 +382,9 @@ export default {
|
|
|
365
382
|
}
|
|
366
383
|
// See: https://stackoverflow.com/a/49917066/1163708
|
|
367
384
|
const a = document.createElement('a')
|
|
368
|
-
a.href =
|
|
385
|
+
a.href = options.url?.startsWith('blob:')
|
|
386
|
+
? options.url
|
|
387
|
+
: this.api.getApiUrl(options)
|
|
369
388
|
a.download = options.filename ?? null
|
|
370
389
|
const { body } = document
|
|
371
390
|
body.appendChild(a)
|
package/src/mixins/TypeMixin.js
CHANGED
|
@@ -3,7 +3,7 @@ import ValidationMixin from './ValidationMixin.js'
|
|
|
3
3
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
4
4
|
import { computeValue } from '../utils/schema.js'
|
|
5
5
|
import { getItem, getParentItem } from '../utils/data.js'
|
|
6
|
-
import {
|
|
6
|
+
import { camelize } from '@ditojs/utils'
|
|
7
7
|
|
|
8
8
|
// @vue/component
|
|
9
9
|
export default {
|
|
@@ -238,22 +238,30 @@ export default {
|
|
|
238
238
|
},
|
|
239
239
|
|
|
240
240
|
// @overridable
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
this
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (!element.focus || this.disabled) {
|
|
248
|
-
;(element.$el || element).scrollIntoView?.()
|
|
249
|
-
}
|
|
241
|
+
async scrollIntoView() {
|
|
242
|
+
await this.focusSchema()
|
|
243
|
+
const { element = this } = this.$refs
|
|
244
|
+
;(element.$el || element).scrollIntoView?.({
|
|
245
|
+
behavior: 'smooth',
|
|
246
|
+
block: 'center'
|
|
250
247
|
})
|
|
251
248
|
},
|
|
252
249
|
|
|
253
|
-
|
|
250
|
+
// @overridable
|
|
251
|
+
async focusElement() {
|
|
252
|
+
await this.focusSchema()
|
|
253
|
+
const { element = this } = this.$refs
|
|
254
|
+
;(element.$el || element).focus?.()
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
async focusSchema() {
|
|
254
258
|
// Also focus this component's schema and panel in case it's a tab.
|
|
255
|
-
this.schemaComponent.focus()
|
|
256
|
-
this.tabComponent?.focus()
|
|
259
|
+
await this.schemaComponent.focus()
|
|
260
|
+
await this.tabComponent?.focus()
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
focus() {
|
|
264
|
+
this.scrollIntoView()
|
|
257
265
|
this.focusElement()
|
|
258
266
|
},
|
|
259
267
|
|
|
@@ -15,6 +15,12 @@ export default {
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
|
|
18
|
+
computed: {
|
|
19
|
+
hasErrors() {
|
|
20
|
+
return !!this.errors
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
|
|
18
24
|
methods: {
|
|
19
25
|
resetValidation() {
|
|
20
26
|
this.isTouched = false
|
|
@@ -60,6 +66,8 @@ export default {
|
|
|
60
66
|
|
|
61
67
|
markTouched() {
|
|
62
68
|
this.isTouched = true
|
|
69
|
+
// Clear currently displayed errors when focusing input.
|
|
70
|
+
this.clearErrors()
|
|
63
71
|
},
|
|
64
72
|
|
|
65
73
|
markDirty() {
|
|
@@ -87,7 +95,7 @@ export default {
|
|
|
87
95
|
this.addError(message, true)
|
|
88
96
|
}
|
|
89
97
|
if (focus) {
|
|
90
|
-
this.
|
|
98
|
+
this.scrollIntoView()
|
|
91
99
|
}
|
|
92
100
|
return true
|
|
93
101
|
},
|
|
@@ -6,11 +6,7 @@ export default {
|
|
|
6
6
|
|
|
7
7
|
computed: {
|
|
8
8
|
errors() {
|
|
9
|
-
return this.schemaComponents.
|
|
10
|
-
(result, { errors }) =>
|
|
11
|
-
errors && result ? result.concat(errors) : errors,
|
|
12
|
-
null
|
|
13
|
-
)
|
|
9
|
+
return this.schemaComponents.flatMap(({ errors }) => errors || [])
|
|
14
10
|
},
|
|
15
11
|
|
|
16
12
|
isTouched() {
|
package/src/styles/_info.scss
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
@import 'tippy.js/animations/shift-away-subtle.css';
|
|
2
|
-
|
|
3
1
|
.dito-info {
|
|
4
2
|
--size: calc(1em * var(--line-height));
|
|
5
3
|
|
|
@@ -19,17 +17,3 @@
|
|
|
19
17
|
width: var(--size);
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
|
-
|
|
23
|
-
.tippy-box[data-theme~='info'] {
|
|
24
|
-
background-color: $color-active;
|
|
25
|
-
color: $color-white;
|
|
26
|
-
filter: drop-shadow(0 2px 4px $color-shadow);
|
|
27
|
-
|
|
28
|
-
> .tippy-arrow::before {
|
|
29
|
-
color: $color-active;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.tippy-content {
|
|
33
|
-
white-space: pre-line;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@use 'sass:color';
|
|
2
|
+
@import 'tippy.js/animations/shift-away-subtle.css';
|
|
3
|
+
|
|
4
|
+
.tippy-box {
|
|
5
|
+
&[data-theme] {
|
|
6
|
+
--color: #{$color-active};
|
|
7
|
+
|
|
8
|
+
font-size: unset;
|
|
9
|
+
line-height: unset;
|
|
10
|
+
background-color: var(--color);
|
|
11
|
+
color: $color-white;
|
|
12
|
+
filter: drop-shadow(0 2px 4px $color-shadow);
|
|
13
|
+
|
|
14
|
+
.tippy-content {
|
|
15
|
+
white-space: pre-line;
|
|
16
|
+
padding: ($input-padding-ver + 2 * $border-width)
|
|
17
|
+
($input-padding-hor + $border-width);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
> .tippy-arrow::before {
|
|
21
|
+
color: var(--color);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&[data-theme~='info'] {
|
|
26
|
+
--color: #{color.adjust($color-active, $lightness: 5%)};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&[data-theme~='error'] {
|
|
30
|
+
--color: #{color.adjust($color-error, $lightness: 5%)};
|
|
31
|
+
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
|
|
34
|
+
> .tippy-arrow {
|
|
35
|
+
transform: unset !important;
|
|
36
|
+
left: 16px !important;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|