Skip to content

Commit 51882e1

Browse files
committed
fix(NumberField): sync display text when value changes while focused
Increment/decrement buttons updated the model but left the input's display text stale when the control was focused. On blur the stale text overwrote the model, reverting the change. Remove the isFocused guard from the value watcher so syncText always runs. This is safe because typing only mutates text directly and never triggers the watcher. Closes #189
1 parent 999be18 commit 51882e1

File tree

2 files changed

+59
-15
lines changed

2 files changed

+59
-15
lines changed

packages/0/src/components/NumberField/NumberFieldControl.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,7 @@
6666
6767
onMounted(syncText)
6868
69-
watch(() => root.value.value, () => {
70-
if (!root.isFocused.value) {
71-
syncText()
72-
}
73-
})
69+
watch(() => root.value.value, syncText)
7470
7571
const displayValue = toRef(() => {
7672
return root.isFocused.value ? text.value : root.display.value

packages/0/src/components/NumberField/index.test.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,21 @@ function mountNumberField (options: {
2929
} = {}): MountResult {
3030
let capturedRootProps: any
3131

32-
const wrapper = mount(NumberField.Root, {
33-
props: {
34-
...(options.model && {
35-
'modelValue': options.model.value,
36-
'onUpdate:modelValue': (v: unknown) => {
37-
options.model!.value = v as number | null
38-
},
39-
}),
40-
...options.props,
41-
},
32+
let wrapper: VueWrapper
33+
34+
const props: Record<string, unknown> = {
35+
...(options.model && {
36+
'modelValue': options.model.value,
37+
'onUpdate:modelValue': (v: unknown) => {
38+
options.model!.value = v as number | null
39+
wrapper.setProps({ modelValue: v })
40+
},
41+
}),
42+
...options.props,
43+
}
44+
45+
wrapper = mount(NumberField.Root, {
46+
props,
4247
slots: {
4348
default: (rootProps: any) => {
4449
capturedRootProps = rootProps
@@ -214,6 +219,49 @@ describe('number-field', () => {
214219
expect(model.value).toBe(0)
215220
})
216221

222+
it('should update display when incrementing while focused', async () => {
223+
const model = ref<number | null>(5)
224+
const { rootProps, controlEl, incrementEl, wait } = mountNumberField({
225+
model,
226+
props: { min: 0, max: 10, step: 1 },
227+
})
228+
await wait()
229+
230+
await controlEl().trigger('focus')
231+
await wait()
232+
expect(rootProps().isFocused).toBe(true)
233+
234+
await incrementEl().trigger('pointerdown', { button: 0 })
235+
await incrementEl().trigger('pointerup')
236+
await wait()
237+
238+
expect(model.value).toBe(6)
239+
expect(rootProps().value).toBe(6)
240+
expect(rootProps().display).toBe('6')
241+
})
242+
243+
it('should preserve incremented value after blur', async () => {
244+
const model = ref<number | null>(5)
245+
const { controlEl, incrementEl, wait } = mountNumberField({
246+
model,
247+
props: { min: 0, max: 10, step: 1 },
248+
})
249+
await wait()
250+
251+
await controlEl().trigger('focus')
252+
await wait()
253+
254+
await incrementEl().trigger('pointerdown', { button: 0 })
255+
await incrementEl().trigger('pointerup')
256+
await wait()
257+
await wait()
258+
259+
await controlEl().trigger('blur')
260+
await wait()
261+
262+
expect(model.value).toBe(6)
263+
})
264+
217265
it('should respect step size', async () => {
218266
const model = ref<number | null>(10)
219267
const { incrementEl, wait } = mountNumberField({

0 commit comments

Comments
 (0)