* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { on, off } from '@opentiny/utils'
import { formatNumber } from '@opentiny/utils'
import { getMiniDecimal, equalsDecimal } from '@opentiny/utils'
import { isNumber } from '@opentiny/utils'
export const initService = (service) => {
const { utils = {} } = service || {}
const noopFn = () => null
return {
getCurrencyPrecision: utils.getCurrencyPrecision || noopFn,
getNumberFormat: utils.getNumberFormat || noopFn
}
}
export const getDecimal = (props) => (value) => getMiniDecimal(value, props.plugin)
export const closePopper = (state) => () => {
state.visible = false
state.editorPhase = 'close'
}
export const popInput =
({ editorState, props, state, api }) =>
(value) => {
value = api.onInputPreprocess(value)
const { fraction, groupSeparator } = state.format
value = value.replace(/^-+/, '-')
const groups = value.split(groupSeparator).map((val) => val.trim())
value = groups.join('')
if (value !== '-' && api.getDecimal(value).isNaN()) {
value = editorState.lastInput
} else {
value = value
.split('.')
.map((a, i) => (i && props.strictInput && typeof fraction === 'number' ? a.substr(0, fraction) : a))
.join('.')
}
editorState.lastInput = value
editorState.amount = value
}
export const toggleVisible =
({ api, props, state, editorState, uiMode, isMobileFirstMode: mf }) =>
() => {
if (state.disabled || !props.popUp) {
return
}
state.lock = true
state.visible = !state.visible
if (state.visible) {
const { date, currency } = state
if (!state.clearValues) {
Object.assign(editorState, {
amount: state.amount,
date,
currency
})
}
if (props.date || props.dateAllowEmpty) {
state.editorPhase = 'selection'
} else {
state.editorPhase = 'currency'
}
if (!mf || uiMode.value !== 'default') {
api.addOutSideEvent(state.visible)
}
} else {
state.editorPhase = 'close'
}
}
export const openDetailEditor =
({ state }) =>
(option, index) => {
const optionPhase = ['currency', 'date']
state.editorPhase = optionPhase[index]
state.visible = true
}
export const closeDetailEidtor =
({ state, props, api }) =>
(triggerCondition) => {
if (!triggerCondition) {
return
}
const inSelectionPhase = state.editorPhase === 'selection'
const isMultipleStep = props.date || props.dateAllowEmpty
if (!inSelectionPhase && isMultipleStep) {
state.editorPhase = 'selection'
state.visible = true
} else {
api.toggleVisible()
}
setTimeout(() => {
api.save(true)
}, 0)
}
export const innerFormat =
({ state, props }) =>
(value) => {
const { fraction, zeroize, rounding } = state.format
const { modelTruncation } = props
const fractionLen = (value.split('.')[1] || '').length
if (value === '-') {
return 0
}
if (!modelTruncation && fractionLen > fraction) {
return value
}
return formatNumber(value, {
fraction,
zeroize,
groupSeparator: '',
decimalSeparator: '.',
rounding
})
}
export const save =
({ api, state, editorState }) =>
(keepOpen) => {
const { amount, date, currency } = editorState
Object.assign(state, { amount, date, currency })
if (keepOpen !== true) {
api.closePopper()
}
let num = api.innerFormat(state.amount + '')
state.amount = isNaN(num) ? 0 : num
if (!equalsDecimal(state.lastInput, state.amount) || state.lastCurrency !== currency || state.lastDate !== date) {
api.emitChange()
}
state.lastInput = state.amount
state.lastCurrency = currency
state.lastDate = date
api.initText()
}
export const reset =
({ state, editorState }) =>
() => {
const { amount, date, currency } = state
Object.assign(editorState, { amount, date, currency })
}
export const emitChange =
({ emit, state, props, api }) =>
() => {
const { date, currency } = state
let emitAmount = props.stringMode ? api.getDecimal(state.amount).toString() : Number(state.amount)
if (props.numAllowEmpty && state.amount === '') {
emitAmount = state.amount
}
state.amount && (state.clearValues = false)
!state.clearValues && emit('update:modelValue', emitAmount)
emit('update:currency', currency)
emit('update:date', date)
emit('change', { amount: emitAmount, date, currency })
}
export const inputFocus =
({ state, props }) =>
() => {
let amount = state.amount + ''
state.isFocus = true
state.lock = false
const { fraction } = state.format
const fractionLen = (amount.split('.')[1] || '').length
if (fractionLen < fraction && props.holdZero) {
amount = formatNumber(amount, { fraction })
}
state.amountText = amount
}
export const inputBlur =
({ api, state, props }) =>
() => {
if (state.amountText !== '') {
let amount = api.innerFormat(state.amountText + '')
if (isNaN(amount)) {
state.amount = ''
}
state.amount = props.holdZero ? amount : api.getDecimal(amount).toString()
}
state.isFocus = false
state.amountText = formatNumber(state.amount, state.format)
if (!equalsDecimal(state.lastInput, state.amount)) {
api.emitChange()
}
state.lastInput = state.amount
}
export const handelClick =
({ api, vm }) =>
(e) => {
const contains = vm.$refs.root.contains(e.target)
if (!contains) {
api.closePopper()
}
}
export const addOutSideEvent = (api) => (visible) => {
if (visible) {
on(document, 'click', api.handelClick)
} else {
off(document, 'click', api.handelClick)
}
}
export const initText =
({ state }) =>
() => {
let amount = state.amount + ''
state.amountText = amount ? (state.isFocus ? amount : formatNumber(state.amount, state.format)) : ''
}
export const onInputPreprocess = (props) => (value) => {
const inputElem = event.target
if (value) {
const i = value.lastIndexOf('-')
if ((i === 0 && !props.negative) || i > 0) {
const tmpArr = value.split('')
tmpArr.splice(i, 1)
inputElem.value = value = tmpArr.join('')
inputElem.selectionStart = inputElem.selectionEnd = i
}
}
return value
}
export const onInput =
({ state, props, api }) =>
(value) => {
value = api.onInputPreprocess(value)
const { fraction, groupSeparator } = state.format
value =
event.target.value !== undefined
? event.target.value.replace(/^-+/, '-')
: value.replace(/^-+/, '-')
const groups = value.split(groupSeparator).map((val) => val.trim())
value = groups.join('')
if (value !== '-' && api.getDecimal(value).isNaN()) {
value = state.lastInput
} else {
value = value
.split('.')
.map((a, i) => (i && props.strictInput && typeof fraction === 'number' ? a.substr(0, fraction) : a))
.join('.')
}
event.target.value = value
state.amount = value
state.amountText = value
}
export const getPrecision = ({ service, props, currency }) => {
const { format = {}, rounding: r, digits, type } = props
let fraction
let rounding = format.rounding
const { getCurrencyPrecision, getNumberFormat } = service
const serFra = getCurrencyPrecision(type, currency) || {}
const serFmt = getNumberFormat() || {}
const defaultFmt = {
groupSeparator: ',',
groupSize: 3,
decimalSeparator: '.',
zeroize: props.holdZero
}
fraction = isNumber(format.fraction) ? format.fraction : isNumber(serFra.fraction) ? serFra.fraction : digits
if (r === false) {
rounding = 0
} else {
rounding = isNumber(rounding) ? rounding : isNumber(serFra.rounding) ? serFra.rounding : 5
}
return { ...defaultFmt, fraction, rounding, ...serFmt, ...format }
}
export const getAmountText =
({ state, props }) =>
() => {
const isFilter = props.shape === 'filter' && props.filter
if (props.hideCurrency && typeof state.amountText === 'string') {
return isFilter
? state.radioVal + state.amountText.replace(state.currency, '')
: state.amountText.replace(state.currency, '')
} else {
return isFilter ? state.radioVal + state.amountText : state.amountText
}
}
export const getAmountTextWithoutCurrncy =
({ state }) =>
() => {
return state.amountText.replace(state.currency, '')
}
export const watchModelValue =
({ api, state }) =>
() => {
let value = api.initAmount()
state.amount = value
state.lastInput = value
api.initText()
}
export const watchCurrency =
({ api, state, editorState }) =>
(value) => {
state.currency = value
editorState.currency = value
state.lastCurrency = value
api.initText()
}
export const watchUiMode =
({ api, isMobileFirstMode: mf }) =>
(value) => {
if (mf && value === 'default') {
api.addOutSideEvent(false)
}
}
export const initAmount =
({ props, api }) =>
() => {
let value = props.modelValue
value = value === null || isNaN(Number(value)) ? '' : value
if (!props.negative && value && Number(value) < 0) {
if (props.stringMode) {
value = api.getDecimal(String(value).replace(/^-/, '')).toString()
} else {
value -= 0
value = Math.abs(value)
}
}
return value
}
export const handleClearClick =
({ state, emit, editorState }) =>
(event) => {
event.stopPropagation()
state.amountText = ''
state.radioVal = ''
editorState.amount = ''
state.clearValues = true
emit('clear')
}
export const handleChange =
({ state, emit }) =>
() => {
emit('filter-change', state.radioVal)
}