Skip to content

feat(extensions): Table extension#10852

Open
mauricekindermann wants to merge 39 commits into
fabricjs:masterfrom
mauricekindermann:feat/table-extension
Open

feat(extensions): Table extension#10852
mauricekindermann wants to merge 39 commits into
fabricjs:masterfrom
mauricekindermann:feat/table-extension

Conversation

@mauricekindermann

@mauricekindermann mauricekindermann commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Alright this one is a doozy. Tables are one of those things everyone needs but nobody wants to build from scratch. The examples I've seen are old, buggy, and feature poor. The problem is for a real design tool, end-users have a pretty high ceiling of expectations and UX behaviours - anyone coming to your tool has no doubt used several other big tools before.

image

Obviously I could have scoped this back in many ways, but what's the fun in that? Most people who want a table for a design tool want a production-ready interactive spreadsheet-like object. And, you can remove or configure what you like with little effort, as the hard bits are solved, you just gut the chaff. Generally working backwards from a solution gets you a lot more, faster, than building up from primitives anyway.

I hope this has a home here - I have a lot more I want to contribute. This pairs with #10851 (cropping controls) - same control styling approach.

What's done

Core

  • Table class extending Group with TableLayoutStrategy (auto-sizing rows to text)
  • reflowOriginX/Y properties control expansion anchor - lets you keep center/center origin for positioning while content reflows down-right like a spreadsheet
  • Full serialization/deserialization

Interaction (via initTableInteraction)

  • Cell selection (click, shift+click range, Ctrl+A select all)
  • Cell editing (double-click or Enter, live text reflow)
  • Internal border drag to resize columns/rows
  • Keyboard nav (arrows, Tab, Escape backs out progressively)
  • Copy/cut/paste cells (Ctrl+C/X/V)
  • Cell merging (Ctrl+M) / unmerging (Ctrl+Shift+M)
  • Add/remove rows and columns via hover indicators

Controls

  • Edge resize controls (same visual style as crop controls)
  • Border drag cursors rotate with table angle

Demo

  • Interactive test UI with full toolbar (cell styling, colors, fonts, merge/unmerge, etc.)

Tests

  • 71 unit tests
  • E2E tests with snapshots

Design decisions

  • Controls use same 2px stroke style as crop PR
  • Row resize respects text content minimum
  • Uses util.capValue, wrapWithFixedAnchor per fabric conventions
  • Interaction layer is separate from Table class - easy to swap or extend

Checklist

  • Unit tests
  • E2E tests with snapshots
  • Uses existing fabric patterns
  • Matches crop controls visual style
  • Interactive demo UI
  • Documentation (happy to add if this lands)

Happy to refactor based on feedback.


Original prototype: #8310

- Table class extends Group with TableLayoutStrategy for grid layout
- Support for rows/columns, cell merging, edge resize controls
- Full serialization with toObject/fromObject
- Add/remove rows and columns dynamically
- Cell styling (fill, stroke, text properties)
- Unit tests (42 passing) and E2E test structure
- Dev testcase for visual testing
- Add getBorderAtPoint() for internal border detection
- Add initTableBorderInteraction() for border drag handling
- Add cell selection (selectCell, clearCellSelection, selectedCells)
- Add drawSelectionOverlay() for visual feedback
- Make styling configurable (selectionColor, selectionWidth, borderThreshold)
- Add minCellWidth to defaults
- 8 new unit tests (50 total)
- Add sizeX/sizeY to edge controls matching crop pattern
- Add README with done/todo checklist
- Add renderTableCircleControl for corners and rotation
- Add renderTableSegmentControl for edge resize handles
- Custom controls replace fabric defaults for visual consistency
- Fix TypeScript errors in tableBorderInteraction
- Update README with usage example
…d navigation

- Rename tableBorderInteraction to tableInteraction (handles all interactions)
- Add cell click selection with shift for range
- Add double-click and Enter to edit cell text
- Add keyboard navigation (arrows, Tab, Escape stages)
- Add Delete/Backspace to clear cell content
- Add tableControls.spec.ts (11 tests)
- Update README with full feature checklist
- 61 tests passing
- Add getBorderCursor with rotation-aware cursor selection
- Fix missing borderScaleFactor for 2px control strokes
- Add 10 cursor tests covering all angles
- Update README (71 tests)
- Add + button indicators that appear when hovering internal borders
- Indicators appear above table (columns) and left of table (rows)
- Click indicator to insert row/column at that position
- Add indicatorOffset, indicatorRadius, indicatorHitRadius as configurable properties
- Add getBorderOrIndicatorAtPoint method for unified hit detection
- Use mouse:down:before event to capture indicator clicks outside table bounds
- Clear selection state on table deselection
- Add unit tests for indicator detection
Override triggerLayout to save and restore anchor position based on
the table's originX/originY settings. This uses Fabric's built-in
getPositionByOrigin/setPositionByOrigin APIs.

With originX='left', originY='top': table grows to bottom-right
With originX='center', originY='center': table expands equally in all directions

Simplify calcBoundingBox to return prevCenter, letting the anchor
preservation handle positioning instead of fighting the LayoutManager.
Add reflowOriginX/reflowOriginY properties to control which point stays
fixed when table size changes (addRow, removeColumn, merge, etc).

Defaults to left/top. Independent of visual originX/originY.
- Remove selectionColor/selectionWidth (use inherited borderColor/borderScaleFactor)
- Remove reflowOriginX/Y (use inherited originX/originY)
- Rename borderWidth → cellStrokeWidth (avoid confusion with Fabric's border)
- Default originX/originY to left/top for top-left anchored expansion
- Use cornerColor for indicator plus sign (was hardcoded white)
- Use borderScaleFactor consistently for line widths
- Simplify hitboxes to rectangles that stop at table edge
- Hover anywhere in hitbox shows indicator visual
- Click only registers on the circle, not the stem
- Prevents overlap with edge resize controls
- Insert column redistributes width equally (Canva-style, table width stays same)
- Remove indicator stem line for cleaner look
- Make + sign 2px thick for better visibility
- Prevent table deselection when clicking insert indicator
- Add edgeResizeMode: 'single' | 'proportional' option
- 'single' (default): only edge column/row changes size
- 'proportional': all columns/rows scale together
- Developer configurable, not serialized
- Remove triggerLayout override (was fighting wrapWithFixedAnchor)
- Add triggerLayoutWithAnchor for API methods (add/remove row/col, merge, etc)
- Control actions use wrapWithFixedAnchor (Fabric pattern)
- Low-level setColumnWidth/setRowHeight let caller decide positioning
- Fix proportional row resize by also updating manualRowHeights
@codesandbox

codesandbox Bot commented Jan 21, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@asturur

asturur commented Jan 22, 2026

Copy link
Copy Markdown
Member

I ll finish to review the crop PR before moving to this.

@mauricekindermann

Copy link
Copy Markdown
Contributor Author

No worries, I've been on a plane with terrible, yet some, wifi. I tried to resolve your threads but the wifi gods would not allow it.

@sonarqubecloud

Copy link
Copy Markdown

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