Skip to content

feat(split): add snap points support to Split and Split.Resizer#37

Merged
gfazioli merged 4 commits intogfazioli:masterfrom
woutervanerp:feat/snap-points
Apr 17, 2026
Merged

feat(split): add snap points support to Split and Split.Resizer#37
gfazioli merged 4 commits intogfazioli:masterfrom
woutervanerp:feat/snap-points

Conversation

@woutervanerp
Copy link
Copy Markdown
Contributor

Summary

Adds optional snap point support to mantine-split-pane.

New props:

  • snapPoints?: number[]
  • snapTolerance?: number

These props are supported on both Split and Split.Resizer. Split provides inherited defaults through the existing resizer context, while Split.Resizer can override behavior per divider.

Details

  • snapping works for mouse, touch, and keyboard resizing
  • works with autoResizers
  • snapTolerance defaults to 10
  • snap points are interpreted as the pixel size of the pane before the resizer
  • invalid snap points are filtered, deduplicated, and sorted
  • negative snapTolerance is treated as 0

Docs and tests

  • added README example
  • added docs section and demo
  • added tests for helper logic, drag snapping, keyboard snapping, autoResizers inheritance, and per-resizer overrides

Verification

Ran:

  • yarn build
  • yarn docgen
  • yarn format:test
  • yarn typecheck
  • yarn eslint package/src docs/demos
  • yarn jest package/src/Resizer/snap.test.ts package/src/Split.test.tsx --runInBand

Copilot AI review requested due to automatic review settings April 1, 2026 17:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds snap point support to @gfazioli/mantine-split-pane so resizers can “snap” to common pane sizes during mouse/touch drag and keyboard resizing. This extends the existing Split → Resizer context inheritance model, allowing split-level defaults with per-resizer overrides.

Changes:

  • Introduces snapPoints and snapTolerance props on Split/Split.Resizer, and applies snapping in the resizer resize logic.
  • Adds internal snapping utilities (normalizeSnapPoints, snapToNearestPoint, calculateSnappedPaneSizes) with dedicated unit tests.
  • Updates README + docs + demos, and expands component tests to cover drag/keyboard snapping and autoResizers inheritance.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Documents snap points usage and adds an example snippet.
package/src/Split.tsx Wires snapPoints/snapTolerance through Split context so resizers can inherit defaults.
package/src/Split.test.tsx Adds integration-style tests for drag/keyboard snapping and inheritance/overrides.
package/src/Split.story.tsx Exposes snap props as Storybook controls for Split.
package/src/Resizer/SplitResizer.tsx Applies snapping during resize (mouse/touch/keyboard) via shared helper.
package/src/Resizer/SplitResizer.story.tsx Exposes snap props as Storybook controls for Resizer.
package/src/Resizer/snap.ts Adds snap point normalization and snapped-size calculation helpers.
package/src/Resizer/snap.test.ts Adds unit tests for snap helper behavior and constraint handling.
docs/docs.mdx Adds docs section describing snap points behavior and inheritance rules.
docs/demos/Split.demo.snap.tsx Adds a new documentation demo showcasing inherited snap points and per-resizer overrides.
docs/demos/index.ts Exports the new snap demo for the docs site.

Comment thread package/src/Resizer/SplitResizer.tsx
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread package/src/Resizer/snap.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@gfazioli
Copy link
Copy Markdown
Owner

gfazioli commented Apr 4, 2026

Review Notes

Excellent contribution! The implementation is clean, well-tested, and follows the project's patterns perfectly. A few non-blocking observations:

Bug fix needed (minor)

normalizeSnapPoints crashes when snapPoints receives a non-array value (e.g., from Storybook's object control). Adding an Array.isArray guard would fix it:

// snap.ts:39
const points = Array.isArray(snapPoints) ? snapPoints : [];

Storybook UX

The current stories only add snapPoints as a generic object control with undefined default — this makes it hard to test interactively. A dedicated SnapPoints story with pre-configured values (e.g., [200, 400, 600]) would improve the Storybook experience.

Future enhancements (non-blocking)

These are not blockers for this PR, but worth tracking for the future:

  1. Percentage / responsive snap points — Currently snapPoints only accepts pixel values (number[]). Supporting percentages or responsive breakpoint objects would be useful for fluid layouts. (See chore: migrate ESLint to oxlint #38)
  2. Snap on "after" pane — Snap points are interpreted relative to the "before" pane only. An option to snap based on the "after" pane size could cover more use cases. (See feat: support percentage and responsive snap points #39)
  3. onSnap callback — An event fired when snapping occurs (with the snap point value) would enable UI feedback like visual indicators or analytics. (See feat: snap points relative to the after pane #40)

Overall: great work 👍

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@woutervanerp
Copy link
Copy Markdown
Contributor Author

Review Notes

Excellent contribution! The implementation is clean, well-tested, and follows the project's patterns perfectly. A few non-blocking observations:

Bug fix needed (minor)

normalizeSnapPoints crashes when snapPoints receives a non-array value (e.g., from Storybook's object control). Adding an Array.isArray guard would fix it:

// snap.ts:39
const points = Array.isArray(snapPoints) ? snapPoints : [];

Storybook UX

The current stories only add snapPoints as a generic object control with undefined default — this makes it hard to test interactively. A dedicated SnapPoints story with pre-configured values (e.g., [200, 400, 600]) would improve the Storybook experience.

Future enhancements (non-blocking)

These are not blockers for this PR, but worth tracking for the future:

  1. Percentage / responsive snap points — Currently snapPoints only accepts pixel values (number[]). Supporting percentages or responsive breakpoint objects would be useful for fluid layouts. (See chore: migrate ESLint to oxlint #38)
  2. Snap on "after" pane — Snap points are interpreted relative to the "before" pane only. An option to snap based on the "after" pane size could cover more use cases. (See feat: support percentage and responsive snap points #39)
  3. onSnap callback — An event fired when snapping occurs (with the snap point value) would enable UI feedback like visual indicators or analytics. (See feat: snap points relative to the after pane #40)

Overall: great work 👍

Really appreciate the thoughtful review and kind feedback, thank you! 🙏
I noticed you already fixed the normalizeSnapPoints issue, really appreciate that.
I also agree with the future enhancement suggestions, but since they’re non-blocking and can be picked up separately, I think this PR is good to merge.

woutervanerp and others added 4 commits April 17, 2026 15:24
- Add Array.isArray guard in normalizeSnapPoints to prevent crash
  when Storybook object control passes non-array values
- Add unit test for non-array snapPoints input
- Add dedicated SnapPoints story with pre-configured values
  (snapPoints=[200,400,600], snapTolerance=20)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gfazioli gfazioli merged commit c49111a into gfazioli:master Apr 17, 2026
gfazioli added a commit that referenced this pull request Apr 17, 2026
…llback

Extends the snap points feature introduced in #37 with:

- Percentage snap points (e.g. `'50%'`), resolved against the combined size
  of the two adjacent panes.
- Responsive snap sets via Mantine breakpoint maps
  (e.g. `{ base: [200], md: [300, 500] }`). Closes #39.
- `snapFrom?: 'before' | 'after'` prop on `Split` and `Split.Resizer` to
  interpret each snap point as the target size of the pane after the
  resizer instead of the pane before. Closes #40.
- `onSnap?: (point: number | null) => void` callback on `Split.Resizer`,
  fired once on snap enter (with the resolved pixel value in the
  `snapFrom` reference) and once on exit (with `null`). Not fired on
  every pointer tick — state-based by design. Closes #41.
- `data-snapping` attribute on the resizer root while inside a snap
  zone, for CSS-driven visual feedback.
- Stricter `normalizeSnapPoints`: rejects negative values and malformed
  percentage strings; trims whitespace from valid percentage strings.

Also extends `useResponsiveValue` to treat arrays as plain values so
that `ResponsiveValue<T[]>` works correctly for array props.
gfazioli added a commit that referenced this pull request Apr 17, 2026
…llback

Extends the snap points feature introduced in #37 with:

- Percentage snap points (e.g. `'50%'`), resolved against the combined size
  of the two adjacent panes.
- Responsive snap sets via Mantine breakpoint maps
  (e.g. `{ base: [200], md: [300, 500] }`). Closes #39.
- `snapFrom?: 'before' | 'after'` prop on `Split` and `Split.Resizer` to
  interpret each snap point as the target size of the pane after the
  resizer instead of the pane before. Closes #40.
- `onSnap?: (point: number | null) => void` callback on `Split.Resizer`,
  fired once on snap enter (with the resolved pixel value in the
  `snapFrom` reference) and once on exit (with `null`). Not fired on
  every pointer tick — state-based by design. Closes #41.
- `data-snapping` attribute on the resizer root while inside a snap
  zone, for CSS-driven visual feedback.
- Stricter `normalizeSnapPoints`: rejects negative values and malformed
  percentage strings; trims whitespace from valid percentage strings.

Also extends `useResponsiveValue` to treat arrays as plain values so
that `ResponsiveValue<T[]>` works correctly for array props.
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.

3 participants