js-kompo-utils 1.0.38 → 1.0.40
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.
|
@@ -32,13 +32,11 @@ export default {
|
|
|
32
32
|
mixins: [CKEditor, Translatable, CKEditorUtilities],
|
|
33
33
|
data() {
|
|
34
34
|
return {
|
|
35
|
-
allVariables: [],
|
|
36
35
|
variableToInsert: null,
|
|
37
36
|
editorInitialized: false
|
|
38
37
|
}
|
|
39
38
|
},
|
|
40
39
|
created() {
|
|
41
|
-
this.allVariables = this.$_config('variables')
|
|
42
40
|
this.$_vlOn("insertVariable", (payload) => {
|
|
43
41
|
if (!payload['uniqueid'] || this.$_config('uniqueId') == payload['uniqueid']) {
|
|
44
42
|
this.insertVariable(payload);
|
|
@@ -46,7 +44,10 @@ export default {
|
|
|
46
44
|
});
|
|
47
45
|
},
|
|
48
46
|
computed: {
|
|
49
|
-
|
|
47
|
+
// Backend may send null entries for conditionally hidden variable groups
|
|
48
|
+
allVariables() {
|
|
49
|
+
return Object.values(this.$_config('variables') || {}).filter(Boolean)
|
|
50
|
+
}
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
</script>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="vlTabs"
|
|
3
3
|
v-bind="$_layoutWrapperAttributes"
|
|
4
4
|
v-show="!$_hidden">
|
|
5
|
-
<ul role="tablist" class="flex">
|
|
5
|
+
<ul v-if="!hasDots" role="tablist" class="flex">
|
|
6
6
|
<li
|
|
7
7
|
v-for="(element, index) in elements"
|
|
8
8
|
:key="index"
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
@selectTab="selectTab(index)" />
|
|
19
19
|
</li>
|
|
20
20
|
</ul>
|
|
21
|
-
<div class="vlTabContent" :class="{ 'vlTabContent--swipeable': isSwipeable }">
|
|
21
|
+
<div ref="content" class="vlTabContent" :class="{ 'vlTabContent--swipeable': isSwipeable }" :style="contentStyle">
|
|
22
22
|
<div v-if="isSwipeable"
|
|
23
|
+
ref="track"
|
|
23
24
|
class="vlTabSwipeContainer"
|
|
24
25
|
:style="swipeContainerStyle"
|
|
25
26
|
@touchstart="onTouchStart"
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
v-for="(tab, index) in elements"
|
|
30
31
|
:key="index"
|
|
31
32
|
:activeTab="tabActive(index)"
|
|
32
|
-
:collapsed="!tabActive(index)"
|
|
33
|
+
:collapsed="hasDots ? tabCollapsedWhenIdle(index) : !tabActive(index)"
|
|
33
34
|
:swipeable="true"
|
|
34
35
|
v-bind="$_attributes(tab)"/>
|
|
35
36
|
</div>
|
|
@@ -41,6 +42,31 @@
|
|
|
41
42
|
v-bind="$_attributes(tab)"/>
|
|
42
43
|
</template>
|
|
43
44
|
</div>
|
|
45
|
+
<div v-if="hasDots && elements.length > 1" class="vlTabDots">
|
|
46
|
+
<div role="tablist" class="vlTabDots__list">
|
|
47
|
+
<button
|
|
48
|
+
v-for="(element, index) in elements"
|
|
49
|
+
:key="index"
|
|
50
|
+
type="button"
|
|
51
|
+
role="tab"
|
|
52
|
+
class="vlTabDot"
|
|
53
|
+
:class="{ 'vlTabDot--active': tabActive(index) }"
|
|
54
|
+
:aria-selected="tabActive(index) ? 'true' : 'false'"
|
|
55
|
+
:aria-label="element.label"
|
|
56
|
+
@click="selectTab(index)" />
|
|
57
|
+
</div>
|
|
58
|
+
<button
|
|
59
|
+
v-if="nextLabel"
|
|
60
|
+
v-show="activeTab < elements.length - 1"
|
|
61
|
+
type="button"
|
|
62
|
+
class="vlTabDots__next"
|
|
63
|
+
@click="moveTab(1)">
|
|
64
|
+
{{ nextLabel }}
|
|
65
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
66
|
+
<path d="M8.91 19.92L15.43 13.4C16.2 12.63 16.2 11.37 15.43 10.6L8.91 4.08"/>
|
|
67
|
+
</svg>
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
44
70
|
</div>
|
|
45
71
|
</template>
|
|
46
72
|
|
|
@@ -57,6 +83,10 @@ export default {
|
|
|
57
83
|
swipeOffset: 0,
|
|
58
84
|
isSwiping: false,
|
|
59
85
|
isAnimating: false,
|
|
86
|
+
// Dots mode: the page sliding out, and the content height pinned during a change
|
|
87
|
+
previousTab: null,
|
|
88
|
+
contentHeight: null,
|
|
89
|
+
heightTimer: null,
|
|
60
90
|
startX: 0,
|
|
61
91
|
startY: 0,
|
|
62
92
|
directionLocked: false,
|
|
@@ -67,7 +97,9 @@ export default {
|
|
|
67
97
|
return this.$_config('activeTab')
|
|
68
98
|
},
|
|
69
99
|
tabParamKey(){
|
|
70
|
-
|
|
100
|
+
var key = this.$_config('tabParamKey')
|
|
101
|
+
|
|
102
|
+
return key === false ? null : (key || 'tab_number')
|
|
71
103
|
},
|
|
72
104
|
selectId(){
|
|
73
105
|
return this.$_config('selectId')
|
|
@@ -78,13 +110,31 @@ export default {
|
|
|
78
110
|
isSwipeable(){
|
|
79
111
|
return !!this.$_config('swipeable')
|
|
80
112
|
},
|
|
113
|
+
hasDots(){
|
|
114
|
+
return !!this.$_config('dots')
|
|
115
|
+
},
|
|
116
|
+
nextLabel(){
|
|
117
|
+
return this.$_config('nextLabel')
|
|
118
|
+
},
|
|
119
|
+
hasArrowKeys(){
|
|
120
|
+
return !!this.$_config('arrowKeys')
|
|
121
|
+
},
|
|
122
|
+
contentStyle(){
|
|
123
|
+
if (this.contentHeight === null) return {}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
height: this.contentHeight + 'px',
|
|
127
|
+
transition: 'height 0.3s ease',
|
|
128
|
+
}
|
|
129
|
+
},
|
|
81
130
|
isMobile(){
|
|
82
131
|
return typeof window !== 'undefined' && window.matchMedia('(max-width: 1023px)').matches
|
|
83
132
|
},
|
|
84
133
|
swipeContainerStyle(){
|
|
85
134
|
var baseOffset = -this.activeTab * 100
|
|
86
135
|
|
|
87
|
-
|
|
136
|
+
// Dots have no labels to click, so the slide is the only cue that the page changed.
|
|
137
|
+
if (!this.isMobile && !this.hasDots) {
|
|
88
138
|
return {
|
|
89
139
|
transform: 'translateX(' + baseOffset + '%)',
|
|
90
140
|
}
|
|
@@ -120,6 +170,7 @@ export default {
|
|
|
120
170
|
this.isAnimating = true
|
|
121
171
|
}
|
|
122
172
|
|
|
173
|
+
this.animateHeightFrom(this.activeTab)
|
|
123
174
|
this.activeTab = index
|
|
124
175
|
this.persistActiveTab()
|
|
125
176
|
this.syncSelect()
|
|
@@ -144,6 +195,60 @@ export default {
|
|
|
144
195
|
tabDisabled(index) {
|
|
145
196
|
return this.elements[index].isDisabled
|
|
146
197
|
},
|
|
198
|
+
// Only the pages on screen are expanded: the outgoing one while it slides away, neighbours while dragging.
|
|
199
|
+
tabCollapsedWhenIdle(index) {
|
|
200
|
+
if (index === this.activeTab) return false
|
|
201
|
+
if (this.isSwiping) return Math.abs(index - this.activeTab) !== 1
|
|
202
|
+
|
|
203
|
+
return !(this.isAnimating && index === this.previousTab)
|
|
204
|
+
},
|
|
205
|
+
pinHeight() {
|
|
206
|
+
if (!this.hasDots || !this.$refs.content) return
|
|
207
|
+
|
|
208
|
+
clearTimeout(this.heightTimer)
|
|
209
|
+
|
|
210
|
+
if (this.contentHeight === null) {
|
|
211
|
+
this.contentHeight = this.$refs.content.offsetHeight
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
// From the pinned height to the incoming page's own height, then back to auto.
|
|
215
|
+
animateHeightFrom(fromIndex) {
|
|
216
|
+
if (!this.hasDots || !this.$refs.content || !this.$refs.track) return
|
|
217
|
+
|
|
218
|
+
this.pinHeight()
|
|
219
|
+
this.previousTab = fromIndex
|
|
220
|
+
|
|
221
|
+
this.$nextTick(() => {
|
|
222
|
+
var tab = this.$refs.track.children[this.activeTab]
|
|
223
|
+
void this.$refs.content.offsetHeight // flush the pinned height so the change transitions
|
|
224
|
+
|
|
225
|
+
if (tab) this.contentHeight = tab.offsetHeight
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
this.releaseHeightLater()
|
|
229
|
+
},
|
|
230
|
+
releaseHeightLater() {
|
|
231
|
+
clearTimeout(this.heightTimer)
|
|
232
|
+
|
|
233
|
+
this.heightTimer = setTimeout(() => {
|
|
234
|
+
this.contentHeight = null
|
|
235
|
+
this.previousTab = null
|
|
236
|
+
}, 300)
|
|
237
|
+
},
|
|
238
|
+
moveTab(direction) {
|
|
239
|
+
var nextIndex = this.findNextEnabledTab(this.activeTab, direction)
|
|
240
|
+
|
|
241
|
+
if (nextIndex !== this.activeTab) {
|
|
242
|
+
this.selectTab(nextIndex)
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
onKeydown(e) {
|
|
246
|
+
if (e.altKey || e.ctrlKey || e.metaKey || !this.$el.offsetParent) return
|
|
247
|
+
if (e.target.closest && e.target.closest('input, textarea, select, [contenteditable]')) return
|
|
248
|
+
|
|
249
|
+
if (e.key === 'ArrowRight') this.moveTab(1)
|
|
250
|
+
if (e.key === 'ArrowLeft') this.moveTab(-1)
|
|
251
|
+
},
|
|
147
252
|
|
|
148
253
|
// --- Swipe methods ---
|
|
149
254
|
|
|
@@ -173,6 +278,7 @@ export default {
|
|
|
173
278
|
}
|
|
174
279
|
|
|
175
280
|
this.directionLocked = true
|
|
281
|
+
this.pinHeight()
|
|
176
282
|
this.isSwiping = true
|
|
177
283
|
}
|
|
178
284
|
|
|
@@ -217,6 +323,7 @@ export default {
|
|
|
217
323
|
}
|
|
218
324
|
|
|
219
325
|
if (nextIndex !== this.activeTab) {
|
|
326
|
+
this.animateHeightFrom(this.activeTab)
|
|
220
327
|
this.activeTab = nextIndex
|
|
221
328
|
this.persistActiveTab()
|
|
222
329
|
this.syncSelect()
|
|
@@ -225,6 +332,10 @@ export default {
|
|
|
225
332
|
}
|
|
226
333
|
// else: snap back (activeTab unchanged, animation brings it back)
|
|
227
334
|
|
|
335
|
+
if (this.previousTab === null) {
|
|
336
|
+
this.releaseHeightLater()
|
|
337
|
+
}
|
|
338
|
+
|
|
228
339
|
this.swipeOffset = 0
|
|
229
340
|
|
|
230
341
|
setTimeout(() => {
|
|
@@ -324,6 +435,13 @@ export default {
|
|
|
324
435
|
},
|
|
325
436
|
mounted(){
|
|
326
437
|
this.syncSelect()
|
|
438
|
+
|
|
439
|
+
if (this.hasArrowKeys) {
|
|
440
|
+
document.addEventListener('keydown', this.onKeydown)
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
beforeDestroy(){
|
|
444
|
+
document.removeEventListener('keydown', this.onKeydown)
|
|
327
445
|
},
|
|
328
446
|
}
|
|
329
447
|
</script>
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
:editor="editor"
|
|
16
16
|
:config="editorConfig" />
|
|
17
|
+
<div v-if="charCounter" class="vlCharCounter" :class="{ 'vlCharCounter--over': charCount > charCounter }">
|
|
18
|
+
{{ charCount }} / {{ charCounter }} {{ charCounterLabel }}
|
|
19
|
+
</div>
|
|
17
20
|
</vl-form-field>
|
|
18
21
|
</template>
|
|
19
22
|
|
|
@@ -23,6 +26,21 @@ import Translatable from 'vue-kompo/js/form/mixins/Translatable'
|
|
|
23
26
|
|
|
24
27
|
export default {
|
|
25
28
|
mixins: [CKEditor, Translatable],
|
|
29
|
+
computed: {
|
|
30
|
+
charCounter(){
|
|
31
|
+
return this.$_config('charCounter')
|
|
32
|
+
},
|
|
33
|
+
charCounterLabel(){
|
|
34
|
+
return this.$_config('charCounterLabel') || ''
|
|
35
|
+
},
|
|
36
|
+
// Visible text of the current language: what the user reads, not the stored HTML.
|
|
37
|
+
charCount(){
|
|
38
|
+
var div = document.createElement('div')
|
|
39
|
+
div.innerHTML = this.currentTranslation || ''
|
|
40
|
+
|
|
41
|
+
return div.textContent.length
|
|
42
|
+
},
|
|
43
|
+
},
|
|
26
44
|
}
|
|
27
45
|
</script>
|
|
28
46
|
|