Skip to content

Conversation

@STRML
Copy link
Collaborator

@STRML STRML commented Dec 10, 2025

Summary

Add a LayoutConstraint interface following the same patterns as Compactor and PositionStrategy. This enables pluggable position and size constraints, replacing hardcoded logic with composable, tree-shakeable constraint functions.

  • Add LayoutConstraint interface with constrainPosition and constrainSize methods
  • Add built-in constraints: gridBounds, minMaxSize, containerBounds, boundedX, boundedY
  • Add factory functions: aspectRatio(), snapToGrid(), minSize(), maxSize()
  • Add constraints prop to GridLayout (default: [gridBounds, minMaxSize])
  • Support per-item constraints via LayoutItem.constraints array
  • Map legacy isBounded prop to containerBounds constraint for backwards compatibility

Usage

Grid-level constraints:

<GridLayout
  constraints={[gridBounds, minMaxSize, aspectRatio(16/9)]}
/>

Per-item constraints:

const layout = [
  { i: 'video', x: 0, y: 0, w: 4, h: 2, constraints: [aspectRatio(16/9)] },
  { i: 'sidebar', x: 4, y: 0, w: 2, h: 4, constraints: [boundedX] }
];

Addresses feature requests

Test plan

  • All existing tests pass (413 tests)
  • New constraint tests added (67 tests) with 100% coverage of constraints.ts
  • Manual testing with dev server for drag/resize behavior
  • Test legacy isBounded prop continues to work

@github-actions github-actions bot added tests use this label for changes in tests documentation use this label for changes in documentation labels Dec 10, 2025
@STRML
Copy link
Collaborator Author

STRML commented Dec 14, 2025

Code review

Found 3 issues:

  1. Example constraint configuration causes boundedX/boundedY to be ineffective. When selecting "Bounded X Only", the constraints array is [gridBounds, minMaxSize, boundedX]. Since gridBounds runs first and clamps both axes, by the time boundedX executes, Y is already constrained. The array should be [boundedX, minMaxSize] to replace gridBounds, not supplement it.

return [gridBounds, minMaxSize, boundedX];
}
case "boundedY": {
return [gridBounds, minMaxSize, boundedY];
}
case "containerBounds": {
return [gridBounds, minMaxSize, containerBounds];
}

  1. snapToGrid(0) causes division by zero returning NaN. The constraint performs Math.round(x / stepX) * stepX with no validation that stepX > 0. Calling snapToGrid(0) produces { x: NaN, y: NaN } which would corrupt the layout.

): { x: number; y: number } {
return {
x: Math.round(x / stepX) * stepX,
y: Math.round(y / stepY) * stepY

  1. effectiveLayoutItem uses stale position during resize from west/north handles. When resizing with handles like "sw", "w", "nw", the item's position changes, but gridBounds.constrainSize uses item.x/item.y from the original props. This causes the constraint to incorrectly limit size based on the old position rather than the new calculated position.

);
const { w: newW, h: newH } = applySizeConstraints(
constraints,
effectiveLayoutItem,
rawSize.w,
rawSize.h,
resizeHandle,
constraintContext
);

Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Add a LayoutConstraint interface following the same patterns as Compactor
and PositionStrategy. This enables pluggable position and size constraints,
replacing hardcoded logic with composable, tree-shakeable constraint functions.

Built-in constraints:
- gridBounds: Keep items within grid boundaries (default)
- minMaxSize: Respect per-item min/max constraints (default)
- containerBounds: Constrain to visible container (replaces isBounded)
- boundedX/boundedY: Axis-specific bounding

Factory functions:
- aspectRatio(ratio): Maintain width-to-height ratio during resize
- snapToGrid(stepX, stepY): Snap positions to coarser grid
- minSize(w, h): Grid-wide minimum size
- maxSize(w, h): Grid-wide maximum size

Usage:
```tsx
<GridLayout
  constraints={[gridBounds, minMaxSize, aspectRatio(16/9)]}
/>

// Per-item constraints
const layout = [
  { i: 'video', x: 0, y: 0, w: 4, h: 2, constraints: [aspectRatio(16/9)] }
];
```

This addresses feature requests for:
- Axis-specific bounding (PR #1298)
- Aspect ratio locking (PR #323, #1777)
- Snap-to-grid positioning
- Custom constraint behavior
Add three new interactive examples demonstrating the pluggable constraints system:

- 22-constraints.jsx: Built-in constraints (gridBounds, minMaxSize, containerBounds, boundedX, boundedY)
- 23-aspect-ratio.jsx: Per-item aspect ratio constraints for video, photo, and banner layouts
- 24-custom-constraints.jsx: Creating custom constraint functions with various use cases

Also updates:
- README.md: Add links to new examples
- CLAUDE.md: Add documentation for example system
- examples/util/vars.js: Register new examples for navigation
…tation

- Add constraints.ts to Package Structure section in CLAUDE.md
- Fix containerBounds to calculate visible rows from containerHeight
- Add rowHeight and margin to ConstraintContext for pixel-to-grid conversion
- containerBounds now falls back to maxRows when containerHeight is 0
- Update RFC to reflect the corrected implementation
- Update tests for new containerBounds behavior
Bug fixes:
- Add validation to snapToGrid factory to prevent division by zero
- Make gridBounds.constrainSize handle-aware for correct bounds calculation
  during west/north resize handles
- Preserve per-item constraints in cloneLayoutItem
- Fix aspectRatio to calculate pixel-accurate proportions accounting for
  different column width vs row height

Example improvements:
- Convert all constraint examples to v2 API (GridLayout + useContainerWidth)
- Fix example constraint order: boundedX/boundedY now replace gridBounds
- Rename example files to match master's numbering (20-22)
- Add constraint exports to index-dev.js for dev server support
- Show constraint source code in custom constraints example
- Remove "top half only" option from custom constraints

Tests:
- Add comprehensive tests for all bug fixes
- Update aspectRatio tests for pixel-aware calculations
- Update snapshots for cloneLayoutItem change
- Add maxRows: 10 to example 20 so boundedY and containerBounds
  constraints have a meaningful limit to enforce
- Remove outdated "initial heights are approximate" note from example 21
  since aspectRatio constraint now calculates pixel-accurate proportions
## Changes

### GridItem.tsx
- Remove react-resizable's minConstraints/maxConstraints that were using
  item.minW/maxW/minH/maxH. Now react-resizable only enforces a minimum
  of 1 grid unit (safety net). This allows our pluggable constraint system
  to be the sole authority on size limits, making constraints=[] work as expected.

### index-dev.js
- Add compactor exports (noCompactor, verticalCompactor) for use in examples

### Example 20 (constraints.jsx)
- Add "No Compaction" checkbox to demonstrate position constraints
- Vertical compaction moves items back up, hiding boundedX/boundedY effects
- With noCompactor, users can clearly see how position constraints work
- Improved explanations for each constraint type

### constraints-test.ts
- Add tests for empty constraints behavior (no limits enforced)
- Add tests for boundedX allowing free Y movement
- Add tests for boundedY allowing free X movement
- Add tests for constraint composition (gridBounds + minMaxSize)
- Verify that without minMaxSize, item.maxW is not enforced
@STRML STRML force-pushed the feat/pluggable-constraints branch from eb36ad2 to 8d6991f Compare December 14, 2025 19:50
- Update RFC 0002 with example links, usage guide, and implementation details
- Add documentation links to all constraint examples (20, 21, 22)
- Examples link to GitHub URLs (for viewing outside repo context)
- RFC uses relative links (for GitHub markdown rendering)
- Document constraint vs compaction interaction
- Add custom constraint creation guide with ConstraintContext details
@STRML
Copy link
Collaborator Author

STRML commented Dec 14, 2025

Code review

Found 1 issue:

  1. RFC documentation uses npm run dev instead of yarn dev (CLAUDE.md says: "Always use yarn instead of npm for all commands in this project.")

Run the examples locally with `npm run dev` and navigate to the constraints examples.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- CLAUDE.md: examples should use v2 API with useContainerWidth hook
- RFC: use yarn dev instead of npm run dev
@STRML STRML merged commit c1b8c02 into master Dec 14, 2025
4 checks passed
@STRML STRML deleted the feat/pluggable-constraints branch December 14, 2025 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation use this label for changes in documentation tests use this label for changes in tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants