quasar 2.8.4 → 2.9.1

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.
Files changed (169) hide show
  1. package/dist/api/QBreadcrumbsEl.json +52 -0
  2. package/dist/api/QBtn.json +41 -6
  3. package/dist/api/QBtnDropdown.json +1 -1
  4. package/dist/api/QChip.json +1 -1
  5. package/dist/api/QEditor.json +7 -0
  6. package/dist/api/QExpansionItem.json +1 -1
  7. package/dist/api/QItem.json +52 -0
  8. package/dist/api/QRating.json +13 -0
  9. package/dist/api/QRouteTab.json +42 -6
  10. package/dist/icon-set/bootstrap-icons.umd.prod.js +1 -1
  11. package/dist/icon-set/eva-icons.umd.prod.js +1 -1
  12. package/dist/icon-set/fontawesome-v5-pro.umd.prod.js +1 -1
  13. package/dist/icon-set/fontawesome-v5.umd.prod.js +1 -1
  14. package/dist/icon-set/fontawesome-v6-pro.umd.prod.js +1 -1
  15. package/dist/icon-set/fontawesome-v6.umd.prod.js +1 -1
  16. package/dist/icon-set/ionicons-v4.umd.prod.js +1 -1
  17. package/dist/icon-set/line-awesome.umd.prod.js +1 -1
  18. package/dist/icon-set/material-icons-outlined.umd.prod.js +1 -1
  19. package/dist/icon-set/material-icons-round.umd.prod.js +1 -1
  20. package/dist/icon-set/material-icons-sharp.umd.prod.js +1 -1
  21. package/dist/icon-set/material-icons.umd.prod.js +1 -1
  22. package/dist/icon-set/material-symbols-outlined.umd.prod.js +1 -1
  23. package/dist/icon-set/material-symbols-rounded.umd.prod.js +1 -1
  24. package/dist/icon-set/material-symbols-sharp.umd.prod.js +1 -1
  25. package/dist/icon-set/mdi-v3.umd.prod.js +1 -1
  26. package/dist/icon-set/mdi-v4.umd.prod.js +1 -1
  27. package/dist/icon-set/mdi-v5.umd.prod.js +1 -1
  28. package/dist/icon-set/mdi-v6.umd.prod.js +1 -1
  29. package/dist/icon-set/svg-bootstrap-icons.umd.prod.js +1 -1
  30. package/dist/icon-set/svg-eva-icons.umd.prod.js +1 -1
  31. package/dist/icon-set/svg-fontawesome-v5.umd.prod.js +1 -1
  32. package/dist/icon-set/svg-fontawesome-v6.umd.prod.js +1 -1
  33. package/dist/icon-set/svg-ionicons-v4.umd.prod.js +1 -1
  34. package/dist/icon-set/svg-ionicons-v5.umd.prod.js +1 -1
  35. package/dist/icon-set/svg-ionicons-v6.umd.prod.js +1 -1
  36. package/dist/icon-set/svg-line-awesome.umd.prod.js +1 -1
  37. package/dist/icon-set/svg-material-icons-outlined.umd.prod.js +1 -1
  38. package/dist/icon-set/svg-material-icons-round.umd.prod.js +1 -1
  39. package/dist/icon-set/svg-material-icons-sharp.umd.prod.js +1 -1
  40. package/dist/icon-set/svg-material-icons.umd.prod.js +1 -1
  41. package/dist/icon-set/svg-material-symbols-outlined.umd.prod.js +1 -1
  42. package/dist/icon-set/svg-material-symbols-rounded.umd.prod.js +1 -1
  43. package/dist/icon-set/svg-material-symbols-sharp.umd.prod.js +1 -1
  44. package/dist/icon-set/svg-mdi-v6.umd.prod.js +1 -1
  45. package/dist/icon-set/svg-themify.umd.prod.js +1 -1
  46. package/dist/icon-set/themify.umd.prod.js +1 -1
  47. package/dist/lang/ar-TN.umd.prod.js +1 -1
  48. package/dist/lang/ar.umd.prod.js +1 -1
  49. package/dist/lang/az-Latn.umd.prod.js +1 -1
  50. package/dist/lang/bg.umd.prod.js +1 -1
  51. package/dist/lang/bn.umd.prod.js +1 -1
  52. package/dist/lang/ca.umd.prod.js +1 -1
  53. package/dist/lang/cs.umd.prod.js +1 -1
  54. package/dist/lang/da.umd.prod.js +1 -1
  55. package/dist/lang/de.umd.prod.js +1 -1
  56. package/dist/lang/el.umd.prod.js +1 -1
  57. package/dist/lang/en-GB.umd.prod.js +1 -1
  58. package/dist/lang/en-US.umd.prod.js +1 -1
  59. package/dist/lang/eo.umd.prod.js +1 -1
  60. package/dist/lang/es.umd.prod.js +1 -1
  61. package/dist/lang/et.umd.prod.js +1 -1
  62. package/dist/lang/eu.umd.prod.js +1 -1
  63. package/dist/lang/fa-IR.umd.prod.js +1 -1
  64. package/dist/lang/fa.umd.prod.js +1 -1
  65. package/dist/lang/fi.umd.prod.js +1 -1
  66. package/dist/lang/fr.umd.prod.js +1 -1
  67. package/dist/lang/gn.umd.prod.js +1 -1
  68. package/dist/lang/he.umd.prod.js +1 -1
  69. package/dist/lang/hr.umd.prod.js +1 -1
  70. package/dist/lang/hu.umd.prod.js +1 -1
  71. package/dist/lang/id.umd.prod.js +1 -1
  72. package/dist/lang/is.umd.prod.js +1 -1
  73. package/dist/lang/it.umd.prod.js +1 -1
  74. package/dist/lang/ja.umd.prod.js +1 -1
  75. package/dist/lang/km.umd.prod.js +1 -1
  76. package/dist/lang/ko-KR.umd.prod.js +1 -1
  77. package/dist/lang/kur-CKB.umd.prod.js +1 -1
  78. package/dist/lang/kz.umd.prod.js +1 -1
  79. package/dist/lang/lt.umd.prod.js +1 -1
  80. package/dist/lang/lu.umd.prod.js +1 -1
  81. package/dist/lang/lv.umd.prod.js +1 -1
  82. package/dist/lang/ml.umd.prod.js +1 -1
  83. package/dist/lang/mm.umd.prod.js +1 -1
  84. package/dist/lang/ms.umd.prod.js +1 -1
  85. package/dist/lang/my.umd.prod.js +1 -1
  86. package/dist/lang/nb-NO.umd.prod.js +1 -1
  87. package/dist/lang/nl.umd.prod.js +2 -2
  88. package/dist/lang/pl.umd.prod.js +1 -1
  89. package/dist/lang/pt-BR.umd.prod.js +1 -1
  90. package/dist/lang/pt.umd.prod.js +1 -1
  91. package/dist/lang/ro.umd.prod.js +1 -1
  92. package/dist/lang/ru.umd.prod.js +1 -1
  93. package/dist/lang/sk.umd.prod.js +1 -1
  94. package/dist/lang/sl.umd.prod.js +1 -1
  95. package/dist/lang/sm.umd.prod.js +1 -1
  96. package/dist/lang/sr-CYR.umd.prod.js +1 -1
  97. package/dist/lang/sr.umd.prod.js +1 -1
  98. package/dist/lang/sv.umd.prod.js +1 -1
  99. package/dist/lang/ta.umd.prod.js +1 -1
  100. package/dist/lang/th.umd.prod.js +1 -1
  101. package/dist/lang/tr.umd.prod.js +1 -1
  102. package/dist/lang/ug.umd.prod.js +1 -1
  103. package/dist/lang/uk.umd.prod.js +1 -1
  104. package/dist/lang/uz-Cyrl.umd.prod.js +1 -1
  105. package/dist/lang/uz-Latn.umd.prod.js +1 -1
  106. package/dist/lang/vi.umd.prod.js +1 -1
  107. package/dist/lang/zh-CN.umd.prod.js +1 -1
  108. package/dist/lang/zh-TW.umd.prod.js +1 -1
  109. package/dist/quasar.cjs.prod.js +2 -2
  110. package/dist/quasar.esm.js +533 -337
  111. package/dist/quasar.esm.prod.js +2 -2
  112. package/dist/quasar.sass +1 -1
  113. package/dist/quasar.umd.js +532 -336
  114. package/dist/quasar.umd.prod.js +2 -2
  115. package/dist/transforms/auto-import.json +7 -3
  116. package/dist/transforms/import-map.json +2 -0
  117. package/dist/types/api/qeditor.d.ts +17 -0
  118. package/dist/types/api.d.ts +1 -0
  119. package/dist/types/index.d.ts +93 -8
  120. package/dist/types/utils/run-sequential-promises.d.ts +1 -1
  121. package/dist/vetur/quasar-attributes.json +4 -0
  122. package/dist/vetur/quasar-tags.json +1 -0
  123. package/dist/web-types/web-types.json +60 -9
  124. package/lang/nl.js +2 -2
  125. package/lang/nl.mjs +2 -2
  126. package/package.json +5 -3
  127. package/src/components/banner/__tests__/QBanner.spec.js +107 -0
  128. package/src/components/bar/__tests__/QBar.spec.js +46 -0
  129. package/src/components/breadcrumbs/QBreadcrumbsEl.js +6 -7
  130. package/src/components/breadcrumbs/QBreadcrumbsEl.json +53 -0
  131. package/src/components/btn/QBtn.js +19 -19
  132. package/src/components/btn/QBtn.json +41 -6
  133. package/src/components/btn/use-btn.js +6 -4
  134. package/src/components/btn-dropdown/QBtnDropdown.json +1 -1
  135. package/src/components/checkbox/QCheckbox.js +1 -2
  136. package/src/components/checkbox/use-checkbox.js +2 -1
  137. package/src/components/chip/QChip.json +1 -1
  138. package/src/components/dialog/QDialog.js +6 -4
  139. package/src/components/drawer/QDrawer.js +7 -4
  140. package/src/components/editor/QEditor.json +9 -0
  141. package/src/components/expansion-item/QExpansionItem.json +1 -1
  142. package/src/components/item/QItem.js +4 -5
  143. package/src/components/item/QItem.json +53 -0
  144. package/src/components/menu/QMenu.js +4 -5
  145. package/src/components/menu/__tests__/QMenu.spec.js +7 -0
  146. package/src/components/popup-edit/QPopupEdit.js +2 -5
  147. package/src/components/radio/QRadio.js +3 -3
  148. package/src/components/rating/QRating.js +48 -10
  149. package/src/components/rating/QRating.json +11 -0
  150. package/src/components/stepper/QStep.js +5 -3
  151. package/src/components/table/QTable.js +3 -5
  152. package/src/components/tabs/QRouteTab.js +6 -4
  153. package/src/components/tabs/QRouteTab.json +42 -6
  154. package/src/components/tabs/QTabs.js +188 -108
  155. package/src/components/tabs/use-tab.js +62 -38
  156. package/src/components/tooltip/QTooltip.js +7 -13
  157. package/src/components/tree/QTree.js +1 -1
  158. package/src/composables/private/__tests__/use-model-toggle.spec.js +2 -0
  159. package/src/composables/private/__tests__/use-transition.spec.js +4 -0
  160. package/src/composables/private/use-router-link.js +80 -43
  161. package/src/composables/private/use-tick.js +15 -9
  162. package/src/composables/private/use-timeout.js +20 -7
  163. package/src/directives/TouchPan.js +1 -1
  164. package/src/directives/TouchRepeat.js +1 -1
  165. package/src/directives/TouchSwipe.js +1 -1
  166. package/src/utils/extend.js +19 -19
  167. package/src/utils/private/inject-obj-prop.js +2 -0
  168. package/src/utils/private/rtl.js +10 -7
  169. package/src/utils/run-sequential-promises.js +1 -1
@@ -1,4 +1,4 @@
1
- import { h, ref, computed, watch, nextTick, onBeforeUnmount, onActivated, onDeactivated, getCurrentInstance, provide } from 'vue'
1
+ import { h, ref, computed, watch, onBeforeUnmount, onActivated, onDeactivated, getCurrentInstance, provide } from 'vue'
2
2
 
3
3
  import QIcon from '../icon/QIcon.js'
4
4
  import QResizeObserver from '../resize-observer/QResizeObserver.js'
@@ -11,7 +11,6 @@ import { noop } from '../../utils/event.js'
11
11
  import { hSlot } from '../../utils/private/render.js'
12
12
  import { tabsKey } from '../../utils/private/symbols.js'
13
13
  import { rtlHasScrollBug } from '../../utils/private/rtl.js'
14
- import { vmIsDestroyed } from '../../utils/private/vm.js'
15
14
 
16
15
  function getIndicatorClass (color, top, vertical) {
17
16
  const pos = vertical === true
@@ -22,7 +21,6 @@ function getIndicatorClass (color, top, vertical) {
22
21
  }
23
22
 
24
23
  const alignValues = [ 'left', 'center', 'right', 'justify' ]
25
- const emptyFn = () => {}
26
24
 
27
25
  export default createComponent({
28
26
  name: 'QTabs',
@@ -68,12 +66,15 @@ export default createComponent({
68
66
  },
69
67
 
70
68
  setup (props, { slots, emit }) {
71
- const vm = getCurrentInstance()
72
- const { proxy: { $q } } = vm
69
+ const { proxy } = getCurrentInstance()
70
+ const { $q } = proxy
73
71
 
74
72
  const { registerTick: registerScrollTick } = useTick()
73
+ const { registerTick: registerUpdateArrowsTick } = useTick()
74
+ const { registerTick: registerAnimateTick } = useTick()
75
+
75
76
  const { registerTimeout: registerFocusTimeout, removeTimeout: removeFocusTimeout } = useTimeout()
76
- const { registerTimeout } = useTimeout()
77
+ const { registerTimeout: registerScrollToTabTimeout, removeTimeout: removeScrollToTabTimeout } = useTimeout()
77
78
 
78
79
  const rootRef = ref(null)
79
80
  const contentRef = ref(null)
@@ -88,10 +89,11 @@ export default createComponent({
88
89
  $q.platform.is.desktop === true || props.mobileArrows === true
89
90
  )
90
91
 
91
- const tabList = []
92
+ const tabDataList = []
93
+ const tabDataListLen = ref(0)
92
94
  const hasFocus = ref(false)
93
95
 
94
- let localFromRoute = false, animateTimer, scrollTimer, unwatchRoute
96
+ let animateTimer, scrollTimer, unwatchRoute
95
97
  let localUpdateArrows = arrowsEnabled.value === true
96
98
  ? updateArrowsFn
97
99
  : noop
@@ -110,6 +112,19 @@ export default createComponent({
110
112
  noCaps: props.noCaps
111
113
  }))
112
114
 
115
+ const hasActiveTab = computed(() => {
116
+ const len = tabDataListLen.value
117
+ const val = currentModel.value
118
+
119
+ for (let i = 0; i < len; i++) {
120
+ if (tabDataList[ i ].name.value === val) {
121
+ return true
122
+ }
123
+ }
124
+
125
+ return false
126
+ })
127
+
113
128
  const alignClass = computed(() => {
114
129
  const align = scrollable.value === true
115
130
  ? 'left'
@@ -151,7 +166,7 @@ export default createComponent({
151
166
  })
152
167
 
153
168
  watch(() => props.outsideArrows, () => {
154
- nextTick(recalculateScroll())
169
+ recalculateScroll()
155
170
  })
156
171
 
157
172
  watch(arrowsEnabled, v => {
@@ -159,12 +174,15 @@ export default createComponent({
159
174
  ? updateArrowsFn
160
175
  : noop
161
176
 
162
- nextTick(recalculateScroll())
177
+ recalculateScroll()
163
178
  })
164
179
 
165
180
  function updateModel ({ name, setCurrent, skipEmit, fromRoute }) {
166
181
  if (currentModel.value !== name) {
167
- skipEmit !== true && emit('update:modelValue', name)
182
+ if (skipEmit !== true && props[ 'onUpdate:modelValue' ] !== void 0) {
183
+ emit('update:modelValue', name)
184
+ }
185
+
168
186
  if (
169
187
  setCurrent === true
170
188
  || props[ 'onUpdate:modelValue' ] === void 0
@@ -173,20 +191,14 @@ export default createComponent({
173
191
  currentModel.value = name
174
192
  }
175
193
  }
176
-
177
- if (fromRoute !== void 0) {
178
- localFromRoute = fromRoute
179
- }
180
194
  }
181
195
 
182
196
  function recalculateScroll () {
183
197
  registerScrollTick(() => {
184
- if (vmIsDestroyed(vm) === false) {
185
- updateContainer({
186
- width: rootRef.value.offsetWidth,
187
- height: rootRef.value.offsetHeight
188
- })
189
- }
198
+ updateContainer({
199
+ width: rootRef.value.offsetWidth,
200
+ height: rootRef.value.offsetHeight
201
+ })
190
202
  })
191
203
  }
192
204
 
@@ -208,27 +220,21 @@ export default createComponent({
208
220
  ),
209
221
  scroll = size > 0 && scrollSize > size // when there is no tab, in Chrome, size === 0 and scrollSize === 1
210
222
 
211
- if (scrollable.value !== scroll) {
212
- scrollable.value = scroll
213
- }
223
+ scrollable.value = scroll
214
224
 
215
225
  // Arrows need to be updated even if the scroll status was already true
216
- scroll === true && nextTick(localUpdateArrows)
226
+ scroll === true && registerUpdateArrowsTick(localUpdateArrows)
217
227
 
218
- const localJustify = size < parseInt(props.breakpoint, 10)
219
-
220
- if (justify.value !== localJustify) {
221
- justify.value = localJustify
222
- }
228
+ justify.value = size < parseInt(props.breakpoint, 10)
223
229
  }
224
230
 
225
231
  function animate (oldName, newName) {
226
232
  const
227
233
  oldTab = oldName !== void 0 && oldName !== null && oldName !== ''
228
- ? tabList.find(tab => tab.name.value === oldName)
234
+ ? tabDataList.find(tab => tab.name.value === oldName)
229
235
  : null,
230
236
  newTab = newName !== void 0 && newName !== null && newName !== ''
231
- ? tabList.find(tab => tab.name.value === newName)
237
+ ? tabDataList.find(tab => tab.name.value === newName)
232
238
  : null
233
239
 
234
240
  if (oldTab && newTab) {
@@ -252,7 +258,7 @@ export default createComponent({
252
258
  : `translate3d(${ oldPos.left - newPos.left }px,0,0) scale3d(${ newPos.width ? oldPos.width / newPos.width : 1 },1,1)`
253
259
 
254
260
  // allow scope updates to kick in (QRouteTab needs more time)
255
- nextTick(() => {
261
+ registerAnimateTick(() => {
256
262
  animateTimer = setTimeout(() => {
257
263
  newEl.style.transition = 'transform .25s cubic-bezier(.4, 0, .2, 1)'
258
264
  newEl.style.transform = 'none'
@@ -307,8 +313,6 @@ export default createComponent({
307
313
 
308
314
  function animScrollTo (value) {
309
315
  stopAnimScroll()
310
- scrollTowards(value)
311
-
312
316
  scrollTimer = setInterval(() => {
313
317
  if (scrollTowards(value) === true) {
314
318
  stopAnimScroll()
@@ -339,10 +343,12 @@ export default createComponent({
339
343
 
340
344
  if (keyCode === 36) { // Home
341
345
  scrollToTabEl(tabs[ 0 ])
346
+ tabs[ 0 ].focus()
342
347
  return true
343
348
  }
344
349
  if (keyCode === 35) { // End
345
350
  scrollToTabEl(tabs[ len - 1 ])
351
+ tabs[ len - 1 ].focus()
346
352
  return true
347
353
  }
348
354
 
@@ -408,77 +414,113 @@ export default createComponent({
408
414
  return done
409
415
  }
410
416
 
411
- function getRouteList () {
412
- return tabList.filter(tab => tab.routerProps !== void 0 && tab.routerProps.hasRouterLink.value === true)
417
+ function hasQueryIncluded (targetQuery, matchingQuery) {
418
+ for (const key in targetQuery) {
419
+ if (targetQuery[ key ] !== matchingQuery[ key ]) {
420
+ return false
421
+ }
422
+ }
423
+
424
+ return true
413
425
  }
414
426
 
415
427
  // do not use directly; use verifyRouteModel() instead
416
428
  function updateActiveRoute () {
417
- let name = null, wasActive = localFromRoute
429
+ let name = null, bestScore = { matchedLen: 0, queryDiff: 9999, hrefLen: 0 }
418
430
 
419
- const
420
- best = { matchedLen: 0, hrefLen: 0, exact: false, found: false },
421
- { hash } = vm.proxy.$route,
422
- model = currentModel.value
423
-
424
- let wasItActive = wasActive === true
425
- ? emptyFn
426
- : tab => {
427
- if (model === tab.name.value) {
428
- wasActive = true
429
- wasItActive = emptyFn
430
- }
431
- }
431
+ const list = tabDataList.filter(tab => tab.routeData !== void 0 && tab.routeData.hasRouterLink.value === true)
432
+ const { hash: currentHash, query: currentQuery } = proxy.$route
433
+ const currentQueryLen = Object.keys(currentQuery).length
432
434
 
433
- const tabList = getRouteList()
435
+ // Vue Router does not keep account of hash & query when matching
436
+ // so we're doing this as well
434
437
 
435
- for (const tab of tabList) {
436
- const exact = tab.routerProps.exact.value === true
438
+ for (const tab of list) {
439
+ const exact = tab.routeData.exact.value === true
437
440
 
438
- if (
439
- tab.routerProps[ exact === true ? 'linkIsExactActive' : 'linkIsActive' ].value !== true
440
- || (best.exact === true && exact !== true)
441
- ) {
442
- wasItActive(tab)
441
+ if (tab.routeData[ exact === true ? 'linkIsExactActive' : 'linkIsActive' ].value !== true) {
442
+ // it cannot match anything as it's not active nor exact-active
443
443
  continue
444
444
  }
445
445
 
446
- const
447
- linkRoute = tab.routerProps.linkRoute.value,
448
- tabHash = linkRoute.hash
446
+ const { hash, query, matched, href } = tab.routeData.resolvedLink.value
447
+ const queryLen = Object.keys(query).length
449
448
 
450
- // Vue Router does not match the hash too, even if link is set to "exact"
451
449
  if (exact === true) {
452
- if (hash === tabHash) {
453
- name = tab.name.value
454
- break
450
+ if (hash !== currentHash) {
451
+ // it's set to exact but it doesn't matches the hash
452
+ continue
455
453
  }
456
- else if (hash !== '' && tabHash !== '') {
457
- wasItActive(tab)
454
+
455
+ if (
456
+ queryLen !== currentQueryLen
457
+ || hasQueryIncluded(currentQuery, query) === false
458
+ ) {
459
+ // it's set to exact but it doesn't matches the query
458
460
  continue
459
461
  }
462
+
463
+ // yey, we found the perfect match (route + hash + query)
464
+ name = tab.name.value
465
+ break
460
466
  }
461
467
 
462
- const
463
- matchedLen = linkRoute.matched.length,
464
- hrefLen = linkRoute.href.length - tabHash.length
468
+ if (hash !== '' && hash !== currentHash) {
469
+ // it has hash and it doesn't matches
470
+ continue
471
+ }
465
472
 
466
473
  if (
467
- matchedLen === best.matchedLen
468
- ? hrefLen > best.hrefLen
469
- : matchedLen > best.matchedLen
474
+ queryLen !== 0
475
+ && hasQueryIncluded(query, currentQuery) === false
470
476
  ) {
477
+ // it has query and it doesn't includes the current one
478
+ continue
479
+ }
480
+
481
+ const newScore = {
482
+ matchedLen: matched.length,
483
+ queryDiff: currentQueryLen - queryLen,
484
+ hrefLen: href.length - hash.length
485
+ }
486
+
487
+ if (newScore.matchedLen > bestScore.matchedLen) {
488
+ // it matches more routes so it's more specific so we set it as current champion
471
489
  name = tab.name.value
472
- Object.assign(best, { matchedLen, hrefLen, exact })
490
+ bestScore = newScore
491
+ continue
492
+ }
493
+ else if (newScore.matchedLen !== bestScore.matchedLen) {
494
+ // it matches less routes than the current champion so we discard it
473
495
  continue
474
496
  }
475
497
 
476
- wasItActive(tab)
498
+ if (newScore.queryDiff < bestScore.queryDiff) {
499
+ // query is closer to the current one so we set it as current champion
500
+ name = tab.name.value
501
+ bestScore = newScore
502
+ }
503
+ else if (newScore.queryDiff !== bestScore.queryDiff) {
504
+ // it matches less routes than the current champion so we discard it
505
+ continue
506
+ }
507
+
508
+ if (newScore.hrefLen > bestScore.hrefLen) {
509
+ // href is lengthier so it's more specific so we set it as current champion
510
+ name = tab.name.value
511
+ bestScore = newScore
512
+ }
477
513
  }
478
514
 
479
- if (wasActive === true || name !== null) {
480
- updateModel({ name, setCurrent: true, fromRoute: true })
515
+ if (
516
+ name === null
517
+ && tabDataList.some(tab => tab.routeData === void 0 && tab.name.value === currentModel.value) === true
518
+ ) {
519
+ // we shouldn't interfere if non-route tab is active
520
+ return
481
521
  }
522
+
523
+ updateModel({ name, setCurrent: true })
482
524
  }
483
525
 
484
526
  function onFocusin (e) {
@@ -496,6 +538,7 @@ export default createComponent({
496
538
  // (it might be other elements focused, like additional QBtn)
497
539
  if (tab && rootRef.value.contains(tab) === true) {
498
540
  hasFocus.value = true
541
+ scrollable.value === true && scrollToTabEl(tab)
499
542
  }
500
543
  }
501
544
  }
@@ -505,22 +548,52 @@ export default createComponent({
505
548
  }
506
549
 
507
550
  function verifyRouteModel () {
508
- if ($tabs.avoidRouteWatcher !== true) {
509
- registerTimeout(updateActiveRoute)
551
+ if ($tabs.avoidRouteWatcher === false) {
552
+ registerScrollToTabTimeout(updateActiveRoute)
553
+ }
554
+ else {
555
+ removeScrollToTabTimeout()
510
556
  }
511
557
  }
512
558
 
513
- function registerTab (getTab) {
514
- tabList.push(getTab)
559
+ function watchRoute () {
560
+ if (unwatchRoute === void 0) {
561
+ const unwatch = watch(() => proxy.$route.fullPath, verifyRouteModel)
562
+ unwatchRoute = () => {
563
+ unwatch()
564
+ unwatchRoute = void 0
565
+ }
566
+ }
567
+ }
515
568
 
516
- const routeList = getRouteList()
569
+ function registerTab (tabData) {
570
+ tabDataList.push(tabData)
571
+ tabDataListLen.value++
517
572
 
518
- if (routeList.length > 0) {
519
- if (unwatchRoute === void 0) {
520
- unwatchRoute = watch(() => vm.proxy.$route, verifyRouteModel)
521
- }
573
+ recalculateScroll()
522
574
 
523
- verifyRouteModel()
575
+ // if it's a QTab
576
+ if (tabData.routeData === void 0) {
577
+ // we should position to the currently active tab (if any)
578
+ registerScrollToTabTimeout(() => {
579
+ if (scrollable.value === true) {
580
+ const value = currentModel.value
581
+ const newTab = value !== void 0 && value !== null && value !== ''
582
+ ? tabDataList.find(tab => tab.name.value === value)
583
+ : null
584
+
585
+ newTab && scrollToTabEl(newTab.rootRef.value)
586
+ }
587
+ })
588
+ }
589
+ // else if it's a QRouteTab with a valid link
590
+ else {
591
+ // start watching route
592
+ watchRoute()
593
+
594
+ if (tabData.routeData.hasRouterLink.value === true) {
595
+ verifyRouteModel()
596
+ }
524
597
  }
525
598
  }
526
599
 
@@ -532,16 +605,18 @@ export default createComponent({
532
605
  * always check the existing list again and infer the changes.
533
606
  */
534
607
  function unregisterTab (tabData) {
535
- tabList.splice(tabList.indexOf(tabData), 1)
608
+ tabDataList.splice(tabDataList.indexOf(tabData), 1)
609
+ tabDataListLen.value--
536
610
 
537
- if (unwatchRoute !== void 0) {
538
- const routeList = getRouteList()
611
+ recalculateScroll()
539
612
 
540
- if (routeList.length === 0) {
613
+ if (unwatchRoute !== void 0 && tabData.routeData !== void 0) {
614
+ // unwatch route if we don't have any QRouteTabs left
615
+ if (tabDataList.every(tab => tab.routeData === void 0) === true) {
541
616
  unwatchRoute()
542
- unwatchRoute = void 0
543
617
  }
544
618
 
619
+ // then update model
545
620
  verifyRouteModel()
546
621
  }
547
622
  }
@@ -550,33 +625,38 @@ export default createComponent({
550
625
  currentModel,
551
626
  tabProps,
552
627
  hasFocus,
628
+ hasActiveTab,
553
629
 
554
630
  registerTab,
555
631
  unregisterTab,
556
632
 
557
633
  verifyRouteModel,
558
634
  updateModel,
559
- recalculateScroll,
560
635
  onKbdNavigate,
561
636
 
562
- avoidRouteWatcher: false
637
+ avoidRouteWatcher: false // false | string (uid)
563
638
  }
564
639
 
565
640
  provide(tabsKey, $tabs)
566
641
 
567
- onBeforeUnmount(() => {
642
+ function cleanup () {
568
643
  clearTimeout(animateTimer)
644
+ stopAnimScroll()
569
645
  unwatchRoute !== void 0 && unwatchRoute()
570
- })
646
+ }
647
+
648
+ let hadRouteWatcher
571
649
 
572
- let shouldActivate = false
650
+ onBeforeUnmount(cleanup)
573
651
 
574
652
  onDeactivated(() => {
575
- shouldActivate = true
653
+ hadRouteWatcher = unwatchRoute !== void 0
654
+ cleanup()
576
655
  })
577
656
 
578
657
  onActivated(() => {
579
- shouldActivate === true && recalculateScroll()
658
+ hadRouteWatcher === true && watchRoute()
659
+ recalculateScroll()
580
660
  })
581
661
 
582
662
  return () => {
@@ -595,22 +675,22 @@ export default createComponent({
595
675
  class: 'q-tabs__arrow q-tabs__arrow--left absolute q-tab__icon'
596
676
  + (leftArrow.value === true ? '' : ' q-tabs__arrow--faded'),
597
677
  name: props.leftIcon || $q.iconSet.tabs[ props.vertical === true ? 'up' : 'left' ],
598
- onMousedown: scrollToStart,
678
+ onMousedownPassive: scrollToStart,
599
679
  onTouchstartPassive: scrollToStart,
600
- onMouseup: stopAnimScroll,
601
- onMouseleave: stopAnimScroll,
602
- onTouchend: stopAnimScroll
680
+ onMouseupPassive: stopAnimScroll,
681
+ onMouseleavePassive: stopAnimScroll,
682
+ onTouchendPassive: stopAnimScroll
603
683
  }),
604
684
 
605
685
  h(QIcon, {
606
686
  class: 'q-tabs__arrow q-tabs__arrow--right absolute q-tab__icon'
607
687
  + (rightArrow.value === true ? '' : ' q-tabs__arrow--faded'),
608
688
  name: props.rightIcon || $q.iconSet.tabs[ props.vertical === true ? 'down' : 'right' ],
609
- onMousedown: scrollToEnd,
689
+ onMousedownPassive: scrollToEnd,
610
690
  onTouchstartPassive: scrollToEnd,
611
- onMouseup: stopAnimScroll,
612
- onMouseleave: stopAnimScroll,
613
- onTouchend: stopAnimScroll
691
+ onMouseupPassive: stopAnimScroll,
692
+ onMouseleavePassive: stopAnimScroll,
693
+ onTouchendPassive: stopAnimScroll
614
694
  })
615
695
  )
616
696
 
@@ -8,8 +8,10 @@ import { hMergeSlot } from '../../utils/private/render.js'
8
8
  import { isKeyCode, shouldIgnoreKey } from '../../utils/private/key-composition.js'
9
9
  import { tabsKey } from '../../utils/private/symbols.js'
10
10
  import { stopAndPrevent } from '../../utils/event.js'
11
+ import uid from '../../utils/uid.js'
12
+ import { isDeepEqual } from '../../utils/is.js'
11
13
 
12
- let uid = 0
14
+ let id = 0
13
15
 
14
16
  export const useTabEmits = [ 'click', 'keydown' ]
15
17
 
@@ -22,7 +24,7 @@ export const useTabProps = {
22
24
 
23
25
  name: {
24
26
  type: [ Number, String ],
25
- default: () => `t_${ uid++ }`
27
+ default: () => `t_${ id++ }`
26
28
  },
27
29
 
28
30
  noCaps: Boolean,
@@ -38,7 +40,7 @@ export const useTabProps = {
38
40
  }
39
41
  }
40
42
 
41
- export default function (props, slots, emit, routerProps) {
43
+ export default function (props, slots, emit, routeData) {
42
44
  const $tabs = inject(tabsKey, () => {
43
45
  console.error('QTab/QRouteTab component needs to be child of QTabs')
44
46
  })
@@ -75,7 +77,7 @@ export default function (props, slots, emit, routerProps) {
75
77
  + (props.icon && props.label && $tabs.tabProps.value.inlineLabel === false ? ' q-tab--full' : '')
76
78
  + (props.noCaps === true || $tabs.tabProps.value.noCaps === true ? ' q-tab--no-caps' : '')
77
79
  + (props.disable === true ? ' disabled' : ' q-focusable q-hoverable cursor-pointer')
78
- + (routerProps !== void 0 && routerProps.linkClass.value !== '' ? ` ${ routerProps.linkClass.value }` : '')
80
+ + (routeData !== void 0 ? routeData.linkClass.value : '')
79
81
  )
80
82
 
81
83
  const innerClass = computed(() =>
@@ -85,53 +87,77 @@ export default function (props, slots, emit, routerProps) {
85
87
  )
86
88
 
87
89
  const tabIndex = computed(() => (
88
- props.disable === true || $tabs.hasFocus.value === true
90
+ (
91
+ props.disable === true
92
+ || $tabs.hasFocus.value === true
93
+ || (isActive.value === false && $tabs.hasActiveTab.value === true)
94
+ )
89
95
  ? -1
90
96
  : props.tabindex || 0
91
97
  ))
92
98
 
93
99
  function onClick (e, keyboard) {
94
- keyboard !== true && blurTargetRef.value !== null && blurTargetRef.value.focus()
95
-
96
- if (props.disable !== true) {
97
- let go
100
+ if (keyboard !== true && blurTargetRef.value !== null) {
101
+ blurTargetRef.value.focus()
102
+ }
98
103
 
99
- if (routerProps !== void 0) {
100
- if (routerProps.hasRouterLink.value === true) {
101
- go = () => {
102
- e.__qNavigate = true
103
- $tabs.avoidRouteWatcher = true
104
+ if (props.disable === true) {
105
+ // we should hinder native navigation though
106
+ if (routeData !== void 0 && routeData.hasRouterLink.value === true) {
107
+ stopAndPrevent(e)
108
+ }
109
+ return
110
+ }
104
111
 
105
- const res = routerProps.navigateToRouterLink(e)
112
+ // do we have a QTab?
113
+ if (routeData === void 0) {
114
+ $tabs.updateModel({ name: props.name })
115
+ emit('click', e)
116
+ return
117
+ }
106
118
 
107
- if (res === false) {
119
+ if (routeData.hasRouterLink.value === true) {
120
+ const go = (opts = {}) => {
121
+ // if requiring to go to another route, then we
122
+ // let the QTabs route watcher do its job,
123
+ // otherwise directly select this
124
+ let hardError
125
+ const reqId = opts.to === void 0 || isDeepEqual(opts.to, props.to) === true
126
+ ? ($tabs.avoidRouteWatcher = uid())
127
+ : null
128
+
129
+ return routeData.navigateToRouterLink(e, { ...opts, returnRouterError: true })
130
+ .catch(err => { hardError = err })
131
+ .then(softError => {
132
+ if (reqId === $tabs.avoidRouteWatcher) {
108
133
  $tabs.avoidRouteWatcher = false
134
+
135
+ // if we don't have any hard errors or any soft errors, except for
136
+ // when navigating to the same route (on all other soft errors,
137
+ // like when navigation was aborted in a nav guard, we don't activate this tab)
138
+ if (
139
+ hardError === void 0 && (
140
+ softError === void 0
141
+ || softError.message.startsWith('Avoided redundant navigation') === true
142
+ )
143
+ ) {
144
+ $tabs.updateModel({ name: props.name })
145
+ }
109
146
  }
110
- else {
111
- res.then(err => {
112
- $tabs.avoidRouteWatcher = false
113
-
114
- if (err === void 0) {
115
- $tabs.updateModel({ name: props.name, fromRoute: true })
116
- }
117
- })
147
+
148
+ if (opts.returnRouterError === true) {
149
+ return hardError !== void 0 ? Promise.reject(hardError) : softError
118
150
  }
119
- }
120
- }
121
- else {
122
- emit('click', e)
123
- return
124
- }
125
- }
126
- else {
127
- go = () => {
128
- $tabs.updateModel({ name: props.name, fromRoute: false })
129
- }
151
+ })
130
152
  }
131
153
 
132
154
  emit('click', e, go)
133
155
  e.defaultPrevented !== true && go()
156
+
157
+ return
134
158
  }
159
+
160
+ emit('click', e)
135
161
  }
136
162
 
137
163
  function onKeydown (e) {
@@ -205,17 +231,15 @@ export default function (props, slots, emit, routerProps) {
205
231
  name: computed(() => props.name),
206
232
  rootRef,
207
233
  tabIndicatorRef,
208
- routerProps
234
+ routeData
209
235
  }
210
236
 
211
237
  onBeforeUnmount(() => {
212
238
  $tabs.unregisterTab(tabData)
213
- $tabs.recalculateScroll()
214
239
  })
215
240
 
216
241
  onMounted(() => {
217
242
  $tabs.registerTab(tabData)
218
- $tabs.recalculateScroll()
219
243
  })
220
244
 
221
245
  function renderTab (tag, customData) {