quasar 2.21.4 → 2.22.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.
- package/README.md +1 -1
- package/dist/api/QUploader.json +1 -1
- package/dist/icon-set/bootstrap-icons.umd.prod.js +1 -1
- package/dist/icon-set/eva-icons.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v5-pro.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v5.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v6-pro.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v6.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v7-pro.umd.prod.js +1 -1
- package/dist/icon-set/fontawesome-v7.umd.prod.js +1 -1
- package/dist/icon-set/ionicons-v4.umd.prod.js +1 -1
- package/dist/icon-set/line-awesome.umd.prod.js +1 -1
- package/dist/icon-set/material-icons-outlined.umd.prod.js +1 -1
- package/dist/icon-set/material-icons-round.umd.prod.js +1 -1
- package/dist/icon-set/material-icons-sharp.umd.prod.js +1 -1
- package/dist/icon-set/material-icons.umd.prod.js +1 -1
- package/dist/icon-set/material-symbols-outlined.umd.prod.js +1 -1
- package/dist/icon-set/material-symbols-rounded.umd.prod.js +1 -1
- package/dist/icon-set/material-symbols-sharp.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v3.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v4.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v5.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v6.umd.prod.js +1 -1
- package/dist/icon-set/mdi-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-bootstrap-icons.umd.prod.js +1 -1
- package/dist/icon-set/svg-eva-icons.umd.prod.js +1 -1
- package/dist/icon-set/svg-fontawesome-v5.umd.prod.js +1 -1
- package/dist/icon-set/svg-fontawesome-v6.umd.prod.js +1 -1
- package/dist/icon-set/svg-fontawesome-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v4.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v5.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v6.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-ionicons-v8.umd.prod.js +1 -1
- package/dist/icon-set/svg-line-awesome.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons-outlined.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons-round.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons-sharp.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-icons.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-symbols-outlined.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-symbols-rounded.umd.prod.js +1 -1
- package/dist/icon-set/svg-material-symbols-sharp.umd.prod.js +1 -1
- package/dist/icon-set/svg-mdi-v6.umd.prod.js +1 -1
- package/dist/icon-set/svg-mdi-v7.umd.prod.js +1 -1
- package/dist/icon-set/svg-themify.umd.prod.js +1 -1
- package/dist/icon-set/themify.umd.prod.js +1 -1
- package/dist/lang/ar-TN.umd.prod.js +1 -1
- package/dist/lang/ar.umd.prod.js +1 -1
- package/dist/lang/az-Latn.umd.prod.js +1 -1
- package/dist/lang/bg.umd.prod.js +1 -1
- package/dist/lang/bn.umd.prod.js +1 -1
- package/dist/lang/bs-BA.umd.prod.js +1 -1
- package/dist/lang/ca.umd.prod.js +1 -1
- package/dist/lang/cs.umd.prod.js +1 -1
- package/dist/lang/da.umd.prod.js +1 -1
- package/dist/lang/de-CH.umd.prod.js +1 -1
- package/dist/lang/de-DE.umd.prod.js +1 -1
- package/dist/lang/de.umd.prod.js +1 -1
- package/dist/lang/el.umd.prod.js +1 -1
- package/dist/lang/en-GB.umd.prod.js +1 -1
- package/dist/lang/en-US.umd.prod.js +1 -1
- package/dist/lang/eo.umd.prod.js +1 -1
- package/dist/lang/es.umd.prod.js +1 -1
- package/dist/lang/et.umd.prod.js +1 -1
- package/dist/lang/eu.umd.prod.js +1 -1
- package/dist/lang/fa-IR.umd.prod.js +1 -1
- package/dist/lang/fa.umd.prod.js +1 -1
- package/dist/lang/fi.umd.prod.js +1 -1
- package/dist/lang/fr.umd.prod.js +1 -1
- package/dist/lang/gn.umd.prod.js +1 -1
- package/dist/lang/he.umd.prod.js +1 -1
- package/dist/lang/hi.umd.prod.js +1 -1
- package/dist/lang/hr.umd.prod.js +1 -1
- package/dist/lang/hu.umd.prod.js +1 -1
- package/dist/lang/id.umd.prod.js +1 -1
- package/dist/lang/is.umd.prod.js +1 -1
- package/dist/lang/it.umd.prod.js +1 -1
- package/dist/lang/ja.umd.prod.js +1 -1
- package/dist/lang/kk.umd.prod.js +1 -1
- package/dist/lang/km.umd.prod.js +1 -1
- package/dist/lang/ko-KR.umd.prod.js +1 -1
- package/dist/lang/kur-CKB.umd.prod.js +1 -1
- package/dist/lang/lb.umd.prod.js +1 -1
- package/dist/lang/lt.umd.prod.js +1 -1
- package/dist/lang/lu.umd.prod.js +1 -1
- package/dist/lang/lv.umd.prod.js +1 -1
- package/dist/lang/mk.umd.prod.js +1 -1
- package/dist/lang/ml.umd.prod.js +1 -1
- package/dist/lang/mm.umd.prod.js +1 -1
- package/dist/lang/ms-MY.umd.prod.js +1 -1
- package/dist/lang/ms.umd.prod.js +1 -1
- package/dist/lang/my.umd.prod.js +1 -1
- package/dist/lang/nb-NO.umd.prod.js +1 -1
- package/dist/lang/nl.umd.prod.js +1 -1
- package/dist/lang/pl.umd.prod.js +1 -1
- package/dist/lang/pt-BR.umd.prod.js +1 -1
- package/dist/lang/pt.umd.prod.js +1 -1
- package/dist/lang/ro.umd.prod.js +1 -1
- package/dist/lang/ru.umd.prod.js +1 -1
- package/dist/lang/sk.umd.prod.js +1 -1
- package/dist/lang/sl.umd.prod.js +1 -1
- package/dist/lang/sm.umd.prod.js +1 -1
- package/dist/lang/sq.umd.prod.js +1 -1
- package/dist/lang/sr-CYR.umd.prod.js +1 -1
- package/dist/lang/sr.umd.prod.js +1 -1
- package/dist/lang/sv.umd.prod.js +1 -1
- package/dist/lang/ta.umd.prod.js +1 -1
- package/dist/lang/th.umd.prod.js +1 -1
- package/dist/lang/tl.umd.prod.js +1 -1
- package/dist/lang/tr.umd.prod.js +1 -1
- package/dist/lang/ug.umd.prod.js +1 -1
- package/dist/lang/uk.umd.prod.js +1 -1
- package/dist/lang/ur-PK.umd.prod.js +1 -1
- package/dist/lang/uz-Cyrl.umd.prod.js +1 -1
- package/dist/lang/uz-Latn.umd.prod.js +1 -1
- package/dist/lang/vi.umd.prod.js +1 -1
- package/dist/lang/zh-CN.umd.prod.js +1 -1
- package/dist/lang/zh-TW.umd.prod.js +1 -1
- package/dist/quasar.client.js +572 -187
- package/dist/quasar.css +4 -0
- package/dist/quasar.prod.css +1 -1
- package/dist/quasar.rtl.css +5 -0
- package/dist/quasar.rtl.prod.css +1 -1
- package/dist/quasar.sass +4 -1
- package/dist/quasar.server.prod.cjs +35 -35
- package/dist/quasar.server.prod.js +25 -25
- package/dist/quasar.umd.js +572 -187
- package/dist/quasar.umd.prod.js +30 -30
- package/dist/types/composables.d.ts +10 -5
- package/dist/types/index.d.ts +4 -4
- package/dist/web-types/web-types.json +1 -1
- package/package.json +6 -6
- package/src/components/carousel/QCarousel.js +2 -0
- package/src/components/chip/QChip.js +20 -4
- package/src/components/date/QDate.js +9 -2
- package/src/components/dialog/QDialog.js +9 -2
- package/src/components/editor/QEditor.js +18 -3
- package/src/components/expansion-item/QExpansionItem.js +6 -1
- package/src/components/input/QInput.js +42 -1
- package/src/components/input/QInput.json +7 -0
- package/src/components/input/use-mask.js +12 -4
- package/src/components/item/QItem.js +10 -1
- package/src/components/menu/QMenu.js +9 -2
- package/src/components/range/QRange.js +2 -1
- package/src/components/rating/QRating.js +27 -8
- package/src/components/rating/QRating.sass +4 -0
- package/src/components/resize-observer/QResizeObserver.js +14 -3
- package/src/components/scroll-area/QScrollArea.js +69 -17
- package/src/components/slide-item/QSlideItem.js +3 -2
- package/src/components/slider/use-slider.js +7 -3
- package/src/components/stepper/StepHeader.js +11 -2
- package/src/components/table/QTable.js +11 -9
- package/src/components/table/QTh.js +20 -1
- package/src/components/table/table-column-selection.js +9 -1
- package/src/components/table/table-pagination.js +5 -2
- package/src/components/table/table-row-selection.js +10 -10
- package/src/components/tabs/QTabs.js +5 -6
- package/src/components/time/QTime.js +35 -10
- package/src/components/tooltip/QTooltip.js +112 -6
- package/src/components/tree/QTree.js +124 -4
- package/src/components/uploader/uploader-core.js +19 -4
- package/src/components/uploader/xhr-uploader-plugin.js +93 -43
- package/src/components/uploader/xhr-uploader-plugin.json +4 -4
- package/src/composables/private.use-field/use-field.js +2 -0
- package/src/composables/private.use-file/use-file.js +21 -12
- package/src/composables/private.use-fullscreen/use-fullscreen.js +8 -3
- package/src/composables/private.use-model-toggle/use-model-toggle.js +2 -0
- package/src/composables/private.use-panel/use-panel.js +6 -2
- package/src/composables/use-interval/use-interval.js +8 -0
- package/src/composables/use-render-cache/use-render-cache.js +15 -10
- package/src/composables/use-timeout/use-timeout.js +8 -0
- package/src/directives/intersection/Intersection.js +2 -2
- package/src/directives/touch-repeat/TouchRepeat.js +3 -1
- package/src/plugins/cookies/Cookies.js +29 -13
- package/src/plugins/meta/Meta.js +57 -9
- package/src/utils/extend/extend.js +4 -0
- package/src/utils/extend/extend.test.js +29 -0
- package/src/utils/scroll/prevent-scroll.js +4 -4
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
580
|
-
|
|
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
|
-
|
|
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'
|
|
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 (
|
|
266
|
-
file.
|
|
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
|
|
|
@@ -31,6 +31,7 @@ function injectPlugin({ props, emit, helpers }) {
|
|
|
31
31
|
const xhrs = ref([])
|
|
32
32
|
const promises = ref([])
|
|
33
33
|
const workingThreads = ref(0)
|
|
34
|
+
const abortedPromises = new WeakSet()
|
|
34
35
|
|
|
35
36
|
const xhrProps = computed(() => ({
|
|
36
37
|
url: getFn(props.url),
|
|
@@ -46,14 +47,17 @@ function injectPlugin({ props, emit, helpers }) {
|
|
|
46
47
|
const isUploading = computed(() => workingThreads.value > 0)
|
|
47
48
|
const isBusy = computed(() => promises.value.length !== 0)
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
const getLiveFiles = files =>
|
|
51
|
+
files.filter(file => helpers.files.value.includes(file))
|
|
50
52
|
|
|
51
53
|
function abort() {
|
|
52
54
|
xhrs.value.forEach(x => {
|
|
53
55
|
x.abort()
|
|
54
56
|
})
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
promises.value.forEach(promise => {
|
|
59
|
+
abortedPromises.add(promise)
|
|
60
|
+
})
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
function upload() {
|
|
@@ -72,54 +76,82 @@ function injectPlugin({ props, emit, helpers }) {
|
|
|
72
76
|
function runFactory(files) {
|
|
73
77
|
workingThreads.value++
|
|
74
78
|
|
|
79
|
+
const failed = (err, promise) => {
|
|
80
|
+
if (helpers.isAlive()) {
|
|
81
|
+
if (promise !== void 0) {
|
|
82
|
+
promises.value = promises.value.filter(p => p !== promise)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const liveFiles = getLiveFiles(files)
|
|
86
|
+
|
|
87
|
+
if (liveFiles.length !== 0) {
|
|
88
|
+
helpers.queuedFiles.value.push(...liveFiles)
|
|
89
|
+
liveFiles.forEach(f => {
|
|
90
|
+
helpers.updateFileStatus(f, 'failed')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
emit('factoryFailed', err, liveFiles)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
workingThreads.value--
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
if (typeof props.factory !== 'function') {
|
|
76
|
-
|
|
101
|
+
startUpload({})
|
|
77
102
|
return
|
|
78
103
|
}
|
|
79
104
|
|
|
80
|
-
|
|
105
|
+
let res
|
|
106
|
+
try {
|
|
107
|
+
res = props.factory(files)
|
|
108
|
+
} catch (err) {
|
|
109
|
+
failed(err)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
81
112
|
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
'factoryFailed',
|
|
85
|
-
new Error('QUploader: factory() does not return properly'),
|
|
86
|
-
files
|
|
87
|
-
)
|
|
88
|
-
workingThreads.value--
|
|
113
|
+
if (Object(res) !== res) {
|
|
114
|
+
failed(new Error('QUploader: factory() does not return properly'))
|
|
89
115
|
} else if (
|
|
90
116
|
typeof res.catch === 'function' &&
|
|
91
117
|
typeof res.then === 'function'
|
|
92
118
|
) {
|
|
93
119
|
promises.value.push(res)
|
|
94
120
|
|
|
95
|
-
const failed = err => {
|
|
96
|
-
if (helpers.isAlive()) {
|
|
97
|
-
promises.value = promises.value.filter(p => p !== res)
|
|
98
|
-
|
|
99
|
-
if (promises.value.length === 0) abortPromises = false
|
|
100
|
-
|
|
101
|
-
helpers.queuedFiles.value.push(...files)
|
|
102
|
-
files.forEach(f => {
|
|
103
|
-
helpers.updateFileStatus(f, 'failed')
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
emit('factoryFailed', err, files)
|
|
107
|
-
workingThreads.value--
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
121
|
res
|
|
112
122
|
.then(factory => {
|
|
113
|
-
if (
|
|
114
|
-
failed(new Error('Aborted'))
|
|
123
|
+
if (abortedPromises.has(res)) {
|
|
124
|
+
failed(new Error('Aborted'), res)
|
|
125
|
+
} else if (Object(factory) !== factory) {
|
|
126
|
+
failed(
|
|
127
|
+
new Error('QUploader: factory() does not return properly'),
|
|
128
|
+
res
|
|
129
|
+
)
|
|
115
130
|
} else if (helpers.isAlive()) {
|
|
116
131
|
promises.value = promises.value.filter(p => p !== res)
|
|
117
|
-
|
|
132
|
+
startUpload(factory)
|
|
118
133
|
}
|
|
119
134
|
})
|
|
120
|
-
.catch(
|
|
135
|
+
.catch(err => {
|
|
136
|
+
failed(err, res)
|
|
137
|
+
})
|
|
121
138
|
} else {
|
|
122
|
-
|
|
139
|
+
startUpload(res)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function startUpload(factory) {
|
|
143
|
+
const liveFiles = getLiveFiles(files)
|
|
144
|
+
|
|
145
|
+
if (liveFiles.length === 0) {
|
|
146
|
+
workingThreads.value--
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
performUpload(liveFiles, factory)
|
|
152
|
+
} catch (err) {
|
|
153
|
+
failed(err)
|
|
154
|
+
}
|
|
123
155
|
}
|
|
124
156
|
}
|
|
125
157
|
|
|
@@ -136,6 +168,10 @@ function injectPlugin({ props, emit, helpers }) {
|
|
|
136
168
|
|
|
137
169
|
if (!url) {
|
|
138
170
|
console.error('q-uploader: invalid or no URL specified')
|
|
171
|
+
helpers.queuedFiles.value.push(...files)
|
|
172
|
+
files.forEach(f => {
|
|
173
|
+
helpers.updateFileStatus(f, 'failed')
|
|
174
|
+
})
|
|
139
175
|
workingThreads.value--
|
|
140
176
|
return
|
|
141
177
|
}
|
|
@@ -166,7 +202,7 @@ function injectPlugin({ props, emit, helpers }) {
|
|
|
166
202
|
let size = localUploadedSize - uploadIndexSize
|
|
167
203
|
for (let i = uploadIndex; size > 0 && i < files.length; i++) {
|
|
168
204
|
const file = files[i],
|
|
169
|
-
uploaded = size
|
|
205
|
+
uploaded = size >= file.size
|
|
170
206
|
|
|
171
207
|
if (uploaded) {
|
|
172
208
|
size -= file.size
|
|
@@ -186,19 +222,33 @@ function injectPlugin({ props, emit, helpers }) {
|
|
|
186
222
|
if (xhr.readyState < 4) return
|
|
187
223
|
|
|
188
224
|
if (xhr.status && xhr.status < 400) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
225
|
+
const liveFiles = getLiveFiles(files)
|
|
226
|
+
const liveUploadSize = liveFiles.reduce(
|
|
227
|
+
(total, file) => total + file.size,
|
|
228
|
+
0
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
helpers.uploadedSize.value += liveUploadSize - localUploadedSize
|
|
232
|
+
|
|
233
|
+
if (liveFiles.length !== 0) {
|
|
234
|
+
helpers.uploadedFiles.value.push(...liveFiles)
|
|
235
|
+
liveFiles.forEach(f => {
|
|
236
|
+
helpers.updateFileStatus(f, 'uploaded')
|
|
237
|
+
})
|
|
238
|
+
emit('uploaded', { files: liveFiles, xhr })
|
|
239
|
+
}
|
|
194
240
|
} else {
|
|
195
241
|
aborted = true
|
|
196
242
|
helpers.uploadedSize.value -= localUploadedSize
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
243
|
+
|
|
244
|
+
const liveFiles = getLiveFiles(files)
|
|
245
|
+
if (liveFiles.length !== 0) {
|
|
246
|
+
helpers.queuedFiles.value.push(...liveFiles)
|
|
247
|
+
liveFiles.forEach(f => {
|
|
248
|
+
helpers.updateFileStatus(f, 'failed')
|
|
249
|
+
})
|
|
250
|
+
emit('failed', { files: liveFiles, xhr })
|
|
251
|
+
}
|
|
202
252
|
}
|
|
203
253
|
|
|
204
254
|
workingThreads.value--
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
105
|
"returns": {
|
|
106
|
-
"type": "
|
|
106
|
+
"type": "Array",
|
|
107
107
|
"desc": "An array consisting of objects with header definitions"
|
|
108
108
|
},
|
|
109
109
|
"category": "upload"
|
|
@@ -138,8 +138,8 @@
|
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
140
|
"returns": {
|
|
141
|
-
"type": "
|
|
142
|
-
"desc": "An array
|
|
141
|
+
"type": "Array",
|
|
142
|
+
"desc": "An array consisting of objects with additional field definitions (used by FormData to be uploaded)"
|
|
143
143
|
},
|
|
144
144
|
"category": "upload"
|
|
145
145
|
},
|
|
@@ -264,7 +264,7 @@
|
|
|
264
264
|
},
|
|
265
265
|
|
|
266
266
|
"factory-failed": {
|
|
267
|
-
"desc": "Emitted when factory function
|
|
267
|
+
"desc": "Emitted when the factory function throws, returns an invalid value, or supplies a Promise which is rejected or aborted",
|
|
268
268
|
"params": {
|
|
269
269
|
"err": {
|
|
270
270
|
"type": "Error",
|