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.
- package/README.md +1 -1
- package/dist/api/Mutation.json +1 -1
- package/dist/api/QLinearProgress.json +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 +654 -207
- package/dist/quasar.css +20 -11
- package/dist/quasar.prod.css +1 -1
- package/dist/quasar.rtl.css +25 -20
- package/dist/quasar.rtl.prod.css +1 -1
- package/dist/quasar.sass +21 -12
- package/dist/quasar.server.prod.cjs +35 -35
- package/dist/quasar.server.prod.js +25 -25
- package/dist/quasar.umd.js +654 -206
- package/dist/quasar.umd.prod.js +19 -19
- package/dist/transforms/import-map.json +1 -1
- package/dist/types/composables.d.ts +15 -5
- package/dist/types/index.d.ts +5 -5
- package/dist/web-types/web-types.json +1 -1
- package/package.json +6 -6
- package/src/components/btn/QBtn.js +1 -1
- 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/fab/QFab.sass +11 -9
- 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/linear-progress/QLinearProgress.js +4 -11
- package/src/components/linear-progress/QLinearProgress.json +1 -1
- package/src/components/linear-progress/QLinearProgress.sass +7 -2
- 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 +71 -11
- package/src/composables/private.use-fullscreen/use-fullscreen.test.js +158 -0
- 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-animation-frame/use-animation-frame.js +47 -0
- package/src/composables/use-animation-frame/use-animation-frame.test.js +122 -0
- 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/composables.js +1 -1
- package/src/directives/intersection/Intersection.js +2 -2
- package/src/directives/mutation/Mutation.json +1 -1
- 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/private.click-outside/click-outside.js +27 -12
- package/src/utils/private.click-outside/click-outside.test.js +132 -0
- package/src/utils/scroll/prevent-scroll.js +4 -4
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { defineComponent } from 'vue'
|
|
4
|
+
|
|
5
|
+
import useAnimationFrame from './use-animation-frame.js'
|
|
6
|
+
|
|
7
|
+
let wrapper
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (wrapper !== null) {
|
|
11
|
+
wrapper.unmount()
|
|
12
|
+
wrapper = null
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
function nextAnimationFrame() {
|
|
17
|
+
const { promise, resolve } = Promise.withResolvers()
|
|
18
|
+
requestAnimationFrame(resolve)
|
|
19
|
+
return promise
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('[useAnimationFrame API]', () => {
|
|
23
|
+
describe('[Functions]', () => {
|
|
24
|
+
describe('[(function)default]', () => {
|
|
25
|
+
test('correctly registers an animation frame', async () => {
|
|
26
|
+
const fn = vi.fn()
|
|
27
|
+
|
|
28
|
+
wrapper = mount(
|
|
29
|
+
defineComponent({
|
|
30
|
+
template: '<div />',
|
|
31
|
+
setup() {
|
|
32
|
+
const { registerAnimationFrame } = useAnimationFrame()
|
|
33
|
+
|
|
34
|
+
registerAnimationFrame(fn)
|
|
35
|
+
return {}
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(fn).not.toHaveBeenCalled()
|
|
41
|
+
await nextAnimationFrame()
|
|
42
|
+
expect(fn).toHaveBeenCalledTimes(1)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('removeAnimationFrame works correctly', async () => {
|
|
46
|
+
const fn = vi.fn()
|
|
47
|
+
|
|
48
|
+
wrapper = mount(
|
|
49
|
+
defineComponent({
|
|
50
|
+
template: '<div />',
|
|
51
|
+
setup() {
|
|
52
|
+
const { registerAnimationFrame, removeAnimationFrame } =
|
|
53
|
+
useAnimationFrame()
|
|
54
|
+
|
|
55
|
+
registerAnimationFrame(fn)
|
|
56
|
+
return { registerAnimationFrame, removeAnimationFrame }
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
expect(fn).not.toHaveBeenCalled()
|
|
62
|
+
wrapper.vm.removeAnimationFrame()
|
|
63
|
+
|
|
64
|
+
await nextAnimationFrame()
|
|
65
|
+
expect(fn).not.toHaveBeenCalled()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('unmount stops animation frame', async () => {
|
|
69
|
+
const fn = vi.fn()
|
|
70
|
+
|
|
71
|
+
wrapper = mount(
|
|
72
|
+
defineComponent({
|
|
73
|
+
template: '<div />',
|
|
74
|
+
setup() {
|
|
75
|
+
const { registerAnimationFrame } = useAnimationFrame()
|
|
76
|
+
|
|
77
|
+
registerAnimationFrame(fn)
|
|
78
|
+
return {}
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
expect(fn).not.toHaveBeenCalled()
|
|
84
|
+
|
|
85
|
+
wrapper.unmount()
|
|
86
|
+
wrapper = null
|
|
87
|
+
|
|
88
|
+
await nextAnimationFrame()
|
|
89
|
+
|
|
90
|
+
expect(fn).not.toHaveBeenCalled()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('can override animation frame', async () => {
|
|
94
|
+
const fn1 = vi.fn()
|
|
95
|
+
const fn2 = vi.fn()
|
|
96
|
+
|
|
97
|
+
wrapper = mount(
|
|
98
|
+
defineComponent({
|
|
99
|
+
template: '<div />',
|
|
100
|
+
setup() {
|
|
101
|
+
const { registerAnimationFrame } = useAnimationFrame()
|
|
102
|
+
return { registerAnimationFrame }
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
expect(fn1).not.toHaveBeenCalled()
|
|
108
|
+
wrapper.vm.registerAnimationFrame(fn1)
|
|
109
|
+
expect(fn1).not.toHaveBeenCalled()
|
|
110
|
+
|
|
111
|
+
wrapper.vm.registerAnimationFrame(fn2)
|
|
112
|
+
expect(fn1).not.toHaveBeenCalled()
|
|
113
|
+
expect(fn2).not.toHaveBeenCalled()
|
|
114
|
+
|
|
115
|
+
await nextAnimationFrame()
|
|
116
|
+
|
|
117
|
+
expect(fn1).not.toHaveBeenCalled()
|
|
118
|
+
expect(fn2).toHaveBeenCalledTimes(1)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCurrentInstance, onBeforeUnmount, onDeactivated } from 'vue'
|
|
2
2
|
|
|
3
3
|
import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
4
|
+
import { noop } from '../../utils/event/event.js'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
7
|
* Usage:
|
|
@@ -9,6 +10,13 @@ import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
export default function useInterval() {
|
|
13
|
+
if (__QUASAR_SSR_SERVER__) {
|
|
14
|
+
return {
|
|
15
|
+
removeInterval: noop,
|
|
16
|
+
registerInterval: noop
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
let timer = null
|
|
13
21
|
const vm = getCurrentInstance()
|
|
14
22
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
export default function useRenderCache() {
|
|
2
|
+
if (__QUASAR_SSR_SERVER__) {
|
|
3
|
+
return {
|
|
4
|
+
getCache: (_, defaultValue) =>
|
|
5
|
+
typeof defaultValue === 'function' ? defaultValue() : defaultValue,
|
|
6
|
+
setCache() {},
|
|
7
|
+
hasCache: () => false,
|
|
8
|
+
clearCache() {}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
2
12
|
let cache = Object.create(null)
|
|
3
13
|
|
|
4
14
|
return {
|
|
5
|
-
getCache:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
? (cache[key] =
|
|
11
|
-
typeof defaultValue === 'function'
|
|
12
|
-
? defaultValue()
|
|
13
|
-
: defaultValue)
|
|
14
|
-
: cache[key],
|
|
15
|
+
getCache: (key, defaultValue) =>
|
|
16
|
+
Object.hasOwn(cache, key)
|
|
17
|
+
? cache[key]
|
|
18
|
+
: (cache[key] =
|
|
19
|
+
typeof defaultValue === 'function' ? defaultValue() : defaultValue),
|
|
15
20
|
|
|
16
21
|
setCache(key, obj) {
|
|
17
22
|
cache[key] = obj
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCurrentInstance, onBeforeUnmount, onDeactivated } from 'vue'
|
|
2
2
|
|
|
3
3
|
import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
4
|
+
import { noop } from '../../utils/event/event.js'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
7
|
* Usage:
|
|
@@ -9,6 +10,13 @@ import { vmIsDestroyed } from '../../utils/private.vm/vm.js'
|
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
export default function useTimeout() {
|
|
13
|
+
if (__QUASAR_SSR_SERVER__) {
|
|
14
|
+
return {
|
|
15
|
+
removeTimeout: noop,
|
|
16
|
+
registerTimeout: noop
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
let timer = null
|
|
13
21
|
const vm = getCurrentInstance()
|
|
14
22
|
|
package/src/composables.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
export { default as useAnimationFrame } from './composables/use-animation-frame/use-animation-frame.js'
|
|
1
2
|
export { default as useDialogPluginComponent } from './composables/use-dialog-plugin-component/use-dialog-plugin-component.js'
|
|
2
3
|
export { default as useFormChild } from './composables/use-form/use-form-child.js'
|
|
3
4
|
export { default as useMeta } from './composables/use-meta/use-meta.js'
|
|
4
5
|
export { default as useQuasar } from './composables/use-quasar/use-quasar.js'
|
|
5
|
-
|
|
6
6
|
export { default as useHydration } from './composables/use-hydration/use-hydration.js'
|
|
7
7
|
export { default as useId } from './composables/use-id/use-id.js'
|
|
8
8
|
export { default as useInterval } from './composables/use-interval/use-interval.js'
|
|
@@ -27,7 +27,7 @@ function update(el, ctx, value) {
|
|
|
27
27
|
|
|
28
28
|
if (changed) {
|
|
29
29
|
ctx.cfg = cfg
|
|
30
|
-
ctx.observer?.
|
|
30
|
+
ctx.observer?.disconnect()
|
|
31
31
|
|
|
32
32
|
ctx.observer = new IntersectionObserver(([entry]) => {
|
|
33
33
|
if (typeof ctx.handler === 'function') {
|
|
@@ -55,7 +55,7 @@ function destroy(el) {
|
|
|
55
55
|
const ctx = el.__qvisible
|
|
56
56
|
|
|
57
57
|
if (ctx !== void 0) {
|
|
58
|
-
ctx.observer?.
|
|
58
|
+
ctx.observer?.disconnect()
|
|
59
59
|
delete el.__qvisible
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"value": {
|
|
7
7
|
"type": ["Function"],
|
|
8
8
|
"desc": "Function to call when mutation occurs; It is HIGHLY recommended to reference it from your vue component scope, otherwise the directive update cycle will continuously recreate the observer which hits performance hard",
|
|
9
|
-
"examples": ["# v-
|
|
9
|
+
"examples": ["# v-mutation=\"myHandler\""],
|
|
10
10
|
|
|
11
11
|
"returns": {
|
|
12
12
|
"type": "Boolean",
|
|
@@ -20,7 +20,11 @@ function read(string) {
|
|
|
20
20
|
// Replace server-side written pluses with spaces.
|
|
21
21
|
// If we can't decode the cookie, ignore it, it's unusable.
|
|
22
22
|
// If we can't parse the cookie, ignore it, it's unusable.
|
|
23
|
-
|
|
23
|
+
try {
|
|
24
|
+
string = decodeURIComponent(string.replaceAll('+', ' '))
|
|
25
|
+
} catch {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
24
28
|
|
|
25
29
|
try {
|
|
26
30
|
const parsed = JSON.parse(string)
|
|
@@ -56,7 +60,12 @@ function parseExpireToSeconds(str) {
|
|
|
56
60
|
totalSeconds += value * numberUnitMultiplierMap[unit]
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
if (hasMatch) return totalSeconds
|
|
64
|
+
|
|
65
|
+
const timestamp = Date.parse(str)
|
|
66
|
+
return Number.isNaN(timestamp)
|
|
67
|
+
? void 0
|
|
68
|
+
: Math.round((timestamp - Date.now()) / 1000)
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
// oxlint-disable-next-line default-param-last
|
|
@@ -69,9 +78,12 @@ function set(key, val, opts = {}, ssr) {
|
|
|
69
78
|
if (Number.isFinite(opts.expires)) {
|
|
70
79
|
maxAge = Math.round(opts.expires * 86_400) // 86400 seconds in a day
|
|
71
80
|
} else if (opts.expires instanceof Date) {
|
|
72
|
-
|
|
81
|
+
const timestamp = opts.expires.getTime()
|
|
82
|
+
if (Number.isFinite(timestamp)) {
|
|
83
|
+
maxAge = Math.round((timestamp - Date.now()) / 1000)
|
|
84
|
+
}
|
|
73
85
|
}
|
|
74
|
-
// String check (eg. "15m", "1h"
|
|
86
|
+
// String check (eg. "15m", "1h", or a date string)
|
|
75
87
|
else if (typeof opts.expires === 'string') {
|
|
76
88
|
maxAge = parseExpireToSeconds(opts.expires)
|
|
77
89
|
}
|
|
@@ -104,13 +116,11 @@ function set(key, val, opts = {}, ssr) {
|
|
|
104
116
|
let all = ssr.req.headers.cookie || ''
|
|
105
117
|
|
|
106
118
|
if (maxAge !== void 0 && isDeletion) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.replace(`${key}=${localVal}`, '')
|
|
113
|
-
}
|
|
119
|
+
const encodedKey = encodeURIComponent(key)
|
|
120
|
+
all = all
|
|
121
|
+
.split('; ')
|
|
122
|
+
.filter(entry => entry.split('=', 1)[0] !== encodedKey)
|
|
123
|
+
.join('; ')
|
|
114
124
|
} else {
|
|
115
125
|
all = all ? `${keyValue}; ${all}` : keyValue
|
|
116
126
|
}
|
|
@@ -135,13 +145,19 @@ function get(key, ssr) {
|
|
|
135
145
|
|
|
136
146
|
for (; i < l; i++) {
|
|
137
147
|
parts = cookies[i].split('=')
|
|
138
|
-
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
name = decodeURIComponent(parts.shift())
|
|
151
|
+
} catch {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
|
|
139
155
|
cookie = parts.join('=')
|
|
140
156
|
|
|
141
157
|
if (!key) {
|
|
142
158
|
result[name] = cookie
|
|
143
159
|
} else if (key === name) {
|
|
144
|
-
result = read(cookie)
|
|
160
|
+
result = read(cookie) ?? null
|
|
145
161
|
break
|
|
146
162
|
}
|
|
147
163
|
}
|
package/src/plugins/meta/Meta.js
CHANGED
|
@@ -4,6 +4,48 @@ import extend from '../../utils/extend/extend.js'
|
|
|
4
4
|
let updateId = null,
|
|
5
5
|
currentClientMeta
|
|
6
6
|
|
|
7
|
+
const encodeHtmlChars = {
|
|
8
|
+
'&': '&',
|
|
9
|
+
'<': '<',
|
|
10
|
+
'>': '>',
|
|
11
|
+
'"': '"',
|
|
12
|
+
"'": '''
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const encodeJsonChars = {
|
|
16
|
+
'<': String.raw`\u003C`,
|
|
17
|
+
'>': String.raw`\u003E`,
|
|
18
|
+
'&': String.raw`\u0026`,
|
|
19
|
+
'\u2028': String.raw`\u2028`,
|
|
20
|
+
'\u2029': String.raw`\u2029`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validAttrName = /^[^\s"'>/=]+$/
|
|
24
|
+
|
|
25
|
+
function isValidAttrName(name) {
|
|
26
|
+
return (
|
|
27
|
+
validAttrName.test(name) && name.includes(String.fromCodePoint(0)) === false
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function encodeHtml(value) {
|
|
32
|
+
return String(value).replaceAll(/[&<>"']/g, char => encodeHtmlChars[char])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function encodeJson(value) {
|
|
36
|
+
return JSON.stringify(value).replaceAll(
|
|
37
|
+
/[<>&\u2028\u2029]/g,
|
|
38
|
+
char => encodeJsonChars[char]
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function protectRawText(value, tagName) {
|
|
43
|
+
return String(value).replaceAll(
|
|
44
|
+
new RegExp(`</${tagName}`, 'gi'),
|
|
45
|
+
`<\\/${tagName}`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
7
49
|
export const clientList = []
|
|
8
50
|
|
|
9
51
|
function normalize(meta) {
|
|
@@ -148,15 +190,17 @@ function apply({ add, remove }) {
|
|
|
148
190
|
|
|
149
191
|
function getAttr(seed) {
|
|
150
192
|
return att => {
|
|
193
|
+
if (isValidAttrName(att) === false) return
|
|
194
|
+
|
|
151
195
|
const val = seed[att]
|
|
152
|
-
return att + (val !== true && val !== void 0 ? `="${val}"` : '')
|
|
196
|
+
return att + (val !== true && val !== void 0 ? `="${encodeHtml(val)}"` : '')
|
|
153
197
|
}
|
|
154
198
|
}
|
|
155
199
|
|
|
156
200
|
function getHead(meta) {
|
|
157
201
|
let output = ''
|
|
158
202
|
if (meta.title) {
|
|
159
|
-
output += `<title>${meta.title}</title>`
|
|
203
|
+
output += `<title>${encodeHtml(meta.title)}</title>`
|
|
160
204
|
}
|
|
161
205
|
;['meta', 'link', 'script'].forEach(type => {
|
|
162
206
|
const metaType = meta[type]
|
|
@@ -165,10 +209,12 @@ function getHead(meta) {
|
|
|
165
209
|
const attrs = Object.keys(metaType[att])
|
|
166
210
|
.filter(item => item !== 'innerHTML')
|
|
167
211
|
.map(getAttr(metaType[att]))
|
|
212
|
+
.filter(Boolean)
|
|
168
213
|
|
|
169
|
-
output += `<${type} ${attrs.join(' ')} data-qmeta="${att}">`
|
|
214
|
+
output += `<${type} ${attrs.join(' ')} data-qmeta="${encodeHtml(att)}">`
|
|
170
215
|
if (type === 'script') {
|
|
171
|
-
output +=
|
|
216
|
+
output +=
|
|
217
|
+
protectRawText(metaType[att].innerHTML || '', 'script') + '</script>'
|
|
172
218
|
}
|
|
173
219
|
}
|
|
174
220
|
})
|
|
@@ -195,7 +241,9 @@ function injectServerMeta(ssrContext) {
|
|
|
195
241
|
normalize(data)
|
|
196
242
|
|
|
197
243
|
const nonce =
|
|
198
|
-
ssrContext.nonce !== void 0
|
|
244
|
+
ssrContext.nonce !== void 0
|
|
245
|
+
? ` nonce="${encodeHtml(ssrContext.nonce)}"`
|
|
246
|
+
: ''
|
|
199
247
|
|
|
200
248
|
const ctx = ssrContext._meta
|
|
201
249
|
|
|
@@ -204,7 +252,7 @@ function injectServerMeta(ssrContext) {
|
|
|
204
252
|
if (htmlAttr.length !== 0) {
|
|
205
253
|
ctx.htmlAttrs +=
|
|
206
254
|
(ctx.htmlAttrs.length !== 0 ? ' ' : '') +
|
|
207
|
-
htmlAttr.map(getAttr(data.htmlAttr)).join(' ')
|
|
255
|
+
htmlAttr.map(getAttr(data.htmlAttr)).filter(Boolean).join(' ')
|
|
208
256
|
}
|
|
209
257
|
|
|
210
258
|
ctx.headTags += getHead(data)
|
|
@@ -214,17 +262,17 @@ function injectServerMeta(ssrContext) {
|
|
|
214
262
|
if (bodyAttr.length !== 0) {
|
|
215
263
|
ctx.bodyAttrs +=
|
|
216
264
|
(ctx.bodyAttrs.length !== 0 ? ' ' : '') +
|
|
217
|
-
bodyAttr.map(getAttr(data.bodyAttr)).join(' ')
|
|
265
|
+
bodyAttr.map(getAttr(data.bodyAttr)).filter(Boolean).join(' ')
|
|
218
266
|
}
|
|
219
267
|
|
|
220
268
|
ctx.bodyTags +=
|
|
221
269
|
Object.keys(data.noscript)
|
|
222
270
|
.map(
|
|
223
271
|
name =>
|
|
224
|
-
`<noscript data-qmeta="${name}">${data.noscript[name]}</noscript>`
|
|
272
|
+
`<noscript data-qmeta="${encodeHtml(name)}">${protectRawText(data.noscript[name], 'noscript')}</noscript>`
|
|
225
273
|
)
|
|
226
274
|
.join('') +
|
|
227
|
-
`<script${nonce} id="qmeta-init">window.__Q_META__=${delete data.noscript &&
|
|
275
|
+
`<script${nonce} id="qmeta-init">window.__Q_META__=${delete data.noscript && encodeJson(data)}</script>`
|
|
228
276
|
}
|
|
229
277
|
|
|
230
278
|
function updateClientMeta() {
|
|
@@ -82,6 +82,35 @@ describe('[extend API]', () => {
|
|
|
82
82
|
|
|
83
83
|
expect(result.a()).toBe(6)
|
|
84
84
|
})
|
|
85
|
+
|
|
86
|
+
test.each([true, false])('does not merge __proto__ (deep: %s)', deep => {
|
|
87
|
+
const target = {}
|
|
88
|
+
const source = JSON.parse(
|
|
89
|
+
'{"__proto__":{"polluted":"prototype pollution"}}'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
extend(deep, target, source)
|
|
93
|
+
|
|
94
|
+
expect(Object.getPrototypeOf(target)).toBe(Object.prototype)
|
|
95
|
+
expect(Object.hasOwn(target, '__proto__')).toBe(false)
|
|
96
|
+
expect(Object.prototype.polluted).toBeUndefined()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('preserves constructor and prototype data properties', () => {
|
|
100
|
+
const result = extend(
|
|
101
|
+
true,
|
|
102
|
+
{},
|
|
103
|
+
{
|
|
104
|
+
constructor: { name: 'custom' },
|
|
105
|
+
prototype: { type: 'custom' }
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
expect(Object.hasOwn(result, 'constructor')).toBe(true)
|
|
110
|
+
expect(Object.hasOwn(result, 'prototype')).toBe(true)
|
|
111
|
+
expect(result.constructor).toStrictEqual({ name: 'custom' })
|
|
112
|
+
expect(result.prototype).toStrictEqual({ type: 'custom' })
|
|
113
|
+
})
|
|
85
114
|
})
|
|
86
115
|
})
|
|
87
116
|
})
|
|
@@ -22,31 +22,46 @@ function globalHandler(evt) {
|
|
|
22
22
|
return
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
let
|
|
25
|
+
// a modal (non-seamless) QDialog shields popups opened before it,
|
|
26
|
+
// so only popups opened after it can be closed by this click (#18091)
|
|
27
|
+
let closableContentEls = null
|
|
28
|
+
let sawClosablePortal = false
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
const proxy = portalProxyList[
|
|
30
|
+
for (let i = portalProxyList.length - 1; i >= 0; i--) {
|
|
31
|
+
const proxy = portalProxyList[i].$
|
|
32
|
+
const name = proxy.type.name
|
|
31
33
|
|
|
32
34
|
// skip QTooltip portals
|
|
33
|
-
if (
|
|
34
|
-
portalIndex--
|
|
35
|
+
if (name === 'QTooltip') {
|
|
35
36
|
continue
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
if (name === 'QDialog') {
|
|
40
|
+
if (proxy.props.seamless !== true) {
|
|
41
|
+
if (sawClosablePortal === false) return
|
|
42
|
+
|
|
43
|
+
closableContentEls = new Set(
|
|
44
|
+
portalProxyList.slice(i + 1).map(vm => vm.contentEl)
|
|
45
|
+
)
|
|
46
|
+
break
|
|
47
|
+
}
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
sawClosablePortal = true
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
for (let i = registeredList.length - 1; i >= 0; i--) {
|
|
48
56
|
const state = registeredList[i]
|
|
49
57
|
|
|
58
|
+
if (
|
|
59
|
+
closableContentEls !== null &&
|
|
60
|
+
!closableContentEls.has(state.innerRef.value)
|
|
61
|
+
) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
if (
|
|
51
66
|
(state.anchorEl.value === null ||
|
|
52
67
|
!state.anchorEl.value.contains(target)) &&
|