[pull] main from tldraw:main#508
Merged
Merged
Conversation
In order to allow SDK consumers to add custom geo types without forking or monkey-patching GeoShapeUtil, this PR adds a `GeoTypeDefinition` interface and a `customGeoTypes` option to `GeoShapeUtil.configure()`. Closes #8076. Custom geo types plug into the existing switch statements and inherit all standard geo shape behavior — labels, resizing, fill/dash/color styling, SVG export, hyperlink support, and the full editing UX — while providing their own path geometry, snap behavior, creation size, style panel icon, and optional double-click handler. ### Concepts | Term | Type | Meaning | |------|------|---------| | `GeoTypeDefinition` | interface | Defines the behavior bundle for a single custom geo type | | `customGeoTypes` | `GeoShapeOptions` field | Map of custom type name → definition, passed via `configure()` | ### Example **Registering a custom geo type:** ```ts import { GeoShapeUtil, PathBuilder } from 'tldraw' const MyGeoShapeUtil = GeoShapeUtil.configure({ customGeoTypes: { 'rounded-rect': { getPath: (w, h, shape, strokeWidth) => { const r = Math.min(w, h) * 0.2 return new PathBuilder() .moveTo(r, 0, { geometry: { isFilled: shape.props.fill !== 'none' } }) .lineTo(w - r, 0) .circularArcTo(r, false, true, w, r) .lineTo(w, h - r) .circularArcTo(r, false, true, w - r, h) .lineTo(r, h) .circularArcTo(r, false, true, 0, h - r) .lineTo(0, r) .circularArcTo(r, false, true, r, 0) .close() }, snapType: 'polygon', icon: 'geo-rectangle', defaultSize: { w: 200, h: 150 }, }, }, }) // Pass to Tldraw <Tldraw shapeUtils={[MyGeoShapeUtil]} /> ``` ### `GeoTypeDefinition` interface | Field | Type | Description | |-------|------|-------------| | `getPath` | `(w, h, shape, strokeWidth) => PathBuilder` | Generate the path geometry for this type | | `snapType` | `'polygon' \| 'blobby'` | `'polygon'` snaps to vertices + center; `'blobby'` snaps to center only | | `icon` | `string` | Icon name for the style panel geo picker | | `defaultSize` | `{ w: number; h: number }` (optional) | Default size when clicking (not dragging). Defaults to 200x200 | | `onDoubleClick` | `(shape) => { props } \| void` (optional) | Custom double-click handler | ### New examples - `shapes/custom-geo-types` — Demonstrates registering two custom geo types (rounded-rect and cross) via `GeoShapeUtil.configure()`, showing how custom types inherit all standard geo behavior ### Change type - [x] `feature` ### Test plan 1. Open the examples app (`yarn dev`) and navigate to Shapes > Custom geo types 2. Verify the two custom shapes (rounded-rect and cross) render correctly with labels and fill 3. Select the geo tool and open the style panel — custom types should appear at the bottom of the geo picker 4. Click to create a shape — should use the custom `defaultSize` 5. Drag to create a shape — should resize normally 6. Verify label editing, fill/dash/color styling, and SVG export work 7. Verify snap points match the configured `snapType` - [x] Unit tests (existing geo tests pass) ### Release notes - Add extensibility API for custom geo shape types via `GeoShapeUtil.configure({ customGeoTypes })`. Custom types define their own path geometry, snap behavior, creation size, style panel icon, and double-click handler while inheriting all standard geo shape behavior. ### API changes - Added `GeoTypeDefinition` interface (public) - Added `customGeoTypes` optional field to `GeoShapeOptions` - Added `configure()` override on `GeoShapeUtil` that registers custom types in the geo enum, path system, and style panel ### Code changes | Section | LOC change | | --------------- | ---------- | | Core code | +152 / -10 | | Automated files | +20 / -18 | | Documentation | +141 / -0 | --------- Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com>
## Summary - adds a `ShapeUtil.isFrameLike()` capability so custom shapes can opt into frame-like behavior (paste parenting, snapping, selection, erasing, export) - adds a `BaseFrameLikeShapeUtil` abstract class — the easiest way to create a custom frame-like shape, with sensible defaults for `isFrameLike`, `providesBackgroundForChildren`, `canReceiveNewChildrenOfType`, `getClipPath`, `onDragShapesIn`, and `onDragShapesOut` - routes frame-specific behaviors in the editor and tools through `editor.getShapeUtil(shape).isFrameLike(shape)` instead of hardcoding the `frame` shape type - refactors `FrameShapeUtil` to extend `BaseFrameLikeShapeUtil`, removing ~80 lines of boilerplate while preserving current frame behavior - adds a portal shapes example demonstrating custom frame-like shapes that teleport children between each other split off from #8030 Closes #7309 Closes #7518 https://github.com/user-attachments/assets/6bd0791e-c25b-48ec-bad9-37492e5c32fe ## Example Creating a custom frame-like shape by extending `BaseFrameLikeShapeUtil`: ```ts class MyContainerUtil extends BaseFrameLikeShapeUtil<MyContainerShape> { static override type = 'my-container' as const static override props = myContainerShapeProps override getDefaultProps() { return { w: 300, h: 200 } } override component(shape: MyContainerShape) { return <SVGContainer>...</SVGContainer> } override indicator(shape: MyContainerShape) { return <rect width={shape.props.w} height={shape.props.h} /> } } ``` ## Test plan - [x] pre-commit checks pass locally - [ ] smoke test paste / duplicate behavior for frame-like shapes - [ ] smoke test snapping, selection, and erasing interactions - [ ] verify the portal shapes example reparents and teleports correctly ### Change type - [x] `feature` ### Release notes - Add `BaseFrameLikeShapeUtil` abstract class to make it easier to build custom frame-like shapes. - Add `ShapeUtil.isFrameLike()` so custom shapes can opt into frame-like behavior (clipping children, full-brush selection, blocking erasure from inside, etc.). ### API changes - Added `BaseFrameLikeShapeUtil` abstract class exported from `@tldraw/editor`, extending `BaseBoxShapeUtil` with defaults for frame-like behavior. - Added `ShapeUtil.isFrameLike(shape)` method (returns `false` by default) for custom shapes to opt into frame behaviors. - `FrameShapeUtil` now extends `BaseFrameLikeShapeUtil` instead of `BaseBoxShapeUtil`. ### Code changes | Section | LOC change | | --------------- | ---------- | | Core code | +169 / -97 | | Tests | +14 / -2 | | Automated files | +19 / -13 | | Documentation | +452 / -0 |
In order to speed up the `groupUsers` synced query, this PR adds indexes on `groupId` for the `group_user` and `group_file` tables. The composite primary keys on these tables start with `userId`/`fileId`, so filtering by `groupId` alone falls back to full-table scans — visible in the Zero Cache SQLite plans as `SCAN group_user USING INDEX group_user_pkey` and `SCAN group_file USING INDEX group_file_pkey`. The new indexes let Zero replicate them to the replica and use proper index searches. ### Change type - [x] `improvement` ### Test plan 1. Run the migration against a dev database. 2. Open a user session that has group memberships and file associations. 3. Inspect the `groupUsers` query's `sqlitePlans` — the `group_user` and `group_file` entries should show `SEARCH … USING INDEX` instead of `SCAN … USING INDEX *_pkey`. ### Release notes - Internal: add `groupId` indexes to `group_user` and `group_file` for faster `groupUsers` sync queries.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )