mirror of https://github.com/vuejs/core.git
fix(custom-elements): use strict number casting
close #4946 close #2598 close #2604 This commit also refactors internal usage of previous loose implementation of `toNumber` to the stricter version where applicable. Use of `looseToNumber` is preserved for `v-model.number` modifier to ensure backwards compatibility and consistency with Vue 2 behavior.
This commit is contained in:
parent
efa2ac54d9
commit
7d0c63ff43
|
@ -2,9 +2,9 @@ import {
|
||||||
extend,
|
extend,
|
||||||
looseEqual,
|
looseEqual,
|
||||||
looseIndexOf,
|
looseIndexOf,
|
||||||
|
looseToNumber,
|
||||||
NOOP,
|
NOOP,
|
||||||
toDisplayString,
|
toDisplayString
|
||||||
toNumber
|
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
ComponentPublicInstance,
|
ComponentPublicInstance,
|
||||||
|
@ -148,7 +148,7 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
||||||
$createElement: () => compatH,
|
$createElement: () => compatH,
|
||||||
_c: () => compatH,
|
_c: () => compatH,
|
||||||
_o: () => legacyMarkOnce,
|
_o: () => legacyMarkOnce,
|
||||||
_n: () => toNumber,
|
_n: () => looseToNumber,
|
||||||
_s: () => toDisplayString,
|
_s: () => toDisplayString,
|
||||||
_l: () => renderList,
|
_l: () => renderList,
|
||||||
_t: i => legacyRenderSlot.bind(null, i),
|
_t: i => legacyRenderSlot.bind(null, i),
|
||||||
|
|
|
@ -10,8 +10,8 @@ import {
|
||||||
isObject,
|
isObject,
|
||||||
isString,
|
isString,
|
||||||
isOn,
|
isOn,
|
||||||
toNumber,
|
UnionToIntersection,
|
||||||
UnionToIntersection
|
looseToNumber
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
|
@ -126,7 +126,7 @@ export function emit(
|
||||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||||
}
|
}
|
||||||
if (number) {
|
if (number) {
|
||||||
args = rawArgs.map(toNumber)
|
args = rawArgs.map(looseToNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,12 @@ import {
|
||||||
} from '../renderer'
|
} from '../renderer'
|
||||||
import { queuePostFlushCb } from '../scheduler'
|
import { queuePostFlushCb } from '../scheduler'
|
||||||
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
|
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
|
||||||
import { pushWarningContext, popWarningContext, warn } from '../warning'
|
import {
|
||||||
|
pushWarningContext,
|
||||||
|
popWarningContext,
|
||||||
|
warn,
|
||||||
|
assertNumber
|
||||||
|
} from '../warning'
|
||||||
import { handleError, ErrorCodes } from '../errorHandling'
|
import { handleError, ErrorCodes } from '../errorHandling'
|
||||||
|
|
||||||
export interface SuspenseProps {
|
export interface SuspenseProps {
|
||||||
|
@ -419,6 +424,10 @@ function createSuspenseBoundary(
|
||||||
} = rendererInternals
|
} = rendererInternals
|
||||||
|
|
||||||
const timeout = toNumber(vnode.props && vnode.props.timeout)
|
const timeout = toNumber(vnode.props && vnode.props.timeout)
|
||||||
|
if (__DEV__) {
|
||||||
|
assertNumber(timeout, `Suspense timeout`)
|
||||||
|
}
|
||||||
|
|
||||||
const suspense: SuspenseBoundary = {
|
const suspense: SuspenseBoundary = {
|
||||||
vnode,
|
vnode,
|
||||||
parent,
|
parent,
|
||||||
|
|
|
@ -104,7 +104,7 @@ export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'
|
||||||
|
|
||||||
export { createRenderer, createHydrationRenderer } from './renderer'
|
export { createRenderer, createHydrationRenderer } from './renderer'
|
||||||
export { queuePostFlushCb } from './scheduler'
|
export { queuePostFlushCb } from './scheduler'
|
||||||
export { warn } from './warning'
|
export { warn, assertNumber } from './warning'
|
||||||
export {
|
export {
|
||||||
handleError,
|
handleError,
|
||||||
callWithErrorHandling,
|
callWithErrorHandling,
|
||||||
|
|
|
@ -162,3 +162,15 @@ function formatProp(key: string, value: unknown, raw?: boolean): any {
|
||||||
return raw ? value : [`${key}=`, value]
|
return raw ? value : [`${key}=`, value]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function assertNumber(val: unknown, type: string) {
|
||||||
|
if (!__DEV__) return
|
||||||
|
if (typeof val !== 'number') {
|
||||||
|
warn(`${type} is not a valid number - ` + `got ${JSON.stringify(val)}.`)
|
||||||
|
} else if (isNaN(val)) {
|
||||||
|
warn(`${type} is NaN - ` + 'the duration expression might be incorrect.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
BaseTransition,
|
BaseTransition,
|
||||||
BaseTransitionProps,
|
BaseTransitionProps,
|
||||||
h,
|
h,
|
||||||
warn,
|
assertNumber,
|
||||||
FunctionalComponent,
|
FunctionalComponent,
|
||||||
compatUtils,
|
compatUtils,
|
||||||
DeprecationTypes
|
DeprecationTypes
|
||||||
|
@ -283,24 +283,10 @@ function normalizeDuration(
|
||||||
|
|
||||||
function NumberOf(val: unknown): number {
|
function NumberOf(val: unknown): number {
|
||||||
const res = toNumber(val)
|
const res = toNumber(val)
|
||||||
if (__DEV__) validateDuration(res)
|
if (__DEV__) assertNumber(res, '<transition> explicit duration')
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateDuration(val: unknown) {
|
|
||||||
if (typeof val !== 'number') {
|
|
||||||
warn(
|
|
||||||
`<transition> explicit duration is not a valid number - ` +
|
|
||||||
`got ${JSON.stringify(val)}.`
|
|
||||||
)
|
|
||||||
} else if (isNaN(val)) {
|
|
||||||
warn(
|
|
||||||
`<transition> explicit duration is NaN - ` +
|
|
||||||
'the duration expression might be incorrect.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addTransitionClass(el: Element, cls: string) {
|
export function addTransitionClass(el: Element, cls: string) {
|
||||||
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
|
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
|
||||||
;(
|
;(
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
looseEqual,
|
looseEqual,
|
||||||
looseIndexOf,
|
looseIndexOf,
|
||||||
invokeArrayFns,
|
invokeArrayFns,
|
||||||
toNumber,
|
looseToNumber,
|
||||||
isSet
|
isSet
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ export const vModelText: ModelDirective<
|
||||||
domValue = domValue.trim()
|
domValue = domValue.trim()
|
||||||
}
|
}
|
||||||
if (castToNumber) {
|
if (castToNumber) {
|
||||||
domValue = toNumber(domValue)
|
domValue = looseToNumber(domValue)
|
||||||
}
|
}
|
||||||
el._assign(domValue)
|
el._assign(domValue)
|
||||||
})
|
})
|
||||||
|
@ -88,7 +88,10 @@ export const vModelText: ModelDirective<
|
||||||
if (trim && el.value.trim() === value) {
|
if (trim && el.value.trim() === value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ((number || el.type === 'number') && toNumber(el.value) === value) {
|
if (
|
||||||
|
(number || el.type === 'number') &&
|
||||||
|
looseToNumber(el.value) === value
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +185,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
||||||
const selectedVal = Array.prototype.filter
|
const selectedVal = Array.prototype.filter
|
||||||
.call(el.options, (o: HTMLOptionElement) => o.selected)
|
.call(el.options, (o: HTMLOptionElement) => o.selected)
|
||||||
.map((o: HTMLOptionElement) =>
|
.map((o: HTMLOptionElement) =>
|
||||||
number ? toNumber(getValue(o)) : getValue(o)
|
number ? looseToNumber(getValue(o)) : getValue(o)
|
||||||
)
|
)
|
||||||
el._assign(
|
el._assign(
|
||||||
el.multiple
|
el.multiple
|
||||||
|
|
|
@ -153,11 +153,23 @@ export const def = (obj: object, key: string | symbol, value: any) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toNumber = (val: any): any => {
|
/**
|
||||||
|
* "123-foo" will be parsed to 123
|
||||||
|
* This is used for the .number modifier in v-model
|
||||||
|
*/
|
||||||
|
export const looseToNumber = (val: any): any => {
|
||||||
const n = parseFloat(val)
|
const n = parseFloat(val)
|
||||||
return isNaN(n) ? val : n
|
return isNaN(n) ? val : n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "123-foo" will be returned as-is
|
||||||
|
*/
|
||||||
|
export const toNumber = (val: any): any => {
|
||||||
|
const n = Number(val)
|
||||||
|
return isNaN(n) ? val : n
|
||||||
|
}
|
||||||
|
|
||||||
let _globalThis: any
|
let _globalThis: any
|
||||||
export const getGlobalThis = (): any => {
|
export const getGlobalThis = (): any => {
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in New Issue