AJoute un champs de type Number (work in progress)
Feature : - Supporte les unités - Supporte les valeurs au clavier (haut / bas) - Supporte le formattingwip-johan
parent
90a55cc285
commit
1a36518f61
|
@ -25,6 +25,7 @@
|
|||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@react-types/numberfield": "^3.1.0",
|
||||
"@react-types/searchfield": "^3.1.2",
|
||||
"@types/cheerio": "^0.22.18",
|
||||
"@types/js-yaml": "^3.12.2",
|
||||
|
@ -65,9 +66,12 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.4",
|
||||
"@react-aria/button": "^3.3.4",
|
||||
"@react-aria/i18n": "^3.3.2",
|
||||
"@react-aria/numberfield": "^3.1.0",
|
||||
"@react-aria/searchfield": "^3.2.0",
|
||||
"@react-aria/textfield": "^3.4.0",
|
||||
"@react-pdf/renderer": "^1.6.10",
|
||||
"@react-stately/numberfield": "^3.0.2",
|
||||
"@react-stately/searchfield": "^3.1.3",
|
||||
"@rehooks/local-storage": "^2.1.1",
|
||||
"@sentry/react": "^6.3.5",
|
||||
|
@ -95,7 +99,6 @@
|
|||
"react-instantsearch-dom": "^6.11.2",
|
||||
"react-markdown": "^4.1.0",
|
||||
"react-monaco-editor": "^0.40.0",
|
||||
"react-number-format": "^4.3.1",
|
||||
"react-redux": "^7.0.3",
|
||||
"react-router-dom": "^5.1.1",
|
||||
"react-router-hash-link": "^1.2.2",
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
.currencyInput__container {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.currencyInput__input:focus {
|
||||
outline: none;
|
||||
}
|
||||
.currencyInput__input {
|
||||
height: inherit;
|
||||
max-height: inherit;
|
||||
border: none;
|
||||
text-align: inherit;
|
||||
font-family: inherit;
|
||||
padding: 0;
|
||||
font-weight: inherit;
|
||||
min-width: 0;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
width: inherit;
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.currencyInput__input::-ms-clear {
|
||||
display: none;
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
import { expect } from 'chai'
|
||||
import { mount, shallow } from 'enzyme'
|
||||
import { match, spy, useFakeTimers } from 'sinon'
|
||||
import CurrencyInput from './CurrencyInput'
|
||||
|
||||
let getInput = (component) => mount(component).find('input')
|
||||
describe('CurrencyInput', () => {
|
||||
it('should render an input', () => {
|
||||
expect(getInput(<CurrencyInput />)).to.have.length(1)
|
||||
})
|
||||
|
||||
it('should accept , as decimal separator in french', () => {
|
||||
const onChange = spy()
|
||||
const input = getInput(<CurrencyInput language="fr" onChange={onChange} />)
|
||||
input.simulate('change', { target: { value: '12,1', focus: () => {} } })
|
||||
expect(onChange).to.have.been.calledWith(
|
||||
match.hasNested('target.value', '12.1')
|
||||
)
|
||||
})
|
||||
|
||||
it('should separate thousand groups', () => {
|
||||
const input1 = getInput(
|
||||
<CurrencyInput value={1000} language="fr" currencySymbol={''} />
|
||||
)
|
||||
const input2 = getInput(
|
||||
<CurrencyInput value={1000} language="en" currencySymbol={''} />
|
||||
)
|
||||
const input3 = getInput(
|
||||
<CurrencyInput value={1000.5} language="en" currencySymbol={''} />
|
||||
)
|
||||
const input4 = getInput(
|
||||
<CurrencyInput value={1000000} language="en" currencySymbol={''} />
|
||||
)
|
||||
expect(input1.instance().value).to.equal('1 000')
|
||||
expect(input2.instance().value).to.equal('1,000')
|
||||
expect(input3.instance().value).to.equal('1,000.5')
|
||||
expect(input4.instance().value).to.equal('1,000,000')
|
||||
})
|
||||
|
||||
it('should handle decimal separator', () => {
|
||||
const input = getInput(<CurrencyInput value={0.5} language="fr" />)
|
||||
expect(input.instance().value).to.equal('0,5')
|
||||
})
|
||||
|
||||
it('should accept negative number', () => {
|
||||
let onChange = spy()
|
||||
const input = getInput(<CurrencyInput onChange={onChange} />)
|
||||
input.simulate('change', { target: { value: '-12', focus: () => {} } })
|
||||
expect(onChange).to.have.been.calledWith(
|
||||
match.hasNested('target.value', '-12')
|
||||
)
|
||||
})
|
||||
|
||||
it('should not accept anything else than number', () => {
|
||||
let onChange = spy()
|
||||
const input = getInput(<CurrencyInput language="fr" onChange={onChange} />)
|
||||
input.simulate('change', { target: { value: '*1/2abc3', focus: () => {} } })
|
||||
expect(onChange).to.have.been.calledWith(
|
||||
match.hasNested('target.value', '123')
|
||||
)
|
||||
})
|
||||
|
||||
it('should pass other props to the input', () => {
|
||||
const input = getInput(<CurrencyInput autoFocus />)
|
||||
expect(input.prop('autoFocus')).to.be.true
|
||||
})
|
||||
|
||||
it('should not call onChange while the decimal part is being written', () => {
|
||||
let onChange = spy()
|
||||
const input = getInput(
|
||||
<CurrencyInput language="fr" value="111" onChange={onChange} />
|
||||
)
|
||||
input.simulate('change', { target: { value: '111,', focus: () => {} } })
|
||||
expect(onChange).not.to.have.been.called
|
||||
})
|
||||
|
||||
it('should change the position of the currency symbol depending on the language', () => {
|
||||
const inputFr = shallow(<CurrencyInput language="fr" />)
|
||||
expect(inputFr.children().last().text()).to.includes('€')
|
||||
const inputEn = shallow(<CurrencyInput language="en" />)
|
||||
expect(inputEn.children().first().text()).to.includes('€')
|
||||
})
|
||||
|
||||
it('should debounce onChange call', () => {
|
||||
const clock = useFakeTimers()
|
||||
let onChange = spy()
|
||||
const input = getInput(
|
||||
<CurrencyInput
|
||||
language="fr"
|
||||
onChange={onChange}
|
||||
debounce={1000}
|
||||
currencySymbol={''}
|
||||
/>
|
||||
)
|
||||
input.simulate('change', { target: { value: '1', focus: () => {} } })
|
||||
expect(onChange).not.to.have.been.called
|
||||
clock.tick(500)
|
||||
input.simulate('change', { target: { value: '12', focus: () => {} } })
|
||||
clock.tick(600)
|
||||
expect(onChange).not.to.have.been.called
|
||||
clock.tick(400)
|
||||
expect(onChange).to.have.been.calledWith(
|
||||
match.hasNested('target.value', '12')
|
||||
)
|
||||
clock.restore()
|
||||
})
|
||||
|
||||
it('should initialize with value of the value prop', () => {
|
||||
const input = getInput(<CurrencyInput value={1} language="fr" />)
|
||||
expect(input.instance().value).to.equal('1')
|
||||
})
|
||||
|
||||
it('should update its value if the value prop changes', () => {
|
||||
const component = mount(<CurrencyInput value={1} language="fr" />)
|
||||
component.setProps({ value: 2 })
|
||||
expect(component.find('input').instance().value).to.equal('2')
|
||||
})
|
||||
|
||||
it('should not call onChange the value is the same as the current input value', () => {
|
||||
let onChange = spy()
|
||||
const wrapper = mount(
|
||||
<CurrencyInput language="fr" value={2000} onChange={onChange} />
|
||||
)
|
||||
const input = wrapper.find('input')
|
||||
input.simulate('change', { target: { value: '2000', focus: () => {} } })
|
||||
wrapper.setProps({ value: '2000' })
|
||||
expect(onChange).not.to.have.been.called
|
||||
})
|
||||
|
||||
it('should adapt its size to its content', () => {
|
||||
const wrapper = mount(<CurrencyInput language="fr" value={1000} />)
|
||||
// It would be better to use `input.offsetWidth` but it's not supported by
|
||||
// Enzyme/JSDOM
|
||||
const getInlineWidth = () =>
|
||||
getComputedStyle(
|
||||
wrapper.find('.currencyInput__container').getDOMNode()
|
||||
).getPropertyValue('width')
|
||||
expect(getInlineWidth()).to.equal('')
|
||||
wrapper.setProps({ value: '1000000' })
|
||||
expect(Number(getInlineWidth().replace(/em$/, ''))).to.be.greaterThan(5)
|
||||
})
|
||||
|
||||
it('should not call onChange if the value is not a correct number', () => {
|
||||
let onChange = spy()
|
||||
mount(<CurrencyInput language="fr" onChange={onChange} />)
|
||||
.find('input')
|
||||
.simulate('change', {
|
||||
target: { value: '-', focus: () => {} },
|
||||
})
|
||||
mount(<CurrencyInput language="fr" onChange={onChange} />)
|
||||
.find('input')
|
||||
.simulate('change', {
|
||||
target: { value: ',', focus: () => {} },
|
||||
})
|
||||
mount(<CurrencyInput language="fr" onChange={onChange} />)
|
||||
.find('input')
|
||||
.simulate('change', {
|
||||
target: { value: ',5', focus: () => {} },
|
||||
})
|
||||
mount(<CurrencyInput language="fr" onChange={onChange} />)
|
||||
.find('input')
|
||||
.simulate('change', {
|
||||
target: { value: '8,', focus: () => {} },
|
||||
})
|
||||
expect(onChange).not.to.have.been.called
|
||||
})
|
||||
})
|
|
@ -1,108 +0,0 @@
|
|||
import classnames from 'classnames'
|
||||
import React, { useMemo, useRef, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatProps } from 'react-number-format'
|
||||
import { currencyFormat, debounce } from '../../utils'
|
||||
import './CurrencyInput.css'
|
||||
|
||||
type CurrencyInputProps = NumberFormatProps & {
|
||||
value?: string | number | null
|
||||
debounce?: number
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
currencySymbol?: string
|
||||
language: string
|
||||
}
|
||||
|
||||
export default function CurrencyInput({
|
||||
value,
|
||||
debounce: debounceTimeout,
|
||||
currencySymbol = '€',
|
||||
onChange,
|
||||
language,
|
||||
missing,
|
||||
className,
|
||||
style,
|
||||
dottedName,
|
||||
...forwardedProps
|
||||
}: CurrencyInputProps) {
|
||||
const valueProp =
|
||||
typeof value === 'number' && Number.isNaN(value) ? '' : value ?? ''
|
||||
|
||||
const [initialValue, setInitialValue] = useState(valueProp)
|
||||
const [currentValue, setCurrentValue] = useState(valueProp)
|
||||
|
||||
const onChangeDebounced = useMemo(
|
||||
() =>
|
||||
debounceTimeout && onChange
|
||||
? debounce(debounceTimeout, onChange)
|
||||
: onChange,
|
||||
[onChange, debounceTimeout]
|
||||
)
|
||||
// We need some mutable reference because the <NumberFormat /> component doesn't provide
|
||||
// the DOM `event` in its custom `onValueChange` handler
|
||||
const nextValue = useRef('')
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
|
||||
// When the component is rendered with a new "value" prop, we reset our local
|
||||
// state
|
||||
if (valueProp !== initialValue) {
|
||||
setCurrentValue(valueProp)
|
||||
setInitialValue(valueProp)
|
||||
}
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// Only trigger the `onChange` event if the value has changed -- and not
|
||||
// only its formating, we don't want to call it when a dot is added in `12.`
|
||||
// for instance
|
||||
if (!nextValue.current || /(\.$)|(^\.)|(-$)/.exec(nextValue.current)) {
|
||||
return
|
||||
}
|
||||
event.persist()
|
||||
event.target = {
|
||||
...event.target,
|
||||
value: nextValue.current,
|
||||
}
|
||||
nextValue.current = ''
|
||||
onChangeDebounced?.(event)
|
||||
}
|
||||
|
||||
const { isCurrencyPrefixed, thousandSeparator, decimalSeparator } =
|
||||
currencyFormat(language)
|
||||
// Autogrow the input
|
||||
const valueLength = currentValue.toString().length
|
||||
const width = `${5 + (valueLength - 5) * 0.75}em`
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, 'currencyInput__container')}
|
||||
style={{ ...(valueLength > 5 ? { width } : {}), ...style }}
|
||||
onFocus={() => inputRef.current?.select()}
|
||||
onClick={() => inputRef.current?.focus()}
|
||||
>
|
||||
{isCurrencyPrefixed && currentValue == '' && <>€ </>}
|
||||
|
||||
<NumberFormat
|
||||
{...forwardedProps}
|
||||
thousandSeparator={thousandSeparator}
|
||||
decimalSeparator={decimalSeparator}
|
||||
allowNegative
|
||||
className="currencyInput__input"
|
||||
inputMode="numeric"
|
||||
getInputRef={inputRef}
|
||||
prefix={
|
||||
isCurrencyPrefixed && currencySymbol ? `${currencySymbol} ` : ''
|
||||
}
|
||||
onValueChange={({ value }) => {
|
||||
setCurrentValue(value)
|
||||
nextValue.current = value
|
||||
.toString()
|
||||
.replace(/^0+(.*)$/, '$1')
|
||||
.replace(/^$/, '0')
|
||||
}}
|
||||
onChange={handleChange}
|
||||
value={currentValue != null ? currentValue : ''}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{!isCurrencyPrefixed && <> €</>}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
.range {
|
||||
-webkit-appearance: none;
|
||||
vertical-align: middle;
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.range::-webkit-slider-runnable-track {
|
||||
background-color: white;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.range[disabled]::-webkit-slider-runnable-track {
|
||||
border: 1px solid white;
|
||||
background-color: transparent;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.range::-moz-range-track {
|
||||
background-color: white;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.range::-ms-track {
|
||||
color: transparent;
|
||||
border: none;
|
||||
background: none;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.range::-ms-fill-lower {
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.range::-ms-fill-upper {
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.range::-ms-tooltip {
|
||||
display: none; /* display and visibility only */
|
||||
}
|
||||
|
||||
.range::-moz-range-thumb {
|
||||
border-radius: 20px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border: 2px solid white;
|
||||
background: none;
|
||||
background-color: var(--color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.range:active::-moz-range-thumb {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.range::-webkit-slider-thumb {
|
||||
-webkit-appearance: none !important;
|
||||
border-radius: 100%;
|
||||
border: 2px solid white;
|
||||
background-color: var(--color);
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.range[disabled]::-webkit-slider-thumb {
|
||||
background-color: transparent;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.range:active::-webkit-slider-thumb {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.range::-ms-thumb {
|
||||
border-radius: 100%;
|
||||
border: 2px solid white;
|
||||
background-color: var(--color);
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.range:active::-ms-thumb {
|
||||
border: none;
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { formatValue } from 'publicodes'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { debounce as debounceFn } from '../utils'
|
||||
import { InputProps } from './conversation/RuleInput'
|
||||
import './PercentageField.css'
|
||||
|
||||
type PercentageFieldProps = InputProps & {
|
||||
debounce: number
|
||||
}
|
||||
|
||||
export default function PercentageField({
|
||||
onChange,
|
||||
value,
|
||||
debounce = 0,
|
||||
}: PercentageFieldProps) {
|
||||
const [localValue, setLocalValue] = useState(value as number)
|
||||
const debouncedOnChange = useCallback(
|
||||
debounce ? debounceFn(debounce, onChange) : onChange,
|
||||
[debounce, onChange]
|
||||
)
|
||||
const language = useTranslation().i18n.language
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
className="range"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
setLocalValue(+value)
|
||||
debouncedOnChange(value)
|
||||
}}
|
||||
type="range"
|
||||
value={localValue}
|
||||
name="volume"
|
||||
min="0"
|
||||
step="0.05"
|
||||
max="1"
|
||||
/>
|
||||
<span style={{ display: 'inline-block', width: '3em' }}>
|
||||
{formatValue(localValue, {
|
||||
language,
|
||||
displayedUnit: '%',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -155,6 +155,9 @@ export function SimulationGoal({
|
|||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
onChange={onChange}
|
||||
formatOptions={{
|
||||
maximumFractionDigits: 0,
|
||||
}}
|
||||
useSwitch
|
||||
/>
|
||||
) : (
|
||||
|
|
|
@ -118,10 +118,6 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
#targetSelection input {
|
||||
margin: 2.7px 0;
|
||||
}
|
||||
|
||||
#targetSelection .targetInput {
|
||||
width: 5.5em;
|
||||
max-width: 7.5em;
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
targetUnitSelector,
|
||||
} from 'Selectors/simulationSelectors'
|
||||
import InputSuggestions from './conversation/InputSuggestions'
|
||||
import CurrencyInput from './CurrencyInput/CurrencyInput'
|
||||
import NumberInput from './conversation/NumberInput'
|
||||
import './TargetSelection.css'
|
||||
import { Appear, FromTop } from './ui/animate'
|
||||
import Emoji from './utils/Emoji'
|
||||
|
@ -177,10 +177,10 @@ function TargetInputOrValue({
|
|||
const isSituationEmpty = Object.keys(situation).length === 0
|
||||
const isActive = target.dottedName in situation
|
||||
const onChange = useCallback(
|
||||
(evt) =>
|
||||
(valeur) =>
|
||||
dispatch(
|
||||
updateSituation(target.dottedName, {
|
||||
valeur: evt.target.value,
|
||||
valeur,
|
||||
unité: targetUnit,
|
||||
})
|
||||
),
|
||||
|
@ -195,19 +195,10 @@ function TargetInputOrValue({
|
|||
{target.question ? (
|
||||
<>
|
||||
{!isFocused && <AnimatedTargetValue value={value} />}
|
||||
<CurrencyInput
|
||||
debounce={750}
|
||||
<NumberInput
|
||||
name={target.dottedName}
|
||||
unit={{ numerators: ['€'], denominators: [] }}
|
||||
value={value}
|
||||
className={classnames(
|
||||
isFocused ||
|
||||
isActive ||
|
||||
isSituationEmpty ||
|
||||
(target.question && isSmallTarget)
|
||||
? 'targetInput'
|
||||
: 'editableTarget',
|
||||
{ focused: isFocused }
|
||||
)}
|
||||
onChange={onChange}
|
||||
onFocus={() => {
|
||||
setFocused(true)
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
import { formatValue, Unit } from 'publicodes'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NumberFormat from 'react-number-format'
|
||||
import { currencyFormat, debounce } from '../../utils'
|
||||
import InputSuggestions from './InputSuggestions'
|
||||
import { InputProps } from './RuleInput'
|
||||
|
||||
// TODO: fusionner Input.js et CurrencyInput.js
|
||||
export default function Input({
|
||||
suggestions,
|
||||
onChange,
|
||||
onSubmit,
|
||||
id,
|
||||
value,
|
||||
missing,
|
||||
unit,
|
||||
autoFocus,
|
||||
}: InputProps & {
|
||||
unit: Unit | undefined
|
||||
}) {
|
||||
const debouncedOnChange = useCallback(debounce(550, onChange), [])
|
||||
const { language } = useTranslation().i18n
|
||||
const unité = formatValue({ nodeValue: value ?? 0, unit }, { language })
|
||||
.replace(/[\d,.]/g, '')
|
||||
.trim()
|
||||
const { thousandSeparator, decimalSeparator } = currencyFormat(language)
|
||||
// const [currentValue, setCurrentValue] = useState(value)
|
||||
return (
|
||||
<div className="step input">
|
||||
<div>
|
||||
<InputSuggestions
|
||||
suggestions={suggestions}
|
||||
onFirstClick={(value) => {
|
||||
onChange(value)
|
||||
}}
|
||||
onSecondClick={() => onSubmit?.('suggestion')}
|
||||
/>
|
||||
<NumberFormat
|
||||
autoFocus={autoFocus}
|
||||
className="suffixed ui__"
|
||||
id={id}
|
||||
thousandSeparator={thousandSeparator}
|
||||
decimalSeparator={decimalSeparator}
|
||||
allowEmptyFormatting={true}
|
||||
// We don't want to call `onValueChange` in case this component is
|
||||
// re-render with a new "value" prop from the outside.
|
||||
onValueChange={({ floatValue }) => {
|
||||
if (floatValue !== value) {
|
||||
debouncedOnChange(
|
||||
floatValue != undefined ? { valeur: floatValue, unité } : {}
|
||||
)
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
{...{ [missing ? 'placeholder' : 'value']: value ?? '' }}
|
||||
/>
|
||||
<span className="suffix"> {unité}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
import { NumberField } from 'DesignSystem/field'
|
||||
import { serializeUnit, Unit } from 'publicodes'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { debounce } from '../../utils'
|
||||
import InputSuggestions from './InputSuggestions'
|
||||
import { InputProps } from './RuleInput'
|
||||
|
||||
export default function NumberInput({
|
||||
suggestions,
|
||||
onChange,
|
||||
onSubmit,
|
||||
value,
|
||||
formatOptions,
|
||||
missing,
|
||||
unit,
|
||||
autoFocus,
|
||||
}: InputProps & {
|
||||
unit: Unit | undefined
|
||||
}) {
|
||||
const unité = serializeUnit(unit)
|
||||
const [currentValue, handleChange] = useState<number | undefined>(
|
||||
!missing && value != null && typeof value === 'number' ? value : undefined
|
||||
)
|
||||
const language = useTranslation().i18n.language
|
||||
const displayedUnit =
|
||||
unit && getSerializedUnit(currentValue ?? 0, unit, language)
|
||||
|
||||
useEffect(() => {
|
||||
if (!missing && value != null && typeof value === 'number') {
|
||||
handleChange(value)
|
||||
}
|
||||
}, [value])
|
||||
formatOptions = {
|
||||
style: 'decimal',
|
||||
...(unit?.numerators.includes('€')
|
||||
? {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
}
|
||||
: {}),
|
||||
...formatOptions,
|
||||
}
|
||||
|
||||
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
|
||||
return (
|
||||
<div className="step input">
|
||||
<div>
|
||||
<InputSuggestions
|
||||
suggestions={suggestions}
|
||||
onFirstClick={(value) => {
|
||||
handleChange(value)
|
||||
setImmediate(() => {
|
||||
onChange(value)
|
||||
})
|
||||
}}
|
||||
onSecondClick={() => onSubmit?.('suggestion')}
|
||||
/>
|
||||
<NumberField
|
||||
autoFocus={autoFocus}
|
||||
displayedUnit={displayedUnit}
|
||||
onChange={(valeur) => {
|
||||
handleChange(valeur)
|
||||
if (!Number.isNaN(valeur)) {
|
||||
debouncedOnChange({ valeur, unité })
|
||||
}
|
||||
}}
|
||||
formatOptions={formatOptions}
|
||||
placeholder={missing && value != null ? value : undefined}
|
||||
value={currentValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// TODO : put this inside publicodes
|
||||
|
||||
function getSerializedUnit(value: number, unit: Unit, locale: string): string {
|
||||
// removing euro, which is a currency not a unit
|
||||
unit = {
|
||||
...unit,
|
||||
numerators: unit.numerators.filter((unit) => unit !== '€'),
|
||||
}
|
||||
|
||||
if (Number.isNaN(value)) {
|
||||
value = 0
|
||||
}
|
||||
|
||||
const formatUnit = getFormatUnit(unit)
|
||||
if (!formatUnit) {
|
||||
return serializeUnit(unit) ?? ''
|
||||
}
|
||||
return (
|
||||
Intl.NumberFormat(locale, {
|
||||
unit: formatUnit,
|
||||
style: 'unit',
|
||||
unitDisplay: 'long',
|
||||
})
|
||||
.formatToParts(value)
|
||||
.find(({ type }) => type === 'unit')?.value ?? ''
|
||||
)
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-unified-intl-numberformat/section6/locales-currencies-tz_proposed_out.html#sec-issanctionedsimpleunitidentifier
|
||||
const UNIT_MAP = {
|
||||
heure: 'hour',
|
||||
jour: 'day',
|
||||
année: 'year',
|
||||
an: 'year',
|
||||
minute: 'minute',
|
||||
mois: 'month',
|
||||
second: 'second',
|
||||
semaine: 'week',
|
||||
} as const
|
||||
|
||||
function getFormatUnit(unit: Unit): Intl.NumberFormatOptions['unit'] | null {
|
||||
if (unit.numerators.length !== 1 || unit.denominators.length > 1) {
|
||||
return null
|
||||
}
|
||||
const numerator = unit.numerators[0]
|
||||
const denominator = unit.denominators[0]
|
||||
if (
|
||||
(numerator && !(numerator in UNIT_MAP)) ||
|
||||
(denominator && !(denominator in UNIT_MAP))
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
let formatUnit = ''
|
||||
if (numerator) {
|
||||
formatUnit += UNIT_MAP[numerator as keyof typeof UNIT_MAP]
|
||||
}
|
||||
if (denominator) {
|
||||
formatUnit += `-per-${UNIT_MAP[denominator as keyof typeof UNIT_MAP]}`
|
||||
}
|
||||
|
||||
return formatUnit
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import Input from 'Components/conversation/Input'
|
||||
import NumberInput from 'Components/conversation/NumberInput'
|
||||
import Question, { Choice } from 'Components/conversation/Question'
|
||||
import SelectCommune from 'Components/conversation/select/SelectCommune'
|
||||
import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
|
||||
import CurrencyInput from 'Components/CurrencyInput/CurrencyInput'
|
||||
import PercentageField from 'Components/PercentageField'
|
||||
|
||||
import ToggleSwitch from 'Components/ui/ToggleSwitch'
|
||||
import { EngineContext } from 'Components/utils/EngineContext'
|
||||
import { DottedName } from 'modele-social'
|
||||
|
@ -45,6 +44,7 @@ type Props<Name extends string = DottedName> = Omit<
|
|||
isTarget?: boolean
|
||||
onSubmit?: (source: string) => void
|
||||
modifiers?: Record<string, string>
|
||||
formatOptions: Intl.NumberFormatOptions
|
||||
}
|
||||
|
||||
export type InputProps<Name extends string = string> = Omit<
|
||||
|
@ -82,7 +82,6 @@ export default function RuleInput({
|
|||
const engine = useContext(EngineContext)
|
||||
const rule = engine.getRule(dottedName)
|
||||
const evaluation = engine.evaluate({ valeur: dottedName, ...modifiers })
|
||||
const language = useTranslation().i18n.language
|
||||
const value = evaluation.nodeValue
|
||||
const commonProps: InputProps<DottedName> = {
|
||||
dottedName,
|
||||
|
@ -154,34 +153,6 @@ export default function RuleInput({
|
|||
)
|
||||
}
|
||||
|
||||
if (evaluation.unit?.numerators.includes('€') && isTarget) {
|
||||
const unité = formatValue(
|
||||
{ nodeValue: value ?? 0, unit: evaluation.unit },
|
||||
{ language }
|
||||
)
|
||||
.replace(/[\d,.]/g, '')
|
||||
.trim()
|
||||
|
||||
return (
|
||||
<>
|
||||
<CurrencyInput
|
||||
className="targetInput"
|
||||
language={language}
|
||||
debounce={750}
|
||||
name={dottedName}
|
||||
{...commonProps}
|
||||
onSubmit={() => {}}
|
||||
onChange={(evt) =>
|
||||
commonProps.onChange({ valeur: evt.target.value, unité })
|
||||
}
|
||||
value={value as number}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (evaluation.unit?.numerators.includes('%') && isTarget) {
|
||||
return <PercentageField {...commonProps} debounce={600} />
|
||||
}
|
||||
if (rule.rawNode.type === 'texte') {
|
||||
return <TextInput {...commonProps} value={value as Evaluation<string>} />
|
||||
}
|
||||
|
@ -192,7 +163,7 @@ export default function RuleInput({
|
|||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
<NumberInput
|
||||
{...commonProps}
|
||||
onSubmit={onSubmit}
|
||||
unit={evaluation.unit}
|
||||
|
|
|
@ -30,21 +30,11 @@ fieldset {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Remove spinner controls from Firefox */
|
||||
input[type='number'] {
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
select {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import { useLocale } from '@react-aria/i18n'
|
||||
import { useNumberField } from '@react-aria/numberfield'
|
||||
import {
|
||||
NumberFieldStateProps,
|
||||
useNumberFieldState,
|
||||
} from '@react-stately/numberfield'
|
||||
import { AriaNumberFieldProps } from '@react-types/numberfield'
|
||||
import { InputHTMLAttributes, useCallback, useRef } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
StyledContainer,
|
||||
StyledDescription,
|
||||
StyledErrorMessage,
|
||||
StyledInput,
|
||||
StyledInputContainer,
|
||||
StyledLabel,
|
||||
StyledSuffix,
|
||||
} from './TextField'
|
||||
|
||||
function useTweakedNumberFieldState(props: NumberFieldStateProps) {
|
||||
const state = useNumberFieldState(props)
|
||||
|
||||
// 1 - Add the equivalence between , and . for decimal in french
|
||||
if (props.locale.startsWith('fr')) {
|
||||
const match = state.inputValue.match(/([^.]*)\.([^.]*)/)
|
||||
if (match) {
|
||||
state.setInputValue(`${match[1]},${match[2]}`)
|
||||
}
|
||||
}
|
||||
// const timeoutId = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
// useEffect(() => {
|
||||
// const clearCurrentTimeout = () => {
|
||||
// if (timeoutId.current) {
|
||||
// return clearTimeout(timeoutId.current)
|
||||
// }
|
||||
// }
|
||||
// clearCurrentTimeout()
|
||||
// timeoutId.current = setTimeout(
|
||||
// () =>
|
||||
// state.inputValue && state.validate(state.inputValue) && state.commit(),
|
||||
// 2000
|
||||
// )
|
||||
// return clearCurrentTimeout
|
||||
// }, [state.inputValue])
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
export default function NumberField(
|
||||
props: AriaNumberFieldProps & { displayedUnit?: string }
|
||||
) {
|
||||
const { locale } = useLocale()
|
||||
|
||||
const state = useTweakedNumberFieldState({
|
||||
...props,
|
||||
locale,
|
||||
})
|
||||
|
||||
const ref = useRef<HTMLInputElement>(null)
|
||||
const {
|
||||
labelProps,
|
||||
inputProps,
|
||||
descriptionProps,
|
||||
errorMessageProps,
|
||||
groupProps,
|
||||
} = useNumberField(props, state, ref)
|
||||
|
||||
const handleClickOnUnit = useCallback(() => {
|
||||
if (!ref.current) {
|
||||
return
|
||||
}
|
||||
ref.current.focus()
|
||||
const length = ref.current.value.length * 2
|
||||
ref.current.setSelectionRange(length * 2, length * 2)
|
||||
}, [])
|
||||
|
||||
const handleDoubleClickOnUnit = useCallback(() => {
|
||||
if (!ref.current) {
|
||||
return
|
||||
}
|
||||
ref.current.focus()
|
||||
const length = ref.current.value.length * 2
|
||||
ref.current.setSelectionRange(0, length * 2)
|
||||
}, [])
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledInputContainer
|
||||
{...groupProps}
|
||||
hasError={!!props.errorMessage || props.validationState === 'invalid'}
|
||||
hasLabel={!!props.label}
|
||||
>
|
||||
<StyledNumberInput
|
||||
{...(inputProps as InputHTMLAttributes<HTMLInputElement>)}
|
||||
placeholder={props.placeholder ?? ''}
|
||||
ref={ref}
|
||||
/>
|
||||
{props.displayedUnit && (
|
||||
<StyledUnit
|
||||
onClick={handleClickOnUnit}
|
||||
onDoubleClick={handleDoubleClickOnUnit}
|
||||
>
|
||||
{props.displayedUnit}
|
||||
</StyledUnit>
|
||||
)}
|
||||
|
||||
{props.label && (
|
||||
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
{props.errorMessage && (
|
||||
<StyledErrorMessage {...errorMessageProps}>
|
||||
{props.errorMessage}
|
||||
</StyledErrorMessage>
|
||||
)}
|
||||
{props.description && (
|
||||
<StyledDescription {...descriptionProps}>
|
||||
{props.description}
|
||||
</StyledDescription>
|
||||
)}
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledUnit = styled(StyledSuffix)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[600]};
|
||||
padding-left: 0 !important;
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
const StyledNumberInput = styled(StyledInput)`
|
||||
padding-right: 0 !important;
|
||||
text-align: right;
|
||||
`
|
|
@ -26,14 +26,17 @@ export default function SearchField(props: AriaSearchFieldProps) {
|
|||
return (
|
||||
<StyledContainer>
|
||||
<StyledInputContainer
|
||||
error={!!props.errorMessage || props.validationState === 'invalid'}
|
||||
hasError={!!props.errorMessage || props.validationState === 'invalid'}
|
||||
hasLabel={!!props.label}
|
||||
>
|
||||
<StyledInput
|
||||
{...(inputProps as InputHTMLAttributes<HTMLInputElement>)}
|
||||
placeholder={inputProps.placeholder ?? ''}
|
||||
ref={ref}
|
||||
/>
|
||||
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
|
||||
{props.label && (
|
||||
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
|
||||
)}
|
||||
{state.value !== '' && (
|
||||
<StyledClearButton {...clearButtonProps}>×</StyledClearButton>
|
||||
)}
|
||||
|
|
|
@ -12,14 +12,17 @@ export default function TextField(props: AriaTextFieldOptions) {
|
|||
return (
|
||||
<StyledContainer>
|
||||
<StyledInputContainer
|
||||
error={!!props.errorMessage || props.validationState === 'invalid'}
|
||||
hasError={!!props.errorMessage || props.validationState === 'invalid'}
|
||||
hasLabel={!!props.label}
|
||||
>
|
||||
<StyledInput
|
||||
{...(inputProps as InputHTMLAttributes<HTMLInputElement>)}
|
||||
placeholder={inputProps.placeholder ?? ''}
|
||||
ref={ref}
|
||||
/>
|
||||
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
|
||||
{props.label && (
|
||||
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
{props.errorMessage && (
|
||||
<StyledErrorMessage {...errorMessageProps}>
|
||||
|
@ -43,21 +46,18 @@ export const StyledContainer = styled.div`
|
|||
export const StyledInput = styled.input`
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
padding: calc(${LABEL_HEIGHT} + ${({ theme }) => theme.spacings.xs})
|
||||
${({ theme }) => theme.spacings.sm} ${({ theme }) => theme.spacings.xs};
|
||||
transition: color 0.2s;
|
||||
`
|
||||
|
||||
export const StyledLabel = styled.label`
|
||||
top: 0%;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
transform: translateY(0%);
|
||||
font-size: 0.75rem;
|
||||
|
@ -79,18 +79,29 @@ export const StyledErrorMessage = styled(StyledDescription)`
|
|||
color: ${({ theme }) => theme.colors.extended.error[400]} !important;
|
||||
`
|
||||
|
||||
export const StyledInputContainer = styled.div<{ error: boolean }>`
|
||||
export const StyledSuffix = styled.span`
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
`
|
||||
|
||||
export const StyledInputContainer = styled.div<{
|
||||
hasError: boolean
|
||||
hasLabel: boolean
|
||||
}>`
|
||||
border-radius: ${({ theme }) => theme.box.borderRadius};
|
||||
border: ${({ theme }) =>
|
||||
`${theme.box.borderWidth} solid ${theme.colors.extended.grey[500]}`};
|
||||
outline: transparent solid 1px;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
background-color: white;
|
||||
align-items: center;
|
||||
transition: all 0.2s;
|
||||
height: ${({ theme }) => theme.spacings.xxxl};
|
||||
|
||||
:focus-within {
|
||||
outline-color: ${({ theme, error }) =>
|
||||
error
|
||||
outline-color: ${({ theme, hasError }) =>
|
||||
hasError
|
||||
? theme.colors.extended.error[400]
|
||||
: theme.colors.bases.primary[600]};
|
||||
}
|
||||
|
@ -102,9 +113,17 @@ export const StyledInputContainer = styled.div<{ error: boolean }>`
|
|||
color: ${({ theme }) => theme.colors.bases.primary[800]};
|
||||
}
|
||||
|
||||
${StyledInput}:not(:focus):placeholder-shown {
|
||||
color: transparent;
|
||||
}
|
||||
${({ hasLabel }) =>
|
||||
hasLabel &&
|
||||
css`
|
||||
${StyledInput}:not(:focus):placeholder-shown {
|
||||
color: transparent;
|
||||
}
|
||||
${StyledInput}:not(:focus):placeholder-shown + ${StyledSuffix} {
|
||||
color: transparent;
|
||||
}
|
||||
`}
|
||||
|
||||
${StyledInput}:not(:focus):placeholder-shown + ${StyledLabel} {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
|
@ -112,8 +131,8 @@ export const StyledInputContainer = styled.div<{ error: boolean }>`
|
|||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
${({ theme, error }) =>
|
||||
error &&
|
||||
${({ theme, hasError }) =>
|
||||
hasError &&
|
||||
css`
|
||||
&& {
|
||||
border-color: ${theme.colors.extended.error[400]};
|
||||
|
@ -122,4 +141,10 @@ export const StyledInputContainer = styled.div<{ error: boolean }>`
|
|||
color: ${theme.colors.extended.error[400]};
|
||||
}
|
||||
`}
|
||||
|
||||
${StyledInput}, ${StyledSuffix} {
|
||||
padding: ${({ hasLabel, theme }) =>
|
||||
css`calc(${hasLabel ? LABEL_HEIGHT : '0rem'} + ${theme.spacings.xs})`}
|
||||
${({ theme }) => theme.spacings.sm} ${({ theme }) => theme.spacings.xs};
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export { default as TextField } from './TextField'
|
||||
export { default as SearchField } from './SearchField'
|
||||
export { default as DateField } from './DateField'
|
||||
export { default as NumberField } from './NumberField'
|
||||
|
|
|
@ -9,6 +9,7 @@ import ruleTranslations from './locales/rules-en.yaml'
|
|||
import translateRules from './locales/translateRules'
|
||||
import translations from './locales/ui-en.yaml'
|
||||
import './sentry'
|
||||
import { I18nProvider } from '@react-aria/i18n'
|
||||
|
||||
i18next.addResourceBundle('en', 'translation', translations)
|
||||
i18next.changeLanguage('en')
|
||||
|
@ -21,4 +22,9 @@ const Root = hot(() => (
|
|||
))
|
||||
|
||||
const anchor = document.querySelector('#js')
|
||||
render(<Root />, anchor)
|
||||
render(
|
||||
<I18nProvider locale="en-GB">
|
||||
<Root />
|
||||
</I18nProvider>,
|
||||
anchor
|
||||
)
|
||||
|
|
|
@ -6,6 +6,8 @@ import { hot } from 'react-hot-loader/root'
|
|||
import 'regenerator-runtime/runtime'
|
||||
import App from './App'
|
||||
import i18next from './locales/i18n'
|
||||
import { I18nProvider } from '@react-aria/i18n'
|
||||
|
||||
import './sentry'
|
||||
|
||||
i18next.changeLanguage('fr')
|
||||
|
@ -13,4 +15,9 @@ i18next.changeLanguage('fr')
|
|||
const Root = hot(() => <App basename="mon-entreprise" rules={rules} />)
|
||||
|
||||
const anchor = document.querySelector('#js')
|
||||
render(<Root />, anchor)
|
||||
render(
|
||||
<I18nProvider locale="fr-FR">
|
||||
<Root />
|
||||
</I18nProvider>,
|
||||
anchor
|
||||
)
|
||||
|
|
76
yarn.lock
76
yarn.lock
|
@ -3076,7 +3076,7 @@
|
|||
"@react-aria/utils" "^3.8.2"
|
||||
"@react-types/shared" "^3.8.0"
|
||||
|
||||
"@react-aria/interactions@^3.6.0":
|
||||
"@react-aria/interactions@^3.5.1", "@react-aria/interactions@^3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.6.0.tgz#63c16e6179e8ae38221e26256d9a7639d7f9b24e"
|
||||
integrity sha512-dMEGYIIhJ3uxDd19Z/rxuqQp9Rx9c46AInrfzAiOijQj/fTmb4ubCsuFOAQrc0sy1HCY1/ntnRZQuRgT/iS74w==
|
||||
|
@ -3095,6 +3095,33 @@
|
|||
"@react-types/label" "^3.5.0"
|
||||
"@react-types/shared" "^3.9.0"
|
||||
|
||||
"@react-aria/live-announcer@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/live-announcer/-/live-announcer-3.0.1.tgz#772888326808d180adc5bc9fa0b4b1416ec08811"
|
||||
integrity sha512-c63UZ4JhXxy29F6FO1LUkQLDRzv17W4g3QQ+sy6tmFw7R5I5r8uh8jR7RCbBX7bdGCLnQDwOQ055KsM/a9MT3A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
"@react-aria/utils" "^3.8.2"
|
||||
"@react-aria/visually-hidden" "^3.2.3"
|
||||
|
||||
"@react-aria/numberfield@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/numberfield/-/numberfield-3.1.0.tgz#b9be9930276e8c6ccaba821e775ca67155d8784c"
|
||||
integrity sha512-szecO5pqd8AiJOcDhj099C+fnuWf0xcB0aUxg7uiikBnTq5RRTMy0P45uVDZneD5Fa7upXcAj4uqMH5+BuJh2A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
"@react-aria/i18n" "^3.3.2"
|
||||
"@react-aria/interactions" "^3.6.0"
|
||||
"@react-aria/live-announcer" "^3.0.1"
|
||||
"@react-aria/spinbutton" "^3.0.1"
|
||||
"@react-aria/textfield" "^3.4.0"
|
||||
"@react-aria/utils" "^3.9.0"
|
||||
"@react-stately/numberfield" "^3.0.2"
|
||||
"@react-types/button" "^3.4.1"
|
||||
"@react-types/numberfield" "^3.1.0"
|
||||
"@react-types/shared" "^3.9.0"
|
||||
"@react-types/textfield" "^3.3.0"
|
||||
|
||||
"@react-aria/searchfield@^3.2.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/searchfield/-/searchfield-3.2.0.tgz#f0f8609c2e3a7ed300209f6aa01f6d782c131f81"
|
||||
|
@ -3110,6 +3137,18 @@
|
|||
"@react-types/searchfield" "^3.1.2"
|
||||
"@react-types/shared" "^3.9.0"
|
||||
|
||||
"@react-aria/spinbutton@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/spinbutton/-/spinbutton-3.0.1.tgz#e0d5595e1c74518ca46acdeebf7bd19022ee5d50"
|
||||
integrity sha512-V2wUhSgJDxSqzo5HPbx7OgGpFeuvxq8/7nNO8mT3cEZfZASUGvjIdCRmAf243qyfo9Yby4zdx9E/BxNOGCZ9cQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
"@react-aria/i18n" "^3.3.2"
|
||||
"@react-aria/live-announcer" "^3.0.1"
|
||||
"@react-aria/utils" "^3.8.2"
|
||||
"@react-types/button" "^3.4.1"
|
||||
"@react-types/shared" "^3.8.0"
|
||||
|
||||
"@react-aria/ssr@^3.0.3", "@react-aria/ssr@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.1.0.tgz#b7163e6224725c30121932a8d1422ef91d1fab22"
|
||||
|
@ -3140,6 +3179,16 @@
|
|||
"@react-types/shared" "^3.9.0"
|
||||
clsx "^1.1.1"
|
||||
|
||||
"@react-aria/visually-hidden@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/visually-hidden/-/visually-hidden-3.2.3.tgz#4779df0a468873550afb42a7f5fcb2411d82db8d"
|
||||
integrity sha512-iAe5EFI7obEOwTnIdAwWrKq+CrIJFGTw85v8fXnQ7CIVGRDblX85GOUww9bzQNPDLLRYWS4VF702ii8kV4+JCw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
"@react-aria/interactions" "^3.5.1"
|
||||
"@react-aria/utils" "^3.8.2"
|
||||
clsx "^1.1.1"
|
||||
|
||||
"@react-pdf/fontkit@^1.11.0", "@react-pdf/fontkit@^1.13.0":
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-pdf/fontkit/-/fontkit-1.13.0.tgz#ec14cc61120e814c1d48cf276771684ec615be9e"
|
||||
|
@ -3207,6 +3256,17 @@
|
|||
dependencies:
|
||||
unicode-trie "^0.3.0"
|
||||
|
||||
"@react-stately/numberfield@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-stately/numberfield/-/numberfield-3.0.2.tgz#2e2831e60cafb7cc4b3124fe5f135a7bbd70e590"
|
||||
integrity sha512-hxJt/Bj9cqJ8EPp9Vb0BL2CMWaRROWvxveiy76zcMMAT1TN33Wjhta+r+RjhJeUqDCHyvgcbYUeyxEbqrcipRA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
"@internationalized/number" "^3.0.2"
|
||||
"@react-stately/utils" "^3.2.2"
|
||||
"@react-types/numberfield" "^3.0.1"
|
||||
"@react-types/shared" "^3.8.0"
|
||||
|
||||
"@react-stately/searchfield@^3.1.3":
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-stately/searchfield/-/searchfield-3.1.3.tgz#c2fe18be4ca8478c3bb3fdebc7e9e4a14ebfae07"
|
||||
|
@ -3255,6 +3315,13 @@
|
|||
dependencies:
|
||||
"@react-types/shared" "^3.9.0"
|
||||
|
||||
"@react-types/numberfield@^3.0.1", "@react-types/numberfield@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-types/numberfield/-/numberfield-3.1.0.tgz#30aff4352a24797a235a74e538a8dd86c5c60af3"
|
||||
integrity sha512-+QfvGqWD/QWOIyOCRDX/KyyV6QWdA/BQZKVpkFd0Vyy11GGT0eiKGyBevlN22/mwQkHbu53smVrRKXlHdB1tUQ==
|
||||
dependencies:
|
||||
"@react-types/shared" "^3.9.0"
|
||||
|
||||
"@react-types/searchfield@^3.1.2":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-types/searchfield/-/searchfield-3.1.2.tgz#184770b67f1fad57a6024ad00b7e42cf934ed54b"
|
||||
|
@ -13852,13 +13919,6 @@ react-monaco-editor@^0.40.0:
|
|||
monaco-editor "*"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-number-format@^4.3.1:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-4.4.1.tgz#d5614dd25edfc21ed48b97356213440081437a94"
|
||||
integrity sha512-ZGFMXZ0U7DcmQ3bSZY3FULOA1mfqreT9NIMYZNoa/ouiSgiTQiYA95Uj2KN8ge6BRr+ghA5vraozqWqsHZQw3Q==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-reconciler@^0.24.0:
|
||||
version "0.24.0"
|
||||
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.24.0.tgz#5a396b2c2f5efe8554134a5935f49f546723f2dd"
|
||||
|
|
Loading…
Reference in New Issue