quasar 2.21.4 → 2.23.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 (193) hide show
  1. package/README.md +1 -1
  2. package/dist/api/Mutation.json +1 -1
  3. package/dist/api/QLinearProgress.json +1 -1
  4. package/dist/api/QUploader.json +1 -1
  5. package/dist/icon-set/bootstrap-icons.umd.prod.js +1 -1
  6. package/dist/icon-set/eva-icons.umd.prod.js +1 -1
  7. package/dist/icon-set/fontawesome-v5-pro.umd.prod.js +1 -1
  8. package/dist/icon-set/fontawesome-v5.umd.prod.js +1 -1
  9. package/dist/icon-set/fontawesome-v6-pro.umd.prod.js +1 -1
  10. package/dist/icon-set/fontawesome-v6.umd.prod.js +1 -1
  11. package/dist/icon-set/fontawesome-v7-pro.umd.prod.js +1 -1
  12. package/dist/icon-set/fontawesome-v7.umd.prod.js +1 -1
  13. package/dist/icon-set/ionicons-v4.umd.prod.js +1 -1
  14. package/dist/icon-set/line-awesome.umd.prod.js +1 -1
  15. package/dist/icon-set/material-icons-outlined.umd.prod.js +1 -1
  16. package/dist/icon-set/material-icons-round.umd.prod.js +1 -1
  17. package/dist/icon-set/material-icons-sharp.umd.prod.js +1 -1
  18. package/dist/icon-set/material-icons.umd.prod.js +1 -1
  19. package/dist/icon-set/material-symbols-outlined.umd.prod.js +1 -1
  20. package/dist/icon-set/material-symbols-rounded.umd.prod.js +1 -1
  21. package/dist/icon-set/material-symbols-sharp.umd.prod.js +1 -1
  22. package/dist/icon-set/mdi-v3.umd.prod.js +1 -1
  23. package/dist/icon-set/mdi-v4.umd.prod.js +1 -1
  24. package/dist/icon-set/mdi-v5.umd.prod.js +1 -1
  25. package/dist/icon-set/mdi-v6.umd.prod.js +1 -1
  26. package/dist/icon-set/mdi-v7.umd.prod.js +1 -1
  27. package/dist/icon-set/svg-bootstrap-icons.umd.prod.js +1 -1
  28. package/dist/icon-set/svg-eva-icons.umd.prod.js +1 -1
  29. package/dist/icon-set/svg-fontawesome-v5.umd.prod.js +1 -1
  30. package/dist/icon-set/svg-fontawesome-v6.umd.prod.js +1 -1
  31. package/dist/icon-set/svg-fontawesome-v7.umd.prod.js +1 -1
  32. package/dist/icon-set/svg-ionicons-v4.umd.prod.js +1 -1
  33. package/dist/icon-set/svg-ionicons-v5.umd.prod.js +1 -1
  34. package/dist/icon-set/svg-ionicons-v6.umd.prod.js +1 -1
  35. package/dist/icon-set/svg-ionicons-v7.umd.prod.js +1 -1
  36. package/dist/icon-set/svg-ionicons-v8.umd.prod.js +1 -1
  37. package/dist/icon-set/svg-line-awesome.umd.prod.js +1 -1
  38. package/dist/icon-set/svg-material-icons-outlined.umd.prod.js +1 -1
  39. package/dist/icon-set/svg-material-icons-round.umd.prod.js +1 -1
  40. package/dist/icon-set/svg-material-icons-sharp.umd.prod.js +1 -1
  41. package/dist/icon-set/svg-material-icons.umd.prod.js +1 -1
  42. package/dist/icon-set/svg-material-symbols-outlined.umd.prod.js +1 -1
  43. package/dist/icon-set/svg-material-symbols-rounded.umd.prod.js +1 -1
  44. package/dist/icon-set/svg-material-symbols-sharp.umd.prod.js +1 -1
  45. package/dist/icon-set/svg-mdi-v6.umd.prod.js +1 -1
  46. package/dist/icon-set/svg-mdi-v7.umd.prod.js +1 -1
  47. package/dist/icon-set/svg-themify.umd.prod.js +1 -1
  48. package/dist/icon-set/themify.umd.prod.js +1 -1
  49. package/dist/lang/ar-TN.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/bs-BA.umd.prod.js +1 -1
  55. package/dist/lang/ca.umd.prod.js +1 -1
  56. package/dist/lang/cs.umd.prod.js +1 -1
  57. package/dist/lang/da.umd.prod.js +1 -1
  58. package/dist/lang/de-CH.umd.prod.js +1 -1
  59. package/dist/lang/de-DE.umd.prod.js +1 -1
  60. package/dist/lang/de.umd.prod.js +1 -1
  61. package/dist/lang/el.umd.prod.js +1 -1
  62. package/dist/lang/en-GB.umd.prod.js +1 -1
  63. package/dist/lang/en-US.umd.prod.js +1 -1
  64. package/dist/lang/eo.umd.prod.js +1 -1
  65. package/dist/lang/es.umd.prod.js +1 -1
  66. package/dist/lang/et.umd.prod.js +1 -1
  67. package/dist/lang/eu.umd.prod.js +1 -1
  68. package/dist/lang/fa-IR.umd.prod.js +1 -1
  69. package/dist/lang/fa.umd.prod.js +1 -1
  70. package/dist/lang/fi.umd.prod.js +1 -1
  71. package/dist/lang/fr.umd.prod.js +1 -1
  72. package/dist/lang/gn.umd.prod.js +1 -1
  73. package/dist/lang/he.umd.prod.js +1 -1
  74. package/dist/lang/hi.umd.prod.js +1 -1
  75. package/dist/lang/hr.umd.prod.js +1 -1
  76. package/dist/lang/hu.umd.prod.js +1 -1
  77. package/dist/lang/id.umd.prod.js +1 -1
  78. package/dist/lang/is.umd.prod.js +1 -1
  79. package/dist/lang/it.umd.prod.js +1 -1
  80. package/dist/lang/ja.umd.prod.js +1 -1
  81. package/dist/lang/kk.umd.prod.js +1 -1
  82. package/dist/lang/km.umd.prod.js +1 -1
  83. package/dist/lang/ko-KR.umd.prod.js +1 -1
  84. package/dist/lang/kur-CKB.umd.prod.js +1 -1
  85. package/dist/lang/lb.umd.prod.js +1 -1
  86. package/dist/lang/lt.umd.prod.js +1 -1
  87. package/dist/lang/lu.umd.prod.js +1 -1
  88. package/dist/lang/lv.umd.prod.js +1 -1
  89. package/dist/lang/mk.umd.prod.js +1 -1
  90. package/dist/lang/ml.umd.prod.js +1 -1
  91. package/dist/lang/mm.umd.prod.js +1 -1
  92. package/dist/lang/ms-MY.umd.prod.js +1 -1
  93. package/dist/lang/ms.umd.prod.js +1 -1
  94. package/dist/lang/my.umd.prod.js +1 -1
  95. package/dist/lang/nb-NO.umd.prod.js +1 -1
  96. package/dist/lang/nl.umd.prod.js +1 -1
  97. package/dist/lang/pl.umd.prod.js +1 -1
  98. package/dist/lang/pt-BR.umd.prod.js +1 -1
  99. package/dist/lang/pt.umd.prod.js +1 -1
  100. package/dist/lang/ro.umd.prod.js +1 -1
  101. package/dist/lang/ru.umd.prod.js +1 -1
  102. package/dist/lang/sk.umd.prod.js +1 -1
  103. package/dist/lang/sl.umd.prod.js +1 -1
  104. package/dist/lang/sm.umd.prod.js +1 -1
  105. package/dist/lang/sq.umd.prod.js +1 -1
  106. package/dist/lang/sr-CYR.umd.prod.js +1 -1
  107. package/dist/lang/sr.umd.prod.js +1 -1
  108. package/dist/lang/sv.umd.prod.js +1 -1
  109. package/dist/lang/ta.umd.prod.js +1 -1
  110. package/dist/lang/th.umd.prod.js +1 -1
  111. package/dist/lang/tl.umd.prod.js +1 -1
  112. package/dist/lang/tr.umd.prod.js +1 -1
  113. package/dist/lang/ug.umd.prod.js +1 -1
  114. package/dist/lang/uk.umd.prod.js +1 -1
  115. package/dist/lang/ur-PK.umd.prod.js +1 -1
  116. package/dist/lang/uz-Cyrl.umd.prod.js +1 -1
  117. package/dist/lang/uz-Latn.umd.prod.js +1 -1
  118. package/dist/lang/vi.umd.prod.js +1 -1
  119. package/dist/lang/zh-CN.umd.prod.js +1 -1
  120. package/dist/lang/zh-TW.umd.prod.js +1 -1
  121. package/dist/quasar.client.js +654 -207
  122. package/dist/quasar.css +20 -11
  123. package/dist/quasar.prod.css +1 -1
  124. package/dist/quasar.rtl.css +25 -20
  125. package/dist/quasar.rtl.prod.css +1 -1
  126. package/dist/quasar.sass +21 -12
  127. package/dist/quasar.server.prod.cjs +35 -35
  128. package/dist/quasar.server.prod.js +25 -25
  129. package/dist/quasar.umd.js +654 -206
  130. package/dist/quasar.umd.prod.js +19 -19
  131. package/dist/transforms/import-map.json +1 -1
  132. package/dist/types/composables.d.ts +15 -5
  133. package/dist/types/index.d.ts +5 -5
  134. package/dist/web-types/web-types.json +1 -1
  135. package/package.json +6 -6
  136. package/src/components/btn/QBtn.js +1 -1
  137. package/src/components/carousel/QCarousel.js +2 -0
  138. package/src/components/chip/QChip.js +20 -4
  139. package/src/components/date/QDate.js +9 -2
  140. package/src/components/dialog/QDialog.js +9 -2
  141. package/src/components/editor/QEditor.js +18 -3
  142. package/src/components/expansion-item/QExpansionItem.js +6 -1
  143. package/src/components/fab/QFab.sass +11 -9
  144. package/src/components/input/QInput.js +42 -1
  145. package/src/components/input/QInput.json +7 -0
  146. package/src/components/input/use-mask.js +12 -4
  147. package/src/components/item/QItem.js +10 -1
  148. package/src/components/linear-progress/QLinearProgress.js +4 -11
  149. package/src/components/linear-progress/QLinearProgress.json +1 -1
  150. package/src/components/linear-progress/QLinearProgress.sass +7 -2
  151. package/src/components/menu/QMenu.js +9 -2
  152. package/src/components/range/QRange.js +2 -1
  153. package/src/components/rating/QRating.js +27 -8
  154. package/src/components/rating/QRating.sass +4 -0
  155. package/src/components/resize-observer/QResizeObserver.js +14 -3
  156. package/src/components/scroll-area/QScrollArea.js +69 -17
  157. package/src/components/slide-item/QSlideItem.js +3 -2
  158. package/src/components/slider/use-slider.js +7 -3
  159. package/src/components/stepper/StepHeader.js +11 -2
  160. package/src/components/table/QTable.js +11 -9
  161. package/src/components/table/QTh.js +20 -1
  162. package/src/components/table/table-column-selection.js +9 -1
  163. package/src/components/table/table-pagination.js +5 -2
  164. package/src/components/table/table-row-selection.js +10 -10
  165. package/src/components/tabs/QTabs.js +5 -6
  166. package/src/components/time/QTime.js +35 -10
  167. package/src/components/tooltip/QTooltip.js +112 -6
  168. package/src/components/tree/QTree.js +124 -4
  169. package/src/components/uploader/uploader-core.js +19 -4
  170. package/src/components/uploader/xhr-uploader-plugin.js +93 -43
  171. package/src/components/uploader/xhr-uploader-plugin.json +4 -4
  172. package/src/composables/private.use-field/use-field.js +2 -0
  173. package/src/composables/private.use-file/use-file.js +21 -12
  174. package/src/composables/private.use-fullscreen/use-fullscreen.js +71 -11
  175. package/src/composables/private.use-fullscreen/use-fullscreen.test.js +158 -0
  176. package/src/composables/private.use-model-toggle/use-model-toggle.js +2 -0
  177. package/src/composables/private.use-panel/use-panel.js +6 -2
  178. package/src/composables/use-animation-frame/use-animation-frame.js +47 -0
  179. package/src/composables/use-animation-frame/use-animation-frame.test.js +122 -0
  180. package/src/composables/use-interval/use-interval.js +8 -0
  181. package/src/composables/use-render-cache/use-render-cache.js +15 -10
  182. package/src/composables/use-timeout/use-timeout.js +8 -0
  183. package/src/composables.js +1 -1
  184. package/src/directives/intersection/Intersection.js +2 -2
  185. package/src/directives/mutation/Mutation.json +1 -1
  186. package/src/directives/touch-repeat/TouchRepeat.js +3 -1
  187. package/src/plugins/cookies/Cookies.js +29 -13
  188. package/src/plugins/meta/Meta.js +57 -9
  189. package/src/utils/extend/extend.js +4 -0
  190. package/src/utils/extend/extend.test.js +29 -0
  191. package/src/utils/private.click-outside/click-outside.js +27 -12
  192. package/src/utils/private.click-outside/click-outside.test.js +132 -0
  193. package/src/utils/scroll/prevent-scroll.js +4 -4
@@ -29,7 +29,7 @@ import useDatetime, {
29
29
  import { createComponent } from '../../utils/private.create/create.js'
30
30
  import { hSlot } from '../../utils/private.render/render.js'
31
31
  import { __splitDate, formatDate } from '../../utils/date/date.js'
32
- import { position } from '../../utils/event/event.js'
32
+ import { position, stopAndPrevent } from '../../utils/event/event.js'
33
33
  import { pad } from '../../utils/format/format.js'
34
34
  import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
35
35
 
@@ -58,6 +58,10 @@ function getCurrentTime() {
58
58
  }
59
59
  }
60
60
 
61
+ function preventSpace(e) {
62
+ if (e.keyCode === 32) stopAndPrevent(e)
63
+ }
64
+
61
65
  function getWheelDist(a, b, threshold) {
62
66
  const diff = Math.abs(a - b)
63
67
  return Math.min(diff, threshold - diff)
@@ -536,11 +540,17 @@ export default createComponent({
536
540
  }
537
541
 
538
542
  function setAmOnKey(e) {
539
- if (e.keyCode === 13) setAm()
543
+ if ([13, 32].includes(e.keyCode)) {
544
+ setAm()
545
+ stopAndPrevent(e)
546
+ }
540
547
  }
541
548
 
542
549
  function setPmOnKey(e) {
543
- if (e.keyCode === 13) setPm()
550
+ if ([13, 32].includes(e.keyCode)) {
551
+ setPm()
552
+ stopAndPrevent(e)
553
+ }
544
554
  }
545
555
 
546
556
  function onClick(evt) {
@@ -562,9 +572,9 @@ export default createComponent({
562
572
  }
563
573
 
564
574
  function onKeyupHour(e) {
565
- if (e.keyCode === 13) {
566
- // ENTER
575
+ if ([13, 32].includes(e.keyCode)) {
567
576
  view.value = 'hour'
577
+ stopAndPrevent(e)
568
578
  } else if ([37, 39].includes(e.keyCode)) {
569
579
  const payload = e.keyCode === 37 ? -1 : 1
570
580
 
@@ -598,9 +608,9 @@ export default createComponent({
598
608
  }
599
609
 
600
610
  function onKeyupMinute(e) {
601
- if (e.keyCode === 13) {
602
- // ENTER
611
+ if ([13, 32].includes(e.keyCode)) {
603
612
  view.value = 'minute'
613
+ stopAndPrevent(e)
604
614
  } else if ([37, 39].includes(e.keyCode)) {
605
615
  const payload = e.keyCode === 37 ? -1 : 1
606
616
 
@@ -631,9 +641,9 @@ export default createComponent({
631
641
  }
632
642
 
633
643
  function onKeyupSecond(e) {
634
- if (e.keyCode === 13) {
635
- // ENTER
644
+ if ([13, 32].includes(e.keyCode)) {
636
645
  view.value = 'second'
646
+ stopAndPrevent(e)
637
647
  } else if ([37, 39].includes(e.keyCode)) {
638
648
  const payload = e.keyCode === 37 ? -1 : 1
639
649
 
@@ -642,7 +652,7 @@ export default createComponent({
642
652
 
643
653
  if (values.length === 0) return
644
654
 
645
- if (innerModel.value.seconds === null) {
655
+ if (innerModel.value.second === null) {
646
656
  setSecond(values[0])
647
657
  } else {
648
658
  const index =
@@ -806,7 +816,10 @@ export default createComponent({
806
816
  ? 'q-time__link--active'
807
817
  : 'cursor-pointer'),
808
818
  tabindex: tabindex.value,
819
+ role: 'button',
820
+ 'aria-pressed': view.value === 'hour' ? 'true' : 'false',
809
821
  onClick: setView.hour,
822
+ onKeydown: preventSpace,
810
823
  onKeyup: onKeyupHour
811
824
  },
812
825
  stringModel.value.hour
@@ -824,6 +837,9 @@ export default createComponent({
824
837
  ? 'q-time__link--active'
825
838
  : 'cursor-pointer'),
826
839
  tabindex: tabindex.value,
840
+ role: 'button',
841
+ 'aria-pressed': view.value === 'minute' ? 'true' : 'false',
842
+ onKeydown: preventSpace,
827
843
  onKeyup: onKeyupMinute,
828
844
  onClick: setView.minute
829
845
  }
@@ -846,6 +862,9 @@ export default createComponent({
846
862
  ? 'q-time__link--active'
847
863
  : 'cursor-pointer'),
848
864
  tabindex: tabindex.value,
865
+ role: 'button',
866
+ 'aria-pressed': view.value === 'second' ? 'true' : 'false',
867
+ onKeydown: preventSpace,
849
868
  onKeyup: onKeyupSecond,
850
869
  onClick: setView.second
851
870
  }
@@ -881,7 +900,10 @@ export default createComponent({
881
900
  'q-time__link ' +
882
901
  (isAM.value ? 'q-time__link--active' : 'cursor-pointer'),
883
902
  tabindex: tabindex.value,
903
+ role: 'button',
904
+ 'aria-pressed': isAM.value ? 'true' : 'false',
884
905
  onClick: setAm,
906
+ onKeydown: preventSpace,
885
907
  onKeyup: setAmOnKey
886
908
  },
887
909
  'AM'
@@ -894,7 +916,10 @@ export default createComponent({
894
916
  'q-time__link ' +
895
917
  (isAM.value ? 'cursor-pointer' : 'q-time__link--active'),
896
918
  tabindex: tabindex.value,
919
+ role: 'button',
920
+ 'aria-pressed': isAM.value ? 'false' : 'true',
897
921
  onClick: setPm,
922
+ onKeydown: preventSpace,
898
923
  onKeyup: setPmOnKey
899
924
  },
900
925
  'PM'
@@ -22,10 +22,15 @@ import useTransition, {
22
22
  } from '../../composables/private.use-transition/use-transition.js'
23
23
  import useTick from '../../composables/use-tick/use-tick.js'
24
24
  import useTimeout from '../../composables/use-timeout/use-timeout.js'
25
+ import useId from '../../composables/use-id/use-id.js'
25
26
 
26
27
  import { createComponent } from '../../utils/private.create/create.js'
27
28
  import { getScrollTarget, scrollTargetProp } from '../../utils/scroll/scroll.js'
28
29
  import { addEvt, cleanEvt, stopAndPrevent } from '../../utils/event/event.js'
30
+ import {
31
+ addEscapeKey,
32
+ removeEscapeKey
33
+ } from '../../utils/private.keyboard/escape-key.js'
29
34
  import { clearSelection } from '../../utils/private.selection/selection.js'
30
35
  import { hSlot } from '../../utils/private.render/render.js'
31
36
  import {
@@ -39,6 +44,8 @@ import {
39
44
  validatePosition
40
45
  } from '../../utils/private.position-engine/position-engine.js'
41
46
 
47
+ let nonSelectableCount = 0
48
+
42
49
  export default createComponent({
43
50
  name: 'QTooltip',
44
51
 
@@ -101,7 +108,11 @@ export default createComponent({
101
108
  emits: [...useModelToggleEmits],
102
109
 
103
110
  setup(props, { slots, emit, attrs }) {
104
- let unwatchPosition, observer
111
+ let unwatchPosition,
112
+ observer,
113
+ removeNonSelectableTimer,
114
+ hasNonSelectable = false,
115
+ describedBy
105
116
 
106
117
  const vm = getCurrentInstance()
107
118
  const {
@@ -110,6 +121,8 @@ export default createComponent({
110
121
 
111
122
  const innerRef = ref(null)
112
123
  const showing = ref(false)
124
+ const targetUid = useId()
125
+ const tooltipId = computed(() => attrs.id || targetUid.value)
113
126
 
114
127
  const anchorOrigin = computed(() =>
115
128
  parsePosition(props.anchor, $q.lang.rtl)
@@ -137,7 +150,7 @@ export default createComponent({
137
150
  processOnMount: true
138
151
  })
139
152
 
140
- Object.assign(anchorEvents, { delayShow, delayHide })
153
+ Object.assign(anchorEvents, { delayShow, delayHide, onFocusin })
141
154
 
142
155
  const { showPortal, hidePortal, renderPortal } = usePortal(
143
156
  vm,
@@ -182,10 +195,26 @@ export default createComponent({
182
195
  onBeforeUnmount(() => {
183
196
  removeClickOutside(clickOutsideProps)
184
197
  })
198
+ } else {
199
+ // dismiss with the ESC key (WCAG 1.4.13) without moving focus;
200
+ // uses the shared escape stack so only the top-most popup reacts
201
+ watch(
202
+ () =>
203
+ // trigger only if it doesn't have external model
204
+ // or else only if the model can be updated (otherwise respect the external model)
205
+ (props.modelValue === null || props['onUpdate:modelValue']) &&
206
+ showing.value === true &&
207
+ props.persistent !== true,
208
+ val => {
209
+ const fn = val === true ? addEscapeKey : removeEscapeKey
210
+ fn(onEscapeKey)
211
+ }
212
+ )
185
213
  }
186
214
 
187
215
  function handleShow(evt) {
188
216
  showPortal()
217
+ addAriaDescription()
189
218
 
190
219
  // should removeTick() if this gets removed
191
220
  registerTick(() => {
@@ -254,7 +283,10 @@ export default createComponent({
254
283
  }
255
284
 
256
285
  unconfigureScrollTarget()
286
+ removeEscapeKey(onEscapeKey)
257
287
  cleanEvt(anchorEvents, 'tooltipTemp')
288
+ removeAriaDescription()
289
+ setNonSelectable(false)
258
290
  }
259
291
 
260
292
  function updatePosition() {
@@ -271,8 +303,13 @@ export default createComponent({
271
303
 
272
304
  function delayShow(evt) {
273
305
  if ($q.platform.is.mobile) {
306
+ if (removeNonSelectableTimer !== void 0) {
307
+ clearTimeout(removeNonSelectableTimer)
308
+ removeNonSelectableTimer = void 0
309
+ }
310
+
274
311
  clearSelection()
275
- document.body.classList.add('non-selectable')
312
+ setNonSelectable(true)
276
313
 
277
314
  const target = anchorEl.value
278
315
  const evts = ['touchmove', 'touchcancel', 'touchend', 'click'].map(
@@ -292,8 +329,9 @@ export default createComponent({
292
329
  cleanEvt(anchorEvents, 'tooltipTemp')
293
330
  clearSelection()
294
331
  // delay needed otherwise selection still occurs
295
- setTimeout(() => {
296
- document.body.classList.remove('non-selectable')
332
+ removeNonSelectableTimer = setTimeout(() => {
333
+ removeNonSelectableTimer = void 0
334
+ setNonSelectable(false)
297
335
  }, 10)
298
336
  }
299
337
 
@@ -303,6 +341,26 @@ export default createComponent({
303
341
  }, props.hideDelay)
304
342
  }
305
343
 
344
+ function onFocusin(evt) {
345
+ const el = evt.target
346
+ if (!el) return
347
+
348
+ // only react to keyboard focus, not to focus coming from a pointer,
349
+ // so the tooltip doesn't pop up when the target is clicked;
350
+ // guard the call for engines that don't support :focus-visible
351
+ try {
352
+ if (el.matches(':focus-visible') === false) return
353
+ } catch {}
354
+
355
+ delayShow(evt)
356
+ }
357
+
358
+ // trigger only if it doesn't have external model
359
+ // or else only if the model can be updated (otherwise respect the external model)
360
+ function onEscapeKey(evt) {
361
+ hide(evt)
362
+ }
363
+
306
364
  function configureAnchorEl() {
307
365
  if (props.noParentEvent || anchorEl.value === null) return
308
366
 
@@ -310,12 +368,59 @@ export default createComponent({
310
368
  ? [[anchorEl.value, 'touchstart', 'delayShow', 'passive']]
311
369
  : [
312
370
  [anchorEl.value, 'mouseenter', 'delayShow', 'passive'],
313
- [anchorEl.value, 'mouseleave', 'delayHide', 'passive']
371
+ [anchorEl.value, 'mouseleave', 'delayHide', 'passive'],
372
+ [anchorEl.value, 'focusin', 'onFocusin', 'passive'],
373
+ [anchorEl.value, 'focusout', 'delayHide', 'passive']
314
374
  ]
315
375
 
316
376
  addEvt(anchorEvents, 'anchor', evts)
317
377
  }
318
378
 
379
+ function setNonSelectable(state) {
380
+ if (hasNonSelectable === state) return
381
+
382
+ hasNonSelectable = state
383
+ nonSelectableCount += state ? 1 : -1
384
+ document.body.classList.toggle('non-selectable', nonSelectableCount > 0)
385
+
386
+ if (!state && removeNonSelectableTimer !== void 0) {
387
+ clearTimeout(removeNonSelectableTimer)
388
+ removeNonSelectableTimer = void 0
389
+ }
390
+ }
391
+
392
+ function addAriaDescription() {
393
+ const el = anchorEl.value,
394
+ id = tooltipId.value
395
+
396
+ if (el === null || id === void 0) return
397
+
398
+ const ids = (el.getAttribute('aria-describedby') || '')
399
+ .split(/\s+/)
400
+ .filter(Boolean)
401
+
402
+ describedBy = { el, id, added: !ids.includes(id) }
403
+
404
+ if (describedBy.added) {
405
+ ids.push(id)
406
+ el.setAttribute('aria-describedby', ids.join(' '))
407
+ }
408
+ }
409
+
410
+ function removeAriaDescription() {
411
+ if (describedBy?.added === true) {
412
+ const { el, id } = describedBy,
413
+ value = (el.getAttribute('aria-describedby') || '')
414
+ .split(/\s+/)
415
+ .filter(entry => entry !== '' && entry !== id)
416
+
417
+ if (value.length === 0) el.removeAttribute('aria-describedby')
418
+ else el.setAttribute('aria-describedby', value.join(' '))
419
+ }
420
+
421
+ describedBy = void 0
422
+ }
423
+
319
424
  function configureScrollTarget() {
320
425
  if (anchorEl.value !== null || props.scrollTarget !== void 0) {
321
426
  localScrollTarget.value = getScrollTarget(
@@ -334,6 +439,7 @@ export default createComponent({
334
439
  'div',
335
440
  {
336
441
  ...attrs,
442
+ id: tooltipId.value,
337
443
  ref: innerRef,
338
444
  class: [
339
445
  'q-tooltip q-tooltip--style q-position-engine no-pointer-events',
@@ -118,11 +118,14 @@ export default createComponent({
118
118
  const lazy = ref({})
119
119
  const innerTicked = ref(props.ticked || [])
120
120
  const innerExpanded = ref(props.expanded || [])
121
+ const focusedKey = ref(null)
121
122
 
122
- let blurTargets = {}
123
+ let blurTargets = {},
124
+ headerTargets = {}
123
125
 
124
126
  onBeforeUpdate(() => {
125
127
  blurTargets = {}
128
+ headerTargets = {}
126
129
  })
127
130
 
128
131
  const classes = computed(
@@ -306,6 +309,38 @@ export default createComponent({
306
309
  return acc
307
310
  })
308
311
 
312
+ const focusableKeys = computed(() => {
313
+ const acc = []
314
+
315
+ function travel(nodes) {
316
+ nodes.forEach(node => {
317
+ const localMeta = meta.value[node[props.nodeKey]]
318
+
319
+ if (props.filter && localMeta.matchesFilter !== true) return
320
+ if (localMeta.link === true) acc.push(localMeta.key)
321
+
322
+ if (
323
+ localMeta.expanded === true &&
324
+ Array.isArray(node[props.childrenKey])
325
+ ) {
326
+ travel(node[props.childrenKey])
327
+ }
328
+ })
329
+ }
330
+
331
+ travel(props.nodes)
332
+ return acc
333
+ })
334
+
335
+ const tabKey = computed(() => {
336
+ const keys = focusableKeys.value
337
+
338
+ if (keys.includes(focusedKey.value)) return focusedKey.value
339
+
340
+ const selected = keys.find(key => meta.value[key].selected === true)
341
+ return selected !== void 0 ? selected : keys[0]
342
+ })
343
+
309
344
  watch(
310
345
  () => props.ticked,
311
346
  val => {
@@ -576,18 +611,36 @@ export default createComponent({
576
611
  : '') +
577
612
  (m.selected === true ? ' q-tree__node--selected' : '') +
578
613
  (m.disabled === true ? ' q-tree__node--disabled' : ''),
579
- tabindex: m.link === true ? 0 : -1,
580
- ariaExpanded: children.length !== 0 ? m.expanded : null,
614
+ ref: el => {
615
+ headerTargets[m.key] = el
616
+ },
617
+ tabindex: m.link === true && m.key === tabKey.value ? 0 : -1,
618
+ 'aria-expanded': isParent
619
+ ? m.expanded
620
+ ? 'true'
621
+ : 'false'
622
+ : null,
623
+ 'aria-selected': m.selectable
624
+ ? m.selected
625
+ ? 'true'
626
+ : 'false'
627
+ : null,
628
+ 'aria-disabled': m.disabled === true ? 'true' : null,
581
629
  role: 'treeitem',
630
+ onFocus() {
631
+ if (m.link === true) focusedKey.value = m.key
632
+ },
582
633
  onClick: e => {
583
634
  onClick(node, m, e)
584
635
  },
585
- onKeypress(e) {
636
+ onKeydown(e) {
586
637
  if (shouldIgnoreKey(e) !== true) {
587
638
  if (e.keyCode === 13) {
588
639
  onClick(node, m, e, true)
589
640
  } else if (e.keyCode === 32) {
590
641
  onExpandClick(node, m, e, true)
642
+ } else if (onNavigationKey(m, e.keyCode)) {
643
+ stopAndPrevent(e)
591
644
  }
592
645
  }
593
646
  }
@@ -722,7 +775,72 @@ export default createComponent({
722
775
  blurTargets[key]?.focus()
723
776
  }
724
777
 
778
+ function focusNode(key) {
779
+ if (key !== void 0) {
780
+ focusedKey.value = key
781
+ headerTargets[key]?.focus()
782
+ }
783
+ }
784
+
785
+ function getFirstFocusableChild(children) {
786
+ for (const child of children) {
787
+ if (props.filter && child.matchesFilter !== true) continue
788
+ if (child.link === true) return child.key
789
+
790
+ if (child.expanded === true) {
791
+ const key = getFirstFocusableChild(child.children)
792
+ if (key !== void 0) return key
793
+ }
794
+ }
795
+ }
796
+
797
+ function onNavigationKey(localMeta, keyCode) {
798
+ const keys = focusableKeys.value,
799
+ index = keys.indexOf(localMeta.key)
800
+
801
+ if (keyCode === 36) {
802
+ focusNode(keys[0])
803
+ return true
804
+ }
805
+ if (keyCode === 35) {
806
+ focusNode(keys.at(-1))
807
+ return true
808
+ }
809
+ if (keyCode === 38) {
810
+ focusNode(keys[index - 1])
811
+ return true
812
+ }
813
+ if (keyCode === 40) {
814
+ focusNode(keys[index + 1])
815
+ return true
816
+ }
817
+ if (keyCode === 39) {
818
+ if (localMeta.isParent !== true && !localMeta.lazy) return true
819
+
820
+ if (localMeta.expanded !== true) {
821
+ setExpanded(localMeta.key, true)
822
+ } else {
823
+ focusNode(getFirstFocusableChild(localMeta.children))
824
+ }
825
+ return true
826
+ }
827
+ if (keyCode === 37) {
828
+ if (localMeta.expanded === true) {
829
+ setExpanded(localMeta.key, false)
830
+ } else {
831
+ let parent = localMeta.parent
832
+ while (parent !== null && parent.link !== true) parent = parent.parent
833
+ focusNode(parent?.key)
834
+ }
835
+ return true
836
+ }
837
+
838
+ return false
839
+ }
840
+
725
841
  function onClick(node, localMeta, e, keyboard) {
842
+ if (localMeta.link === true) focusedKey.value = localMeta.key
843
+
726
844
  if (keyboard !== true && localMeta.selectable !== false) {
727
845
  blur(localMeta.key)
728
846
  }
@@ -749,6 +867,8 @@ export default createComponent({
749
867
  }
750
868
 
751
869
  function onExpandClick(node, localMeta, e, keyboard) {
870
+ if (localMeta.link === true) focusedKey.value = localMeta.key
871
+
752
872
  if (e !== void 0) {
753
873
  stopAndPrevent(e)
754
874
  }
@@ -93,7 +93,11 @@ export function getRenderer(getPlugin, expose) {
93
93
  file.__uploaded = status === 'uploaded' ? file.size : uploadedSize
94
94
 
95
95
  file.__progress =
96
- status === 'uploaded' ? 1 : Math.min(0.9999, file.__uploaded / file.size)
96
+ status === 'uploaded'
97
+ ? 1
98
+ : file.size === 0
99
+ ? 0
100
+ : Math.min(0.9999, file.__uploaded / file.size)
97
101
 
98
102
  file.__progressLabel = getProgressLabel(file.__progress)
99
103
  proxy.$forceUpdate()
@@ -216,7 +220,9 @@ export function getRenderer(getPlugin, expose) {
216
220
 
217
221
  function removeUploadedFiles() {
218
222
  if (!props.disable) {
219
- batchRemoveFiles(['uploaded'], () => {
223
+ batchRemoveFiles(['uploaded'], ({ size }) => {
224
+ state.uploadedSize.value -= size
225
+ uploadSize.value -= size
220
226
  state.uploadedFiles.value = []
221
227
  })
222
228
  }
@@ -258,12 +264,16 @@ export function getRenderer(getPlugin, expose) {
258
264
  function removeFile(file) {
259
265
  if (props.disable) return
260
266
 
267
+ const isUploading = file.__status === 'uploading'
268
+
261
269
  if (file.__status === 'uploaded') {
270
+ state.uploadedSize.value -= file.size
271
+ uploadSize.value -= file.size
262
272
  state.uploadedFiles.value = state.uploadedFiles.value.filter(
263
273
  f => f.__key !== file.__key
264
274
  )
265
- } else if (file.__status === 'uploading') {
266
- file.__abort()
275
+ } else if (isUploading) {
276
+ uploadSize.value -= file.size
267
277
  } else {
268
278
  uploadSize.value -= file.size
269
279
  }
@@ -281,6 +291,11 @@ export function getRenderer(getPlugin, expose) {
281
291
  state.queuedFiles.value = state.queuedFiles.value.filter(
282
292
  f => f.__key !== file.__key
283
293
  )
294
+
295
+ if (isUploading) {
296
+ file.__abort()
297
+ }
298
+
284
299
  emit('removed', [file])
285
300
  }
286
301