Skip to content

fix(NumberField): snap off-grid value to the nearest grid line when stepping - #2759

Merged
zernonia merged 3 commits into
unovue:v2from
xerox0213:fix/number-field-step-snapping
Jun 26, 2026
Merged

zernonia merged 3 commits into
unovue:v2from
xerox0213:fix/number-field-step-snapping

Conversation

@xerox0213

@xerox0213 xerox0213 commented Jun 24, 2026 •

Copy link
Copy Markdown
Contributor

🔗 Linked issue

#2758

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Fixes increment/decrement overshooting when the value is off the step grid.

Previously a tick added a full step and then rounded to nearest, so an off-grid value skipped a line (18.98 + step 1 → 20 instead of 19; 18.11 − step 1 → 17 instead of 18). Stepping now snaps to the nearest grid line in the requested direction (HTML stepUp/stepDown semantics); values already on the grid still move by a full step.

The +/- disabled state had the same root cause: isIncreaseDisabled/isDecreaseDisabled used the raw value ± step instead of the aligned next value, so a valid off-grid move (e.g. 8 → 9 with max=10, step=3) was wrongly disabled. Both now reuse the same getNextValue calculation as the handler.

Note: an earlier version of this PR also stopped snapping manually-typed values on
commit. That was dropped per review — snapping the committed value to the step grid is
the intended behavior with stepSnapping. Allowing off-grid typed values is handled
separately as an opt-in (allowInvalid) in #2761.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

Summary by CodeRabbit

  • New Features

    • Number field increment and decrement actions now respect step snapping more consistently, including off-grid values and multi-step changes.
    • Boundary checks are improved so controls only stay enabled when the next valid value still fits within the allowed range.
  • Bug Fixes

    • Fixed cases where stepping from an empty or invalid value could produce an out-of-range result.
    • Improved behavior near minimum and maximum limits so values snap correctly instead of moving by a simple raw step.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1197e3a5-2448-4a28-a829-f2af00a8ac42

📥 Commits

Reviewing files that changed from the base of the PR and between 0f5b088 and 1a0cea6.

📒 Files selected for processing (2)
  • packages/core/src/NumberField/NumberField.test.ts
  • packages/core/src/NumberField/NumberFieldRoot.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/NumberField/NumberFieldRoot.vue
  • packages/core/src/NumberField/NumberField.test.ts

📝 Walkthrough

Walkthrough

NumberFieldRoot.vue now computes stepper changes with step-aware alignment and updates disabled-state checks from the computed next value. NumberField.test.ts adds coverage for step-snapping behavior and min/max boundary cases.

Changes

NumberField Step-Snapping

Layer / File(s) Summary
Step-aware next value logic
packages/core/src/NumberField/NumberFieldRoot.vue
getNextValue() computes increase and decrease outcomes with directional step snapping before clamping. Disabled-state checks use that computed value, and handleChangingValue() resets NaN input to min ?? 0 before applying the same path.
Step-snapping coverage
packages/core/src/NumberField/NumberField.test.ts
Adds stepSnapping: true coverage for off-grid increment and decrement behavior, on-grid step advancement, boundary enablement and disablement, and clamped fallback handling for empty input.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐇 I hopped by steps, then snapped to fit,
Off-grid numbers found their bit.
Buttons danced at min and max,
Gridlines kept the tidy tracks.
Hop hop—now every click lands right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main NumberField stepping fix for off-grid values and is clear and specific.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: private package registry requires authentication. Disable ESLint in CodeRabbit settings or use public packages.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jun 24, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/reka-ui@2759

commit: 1a0cea6

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/NumberField/NumberFieldRoot.vue`:
- Around line 134-154: Update the disabled-state logic to use the same
step-aligned next value as the input handler in NumberFieldRoot.vue. The current
checks in isIncreaseDisabled and isDecreaseDisabled still rely on raw value
plus/minus step, so they can disagree with the new snapping behavior. Extract
the next-value calculation from the existing step handling path and reuse it in
both disabled helpers, then add a regression test covering the boundary cases
where alignment allows movement even though raw arithmetic would cross max or a
truthy min.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9a68ae5-e53b-4c56-ba5b-9d241645838f

📥 Commits

Reviewing files that changed from the base of the PR and between 04cba00 and ad1113e.

📒 Files selected for processing (2)
  • packages/core/src/NumberField/NumberField.test.ts
  • packages/core/src/NumberField/NumberFieldRoot.vue

Comment thread packages/core/src/NumberField/NumberFieldRoot.vue Outdated
@xerox0213 xerox0213 mentioned this pull request Jun 24, 2026
4 of 8 tasks
@zernonia

Copy link
Copy Markdown
Member

Thanks for the PR! The off-grid stepping fix (bug #2) looks correct — increment/decrement now snap to the nearest grid line in the right direction instead of overshooting. 👍

On bug #1 though, I don't think snapping typed values on blur is actually a bug. The current design — validating and snapping the committed value to the step grid — is the intended behavior. With stepSnapping enabled, the field is meant to always hold a valid step value, so typing 2.5 with step=1 snapping to 2/3 on commit is expected, not incorrect.

If the goal is to allow off-grid typed values, that'd be better as an opt-in flag rather than changing the default commit behavior. The stepping fix can still land independently.

isIncreaseDisabled/isDecreaseDisabled used the raw value ± step instead of the
aligned next value, so a valid off-grid move (e.g. 8 -> 9 with max=10, step=3)
was wrongly disabled. Both now reuse getNextValue, the same calc as the handler.
@xerox0213
xerox0213 force-pushed the fix/number-field-step-snapping branch from df521db to 0f5b088 Compare June 25, 2026 06:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/NumberField/NumberFieldRoot.vue`:
- Around line 149-151: The NaN fallback in NumberFieldRoot’s input handling
stores a raw 0/min fallback without enforcing the same bounds as normal input
updates. Update the branch in NumberFieldRoot’s value parsing logic so the
fallback value is passed through clampInputValue() before assigning to
modelValue, keeping the min/max contract consistent when the input is empty or
unparseable. Use the existing clampInputValue helper and the current modelValue
update path to locate the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae0f5d13-87ee-413f-963e-03b89815518d

📥 Commits

Reviewing files that changed from the base of the PR and between df521db and 0f5b088.

📒 Files selected for processing (2)
  • packages/core/src/NumberField/NumberField.test.ts
  • packages/core/src/NumberField/NumberFieldRoot.vue

Comment thread packages/core/src/NumberField/NumberFieldRoot.vue
@xerox0213 xerox0213 changed the title fix(NumberField): correct stepSnapping for typed input and off-grid stepping fix(NumberField): snap off-grid value to the nearest grid line when stepping Jun 25, 2026
When the input is empty or unparseable, the fallback wrote `min ?? 0` directly,
which violates the range when only a negative max is set. It now goes through
clampInputValue so the same min/max contract applies.
@xerox0213

xerox0213 commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the PR! The off-grid stepping fix (bug #2) looks correct — increment/decrement now snap to the nearest grid line in the right direction instead of overshooting. 👍

On bug #1 though, I don't think snapping typed values on blur is actually a bug. The current design — validating and snapping the committed value to the step grid — is the intended behavior. With stepSnapping enabled, the field is meant to always hold a valid step value, so typing 2.5 with step=1 snapping to 2/3 on commit is expected, not incorrect.

If the goal is to allow off-grid typed values, that'd be better as an opt-in flag rather than changing the default commit behavior. The stepping fix can still land independently.

@zernonia Thanks for the review!

Agreed on bug #1 — you're right that snapping the committed value to the step grid is the intended behavior when stepSnapping is on. I've dropped that change, so the default commit behavior is unchanged (typing 2.5 with step=1 still snaps on blur).

This PR now contains only the off-grid stepping fix, plus the related disabled-state consistency fix (isIncreaseDisabled/isDecreaseDisabled now reuse the same aligned next-value calc, so a valid off-grid move like 8 → 9 with max=10/step=3 is no longer wrongly disabled). I also routed the empty/NaN fallback through clampInputValue so a negative max isn't violated.

For the off-grid use case, the opt-in lives in a separate PR: #2761 — an allowInvalid prop that keeps typed input exactly as entered (skips both range clamping and step snapping; stepping still clamps/snaps). I kept it as a single prop rather than splitting into range/step flags — happy to rename or split if you'd prefer.

@zernonia
zernonia merged commit 3c9bedf into unovue:v2 Jun 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants