quasar 2.3.4 → 2.4.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.
Files changed (204) hide show
  1. package/dist/api/Loading.json +2 -6
  2. package/dist/api/QBreadcrumbsEl.json +31 -10
  3. package/dist/api/QBtn.json +30 -14
  4. package/dist/api/QBtnDropdown.json +30 -14
  5. package/dist/api/QBtnToggle.json +3 -0
  6. package/dist/api/QChatMessage.json +4 -12
  7. package/dist/api/QExpansionItem.json +31 -10
  8. package/dist/api/QFab.json +30 -0
  9. package/dist/api/QFabAction.json +8 -0
  10. package/dist/api/QField.json +1 -0
  11. package/dist/api/QFile.json +1 -0
  12. package/dist/api/QInput.json +1 -0
  13. package/dist/api/QItem.json +31 -10
  14. package/dist/api/QOptionGroup.json +74 -4
  15. package/dist/api/QRange.json +592 -107
  16. package/dist/api/QRouteTab.json +31 -11
  17. package/dist/api/QSelect.json +3 -6
  18. package/dist/api/QSlider.json +504 -39
  19. package/dist/api/QUploader.json +16 -2
  20. package/dist/icon-set/bootstrap-icons.umd.prod.js +1 -1
  21. package/dist/icon-set/eva-icons.umd.prod.js +1 -1
  22. package/dist/icon-set/fontawesome-v5-pro.umd.prod.js +1 -1
  23. package/dist/icon-set/fontawesome-v5.umd.prod.js +1 -1
  24. package/dist/icon-set/ionicons-v4.umd.prod.js +1 -1
  25. package/dist/icon-set/line-awesome.umd.prod.js +1 -1
  26. package/dist/icon-set/material-icons-outlined.umd.prod.js +1 -1
  27. package/dist/icon-set/material-icons-round.umd.prod.js +1 -1
  28. package/dist/icon-set/material-icons-sharp.umd.prod.js +1 -1
  29. package/dist/icon-set/material-icons.umd.prod.js +1 -1
  30. package/dist/icon-set/mdi-v3.umd.prod.js +1 -1
  31. package/dist/icon-set/mdi-v4.umd.prod.js +1 -1
  32. package/dist/icon-set/mdi-v5.umd.prod.js +1 -1
  33. package/dist/icon-set/mdi-v6.umd.prod.js +1 -1
  34. package/dist/icon-set/svg-bootstrap-icons.umd.prod.js +1 -1
  35. package/dist/icon-set/svg-eva-icons.umd.prod.js +1 -1
  36. package/dist/icon-set/svg-fontawesome-v5.umd.prod.js +1 -1
  37. package/dist/icon-set/svg-ionicons-v4.umd.prod.js +1 -1
  38. package/dist/icon-set/svg-ionicons-v5.umd.prod.js +1 -1
  39. package/dist/icon-set/svg-ionicons-v6.umd.prod.js +1 -1
  40. package/dist/icon-set/svg-line-awesome.umd.prod.js +1 -1
  41. package/dist/icon-set/svg-material-icons-outlined.umd.prod.js +1 -1
  42. package/dist/icon-set/svg-material-icons-round.umd.prod.js +1 -1
  43. package/dist/icon-set/svg-material-icons-sharp.umd.prod.js +1 -1
  44. package/dist/icon-set/svg-material-icons.umd.prod.js +1 -1
  45. package/dist/icon-set/svg-mdi-v4.umd.prod.js +1 -1
  46. package/dist/icon-set/svg-mdi-v5.umd.prod.js +1 -1
  47. package/dist/icon-set/svg-mdi-v6.umd.prod.js +1 -1
  48. package/dist/icon-set/svg-themify.umd.prod.js +1 -1
  49. package/dist/icon-set/themify.umd.prod.js +1 -1
  50. package/dist/lang/ar.umd.prod.js +1 -1
  51. package/dist/lang/az-Latn.umd.prod.js +1 -1
  52. package/dist/lang/bg.umd.prod.js +1 -1
  53. package/dist/lang/bn.umd.prod.js +1 -1
  54. package/dist/lang/ca.umd.prod.js +1 -1
  55. package/dist/lang/cs.umd.prod.js +1 -1
  56. package/dist/lang/da.umd.prod.js +1 -1
  57. package/dist/lang/de.umd.prod.js +1 -1
  58. package/dist/lang/el.umd.prod.js +1 -1
  59. package/dist/lang/en-GB.umd.prod.js +1 -1
  60. package/dist/lang/en-US.umd.prod.js +1 -1
  61. package/dist/lang/eo.umd.prod.js +1 -1
  62. package/dist/lang/es.umd.prod.js +1 -1
  63. package/dist/lang/et.umd.prod.js +1 -1
  64. package/dist/lang/fa-IR.umd.prod.js +1 -1
  65. package/dist/lang/fa.umd.prod.js +1 -1
  66. package/dist/lang/fi.umd.prod.js +1 -1
  67. package/dist/lang/fr.umd.prod.js +1 -1
  68. package/dist/lang/gn.umd.prod.js +1 -1
  69. package/dist/lang/he.umd.prod.js +1 -1
  70. package/dist/lang/hr.umd.prod.js +1 -1
  71. package/dist/lang/hu.umd.prod.js +1 -1
  72. package/dist/lang/id.umd.prod.js +1 -1
  73. package/dist/lang/is.umd.prod.js +1 -1
  74. package/dist/lang/it.umd.prod.js +1 -1
  75. package/dist/lang/ja.umd.prod.js +1 -1
  76. package/dist/lang/km.umd.prod.js +1 -1
  77. package/dist/lang/ko-KR.umd.prod.js +1 -1
  78. package/dist/lang/kur-CKB.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/ms.umd.prod.js +1 -1
  84. package/dist/lang/nb-NO.umd.prod.js +1 -1
  85. package/dist/lang/nl.umd.prod.js +1 -1
  86. package/dist/lang/pl.umd.prod.js +1 -1
  87. package/dist/lang/pt-BR.umd.prod.js +1 -1
  88. package/dist/lang/pt.umd.prod.js +1 -1
  89. package/dist/lang/ro.umd.prod.js +1 -1
  90. package/dist/lang/ru.umd.prod.js +1 -1
  91. package/dist/lang/sk.umd.prod.js +1 -1
  92. package/dist/lang/sl.umd.prod.js +1 -1
  93. package/dist/lang/sr-CYR.umd.prod.js +1 -1
  94. package/dist/lang/sr.umd.prod.js +1 -1
  95. package/dist/lang/sv.umd.prod.js +1 -1
  96. package/dist/lang/ta.umd.prod.js +1 -1
  97. package/dist/lang/th.umd.prod.js +1 -1
  98. package/dist/lang/tr.umd.prod.js +1 -1
  99. package/dist/lang/ug.umd.prod.js +1 -1
  100. package/dist/lang/uk.umd.prod.js +1 -1
  101. package/dist/lang/vi.umd.prod.js +1 -1
  102. package/dist/lang/zh-CN.umd.prod.js +1 -1
  103. package/dist/lang/zh-TW.umd.prod.js +1 -1
  104. package/dist/quasar.cjs.prod.js +2 -2
  105. package/dist/quasar.css +264 -183
  106. package/dist/quasar.esm.prod.js +2 -2
  107. package/dist/quasar.prod.css +1 -1
  108. package/dist/quasar.rtl.css +331 -262
  109. package/dist/quasar.rtl.prod.css +1 -1
  110. package/dist/quasar.sass +235 -178
  111. package/dist/quasar.umd.js +16537 -16226
  112. package/dist/quasar.umd.prod.js +2 -2
  113. package/dist/ssr-directives/Morph.js +1 -1
  114. package/dist/transforms/loader-asset-urls.json +20 -0
  115. package/dist/types/api/slider.d.ts +46 -0
  116. package/dist/types/api/validation.d.ts +4 -0
  117. package/dist/types/api.d.ts +2 -0
  118. package/dist/types/composables.d.ts +3 -3
  119. package/dist/types/index.d.ts +594 -120
  120. package/dist/vetur/quasar-attributes.json +250 -82
  121. package/dist/vetur/quasar-tags.json +59 -17
  122. package/dist/web-types/web-types.json +578 -149
  123. package/package.json +1 -1
  124. package/src/api.extends.json +0 -1
  125. package/src/components/breadcrumbs/QBreadcrumbs.js +7 -2
  126. package/src/components/breadcrumbs/QBreadcrumbs.sass +0 -3
  127. package/src/components/breadcrumbs/QBreadcrumbsEl.js +14 -8
  128. package/src/components/btn/QBtn.js +5 -5
  129. package/src/components/btn/use-btn.js +21 -21
  130. package/src/components/btn/use-btn.json +22 -13
  131. package/src/components/btn-toggle/QBtnToggle.json +3 -0
  132. package/src/components/checkbox/use-checkbox.js +1 -1
  133. package/src/components/color/QColor.js +32 -26
  134. package/src/components/color/QColor.sass +10 -23
  135. package/src/components/date/QDate.sass +2 -0
  136. package/src/components/drawer/QDrawer.js +18 -15
  137. package/src/components/editor/QEditor.js +1 -1
  138. package/src/components/editor/QEditor.sass +10 -1
  139. package/src/components/expansion-item/QExpansionItem.js +4 -1
  140. package/src/components/fab/QFab.js +18 -12
  141. package/src/components/fab/QFab.json +33 -0
  142. package/src/components/fab/QFab.sass +1 -1
  143. package/src/components/fab/QFabAction.js +14 -7
  144. package/src/components/fab/QFabAction.json +10 -0
  145. package/src/components/file/QFile.js +12 -5
  146. package/src/components/file/QFile.sass +4 -2
  147. package/src/components/footer/QFooter.js +1 -1
  148. package/src/components/header/QHeader.js +1 -1
  149. package/src/components/icon/QIcon.js +1 -1
  150. package/src/components/infinite-scroll/QInfiniteScroll.js +4 -5
  151. package/src/components/item/QItem.js +2 -3
  152. package/src/components/option-group/QOptionGroup.js +3 -0
  153. package/src/components/option-group/QOptionGroup.json +48 -2
  154. package/src/components/parallax/QParallax.js +4 -2
  155. package/src/components/popup-edit/QPopupEdit.js +2 -5
  156. package/src/components/radio/QRadio.js +2 -7
  157. package/src/components/range/QRange.js +103 -222
  158. package/src/components/range/QRange.json +11 -121
  159. package/src/components/scroll-area/QScrollArea.js +3 -1
  160. package/src/components/slider/QSlider.js +46 -132
  161. package/src/components/slider/QSlider.json +1 -121
  162. package/src/components/slider/QSlider.sass +198 -132
  163. package/src/components/slider/use-slider.js +453 -109
  164. package/src/components/slider/use-slider.json +546 -0
  165. package/src/components/stepper/QStepper.js +3 -3
  166. package/src/components/stepper/QStepper.sass +24 -26
  167. package/src/components/table/QTable.js +26 -46
  168. package/src/components/tabs/QRouteTab.js +1 -2
  169. package/src/components/tabs/QRouteTab.json +0 -7
  170. package/src/components/tabs/QTabs.js +71 -20
  171. package/src/components/tabs/use-tab.js +26 -13
  172. package/src/components/tree/QTree.js +14 -12
  173. package/src/components/uploader/QUploader.json +14 -2
  174. package/src/components/uploader/uploader-core.js +16 -9
  175. package/src/components/virtual-scroll/QVirtualScroll.sass +1 -0
  176. package/src/components/virtual-scroll/use-virtual-scroll.js +30 -17
  177. package/src/composables/private/use-field.js +5 -5
  178. package/src/composables/private/use-file.js +20 -5
  179. package/src/composables/private/use-form.js +2 -3
  180. package/src/composables/private/use-fullscreen.js +15 -4
  181. package/src/composables/private/use-router-link.js +44 -23
  182. package/src/composables/private/use-router-link.json +26 -10
  183. package/src/composables/private/use-split-attrs.js +4 -4
  184. package/src/composables/private/use-validate.js +21 -15
  185. package/src/composables/private/use-validate.json +1 -0
  186. package/src/css/core/helpers.sass +3 -0
  187. package/src/css/core/positioning.sass +5 -0
  188. package/src/directives/ScrollFire.js +1 -0
  189. package/src/icon-set.js +2 -4
  190. package/src/plugins/AppFullscreen.js +70 -53
  191. package/src/plugins/AppVisibility.js +2 -3
  192. package/src/plugins/BottomSheet.js +3 -5
  193. package/src/plugins/Dialog.js +3 -5
  194. package/src/plugins/LoadingBar.js +17 -18
  195. package/src/plugins/Notify.js +296 -295
  196. package/src/plugins/Platform.js +14 -14
  197. package/src/utils/date.js +4 -4
  198. package/src/utils/dom.js +2 -2
  199. package/src/utils/open-url.js +2 -2
  200. package/src/utils/patterns.js +1 -0
  201. package/src/utils/private/define-reactive-plugin.js +10 -8
  202. package/src/utils/private/global-dialog.js +6 -8
  203. package/src/utils/private/inject-obj-prop.js +13 -0
  204. package/src/utils/private/is.js +2 -2
@@ -15,8 +15,8 @@ const scrollToEdges = [
15
15
  'end-force'
16
16
  ]
17
17
 
18
- const slice = Array.prototype.slice
19
18
  let id = 1
19
+ const filterProto = Array.prototype.filter
20
20
 
21
21
  const setOverflowAnchor = __QUASAR_SSR__ || window.getComputedStyle(document.body).overflowAnchor === void 0
22
22
  ? noop
@@ -66,7 +66,7 @@ function getScrollDetails (
66
66
  if (horizontal === true) {
67
67
  if (parent === window) {
68
68
  details.scrollStart = window.pageXOffset || window.scrollX || document.body.scrollLeft || 0
69
- details.scrollViewSize += window.innerWidth
69
+ details.scrollViewSize += document.documentElement.clientWidth
70
70
  }
71
71
  else {
72
72
  details.scrollStart = parentCalc.scrollLeft
@@ -81,7 +81,7 @@ function getScrollDetails (
81
81
  else {
82
82
  if (parent === window) {
83
83
  details.scrollStart = window.pageYOffset || window.scrollY || document.body.scrollTop || 0
84
- details.scrollViewSize += window.innerHeight
84
+ details.scrollViewSize += document.documentElement.clientHeight
85
85
  }
86
86
  else {
87
87
  details.scrollStart = parentCalc.scrollTop
@@ -130,10 +130,16 @@ function getScrollDetails (
130
130
  }
131
131
 
132
132
  function setScroll (parent, scroll, horizontal, rtl) {
133
+ if (scroll === 'end') {
134
+ scroll = (parent === window ? document.body : parent)[
135
+ horizontal === true ? 'scrollWidth' : 'scrollHeight'
136
+ ]
137
+ }
138
+
133
139
  if (parent === window) {
134
140
  if (horizontal === true) {
135
141
  if (rtl === true) {
136
- scroll = (rtlHasScrollBug === true ? document.body.scrollWidth - window.innerWidth : 0) - scroll
142
+ scroll = (rtlHasScrollBug === true ? document.body.scrollWidth - document.documentElement.clientWidth : 0) - scroll
137
143
  }
138
144
  window.scrollTo(scroll, window.pageYOffset || window.scrollY || document.body.scrollTop || 0)
139
145
  }
@@ -398,20 +404,17 @@ export function useVirtualScroll ({
398
404
  }
399
405
 
400
406
  const { activeElement } = document
407
+ const contentEl = contentRef.value
401
408
  if (
402
409
  rangeChanged === true
403
- && contentRef.value !== null
404
- && contentRef.value !== activeElement
405
- && contentRef.value.contains(activeElement) === true
410
+ && contentEl !== null
411
+ && contentEl !== activeElement
412
+ && contentEl.contains(activeElement) === true
406
413
  ) {
407
- const onBlurFn = () => {
408
- contentRef.value.focus()
409
- }
414
+ contentEl.addEventListener('focusout', onBlurRefocusFn)
410
415
 
411
- activeElement.addEventListener('blur', onBlurFn, true)
412
-
413
- requestAnimationFrame(() => {
414
- activeElement.removeEventListener('blur', onBlurFn, true)
416
+ setTimeout(() => {
417
+ contentEl !== void 0 && contentEl.removeEventListener('focusout', onBlurRefocusFn)
415
418
  })
416
419
  }
417
420
 
@@ -489,8 +492,10 @@ export function useVirtualScroll ({
489
492
 
490
493
  if (contentEl) {
491
494
  const
492
- children = slice.call(contentEl.children)
493
- .filter(el => el.classList.contains('q-virtual-scroll--skip') === false),
495
+ children = filterProto.call(
496
+ contentEl.children,
497
+ el => el.classList && el.classList.contains('q-virtual-scroll--skip') === false
498
+ ),
494
499
  childrenLength = children.length,
495
500
  sizeFn = props.virtualScrollHorizontal === true
496
501
  ? el => el.getBoundingClientRect().width
@@ -521,6 +526,10 @@ export function useVirtualScroll ({
521
526
  }
522
527
  }
523
528
 
529
+ function onBlurRefocusFn () {
530
+ contentRef.value !== void 0 && contentRef.value.focus()
531
+ }
532
+
524
533
  function localResetVirtualScroll (toIndex, fullReset) {
525
534
  const defaultSize = 1 * virtualScrollItemSizeComputed.value
526
535
 
@@ -673,7 +682,10 @@ export function useVirtualScroll ({
673
682
  }
674
683
 
675
684
  setVirtualScrollSize()
676
- const onVirtualScrollEvt = debounce(localOnVirtualScrollEvt, $q.platform.is.ios === true ? 120 : 35)
685
+ const onVirtualScrollEvt = debounce(
686
+ localOnVirtualScrollEvt,
687
+ $q.platform.is.ios === true ? 120 : 35
688
+ )
677
689
 
678
690
  onBeforeMount(() => {
679
691
  setVirtualScrollSize()
@@ -698,6 +710,7 @@ export function useVirtualScroll ({
698
710
  setOverflowAnchor !== noop && onBeforeUnmount(() => {
699
711
  const styleSheet = document.getElementById(vsId + '_ss')
700
712
  styleSheet !== null && styleSheet.remove()
713
+ onVirtualScrollEvt.cancel()
701
714
  })
702
715
 
703
716
  // expose public methods
@@ -168,7 +168,7 @@ export default function (state) {
168
168
  isDirtyModel,
169
169
  hasRules,
170
170
  hasError,
171
- computedErrorMessage,
171
+ errorMessage,
172
172
  resetValidation
173
173
  } = useValidate(state.focused, state.innerLoading)
174
174
 
@@ -348,7 +348,7 @@ export default function (state) {
348
348
  nextTick(() => {
349
349
  resetValidation()
350
350
 
351
- if (props.lazyRules !== 'ondemand' && $q.platform.is.mobile !== true) {
351
+ if ($q.platform.is.mobile !== true) {
352
352
  isDirtyModel.value = false
353
353
  }
354
354
  })
@@ -473,9 +473,9 @@ export default function (state) {
473
473
  let msg, key
474
474
 
475
475
  if (hasError.value === true) {
476
- if (computedErrorMessage.value !== null) {
477
- msg = [ h('div', { role: 'alert' }, computedErrorMessage.value) ]
478
- key = `q--slot-error-${ computedErrorMessage.value }`
476
+ if (errorMessage.value !== null) {
477
+ msg = [ h('div', { role: 'alert' }, errorMessage.value) ]
478
+ key = `q--slot-error-${ errorMessage.value }`
479
479
  }
480
480
  else {
481
481
  msg = hSlot(slots.error)
@@ -1,6 +1,6 @@
1
1
  import { h, computed, getCurrentInstance } from 'vue'
2
2
 
3
- import { stopAndPrevent } from '../../utils/event.js'
3
+ import { stop, stopAndPrevent } from '../../utils/event.js'
4
4
 
5
5
  function filterFiles (files, rejectedFiles, failedPropValidation, filterFn) {
6
6
  const acceptedFiles = []
@@ -62,8 +62,18 @@ export default function ({
62
62
 
63
63
  function pickFiles (e) {
64
64
  if (editable.value) {
65
- const input = getFileInput()
66
- input && input.click(e)
65
+ if (e !== Object(e)) {
66
+ e = { target: null }
67
+ }
68
+
69
+ if (e.target !== null && e.target.matches('input[type="file"]') === true) {
70
+ // stop propagation if it's not a real pointer event
71
+ e.clientX === 0 && e.clientY === 0 && stop(e)
72
+ }
73
+ else {
74
+ const input = getFileInput()
75
+ input && input !== e.target && input.click(e)
76
+ }
67
77
  }
68
78
  }
69
79
 
@@ -112,10 +122,15 @@ export default function ({
112
122
  files = [ files[ 0 ] ]
113
123
  }
114
124
 
125
+ // Compute key to use for each file
126
+ files.forEach(file => {
127
+ file.__key = file.webkitRelativePath + file.lastModified + file.name + file.size
128
+ })
129
+
115
130
  // Avoid duplicate files
116
- const filenameMap = currentFileList.map(entry => entry.name)
131
+ const filenameMap = currentFileList.map(entry => entry.__key)
117
132
  files = filterFiles(files, rejectedFiles, 'duplicate', file => {
118
- return filenameMap.includes(file.name) === false
133
+ return filenameMap.includes(file.__key) === false
119
134
  })
120
135
 
121
136
  if (files.length === 0) { return done() }
@@ -12,13 +12,12 @@ export function useFormAttrs (props) {
12
12
  }))
13
13
  }
14
14
 
15
- export function useFormInject (formAttrs = {}, formDomProps = {}) {
15
+ export function useFormInject (formAttrs = {}) {
16
16
  return (child, action, className) => {
17
17
  child[ action ](
18
18
  h('input', {
19
19
  class: 'hidden' + (className || ''),
20
- ...formAttrs.value,
21
- ...formDomProps.value
20
+ ...formAttrs.value
22
21
  })
23
22
  )
24
23
  }
@@ -3,6 +3,8 @@ import { ref, watch, onBeforeMount, onMounted, onBeforeUnmount, getCurrentInstan
3
3
  import History from '../../history.js'
4
4
  import { vmHasRouter } from '../../utils/private/vm.js'
5
5
 
6
+ let counter = 0
7
+
6
8
  export const useFullscreenProps = {
7
9
  fullscreen: Boolean,
8
10
  noRouteFullscreenExit: Boolean
@@ -50,7 +52,11 @@ export default function () {
50
52
  container = proxy.$el.parentNode
51
53
  container.replaceChild(fullscreenFillerNode, proxy.$el)
52
54
  document.body.appendChild(proxy.$el)
53
- document.body.classList.add('q-body--fullscreen-mixin')
55
+
56
+ counter++
57
+ if (counter === 1) {
58
+ document.body.classList.add('q-body--fullscreen-mixin')
59
+ }
54
60
 
55
61
  historyEntry = {
56
62
  handler: exitFullscreen
@@ -69,11 +75,16 @@ export default function () {
69
75
  }
70
76
 
71
77
  container.replaceChild(proxy.$el, fullscreenFillerNode)
72
- document.body.classList.remove('q-body--fullscreen-mixin')
73
78
  inFullscreen.value = false
74
79
 
75
- if (proxy.$el.scrollIntoView !== void 0) {
76
- setTimeout(() => { proxy.$el.scrollIntoView() })
80
+ counter = Math.max(0, counter - 1)
81
+
82
+ if (counter === 0) {
83
+ document.body.classList.remove('q-body--fullscreen-mixin')
84
+
85
+ if (proxy.$el.scrollIntoView !== void 0) {
86
+ setTimeout(() => { proxy.$el.scrollIntoView() })
87
+ }
77
88
  }
78
89
  }
79
90
 
@@ -79,6 +79,7 @@ function isSameRouteLocationParams (a, b) {
79
79
  }
80
80
 
81
81
  export const useRouterLinkProps = {
82
+ // router-link
82
83
  to: [ String, Object ],
83
84
  replace: Boolean,
84
85
  exact: Boolean,
@@ -90,23 +91,33 @@ export const useRouterLinkProps = {
90
91
  type: String,
91
92
  default: 'q-router-link--exact-active'
92
93
  },
94
+
95
+ // regular <a> link
96
+ href: String,
97
+ target: String,
98
+
99
+ // state
93
100
  disable: Boolean
94
101
  }
95
102
 
96
- export default function () {
103
+ // external props: type, tag
104
+
105
+ export default function (fallbackTag) {
97
106
  const vm = getCurrentInstance()
98
- const { props, attrs, proxy } = vm
107
+ const { props, proxy } = vm
99
108
 
100
109
  const hasRouter = vmHasRouter(vm)
110
+ const hasHrefLink = computed(() => props.disable !== true && props.href !== void 0)
101
111
 
102
- const hasLinkConfigured = computed(() =>
112
+ const hasRouterLinkProps = computed(() =>
103
113
  hasRouter === true
104
114
  && props.disable !== true
115
+ && hasHrefLink.value !== true
105
116
  && props.to !== void 0 && props.to !== null && props.to !== ''
106
117
  )
107
118
 
108
119
  const linkRoute = computed(() => {
109
- if (hasLinkConfigured.value === true) {
120
+ if (hasRouterLinkProps.value === true) {
110
121
  try { return proxy.$router.resolve(props.to) }
111
122
  catch (err) {}
112
123
  }
@@ -114,16 +125,33 @@ export default function () {
114
125
  return null
115
126
  })
116
127
 
117
- const hasLink = computed(() => linkRoute.value !== null)
128
+ const hasRouterLink = computed(() => linkRoute.value !== null)
129
+ const hasLink = computed(() => hasHrefLink.value === true || hasRouterLink.value === true)
118
130
 
119
131
  const linkTag = computed(() => (
120
- hasLink.value === true
132
+ props.type === 'a' || hasLink.value === true
121
133
  ? 'a'
122
- : (props.tag || 'div')
134
+ : (props.tag || fallbackTag || 'div')
135
+ ))
136
+
137
+ const linkProps = computed(() => (
138
+ hasHrefLink.value === true
139
+ ? {
140
+ href: props.href,
141
+ target: props.target
142
+ }
143
+ : (
144
+ hasRouterLink.value === true
145
+ ? {
146
+ href: linkRoute.value.href,
147
+ target: props.target
148
+ }
149
+ : {}
150
+ )
123
151
  ))
124
152
 
125
153
  const linkActiveIndex = computed(() => {
126
- if (hasLink.value === false) {
154
+ if (hasRouterLink.value === false) {
127
155
  return null
128
156
  }
129
157
 
@@ -170,7 +198,7 @@ export default function () {
170
198
  })
171
199
 
172
200
  const linkIsActive = computed(() =>
173
- hasLink.value === true
201
+ hasRouterLink.value === true
174
202
  && linkActiveIndex.value > -1
175
203
  && includesParams(proxy.$route.params, linkRoute.value.params)
176
204
  )
@@ -182,7 +210,7 @@ export default function () {
182
210
  )
183
211
 
184
212
  const linkClass = computed(() => (
185
- hasLink.value === true
213
+ hasRouterLink.value === true
186
214
  ? (
187
215
  linkIsExactActive.value === true
188
216
  ? ` ${ props.exactActiveClass } ${ props.activeClass }`
@@ -195,18 +223,8 @@ export default function () {
195
223
  : ''
196
224
  ))
197
225
 
198
- const linkProps = computed(() => (
199
- hasLink.value === true
200
- ? {
201
- href: linkRoute.value.href,
202
- target: attrs.target,
203
- role: 'link'
204
- }
205
- : {}
206
- ))
207
-
208
226
  // should match RouterLink from Vue Router
209
- function navigateToLink (e) {
227
+ function navigateToRouterLink (e) {
210
228
  if (
211
229
  // component is not disabled
212
230
  props.disable === true
@@ -222,7 +240,7 @@ export default function () {
222
240
  || (e.button !== undefined && e.button !== 0)
223
241
 
224
242
  // don't redirect if it should open in a new window
225
- || attrs.target === '_blank'
243
+ || props.target === '_blank'
226
244
  ) {
227
245
  return false
228
246
  }
@@ -234,7 +252,10 @@ export default function () {
234
252
  }
235
253
 
236
254
  return {
255
+ hasRouterLink,
256
+ hasHrefLink,
237
257
  hasLink,
258
+
238
259
  linkTag,
239
260
  linkRoute,
240
261
  linkIsActive,
@@ -242,6 +263,6 @@ export default function () {
242
263
  linkClass,
243
264
  linkProps,
244
265
 
245
- navigateToLink
266
+ navigateToRouterLink
246
267
  }
247
268
  }
@@ -2,38 +2,54 @@
2
2
  "props": {
3
3
  "to": {
4
4
  "type": [ "String", "Object" ],
5
- "desc": "Equivalent to Vue Router <router-link> 'to' property",
5
+ "desc": "Equivalent to Vue Router <router-link> 'to' property; Superseeded by 'href' prop if used",
6
6
  "examples": [
7
7
  "/home/dashboard",
8
8
  ":to=\"{ name: 'my-route-name' }\""
9
9
  ],
10
- "category": "behavior"
10
+ "category": "navigation"
11
11
  },
12
12
 
13
13
  "exact": {
14
14
  "type": "Boolean",
15
- "desc": "Equivalent to Vue Router <router-link> 'exact' property",
16
- "category": "behavior"
15
+ "desc": "Equivalent to Vue Router <router-link> 'exact' property; Superseeded by 'href' prop if used",
16
+ "category": "navigation"
17
17
  },
18
18
 
19
19
  "replace": {
20
20
  "type": "Boolean",
21
- "desc": "Equivalent to Vue Router <router-link> 'replace' property",
22
- "category": "behavior"
21
+ "desc": "Equivalent to Vue Router <router-link> 'replace' property; Superseeded by 'href' prop if used",
22
+ "category": "navigation"
23
23
  },
24
24
 
25
25
  "active-class": {
26
26
  "type": "String",
27
- "desc": "Equivalent to Vue Router <router-link> 'active-class' property",
27
+ "desc": "Equivalent to Vue Router <router-link> 'active-class' property; Superseeded by 'href' prop if used",
28
28
  "examples": [ "my-active-class" ],
29
- "category": "behavior"
29
+ "category": "navigation"
30
30
  },
31
31
 
32
32
  "exact-active-class": {
33
33
  "type": "String",
34
- "desc": "Equivalent to Vue Router <router-link> 'active-class' property",
34
+ "desc": "Equivalent to Vue Router <router-link> 'active-class' property; Superseeded by 'href' prop if used",
35
35
  "examples": [ "my-exact-active-class" ],
36
- "category": "behavior"
36
+ "category": "navigation"
37
+ },
38
+
39
+ "href": {
40
+ "type": "String",
41
+ "desc": "Native <a> link href attribute; Has priority over the 'to'/'exact'/'replace'/'active-class'/'exact-active-class' props",
42
+ "examples": [ "http://quasar.dev" ],
43
+ "category": "navigation",
44
+ "addedIn": "v2.4"
45
+ },
46
+
47
+ "target": {
48
+ "type": "String",
49
+ "desc": "Native <a> link target attribute; Use it only along with 'href' prop; Has priority over the 'to'/'exact'/'replace'/'active-class'/'exact-active-class' props",
50
+ "examples": [ "_blank", "_self", "_parent", "_top" ],
51
+ "category": "navigation",
52
+ "addedIn": "v2.4"
37
53
  },
38
54
 
39
55
  "disable": {
@@ -12,17 +12,17 @@ export default function (attrs, vnode) {
12
12
  const attributes = {}
13
13
  const listeners = {}
14
14
 
15
- Object.keys(attrs).forEach(key => {
15
+ for (const key in attrs) {
16
16
  if (key !== 'class' && key !== 'style' && listenerRE.test(key) === false) {
17
17
  attributes[ key ] = attrs[ key ]
18
18
  }
19
- })
19
+ }
20
20
 
21
- Object.keys(vnode.props).forEach(key => {
21
+ for (const key in vnode.props) {
22
22
  if (listenerRE.test(key) === true) {
23
23
  listeners[ key ] = vnode.props[ key ]
24
24
  }
25
- })
25
+ }
26
26
 
27
27
  acc.attributes.value = attributes
28
28
  acc.listeners.value = listeners
@@ -2,6 +2,8 @@ import { ref, computed, watch, onBeforeUnmount, getCurrentInstance } from 'vue'
2
2
 
3
3
  import useFormChild from '../use-form-child.js'
4
4
  import { testPattern } from '../../utils/patterns.js'
5
+ import { debounce } from '../../utils.js'
6
+ import { injectProp } from '../../utils/private/inject-obj-prop.js'
5
7
 
6
8
  const lazyRulesValues = [ true, false, 'ondemand' ]
7
9
 
@@ -35,7 +37,8 @@ export default function (focused, innerLoading) {
35
37
  let validateIndex = 0, unwatchRules
36
38
 
37
39
  const hasRules = computed(() =>
38
- props.rules !== void 0
40
+ props.disable !== true
41
+ && props.rules !== void 0
39
42
  && props.rules !== null
40
43
  && props.rules.length > 0
41
44
  )
@@ -44,7 +47,7 @@ export default function (focused, innerLoading) {
44
47
  props.error === true || innerError.value === true
45
48
  )
46
49
 
47
- const computedErrorMessage = computed(() => (
50
+ const errorMessage = computed(() => (
48
51
  typeof props.errorMessage === 'string' && props.errorMessage.length > 0
49
52
  ? props.errorMessage
50
53
  : innerErrorMessage.value
@@ -69,15 +72,16 @@ export default function (focused, innerLoading) {
69
72
  }, { immediate: true })
70
73
 
71
74
  watch(focused, val => {
72
- if (props.lazyRules !== 'ondemand') {
73
- if (val === true) {
74
- if (isDirtyModel.value === null) {
75
- isDirtyModel.value = false
76
- }
75
+ if (val === true) {
76
+ if (isDirtyModel.value === null) {
77
+ isDirtyModel.value = false
77
78
  }
78
- else if (isDirtyModel.value === false && hasRules.value === true) {
79
- isDirtyModel.value = true
80
- validate()
79
+ }
80
+ else if (isDirtyModel.value === false) {
81
+ isDirtyModel.value = true
82
+
83
+ if (hasRules.value === true && props.lazyRules !== 'ondemand') {
84
+ debouncedValidate()
81
85
  }
82
86
  }
83
87
  })
@@ -88,6 +92,7 @@ export default function (focused, innerLoading) {
88
92
  isDirtyModel.value = null
89
93
  innerError.value = false
90
94
  innerErrorMessage.value = null
95
+ debouncedValidate.cancel()
91
96
  }
92
97
 
93
98
  /*
@@ -189,25 +194,26 @@ export default function (focused, innerLoading) {
189
194
  && props.lazyRules !== 'ondemand'
190
195
  && (isDirtyModel.value === true || (props.lazyRules !== true && changedRules !== true))
191
196
  ) {
192
- validate()
197
+ debouncedValidate()
193
198
  }
194
199
  }
195
200
 
201
+ const debouncedValidate = debounce(validate, 0)
202
+
196
203
  onBeforeUnmount(() => {
197
204
  unwatchRules !== void 0 && unwatchRules()
205
+ debouncedValidate.cancel()
198
206
  })
199
207
 
200
208
  // expose public methods & props
201
209
  Object.assign(proxy, { resetValidation, validate })
202
- Object.defineProperty(proxy, 'hasError', {
203
- get: () => hasError.value
204
- })
210
+ injectProp(proxy, 'hasError', () => hasError.value)
205
211
 
206
212
  return {
207
213
  isDirtyModel,
208
214
  hasRules,
209
215
  hasError,
210
- computedErrorMessage,
216
+ errorMessage,
211
217
 
212
218
  validate,
213
219
  resetValidation
@@ -28,6 +28,7 @@
28
28
 
29
29
  "rules": {
30
30
  "type": "Array",
31
+ "tsType": "ValidationRule",
31
32
  "desc": "Array of Functions/Strings; If String, then it must be a name of one of the embedded validation rules",
32
33
  "examples": [
33
34
  ":rules=\"[ val => val.length <= 3 || 'Please use maximum 3 characters' ]\"",
@@ -49,6 +49,9 @@
49
49
  outline: 0
50
50
  text-decoration: none
51
51
 
52
+ &--focusable:focus-visible
53
+ text-decoration: underline dashed currentColor 1px
54
+
52
55
  body.electron
53
56
  .q-electron-drag
54
57
  -webkit-user-select: none
@@ -69,6 +69,11 @@
69
69
  max-width: 100vw
70
70
  max-height: 100vh
71
71
 
72
+ body.q-ios-padding .fullscreen
73
+ padding-top: $ios-statusbar-height !important
74
+ padding-top: env(safe-area-inset-top) !important
75
+ padding-bottom: env(safe-area-inset-bottom) !important
76
+
72
77
  .absolute-full, .fullscreen, .fixed-full
73
78
  top: 0
74
79
  right: 0
@@ -61,6 +61,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
61
61
  beforeUnmount (el) {
62
62
  const ctx = el.__qscrollfire
63
63
  ctx.scrollTarget.removeEventListener('scroll', ctx.scroll, passive)
64
+ ctx.scroll.cancel()
64
65
  delete el.__qscrollfire
65
66
  }
66
67
  }
package/src/icon-set.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import defineReactivePlugin from './utils/private/define-reactive-plugin.js'
2
2
  import materialIcons from '../icon-set/material-icons.js'
3
+ import { injectProp } from './utils/private/inject-obj-prop.js'
3
4
 
4
5
  const Plugin = defineReactivePlugin({
5
6
  iconMapFn: null,
@@ -42,10 +43,7 @@ const Plugin = defineReactivePlugin({
42
43
 
43
44
  $q.iconSet = this.__icons
44
45
 
45
- Object.defineProperty($q, 'iconMapFn', {
46
- get: () => this.iconMapFn,
47
- set: val => { this.iconMapFn = val }
48
- })
46
+ injectProp($q, 'iconMapFn', () => this.iconMapFn, val => { this.iconMapFn = val })
49
47
 
50
48
  if (this.__installed === true) {
51
49
  iconSet !== void 0 && this.set(iconSet)