js-kompo-utils 1.0.39 → 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.
@@ -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
- return this.$_config('tabParamKey') || 'tab_number'
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
- if (!this.isMobile) {
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-kompo-utils",
3
- "version": "1.0.39",
3
+ "version": "1.0.40",
4
4
  "description": "",
5
5
  "main": "app.js",
6
6
  "scripts": {