fix(InputNumber): work uncontrolled with only a default value (nuxt/ui@2d4782b) - #552
Merged
Conversation
…2d4782b) `useVModel` kept its own copy of the value and that copy was bound to `NumberFieldRoot`'s `model-value`. With only a `defaultValue` and no `v-model`, reka has internal state of its own and the two disagreed — the field stepped off the wrapper's copy instead of off reka's, so an uncontrolled `InputNumber` did not increment at all. Removing the wrapper alone trades that for a different bug, which is why the fix has a second half. Reka writes `update:modelValue` on every write — blur, Enter, stepping at a bound — whether or not the value moved, and with `useVModel` gone nothing absorbs those. So `onUpdate` gains an equality guard and emits by hand, and `useForwardProps` loses its `emits` argument for the same reason: the root must not re-emit what `onUpdate` now owns. The guard's second clause is not redundant. `undefined` and `null` are different values but the same emptiness — the `.optional` modifier produces the first, a caller supplies the second. Verified against the pre-image rather than assumed: with the component reverted and upstream's four tests kept, all four fail; with the port, all pass. The defect was live here. Mutations separate the two halves — dropping the emit turns 14 red, dropping the guard turns 6 red, and keeping only `value === props.modelValue` while dropping the `== null` clause turns 2 red. That last one shows the clause is load-bearing rather than defensive padding no test would miss. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb
`ebd4adfc` went in with `pending-merge` in both fields, as PORTING.md §6 step 4 requires. That PR has now merged: - `ebd4adfc` → #551 / `d57db682` Carried here rather than in its own bookkeeping PR, per §6 step 4: the next port in a run reconciles the previous entry, and only the last entry needs a PR of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb
IgorShevchik
added a commit
that referenced
this pull request
Sep 8, 2026
…42bf3b) (#553) * chore(deps): update tiptap to ^3.31.3, pin prosemirror-view (nuxt/ui@042bf3b) All 25 `@tiptap/*` declarations across five manifests sat at `^3.30.5`, upstream's exact pre-image, `playgrounds/demo` included. The `@tiptap/pm` override moves with the family, as its own comment instructs. The bump broke `typecheck` in all three copies of the editor completion extension: TS2322: Type '(state: EditorState) => DecorationSet' is not assignable to '(state: EditorState) => DecorationSource | null | undefined' That reads like an API change and is not one. It is two copies of `prosemirror-view`, so `DecorationSet` comes from one and `DecorationSource` from the other and the two are unrelated types. Measured against `main`: `prosemirror-view` was 1.42.0 alone, and after the bump 1.42.0 *and* 1.42.3. `@tiptap/pm@3.31.3` wants 1.42.3, while `prosemirror-dropcursor`, `-gapcursor`, `-history`, `-state` and `-tables` peer-accept a range and had settled on 1.42.0. This is the failure the `@tiptap/pm` override exists to prevent, arriving one level down — and that override could not prevent it, because pinning the tiptap wrapper keeps one wrapper, not one `prosemirror-view`. So it gains a sibling, with the reasoning beside it. A single 1.42.3 afterwards, and types are clean. `@tiptap/core` resolves to two versions and that is not this commit's doing: `main` already had 3.30.2 and 3.30.5. The older line lives entirely under `@nuxt/ui@4.8.2`, reached through `nuxtseo-layer-devtools` — a devtools dependency that never touches our `Editor`. No importer references it. Recorded so a reader comparing counts does not suspect this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb * chore(sync): reconcile the #552 ledger entry `2d4782ba` went in with `pending-merge` in both fields, as PORTING.md §6 step 4 requires. That PR has now merged: - `2d4782ba` → #552 / `704312db` Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb --------- Co-authored-by: Shevchik Igor <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Port of
nuxt/ui@2d4782ba— third of ten in this run.The defect, and it was live here
useVModel(props, 'modelValue', emits, { defaultValue })kept its own copy of the value, and that copy was bound toNumberFieldRoot'smodel-value. With only adefaultValueand nov-model, reka has internal state of its own — and the two disagreed. The field stepped off the wrapper's copy instead of off reka's, so an uncontrolledInputNumberdid not increment at all.Removing the wrapper alone trades that for a different bug, which is why the fix has a second half. Reka writes
update:modelValueon every write — blur, Enter, stepping at a bound — whether or not the value moved, and withuseVModelgone nothing absorbs those. SoonUpdategains an equality guard and emits by hand:The second clause is not redundant:
undefinedandnullare different values but the same emptiness — the.optionalmodifier produces the first, a caller supplies the second.useForwardProps(…, emits)loses its second argument for the same reason, so the root cannot re-emit whatonUpdatenow owns.Applies verbatim. Our pre-image is upstream's at all five sites,
eslint-disablecomment included,useVModelappears nowhere else in the file, and the selectors and imports the new tests need (data-slot="increment",flushPromises,test) were already present.Verified against the pre-image, not assumed
With the component reverted and upstream's four tests kept, all four fail; with the port, all pass. So this fixes a defect that was live in this fork rather than a theoretical one.
Mutations, and the counts separate the two halves of the fix:
emits('update:modelValue', …)callvalue === props.modelValue, drop the== nullclauseThe last row is the one worth having: it shows the
== nullclause is load-bearing and covered, rather than defensive padding no test would miss.Bookkeeping
.sync/nuxt-ui.json— cursorebd4adfc→2d4782ba, entry added; andebd4adfcreconciled to chore(deps): update non-major dependencies, pin happy-dom (nuxt/ui@ebd4adf) #551 /d57db682, per §6 step 4 (the next port closes the previous entry, so only the last needs a PR of its own)..sync/dep-parity.json— refreshed at the new cursor..sync/log/2d4782ba….md— the long-form reasoning.Local gate green:
lint·typecheck·build(3.87 MB) ·test(347 files, 7895 passed, 6 skipped) ·test:module·repl:build.Still ahead
Seven upstream commits:
042bf3b7(tiptap deps),7224333b(upstream's own release),5fd94e13(theme editor docs),9076ca2d(virtualizer size fallback), and three docs-only commits.🤖 Generated with Claude Code
https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb
Generated by Claude Code