@tldraw/tlschema 4.3.0-next.7810f2131b3c → 4.3.0-next.7f179bd04d6c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/records/TLShape.ts"],
4
- "sourcesContent": ["import {\n\tRecordId,\n\tUnknownRecord,\n\tcreateMigrationIds,\n\tcreateRecordMigrationSequence,\n\tcreateRecordType,\n} from '@tldraw/store'\nimport { mapObjectMapValues, uniqueId } from '@tldraw/utils'\nimport { T } from '@tldraw/validate'\nimport { SchemaPropsInfo } from '../createTLSchema'\nimport { TLPropsMigrations } from '../recordsWithProps'\nimport { TLArrowShape } from '../shapes/TLArrowShape'\nimport { TLBaseShape, createShapeValidator } from '../shapes/TLBaseShape'\nimport { TLBookmarkShape } from '../shapes/TLBookmarkShape'\nimport { TLDrawShape } from '../shapes/TLDrawShape'\nimport { TLEmbedShape } from '../shapes/TLEmbedShape'\nimport { TLFrameShape } from '../shapes/TLFrameShape'\nimport { TLGeoShape } from '../shapes/TLGeoShape'\nimport { TLGroupShape } from '../shapes/TLGroupShape'\nimport { TLHighlightShape } from '../shapes/TLHighlightShape'\nimport { TLImageShape } from '../shapes/TLImageShape'\nimport { TLLineShape } from '../shapes/TLLineShape'\nimport { TLNoteShape } from '../shapes/TLNoteShape'\nimport { TLTextShape } from '../shapes/TLTextShape'\nimport { TLVideoShape } from '../shapes/TLVideoShape'\nimport { StyleProp } from '../styles/StyleProp'\nimport { TLPageId } from './TLPage'\n\n/**\n * The default set of shapes that are available in the editor.\n *\n * This union type represents all the built-in shape types supported by tldraw,\n * including arrows, bookmarks, drawings, embeds, frames, geometry shapes,\n * groups, images, lines, notes, text, videos, and highlights.\n *\n * @example\n * ```ts\n * // Check if a shape is a default shape type\n * function isDefaultShape(shape: TLShape): shape is TLDefaultShape {\n * const defaultTypes = ['arrow', 'bookmark', 'draw', 'embed', 'frame', 'geo', 'group', 'image', 'line', 'note', 'text', 'video', 'highlight']\n * return defaultTypes.includes(shape.type)\n * }\n * ```\n *\n * @public\n */\nexport type TLDefaultShape =\n\t| TLArrowShape\n\t| TLBookmarkShape\n\t| TLDrawShape\n\t| TLEmbedShape\n\t| TLFrameShape\n\t| TLGeoShape\n\t| TLGroupShape\n\t| TLImageShape\n\t| TLLineShape\n\t| TLNoteShape\n\t| TLTextShape\n\t| TLVideoShape\n\t| TLHighlightShape\n\n/**\n * A type for a shape that is available in the editor but whose type is\n * unknown\u2014either one of the editor's default shapes or else a custom shape.\n *\n * This is useful when working with shapes generically without knowing their specific type.\n * The shape type is a string and props are a generic object.\n *\n * @example\n * ```ts\n * // Handle any shape regardless of its specific type\n * function processUnknownShape(shape: TLUnknownShape) {\n * console.log(`Processing shape of type: ${shape.type}`)\n * console.log(`Position: (${shape.x}, ${shape.y})`)\n * }\n * ```\n *\n * @public\n */\nexport type TLUnknownShape = TLBaseShape<string, object>\n\n/**\n * The set of all shapes that are available in the editor, including unknown shapes.\n *\n * This is the primary shape type used throughout tldraw. It includes both the\n * built-in default shapes and any custom shapes that might be added.\n *\n * @example\n * ```ts\n * // Work with any shape in the editor\n * function moveShape(shape: TLShape, deltaX: number, deltaY: number): TLShape {\n * return {\n * ...shape,\n * x: shape.x + deltaX,\n * y: shape.y + deltaY\n * }\n * }\n * ```\n *\n * @public\n */\nexport type TLShape = TLDefaultShape | TLUnknownShape\n\n/**\n * A partial version of a shape, useful for updates and patches.\n *\n * This type represents a shape where all properties except `id` and `type` are optional.\n * It's commonly used when updating existing shapes or creating shape patches.\n *\n * @example\n * ```ts\n * // Update a shape's position\n * const shapeUpdate: TLShapePartial = {\n * id: 'shape:123',\n * type: 'geo',\n * x: 100,\n * y: 200\n * }\n *\n * // Update shape properties\n * const propsUpdate: TLShapePartial<TLGeoShape> = {\n * id: 'shape:123',\n * type: 'geo',\n * props: {\n * w: 150,\n * h: 100\n * }\n * }\n * ```\n *\n * @public\n */\nexport type TLShapePartial<T extends TLShape = TLShape> = T extends T\n\t? {\n\t\t\tid: TLShapeId\n\t\t\ttype: T['type']\n\t\t\tprops?: Partial<T['props']>\n\t\t\tmeta?: Partial<T['meta']>\n\t\t} & Partial<Omit<T, 'type' | 'id' | 'props' | 'meta'>>\n\t: never\n\n/**\n * A unique identifier for a shape record.\n *\n * Shape IDs are branded strings that start with \"shape:\" followed by a unique identifier.\n * This type-safe approach prevents mixing up different types of record IDs.\n *\n * @example\n * ```ts\n * const shapeId: TLShapeId = createShapeId() // \"shape:abc123\"\n * const customId: TLShapeId = createShapeId('my-custom-id') // \"shape:my-custom-id\"\n * ```\n *\n * @public\n */\nexport type TLShapeId = RecordId<TLUnknownShape>\n\n/**\n * The ID of a shape's parent, which can be either a page or another shape.\n *\n * Shapes can be parented to pages (for top-level shapes) or to other shapes\n * (for shapes inside frames or groups).\n *\n * @example\n * ```ts\n * // Shape parented to a page\n * const pageParentId: TLParentId = 'page:main'\n *\n * // Shape parented to another shape (e.g., inside a frame)\n * const shapeParentId: TLParentId = 'shape:frame123'\n * ```\n *\n * @public\n */\nexport type TLParentId = TLPageId | TLShapeId\n\n/**\n * Migration version IDs for the root shape schema.\n *\n * These track the evolution of the base shape structure over time, ensuring\n * that shapes created in older versions can be migrated to newer formats.\n *\n * @example\n * ```ts\n * // Check if a migration needs to be applied\n * if (shapeVersion < rootShapeVersions.AddIsLocked) {\n * // Apply isLocked migration\n * }\n * ```\n *\n * @public\n */\nexport const rootShapeVersions = createMigrationIds('com.tldraw.shape', {\n\tAddIsLocked: 1,\n\tHoistOpacity: 2,\n\tAddMeta: 3,\n\tAddWhite: 4,\n} as const)\n\n/**\n * Migration sequence for the root shape record type.\n *\n * This sequence defines how shape records should be transformed when migrating\n * between different schema versions. Each migration handles a specific version\n * upgrade, ensuring data compatibility across tldraw versions.\n *\n * @public\n */\nexport const rootShapeMigrations = createRecordMigrationSequence({\n\tsequenceId: 'com.tldraw.shape',\n\trecordType: 'shape',\n\tsequence: [\n\t\t{\n\t\t\tid: rootShapeVersions.AddIsLocked,\n\t\t\tup: (record: any) => {\n\t\t\t\trecord.isLocked = false\n\t\t\t},\n\t\t\tdown: (record: any) => {\n\t\t\t\tdelete record.isLocked\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: rootShapeVersions.HoistOpacity,\n\t\t\tup: (record: any) => {\n\t\t\t\trecord.opacity = Number(record.props.opacity ?? '1')\n\t\t\t\tdelete record.props.opacity\n\t\t\t},\n\t\t\tdown: (record: any) => {\n\t\t\t\tconst opacity = record.opacity\n\t\t\t\tdelete record.opacity\n\t\t\t\trecord.props.opacity =\n\t\t\t\t\topacity < 0.175\n\t\t\t\t\t\t? '0.1'\n\t\t\t\t\t\t: opacity < 0.375\n\t\t\t\t\t\t\t? '0.25'\n\t\t\t\t\t\t\t: opacity < 0.625\n\t\t\t\t\t\t\t\t? '0.5'\n\t\t\t\t\t\t\t\t: opacity < 0.875\n\t\t\t\t\t\t\t\t\t? '0.75'\n\t\t\t\t\t\t\t\t\t: '1'\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: rootShapeVersions.AddMeta,\n\t\t\tup: (record: any) => {\n\t\t\t\trecord.meta = {}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: rootShapeVersions.AddWhite,\n\t\t\tup: (_record) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t\tdown: (record: any) => {\n\t\t\t\tif (record.props.color === 'white') {\n\t\t\t\t\trecord.props.color = 'black'\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t],\n})\n\n/**\n * Type guard to check if a record is a shape.\n *\n * @param record - The record to check\n * @returns True if the record is a shape, false otherwise\n *\n * @example\n * ```ts\n * const record = store.get('shape:abc123')\n * if (isShape(record)) {\n * console.log(`Shape type: ${record.type}`)\n * console.log(`Position: (${record.x}, ${record.y})`)\n * }\n * ```\n *\n * @public\n */\nexport function isShape(record?: UnknownRecord): record is TLShape {\n\tif (!record) return false\n\treturn record.typeName === 'shape'\n}\n\n/**\n * Type guard to check if a string is a valid shape ID.\n *\n * @param id - The string to check\n * @returns True if the string is a valid shape ID, false otherwise\n *\n * @example\n * ```ts\n * const id = 'shape:abc123'\n * if (isShapeId(id)) {\n * const shape = store.get(id) // TypeScript knows id is TLShapeId\n * }\n *\n * // Check user input\n * function selectShape(id: string) {\n * if (isShapeId(id)) {\n * editor.selectShape(id)\n * } else {\n * console.error('Invalid shape ID format')\n * }\n * }\n * ```\n *\n * @public\n */\nexport function isShapeId(id?: string): id is TLShapeId {\n\tif (!id) return false\n\treturn id.startsWith('shape:')\n}\n\n/**\n * Creates a new shape ID.\n *\n * @param id - Optional custom ID suffix. If not provided, a unique ID will be generated\n * @returns A new shape ID with the \"shape:\" prefix\n *\n * @example\n * ```ts\n * // Create a shape with auto-generated ID\n * const shapeId = createShapeId() // \"shape:abc123\"\n *\n * // Create a shape with custom ID\n * const customShapeId = createShapeId('my-rectangle') // \"shape:my-rectangle\"\n *\n * // Use in shape creation\n * const newShape: TLGeoShape = {\n * id: createShapeId(),\n * type: 'geo',\n * x: 100,\n * y: 200,\n * // ... other properties\n * }\n * ```\n *\n * @public\n */\nexport function createShapeId(id?: string): TLShapeId {\n\treturn `shape:${id ?? uniqueId()}` as TLShapeId\n}\n\n/**\n * Extracts style properties from a shape's props definition and maps them to their property keys.\n *\n * This function analyzes shape property validators to identify which ones are style properties\n * and creates a mapping from StyleProp instances to their corresponding property keys.\n * It also validates that each style property is only used once per shape.\n *\n * @param props - Record of property validators for a shape type\n * @returns Map from StyleProp instances to their property keys\n * @throws Error if a style property is used more than once in the same shape\n *\n * @example\n * ```ts\n * const geoShapeProps = {\n * color: DefaultColorStyle,\n * fill: DefaultFillStyle,\n * width: T.number,\n * height: T.number\n * }\n *\n * const styleMap = getShapePropKeysByStyle(geoShapeProps)\n * // styleMap.get(DefaultColorStyle) === 'color'\n * // styleMap.get(DefaultFillStyle) === 'fill'\n * ```\n *\n * @internal\n */\nexport function getShapePropKeysByStyle(props: Record<string, T.Validatable<any>>) {\n\tconst propKeysByStyle = new Map<StyleProp<unknown>, string>()\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tif (prop instanceof StyleProp) {\n\t\t\tif (propKeysByStyle.has(prop)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Duplicate style prop ${prop.id}. Each style prop can only be used once within a shape.`\n\t\t\t\t)\n\t\t\t}\n\t\t\tpropKeysByStyle.set(prop, key)\n\t\t}\n\t}\n\treturn propKeysByStyle\n}\n\n/**\n * Creates a migration sequence for shape properties.\n *\n * This is a pass-through function that maintains the same structure as the input.\n * It's used for consistency and to provide a clear API for defining shape property migrations.\n *\n * @param migrations - The migration sequence to create\n * @returns The same migration sequence (pass-through)\n *\n * @example\n * ```ts\n * const myShapeMigrations = createShapePropsMigrationSequence({\n * sequence: [\n * {\n * id: 'com.myapp.shape.custom/1.0.0',\n * up: (props) => ({ ...props, newProperty: 'default' }),\n * down: ({ newProperty, ...props }) => props\n * }\n * ]\n * })\n * ```\n *\n * @public\n */\nexport function createShapePropsMigrationSequence(\n\tmigrations: TLPropsMigrations\n): TLPropsMigrations {\n\treturn migrations\n}\n\n/**\n * Creates properly formatted migration IDs for shape properties.\n *\n * Generates standardized migration IDs following the convention:\n * `com.tldraw.shape.{shapeType}/{version}`\n *\n * @param shapeType - The type of shape these migrations apply to\n * @param ids - Record mapping migration names to version numbers\n * @returns Record with the same keys but formatted migration ID values\n *\n * @example\n * ```ts\n * const myShapeVersions = createShapePropsMigrationIds('custom', {\n * AddColor: 1,\n * AddSize: 2,\n * RefactorProps: 3\n * })\n * // Result: {\n * // AddColor: 'com.tldraw.shape.custom/1',\n * // AddSize: 'com.tldraw.shape.custom/2',\n * // RefactorProps: 'com.tldraw.shape.custom/3'\n * // }\n * ```\n *\n * @public\n */\nexport function createShapePropsMigrationIds<\n\tconst S extends string,\n\tconst T extends Record<string, number>,\n>(shapeType: S, ids: T): { [k in keyof T]: `com.tldraw.shape.${S}/${T[k]}` } {\n\treturn mapObjectMapValues(ids, (_k, v) => `com.tldraw.shape.${shapeType}/${v}`) as any\n}\n\n/**\n * Creates the record type definition for shapes.\n *\n * This function generates a complete record type for shapes that includes validation\n * for all registered shape types. It combines the base shape properties with\n * type-specific properties and creates a union validator that can handle any\n * registered shape type.\n *\n * @param shapes - Record of shape type names to their schema configuration\n * @returns A complete RecordType for shapes with proper validation and default properties\n *\n * @example\n * ```ts\n * const shapeRecordType = createShapeRecordType({\n * geo: { props: geoShapeProps, migrations: geoMigrations },\n * arrow: { props: arrowShapeProps, migrations: arrowMigrations }\n * })\n * ```\n *\n * @internal\n */\nexport function createShapeRecordType(shapes: Record<string, SchemaPropsInfo>) {\n\treturn createRecordType<TLShape>('shape', {\n\t\tscope: 'document',\n\t\tvalidator: T.model(\n\t\t\t'shape',\n\t\t\tT.union(\n\t\t\t\t'type',\n\t\t\t\tmapObjectMapValues(shapes, (type, { props, meta }) =>\n\t\t\t\t\tcreateShapeValidator(type, props, meta)\n\t\t\t\t)\n\t\t\t)\n\t\t),\n\t}).withDefaultProperties(() => ({\n\t\tx: 0,\n\t\ty: 0,\n\t\trotation: 0,\n\t\tisLocked: false,\n\t\topacity: 1,\n\t\tmeta: {},\n\t}))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n\tRecordId,\n\tUnknownRecord,\n\tcreateMigrationIds,\n\tcreateRecordMigrationSequence,\n\tcreateRecordType,\n} from '@tldraw/store'\nimport { mapObjectMapValues, uniqueId } from '@tldraw/utils'\nimport { T } from '@tldraw/validate'\nimport { SchemaPropsInfo } from '../createTLSchema'\nimport { TLPropsMigrations } from '../recordsWithProps'\nimport { TLArrowShape } from '../shapes/TLArrowShape'\nimport { TLBaseShape, createShapeValidator } from '../shapes/TLBaseShape'\nimport { TLBookmarkShape } from '../shapes/TLBookmarkShape'\nimport { TLDrawShape } from '../shapes/TLDrawShape'\nimport { TLEmbedShape } from '../shapes/TLEmbedShape'\nimport { TLFrameShape } from '../shapes/TLFrameShape'\nimport { TLGeoShape } from '../shapes/TLGeoShape'\nimport { TLGroupShape } from '../shapes/TLGroupShape'\nimport { TLHighlightShape } from '../shapes/TLHighlightShape'\nimport { TLImageShape } from '../shapes/TLImageShape'\nimport { TLLineShape } from '../shapes/TLLineShape'\nimport { TLNoteShape } from '../shapes/TLNoteShape'\nimport { TLTextShape } from '../shapes/TLTextShape'\nimport { TLVideoShape } from '../shapes/TLVideoShape'\nimport { StyleProp } from '../styles/StyleProp'\nimport { TLPageId } from './TLPage'\n\n/**\n * The default set of shapes that are available in the editor.\n *\n * This union type represents all the built-in shape types supported by tldraw,\n * including arrows, bookmarks, drawings, embeds, frames, geometry shapes,\n * groups, images, lines, notes, text, videos, and highlights.\n *\n * @example\n * ```ts\n * // Check if a shape is a default shape type\n * function isDefaultShape(shape: TLShape): shape is TLDefaultShape {\n * const defaultTypes = ['arrow', 'bookmark', 'draw', 'embed', 'frame', 'geo', 'group', 'image', 'line', 'note', 'text', 'video', 'highlight']\n * return defaultTypes.includes(shape.type)\n * }\n * ```\n *\n * @public\n */\nexport type TLDefaultShape =\n\t| TLArrowShape\n\t| TLBookmarkShape\n\t| TLDrawShape\n\t| TLEmbedShape\n\t| TLFrameShape\n\t| TLGeoShape\n\t| TLGroupShape\n\t| TLImageShape\n\t| TLLineShape\n\t| TLNoteShape\n\t| TLTextShape\n\t| TLVideoShape\n\t| TLHighlightShape\n\n/**\n * A type for a shape that is available in the editor but whose type is\n * unknown\u2014either one of the editor's default shapes or else a custom shape.\n *\n * This is useful when working with shapes generically without knowing their specific type.\n * The shape type is a string and props are a generic object.\n *\n * @example\n * ```ts\n * // Handle any shape regardless of its specific type\n * function processUnknownShape(shape: TLUnknownShape) {\n * console.log(`Processing shape of type: ${shape.type}`)\n * console.log(`Position: (${shape.x}, ${shape.y})`)\n * }\n * ```\n *\n * @public\n */\nexport type TLUnknownShape = TLBaseShape<string, object>\n\n/** @public */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface TLGlobalShapePropsMap {}\n\n/** @public */\n// prettier-ignore\nexport type TLIndexedShapes = {\n\t// We iterate over a union of augmented keys and default shape types.\n\t// This allows us to include (or conditionally exclude or override) the default shapes in one go.\n\t//\n\t// In the `as` clause we are filtering out disabled shapes.\n\t[K in keyof TLGlobalShapePropsMap | TLDefaultShape['type'] as K extends TLDefaultShape['type']\n\t\t? // core shapes are always available and cannot be overridden so we just include them\n\t\t\tK extends 'group'\n\t\t\t? K\n\t\t\t: K extends keyof TLGlobalShapePropsMap\n\t\t\t\t? // if it extends a nullish value the user has disabled this shape type so we filter it out with never\n\t\t\t\t\tTLGlobalShapePropsMap[K] extends null | undefined\n\t\t\t\t\t? never\n\t\t\t\t\t: K\n\t\t\t\t: K\n\t\t: K]: K extends 'group'\n\t\t? // core shapes are always available and cannot be overridden so we just include them\n\t\t\tExtract<TLDefaultShape, { type: K }>\n\t\t: K extends TLDefaultShape['type']\n\t\t\t? // if it's a default shape type we need to check if it's been overridden\n\t\t\t\tK extends keyof TLGlobalShapePropsMap\n\t\t\t\t? // if it has been overriden then use the custom shape definition\n\t\t\t\t\tTLBaseShape<K, TLGlobalShapePropsMap[K]>\n\t\t\t\t: // if it has not been overriden then reuse existing type aliases for better type display\n\t\t\t\t\tExtract<TLDefaultShape, { type: K }>\n\t\t\t: // use the custom shape definition\n\t\t\t\tTLBaseShape<K, TLGlobalShapePropsMap[K & keyof TLGlobalShapePropsMap]>\n}\n\n/**\n * The set of all shapes that are available in the editor.\n *\n * This is the primary shape type used throughout tldraw. It includes both the\n * built-in default shapes and any custom shapes that might be added.\n *\n * You can use this type without a type argument to work with any shape, or pass\n * a specific shape type string (e.g., `'geo'`, `'arrow'`, `'text'`) to narrow\n * down to that specific shape type.\n *\n * @example\n * ```ts\n * // Work with any shape in the editor\n * function moveShape(shape: TLShape, deltaX: number, deltaY: number): TLShape {\n * return {\n * ...shape,\n * x: shape.x + deltaX,\n * y: shape.y + deltaY\n * }\n * }\n *\n * // Narrow to a specific shape type by passing the type as a generic argument\n * function getArrowLabel(shape: TLShape<'arrow'>): string {\n * return shape.props.text // TypeScript knows this is a TLArrowShape\n * }\n * ```\n *\n * @public\n */\nexport type TLShape<K extends keyof TLIndexedShapes = keyof TLIndexedShapes> = TLIndexedShapes[K]\n\n/**\n * A partial version of a shape, useful for updates and patches.\n *\n * This type represents a shape where all properties except `id` and `type` are optional.\n * It's commonly used when updating existing shapes or creating shape patches.\n *\n * @example\n * ```ts\n * // Update a shape's position\n * const shapeUpdate: TLShapePartial = {\n * id: 'shape:123',\n * type: 'geo',\n * x: 100,\n * y: 200\n * }\n *\n * // Update shape properties\n * const propsUpdate: TLShapePartial<TLGeoShape> = {\n * id: 'shape:123',\n * type: 'geo',\n * props: {\n * w: 150,\n * h: 100\n * }\n * }\n * ```\n *\n * @public\n */\nexport type TLShapePartial<T extends TLShape = TLShape> = T extends T\n\t? {\n\t\t\tid: TLShapeId\n\t\t\ttype: T['type']\n\t\t\tprops?: Partial<T['props']>\n\t\t\tmeta?: Partial<T['meta']>\n\t\t} & Partial<Omit<T, 'type' | 'id' | 'props' | 'meta'>>\n\t: never\n\n/**\n * A partial version of a shape, useful for creating shapes.\n *\n * This type represents a shape where all properties except `type` are optional.\n * It's commonly used when creating shapes.\n *\n * @example\n * ```ts\n * // Create a shape\n * const shapeCreate: TLCreateShapePartial = {\n * type: 'geo',\n * x: 100,\n * y: 200\n * }\n *\n * // Create shape properties\n * const propsCreate: TLCreateShapePartial<TLGeoShape> = {\n * type: 'geo',\n * props: {\n * w: 150,\n * h: 100\n * }\n * }\n * ```\n *\n * @public\n */\nexport type TLCreateShapePartial<T extends TLShape = TLShape> = T extends T\n\t? {\n\t\t\ttype: T['type']\n\t\t\tprops?: Partial<T['props']>\n\t\t\tmeta?: Partial<T['meta']>\n\t\t} & Partial<Omit<T, 'type' | 'props' | 'meta'>>\n\t: never\n\n/**\n * Extract a shape type by its props.\n *\n * This utility type takes a props object type and returns the corresponding shape type\n * from the TLShape union whose props match the given type.\n *\n * @example\n * ```ts\n * type MyShape = ExtractShapeByProps<{ w: number; h: number }>\n * // MyShape is now the type of shape(s) that have props with w and h as numbers\n * ```\n *\n * @public\n */\nexport type ExtractShapeByProps<P> = Extract<TLShape, { props: P }>\n\n/**\n * A unique identifier for a shape record.\n *\n * Shape IDs are branded strings that start with \"shape:\" followed by a unique identifier.\n * This type-safe approach prevents mixing up different types of record IDs.\n *\n * @example\n * ```ts\n * const shapeId: TLShapeId = createShapeId() // \"shape:abc123\"\n * const customId: TLShapeId = createShapeId('my-custom-id') // \"shape:my-custom-id\"\n * ```\n *\n * @public\n */\nexport type TLShapeId = RecordId<TLShape>\n\n/**\n * The ID of a shape's parent, which can be either a page or another shape.\n *\n * Shapes can be parented to pages (for top-level shapes) or to other shapes\n * (for shapes inside frames or groups).\n *\n * @example\n * ```ts\n * // Shape parented to a page\n * const pageParentId: TLParentId = 'page:main'\n *\n * // Shape parented to another shape (e.g., inside a frame)\n * const shapeParentId: TLParentId = 'shape:frame123'\n * ```\n *\n * @public\n */\nexport type TLParentId = TLPageId | TLShapeId\n\n/**\n * Migration version IDs for the root shape schema.\n *\n * These track the evolution of the base shape structure over time, ensuring\n * that shapes created in older versions can be migrated to newer formats.\n *\n * @example\n * ```ts\n * // Check if a migration needs to be applied\n * if (shapeVersion < rootShapeVersions.AddIsLocked) {\n * // Apply isLocked migration\n * }\n * ```\n *\n * @public\n */\nexport const rootShapeVersions = createMigrationIds('com.tldraw.shape', {\n\tAddIsLocked: 1,\n\tHoistOpacity: 2,\n\tAddMeta: 3,\n\tAddWhite: 4,\n})\n\n/**\n * Migration sequence for the root shape record type.\n *\n * This sequence defines how shape records should be transformed when migrating\n * between different schema versions. Each migration handles a specific version\n * upgrade, ensuring data compatibility across tldraw versions.\n *\n * @public\n */\nexport const rootShapeMigrations = createRecordMigrationSequence({\n\tsequenceId: 'com.tldraw.shape',\n\trecordType: 'shape',\n\tsequence: [\n\t\t{\n\t\t\tid: rootShapeVersions.AddIsLocked,\n\t\t\tup: (record: any) => {\n\t\t\t\trecord.isLocked = false\n\t\t\t},\n\t\t\tdown: (record: any) => {\n\t\t\t\tdelete record.isLocked\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: rootShapeVersions.HoistOpacity,\n\t\t\tup: (record: any) => {\n\t\t\t\trecord.opacity = Number(record.props.opacity ?? '1')\n\t\t\t\tdelete record.props.opacity\n\t\t\t},\n\t\t\tdown: (record: any) => {\n\t\t\t\tconst opacity = record.opacity\n\t\t\t\tdelete record.opacity\n\t\t\t\trecord.props.opacity =\n\t\t\t\t\topacity < 0.175\n\t\t\t\t\t\t? '0.1'\n\t\t\t\t\t\t: opacity < 0.375\n\t\t\t\t\t\t\t? '0.25'\n\t\t\t\t\t\t\t: opacity < 0.625\n\t\t\t\t\t\t\t\t? '0.5'\n\t\t\t\t\t\t\t\t: opacity < 0.875\n\t\t\t\t\t\t\t\t\t? '0.75'\n\t\t\t\t\t\t\t\t\t: '1'\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: rootShapeVersions.AddMeta,\n\t\t\tup: (record: any) => {\n\t\t\t\trecord.meta = {}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: rootShapeVersions.AddWhite,\n\t\t\tup: (_record) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t\tdown: (record: any) => {\n\t\t\t\tif (record.props.color === 'white') {\n\t\t\t\t\trecord.props.color = 'black'\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t],\n})\n\n/**\n * Type guard to check if a record is a shape.\n *\n * @param record - The record to check\n * @returns True if the record is a shape, false otherwise\n *\n * @example\n * ```ts\n * const record = store.get('shape:abc123')\n * if (isShape(record)) {\n * console.log(`Shape type: ${record.type}`)\n * console.log(`Position: (${record.x}, ${record.y})`)\n * }\n * ```\n *\n * @public\n */\nexport function isShape(record?: UnknownRecord): record is TLShape {\n\tif (!record) return false\n\treturn record.typeName === 'shape'\n}\n\n/**\n * Type guard to check if a string is a valid shape ID.\n *\n * @param id - The string to check\n * @returns True if the string is a valid shape ID, false otherwise\n *\n * @example\n * ```ts\n * const id = 'shape:abc123'\n * if (isShapeId(id)) {\n * const shape = store.get(id) // TypeScript knows id is TLShapeId\n * }\n *\n * // Check user input\n * function selectShape(id: string) {\n * if (isShapeId(id)) {\n * editor.selectShape(id)\n * } else {\n * console.error('Invalid shape ID format')\n * }\n * }\n * ```\n *\n * @public\n */\nexport function isShapeId(id?: string): id is TLShapeId {\n\tif (!id) return false\n\treturn id.startsWith('shape:')\n}\n\n/**\n * Creates a new shape ID.\n *\n * @param id - Optional custom ID suffix. If not provided, a unique ID will be generated\n * @returns A new shape ID with the \"shape:\" prefix\n *\n * @example\n * ```ts\n * // Create a shape with auto-generated ID\n * const shapeId = createShapeId() // \"shape:abc123\"\n *\n * // Create a shape with custom ID\n * const customShapeId = createShapeId('my-rectangle') // \"shape:my-rectangle\"\n *\n * // Use in shape creation\n * const newShape: TLGeoShape = {\n * id: createShapeId(),\n * type: 'geo',\n * x: 100,\n * y: 200,\n * // ... other properties\n * }\n * ```\n *\n * @public\n */\nexport function createShapeId(id?: string): TLShapeId {\n\treturn `shape:${id ?? uniqueId()}` as TLShapeId\n}\n\n/**\n * Extracts style properties from a shape's props definition and maps them to their property keys.\n *\n * This function analyzes shape property validators to identify which ones are style properties\n * and creates a mapping from StyleProp instances to their corresponding property keys.\n * It also validates that each style property is only used once per shape.\n *\n * @param props - Record of property validators for a shape type\n * @returns Map from StyleProp instances to their property keys\n * @throws Error if a style property is used more than once in the same shape\n *\n * @example\n * ```ts\n * const geoShapeProps = {\n * color: DefaultColorStyle,\n * fill: DefaultFillStyle,\n * width: T.number,\n * height: T.number\n * }\n *\n * const styleMap = getShapePropKeysByStyle(geoShapeProps)\n * // styleMap.get(DefaultColorStyle) === 'color'\n * // styleMap.get(DefaultFillStyle) === 'fill'\n * ```\n *\n * @internal\n */\nexport function getShapePropKeysByStyle(props: Record<string, T.Validatable<any>>) {\n\tconst propKeysByStyle = new Map<StyleProp<unknown>, string>()\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tif (prop instanceof StyleProp) {\n\t\t\tif (propKeysByStyle.has(prop)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Duplicate style prop ${prop.id}. Each style prop can only be used once within a shape.`\n\t\t\t\t)\n\t\t\t}\n\t\t\tpropKeysByStyle.set(prop, key)\n\t\t}\n\t}\n\treturn propKeysByStyle\n}\n\n/**\n * Creates a migration sequence for shape properties.\n *\n * This is a pass-through function that maintains the same structure as the input.\n * It's used for consistency and to provide a clear API for defining shape property migrations.\n *\n * @param migrations - The migration sequence to create\n * @returns The same migration sequence (pass-through)\n *\n * @example\n * ```ts\n * const myShapeMigrations = createShapePropsMigrationSequence({\n * sequence: [\n * {\n * id: 'com.myapp.shape.custom/1.0.0',\n * up: (props) => ({ ...props, newProperty: 'default' }),\n * down: ({ newProperty, ...props }) => props\n * }\n * ]\n * })\n * ```\n *\n * @public\n */\nexport function createShapePropsMigrationSequence(\n\tmigrations: TLPropsMigrations\n): TLPropsMigrations {\n\treturn migrations\n}\n\n/**\n * Creates properly formatted migration IDs for shape properties.\n *\n * Generates standardized migration IDs following the convention:\n * `com.tldraw.shape.{shapeType}/{version}`\n *\n * @param shapeType - The type of shape these migrations apply to\n * @param ids - Record mapping migration names to version numbers\n * @returns Record with the same keys but formatted migration ID values\n *\n * @example\n * ```ts\n * const myShapeVersions = createShapePropsMigrationIds('custom', {\n * AddColor: 1,\n * AddSize: 2,\n * RefactorProps: 3\n * })\n * // Result: {\n * // AddColor: 'com.tldraw.shape.custom/1',\n * // AddSize: 'com.tldraw.shape.custom/2',\n * // RefactorProps: 'com.tldraw.shape.custom/3'\n * // }\n * ```\n *\n * @public\n */\nexport function createShapePropsMigrationIds<\n\tconst S extends string,\n\tconst T extends Record<string, number>,\n>(shapeType: S, ids: T): { [k in keyof T]: `com.tldraw.shape.${S}/${T[k]}` } {\n\treturn mapObjectMapValues(ids, (_k, v) => `com.tldraw.shape.${shapeType}/${v}`) as any\n}\n\n/**\n * Creates the record type definition for shapes.\n *\n * This function generates a complete record type for shapes that includes validation\n * for all registered shape types. It combines the base shape properties with\n * type-specific properties and creates a union validator that can handle any\n * registered shape type.\n *\n * @param shapes - Record of shape type names to their schema configuration\n * @returns A complete RecordType for shapes with proper validation and default properties\n *\n * @example\n * ```ts\n * const shapeRecordType = createShapeRecordType({\n * geo: { props: geoShapeProps, migrations: geoMigrations },\n * arrow: { props: arrowShapeProps, migrations: arrowMigrations }\n * })\n * ```\n *\n * @internal\n */\nexport function createShapeRecordType(shapes: Record<string, SchemaPropsInfo>) {\n\treturn createRecordType('shape', {\n\t\tscope: 'document',\n\t\tvalidator: T.model(\n\t\t\t'shape',\n\t\t\tT.union(\n\t\t\t\t'type',\n\t\t\t\tmapObjectMapValues(shapes, (type, { props, meta }) =>\n\t\t\t\t\tcreateShapeValidator(type, props, meta)\n\t\t\t\t)\n\t\t\t)\n\t\t),\n\t}).withDefaultProperties(() => ({\n\t\tx: 0,\n\t\ty: 0,\n\t\trotation: 0,\n\t\tisLocked: false,\n\t\topacity: 1,\n\t\tmeta: {},\n\t}))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shapes/TLBaseShape.ts"],
4
- "sourcesContent": ["import { BaseRecord } from '@tldraw/store'\nimport { IndexKey, JsonObject } from '@tldraw/utils'\nimport { T } from '@tldraw/validate'\nimport { TLOpacityType, opacityValidator } from '../misc/TLOpacity'\nimport { idValidator } from '../misc/id-validator'\nimport { TLParentId, TLShapeId } from '../records/TLShape'\n\n/**\n * Base interface for all shapes in tldraw.\n *\n * This interface defines the common properties that all shapes share, regardless of their\n * specific type. Every shape extends this base with additional type-specific properties.\n *\n * @example\n * ```ts\n * // Define a custom shape type\n * interface MyCustomShape extends TLBaseShape<'custom', { size: number; color: string }> {}\n *\n * // Create a shape instance\n * const myShape: MyCustomShape = {\n * id: 'shape:abc123',\n * typeName: 'shape',\n * type: 'custom',\n * x: 100,\n * y: 200,\n * rotation: 0,\n * index: 'a1',\n * parentId: 'page:main',\n * isLocked: false,\n * opacity: 1,\n * props: {\n * size: 50,\n * color: 'blue'\n * },\n * meta: {}\n * }\n * ```\n *\n * @public\n */\nexport interface TLBaseShape<Type extends string, Props extends object>\n\textends BaseRecord<'shape', TLShapeId> {\n\ttype: Type\n\tx: number\n\ty: number\n\trotation: number\n\tindex: IndexKey\n\tparentId: TLParentId\n\tisLocked: boolean\n\topacity: TLOpacityType\n\tprops: Props\n\tmeta: JsonObject\n}\n\n/**\n * Validator for parent IDs, ensuring they follow the correct format.\n *\n * Parent IDs must start with either \"page:\" (for shapes directly on a page)\n * or \"shape:\" (for shapes inside other shapes like frames or groups).\n *\n * @example\n * ```ts\n * // Valid parent IDs\n * const pageParent = parentIdValidator.validate('page:main') // \u2713\n * const shapeParent = parentIdValidator.validate('shape:frame1') // \u2713\n *\n * // Invalid parent ID (throws error)\n * const invalid = parentIdValidator.validate('invalid:123') // \u2717\n * ```\n *\n * @public\n */\nexport const parentIdValidator = T.string.refine((id) => {\n\tif (!id.startsWith('page:') && !id.startsWith('shape:')) {\n\t\tthrow new Error('Parent ID must start with \"page:\" or \"shape:\"')\n\t}\n\treturn id as TLParentId\n})\n\n/**\n * Validator for shape IDs, ensuring they follow the \"shape:\" format.\n *\n * @example\n * ```ts\n * const validId = shapeIdValidator.validate('shape:abc123') // \u2713\n * const invalidId = shapeIdValidator.validate('page:abc123') // \u2717 throws error\n * ```\n *\n * @public\n */\nexport const shapeIdValidator = idValidator<TLShapeId>('shape')\n\n/**\n * Creates a validator for a specific shape type.\n *\n * This function generates a complete validator that can validate shape records\n * of the specified type, including both the base shape properties and any\n * custom properties and metadata specific to that shape type.\n *\n * @param type - The string literal type for this shape (e.g., 'geo', 'arrow')\n * @param props - Optional validator configuration for shape-specific properties\n * @param meta - Optional validator configuration for shape-specific metadata\n * @returns A validator that can validate complete shape records of the specified type\n *\n * @example\n * ```ts\n * // Create a validator for a custom shape type\n * const customShapeValidator = createShapeValidator('custom', {\n * width: T.number,\n * height: T.number,\n * color: T.string\n * })\n *\n * // Use the validator to validate shape data\n * const shapeData = {\n * id: 'shape:abc123',\n * typeName: 'shape',\n * type: 'custom',\n * x: 100,\n * y: 200,\n * // ... other base properties\n * props: {\n * width: 150,\n * height: 100,\n * color: 'red'\n * }\n * }\n *\n * const validatedShape = customShapeValidator.validate(shapeData)\n * ```\n *\n * @public\n */\nexport function createShapeValidator<\n\tType extends string,\n\tProps extends JsonObject,\n\tMeta extends JsonObject,\n>(\n\ttype: Type,\n\tprops?: { [K in keyof Props]: T.Validatable<Props[K]> },\n\tmeta?: { [K in keyof Meta]: T.Validatable<Meta[K]> }\n) {\n\treturn T.object<TLBaseShape<Type, Props>>({\n\t\tid: shapeIdValidator,\n\t\ttypeName: T.literal('shape'),\n\t\tx: T.number,\n\t\ty: T.number,\n\t\trotation: T.number,\n\t\tindex: T.indexKey,\n\t\tparentId: parentIdValidator,\n\t\ttype: T.literal(type),\n\t\tisLocked: T.boolean,\n\t\topacity: opacityValidator,\n\t\tprops: props ? T.object(props) : (T.jsonValue as any),\n\t\tmeta: meta ? T.object(meta) : (T.jsonValue as any),\n\t})\n}\n"],
5
- "mappings": "AAEA,SAAS,SAAS;AAClB,SAAwB,wBAAwB;AAChD,SAAS,mBAAmB;AAoErB,MAAM,oBAAoB,EAAE,OAAO,OAAO,CAAC,OAAO;AACxD,MAAI,CAAC,GAAG,WAAW,OAAO,KAAK,CAAC,GAAG,WAAW,QAAQ,GAAG;AACxD,UAAM,IAAI,MAAM,+CAA+C;AAAA,EAChE;AACA,SAAO;AACR,CAAC;AAaM,MAAM,mBAAmB,YAAuB,OAAO;AA2CvD,SAAS,qBAKf,MACA,OACA,MACC;AACD,SAAO,EAAE,OAAiC;AAAA,IACzC,IAAI;AAAA,IACJ,UAAU,EAAE,QAAQ,OAAO;AAAA,IAC3B,GAAG,EAAE;AAAA,IACL,GAAG,EAAE;AAAA,IACL,UAAU,EAAE;AAAA,IACZ,OAAO,EAAE;AAAA,IACT,UAAU;AAAA,IACV,MAAM,EAAE,QAAQ,IAAI;AAAA,IACpB,UAAU,EAAE;AAAA,IACZ,SAAS;AAAA,IACT,OAAO,QAAQ,EAAE,OAAO,KAAK,IAAK,EAAE;AAAA,IACpC,MAAM,OAAO,EAAE,OAAO,IAAI,IAAK,EAAE;AAAA,EAClC,CAAC;AACF;",
4
+ "sourcesContent": ["import { IndexKey, JsonObject } from '@tldraw/utils'\nimport { T } from '@tldraw/validate'\nimport { TLOpacityType, opacityValidator } from '../misc/TLOpacity'\nimport { idValidator } from '../misc/id-validator'\nimport { TLParentId, TLShapeId } from '../records/TLShape'\n\n/**\n * Base interface for all shapes in tldraw.\n *\n * This interface defines the common properties that all shapes share, regardless of their\n * specific type. Every default shape extends this base with additional type-specific properties.\n *\n * Custom shapes should be defined by augmenting the TLGlobalShapePropsMap type and getting the shape type from the TLShape type.\n *\n * @example\n * ```ts\n * // Define a default shape type\n * interface TLArrowShape extends TLBaseShape<'arrow', {\n * kind: TLArrowShapeKind\n * labelColor: TLDefaultColorStyle\n * color: TLDefaultColorStyle\n * fill: TLDefaultFillStyle\n * dash: TLDefaultDashStyle\n * size: TLDefaultSizeStyle\n * arrowheadStart: TLArrowShapeArrowheadStyle\n * arrowheadEnd: TLArrowShapeArrowheadStyle\n * font: TLDefaultFontStyle\n * start: VecModel\n * end: VecModel\n * bend: number\n * richText: TLRichText\n * labelPosition: number\n * scale: number\n * elbowMidPoint: number\n * }> {}\n *\n * // Create a shape instance\n * const arrowShape: TLArrowShape = {\n * id: 'shape:abc123',\n * typeName: 'shape',\n * type: 'arrow',\n * x: 100,\n * y: 200,\n * rotation: 0,\n * index: 'a1',\n * parentId: 'page:main',\n * isLocked: false,\n * opacity: 1,\n * props: {\n * kind: 'arc',\n * start: { x: 0, y: 0 },\n * end: { x: 100, y: 100 },\n * // ... other props\n * },\n * meta: {}\n * }\n * ```\n *\n * @public\n */\nexport interface TLBaseShape<Type extends string, Props extends object> {\n\t// using real `extends BaseRecord<'shape', TLShapeId>` introduces a circularity in the types\n\t// and for that reason those \"base members\" have to be declared manually here\n\treadonly id: TLShapeId\n\treadonly typeName: 'shape'\n\n\ttype: Type\n\tx: number\n\ty: number\n\trotation: number\n\tindex: IndexKey\n\tparentId: TLParentId\n\tisLocked: boolean\n\topacity: TLOpacityType\n\tprops: Props\n\tmeta: JsonObject\n}\n\n/**\n * Validator for parent IDs, ensuring they follow the correct format.\n *\n * Parent IDs must start with either \"page:\" (for shapes directly on a page)\n * or \"shape:\" (for shapes inside other shapes like frames or groups).\n *\n * @example\n * ```ts\n * // Valid parent IDs\n * const pageParent = parentIdValidator.validate('page:main') // \u2713\n * const shapeParent = parentIdValidator.validate('shape:frame1') // \u2713\n *\n * // Invalid parent ID (throws error)\n * const invalid = parentIdValidator.validate('invalid:123') // \u2717\n * ```\n *\n * @public\n */\nexport const parentIdValidator = T.string.refine((id) => {\n\tif (!id.startsWith('page:') && !id.startsWith('shape:')) {\n\t\tthrow new Error('Parent ID must start with \"page:\" or \"shape:\"')\n\t}\n\treturn id as TLParentId\n})\n\n/**\n * Validator for shape IDs, ensuring they follow the \"shape:\" format.\n *\n * @example\n * ```ts\n * const validId = shapeIdValidator.validate('shape:abc123') // \u2713\n * const invalidId = shapeIdValidator.validate('page:abc123') // \u2717 throws error\n * ```\n *\n * @public\n */\nexport const shapeIdValidator = idValidator<TLShapeId>('shape')\n\n/**\n * Creates a validator for a specific shape type.\n *\n * This function generates a complete validator that can validate shape records\n * of the specified type, including both the base shape properties and any\n * custom properties and metadata specific to that shape type.\n *\n * @param type - The string literal type for this shape (e.g., 'geo', 'arrow')\n * @param props - Optional validator configuration for shape-specific properties\n * @param meta - Optional validator configuration for shape-specific metadata\n * @returns A validator that can validate complete shape records of the specified type\n *\n * @example\n * ```ts\n * // Create a validator for a custom shape type\n * const customShapeValidator = createShapeValidator('custom', {\n * width: T.number,\n * height: T.number,\n * color: T.string\n * })\n *\n * // Use the validator to validate shape data\n * const shapeData = {\n * id: 'shape:abc123',\n * typeName: 'shape',\n * type: 'custom',\n * x: 100,\n * y: 200,\n * // ... other base properties\n * props: {\n * width: 150,\n * height: 100,\n * color: 'red'\n * }\n * }\n *\n * const validatedShape = customShapeValidator.validate(shapeData)\n * ```\n *\n * @public\n */\nexport function createShapeValidator<\n\tType extends string,\n\tProps extends JsonObject,\n\tMeta extends JsonObject,\n>(\n\ttype: Type,\n\tprops?: { [K in keyof Props]: T.Validatable<Props[K]> },\n\tmeta?: { [K in keyof Meta]: T.Validatable<Meta[K]> }\n) {\n\treturn T.object<TLBaseShape<Type, Props>>({\n\t\tid: shapeIdValidator,\n\t\ttypeName: T.literal('shape'),\n\t\tx: T.number,\n\t\ty: T.number,\n\t\trotation: T.number,\n\t\tindex: T.indexKey,\n\t\tparentId: parentIdValidator,\n\t\ttype: T.literal(type),\n\t\tisLocked: T.boolean,\n\t\topacity: opacityValidator,\n\t\tprops: props ? T.object(props) : (T.jsonValue as any),\n\t\tmeta: meta ? T.object(meta) : (T.jsonValue as any),\n\t})\n}\n"],
5
+ "mappings": "AACA,SAAS,SAAS;AAClB,SAAwB,wBAAwB;AAChD,SAAS,mBAAmB;AA6FrB,MAAM,oBAAoB,EAAE,OAAO,OAAO,CAAC,OAAO;AACxD,MAAI,CAAC,GAAG,WAAW,OAAO,KAAK,CAAC,GAAG,WAAW,QAAQ,GAAG;AACxD,UAAM,IAAI,MAAM,+CAA+C;AAAA,EAChE;AACA,SAAO;AACR,CAAC;AAaM,MAAM,mBAAmB,YAAuB,OAAO;AA2CvD,SAAS,qBAKf,MACA,OACA,MACC;AACD,SAAO,EAAE,OAAiC;AAAA,IACzC,IAAI;AAAA,IACJ,UAAU,EAAE,QAAQ,OAAO;AAAA,IAC3B,GAAG,EAAE;AAAA,IACL,GAAG,EAAE;AAAA,IACL,UAAU,EAAE;AAAA,IACZ,OAAO,EAAE;AAAA,IACT,UAAU;AAAA,IACV,MAAM,EAAE,QAAQ,IAAI;AAAA,IACpB,UAAU,EAAE;AAAA,IACZ,SAAS;AAAA,IACT,OAAO,QAAQ,EAAE,OAAO,KAAK,IAAK,EAAE;AAAA,IACpC,MAAM,OAAO,EAAE,OAAO,IAAI,IAAK,EAAE;AAAA,EAClC,CAAC;AACF;",
6
6
  "names": []
7
7
  }
@@ -16,7 +16,7 @@ const storeMigrations = createMigrationSequence({
16
16
  scope: "store",
17
17
  up: (store) => {
18
18
  for (const [id, record] of objectMapEntries(store)) {
19
- if (record.typeName === "shape" && (record.type === "icon" || record.type === "code")) {
19
+ if (record.typeName === "shape" && "type" in record && (record.type === "icon" || record.type === "code")) {
20
20
  delete store[id];
21
21
  }
22
22
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/store-migrations.ts"],
4
- "sourcesContent": ["import { createMigrationIds, createMigrationSequence } from '@tldraw/store'\nimport { IndexKey, objectMapEntries } from '@tldraw/utils'\nimport { TLPage } from './records/TLPage'\nimport { TLShape } from './records/TLShape'\nimport { TLLineShape } from './shapes/TLLineShape'\n\n/**\n * Migration version constants for store-level schema changes.\n * Each version represents a breaking change that requires data transformation.\n *\n * @internal\n */\nconst Versions = createMigrationIds('com.tldraw.store', {\n\tRemoveCodeAndIconShapeTypes: 1,\n\tAddInstancePresenceType: 2,\n\tRemoveTLUserAndPresenceAndAddPointer: 3,\n\tRemoveUserDocument: 4,\n\tFixIndexKeys: 5,\n} as const)\n\n/**\n * Migration version identifiers for store-level migrations.\n * These versions track changes to the overall store structure and data model.\n *\n * @example\n * ```ts\n * import { storeVersions } from '@tldraw/tlschema'\n *\n * // Check if a specific migration version exists\n * const hasRemoveCodeShapes = storeVersions.RemoveCodeAndIconShapeTypes\n * ```\n *\n * @public\n */\nexport { Versions as storeVersions }\n\n/**\n * Store-level migration sequence that handles evolution of the tldraw data model.\n * These migrations run when the store schema version changes and ensure backward\n * compatibility by transforming old data structures to new formats.\n *\n * The migrations handle:\n * - Removal of deprecated shape types (code, icon)\n * - Addition of new record types (instance presence)\n * - Cleanup of obsolete user and presence data\n * - Removal of deprecated user document records\n *\n * @example\n * ```ts\n * import { storeMigrations } from '@tldraw/tlschema'\n * import { migrate } from '@tldraw/store'\n *\n * // Apply store migrations to old data\n * const migratedStore = migrate({\n * store: oldStoreData,\n * migrations: storeMigrations,\n * fromVersion: 0,\n * toVersion: storeMigrations.currentVersion\n * })\n * ```\n *\n * @public\n */\nexport const storeMigrations = createMigrationSequence({\n\tsequenceId: 'com.tldraw.store',\n\tretroactive: false,\n\tsequence: [\n\t\t{\n\t\t\tid: Versions.RemoveCodeAndIconShapeTypes,\n\t\t\tscope: 'store',\n\t\t\tup: (store) => {\n\t\t\t\tfor (const [id, record] of objectMapEntries(store)) {\n\t\t\t\t\tif (\n\t\t\t\t\t\trecord.typeName === 'shape' &&\n\t\t\t\t\t\t((record as TLShape).type === 'icon' || (record as TLShape).type === 'code')\n\t\t\t\t\t) {\n\t\t\t\t\t\tdelete store[id]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: Versions.AddInstancePresenceType,\n\t\t\tscope: 'store',\n\t\t\tup(_store) {\n\t\t\t\t// noop\n\t\t\t\t// there used to be a down migration for this but we made down migrations optional\n\t\t\t\t// and we don't use them on store-level migrations so we can just remove it\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// remove user and presence records and add pointer records\n\t\t\tid: Versions.RemoveTLUserAndPresenceAndAddPointer,\n\t\t\tscope: 'store',\n\t\t\tup: (store) => {\n\t\t\t\tfor (const [id, record] of objectMapEntries(store)) {\n\t\t\t\t\tif (record.typeName.match(/^(user|user_presence)$/)) {\n\t\t\t\t\t\tdelete store[id]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// remove user document records\n\t\t\tid: Versions.RemoveUserDocument,\n\t\t\tscope: 'store',\n\t\t\tup: (store) => {\n\t\t\t\tfor (const [id, record] of objectMapEntries(store)) {\n\t\t\t\t\tif (record.typeName.match('user_document')) {\n\t\t\t\t\t\tdelete store[id]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: Versions.FixIndexKeys,\n\t\t\tscope: 'record',\n\t\t\tup: (record) => {\n\t\t\t\tif (['shape', 'page'].includes(record.typeName) && 'index' in record) {\n\t\t\t\t\tconst recordWithIndex = record as TLShape | TLPage\n\t\t\t\t\t// Our newer fractional indexed library (more correctly) validates that indices\n\t\t\t\t\t// do not end with 0. ('a0' being an exception)\n\t\t\t\t\tif (recordWithIndex.index.endsWith('0') && recordWithIndex.index !== 'a0') {\n\t\t\t\t\t\trecordWithIndex.index = (recordWithIndex.index.slice(0, -1) +\n\t\t\t\t\t\t\tgetNRandomBase62Digits(3)) as IndexKey\n\t\t\t\t\t}\n\t\t\t\t\t// Line shapes have 'points' that have indices as well.\n\t\t\t\t\tif (record.typeName === 'shape' && (recordWithIndex as TLShape).type === 'line') {\n\t\t\t\t\t\tconst lineShape = recordWithIndex as TLLineShape\n\t\t\t\t\t\tfor (const [_, point] of objectMapEntries(lineShape.props.points)) {\n\t\t\t\t\t\t\tif (point.index.endsWith('0') && point.index !== 'a0') {\n\t\t\t\t\t\t\t\tpoint.index = (point.index.slice(0, -1) + getNRandomBase62Digits(3)) as IndexKey\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: () => {\n\t\t\t\t// noop\n\t\t\t\t// Enables tlsync to support older clients so as to not force people to refresh immediately after deploying.\n\t\t\t},\n\t\t},\n\t],\n})\n\nconst BASE_62_DIGITS_WITHOUT_ZERO = '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst getRandomBase62Digit = () => {\n\treturn BASE_62_DIGITS_WITHOUT_ZERO.charAt(\n\t\tMath.floor(Math.random() * BASE_62_DIGITS_WITHOUT_ZERO.length)\n\t)\n}\n\nconst getNRandomBase62Digits = (n: number) => {\n\treturn Array.from({ length: n }, getRandomBase62Digit).join('')\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB,+BAA+B;AAC5D,SAAmB,wBAAwB;AAW3C,MAAM,WAAW,mBAAmB,oBAAoB;AAAA,EACvD,6BAA6B;AAAA,EAC7B,yBAAyB;AAAA,EACzB,sCAAsC;AAAA,EACtC,oBAAoB;AAAA,EACpB,cAAc;AACf,CAAU;AA6CH,MAAM,kBAAkB,wBAAwB;AAAA,EACtD,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,UAAU;AAAA,IACT;AAAA,MACC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,UAAU;AACd,mBAAW,CAAC,IAAI,MAAM,KAAK,iBAAiB,KAAK,GAAG;AACnD,cACC,OAAO,aAAa,YAClB,OAAmB,SAAS,UAAW,OAAmB,SAAS,SACpE;AACD,mBAAO,MAAM,EAAE;AAAA,UAChB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,GAAG,QAAQ;AAAA,MAIX;AAAA,IACD;AAAA,IACA;AAAA;AAAA,MAEC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,UAAU;AACd,mBAAW,CAAC,IAAI,MAAM,KAAK,iBAAiB,KAAK,GAAG;AACnD,cAAI,OAAO,SAAS,MAAM,wBAAwB,GAAG;AACpD,mBAAO,MAAM,EAAE;AAAA,UAChB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA;AAAA,MAEC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,UAAU;AACd,mBAAW,CAAC,IAAI,MAAM,KAAK,iBAAiB,KAAK,GAAG;AACnD,cAAI,OAAO,SAAS,MAAM,eAAe,GAAG;AAC3C,mBAAO,MAAM,EAAE;AAAA,UAChB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,WAAW;AACf,YAAI,CAAC,SAAS,MAAM,EAAE,SAAS,OAAO,QAAQ,KAAK,WAAW,QAAQ;AACrE,gBAAM,kBAAkB;AAGxB,cAAI,gBAAgB,MAAM,SAAS,GAAG,KAAK,gBAAgB,UAAU,MAAM;AAC1E,4BAAgB,QAAS,gBAAgB,MAAM,MAAM,GAAG,EAAE,IACzD,uBAAuB,CAAC;AAAA,UAC1B;AAEA,cAAI,OAAO,aAAa,WAAY,gBAA4B,SAAS,QAAQ;AAChF,kBAAM,YAAY;AAClB,uBAAW,CAAC,GAAG,KAAK,KAAK,iBAAiB,UAAU,MAAM,MAAM,GAAG;AAClE,kBAAI,MAAM,MAAM,SAAS,GAAG,KAAK,MAAM,UAAU,MAAM;AACtD,sBAAM,QAAS,MAAM,MAAM,MAAM,GAAG,EAAE,IAAI,uBAAuB,CAAC;AAAA,cACnE;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACA,MAAM,MAAM;AAAA,MAGZ;AAAA,IACD;AAAA,EACD;AACD,CAAC;AAED,MAAM,8BAA8B;AACpC,MAAM,uBAAuB,MAAM;AAClC,SAAO,4BAA4B;AAAA,IAClC,KAAK,MAAM,KAAK,OAAO,IAAI,4BAA4B,MAAM;AAAA,EAC9D;AACD;AAEA,MAAM,yBAAyB,CAAC,MAAc;AAC7C,SAAO,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,oBAAoB,EAAE,KAAK,EAAE;AAC/D;",
4
+ "sourcesContent": ["import { createMigrationIds, createMigrationSequence } from '@tldraw/store'\nimport { IndexKey, objectMapEntries } from '@tldraw/utils'\nimport { TLPage } from './records/TLPage'\nimport { TLShape } from './records/TLShape'\nimport { TLLineShape } from './shapes/TLLineShape'\n\n/**\n * Migration version constants for store-level schema changes.\n * Each version represents a breaking change that requires data transformation.\n *\n * @internal\n */\nconst Versions = createMigrationIds('com.tldraw.store', {\n\tRemoveCodeAndIconShapeTypes: 1,\n\tAddInstancePresenceType: 2,\n\tRemoveTLUserAndPresenceAndAddPointer: 3,\n\tRemoveUserDocument: 4,\n\tFixIndexKeys: 5,\n} as const)\n\n/**\n * Migration version identifiers for store-level migrations.\n * These versions track changes to the overall store structure and data model.\n *\n * @example\n * ```ts\n * import { storeVersions } from '@tldraw/tlschema'\n *\n * // Check if a specific migration version exists\n * const hasRemoveCodeShapes = storeVersions.RemoveCodeAndIconShapeTypes\n * ```\n *\n * @public\n */\nexport { Versions as storeVersions }\n\n/**\n * Store-level migration sequence that handles evolution of the tldraw data model.\n * These migrations run when the store schema version changes and ensure backward\n * compatibility by transforming old data structures to new formats.\n *\n * The migrations handle:\n * - Removal of deprecated shape types (code, icon)\n * - Addition of new record types (instance presence)\n * - Cleanup of obsolete user and presence data\n * - Removal of deprecated user document records\n *\n * @example\n * ```ts\n * import { storeMigrations } from '@tldraw/tlschema'\n * import { migrate } from '@tldraw/store'\n *\n * // Apply store migrations to old data\n * const migratedStore = migrate({\n * store: oldStoreData,\n * migrations: storeMigrations,\n * fromVersion: 0,\n * toVersion: storeMigrations.currentVersion\n * })\n * ```\n *\n * @public\n */\nexport const storeMigrations = createMigrationSequence({\n\tsequenceId: 'com.tldraw.store',\n\tretroactive: false,\n\tsequence: [\n\t\t{\n\t\t\tid: Versions.RemoveCodeAndIconShapeTypes,\n\t\t\tscope: 'store',\n\t\t\tup: (store) => {\n\t\t\t\tfor (const [id, record] of objectMapEntries(store)) {\n\t\t\t\t\tif (\n\t\t\t\t\t\trecord.typeName === 'shape' &&\n\t\t\t\t\t\t'type' in record &&\n\t\t\t\t\t\t(record.type === 'icon' || record.type === 'code')\n\t\t\t\t\t) {\n\t\t\t\t\t\tdelete store[id]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: Versions.AddInstancePresenceType,\n\t\t\tscope: 'store',\n\t\t\tup(_store) {\n\t\t\t\t// noop\n\t\t\t\t// there used to be a down migration for this but we made down migrations optional\n\t\t\t\t// and we don't use them on store-level migrations so we can just remove it\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// remove user and presence records and add pointer records\n\t\t\tid: Versions.RemoveTLUserAndPresenceAndAddPointer,\n\t\t\tscope: 'store',\n\t\t\tup: (store) => {\n\t\t\t\tfor (const [id, record] of objectMapEntries(store)) {\n\t\t\t\t\tif (record.typeName.match(/^(user|user_presence)$/)) {\n\t\t\t\t\t\tdelete store[id]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// remove user document records\n\t\t\tid: Versions.RemoveUserDocument,\n\t\t\tscope: 'store',\n\t\t\tup: (store) => {\n\t\t\t\tfor (const [id, record] of objectMapEntries(store)) {\n\t\t\t\t\tif (record.typeName.match('user_document')) {\n\t\t\t\t\t\tdelete store[id]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: Versions.FixIndexKeys,\n\t\t\tscope: 'record',\n\t\t\tup: (record) => {\n\t\t\t\tif (['shape', 'page'].includes(record.typeName) && 'index' in record) {\n\t\t\t\t\tconst recordWithIndex = record as TLShape | TLPage\n\t\t\t\t\t// Our newer fractional indexed library (more correctly) validates that indices\n\t\t\t\t\t// do not end with 0. ('a0' being an exception)\n\t\t\t\t\tif (recordWithIndex.index.endsWith('0') && recordWithIndex.index !== 'a0') {\n\t\t\t\t\t\trecordWithIndex.index = (recordWithIndex.index.slice(0, -1) +\n\t\t\t\t\t\t\tgetNRandomBase62Digits(3)) as IndexKey\n\t\t\t\t\t}\n\t\t\t\t\t// Line shapes have 'points' that have indices as well.\n\t\t\t\t\tif (record.typeName === 'shape' && (recordWithIndex as TLShape).type === 'line') {\n\t\t\t\t\t\tconst lineShape = recordWithIndex as TLLineShape\n\t\t\t\t\t\tfor (const [_, point] of objectMapEntries(lineShape.props.points)) {\n\t\t\t\t\t\t\tif (point.index.endsWith('0') && point.index !== 'a0') {\n\t\t\t\t\t\t\t\tpoint.index = (point.index.slice(0, -1) + getNRandomBase62Digits(3)) as IndexKey\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: () => {\n\t\t\t\t// noop\n\t\t\t\t// Enables tlsync to support older clients so as to not force people to refresh immediately after deploying.\n\t\t\t},\n\t\t},\n\t],\n})\n\nconst BASE_62_DIGITS_WITHOUT_ZERO = '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst getRandomBase62Digit = () => {\n\treturn BASE_62_DIGITS_WITHOUT_ZERO.charAt(\n\t\tMath.floor(Math.random() * BASE_62_DIGITS_WITHOUT_ZERO.length)\n\t)\n}\n\nconst getNRandomBase62Digits = (n: number) => {\n\treturn Array.from({ length: n }, getRandomBase62Digit).join('')\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB,+BAA+B;AAC5D,SAAmB,wBAAwB;AAW3C,MAAM,WAAW,mBAAmB,oBAAoB;AAAA,EACvD,6BAA6B;AAAA,EAC7B,yBAAyB;AAAA,EACzB,sCAAsC;AAAA,EACtC,oBAAoB;AAAA,EACpB,cAAc;AACf,CAAU;AA6CH,MAAM,kBAAkB,wBAAwB;AAAA,EACtD,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,UAAU;AAAA,IACT;AAAA,MACC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,UAAU;AACd,mBAAW,CAAC,IAAI,MAAM,KAAK,iBAAiB,KAAK,GAAG;AACnD,cACC,OAAO,aAAa,WACpB,UAAU,WACT,OAAO,SAAS,UAAU,OAAO,SAAS,SAC1C;AACD,mBAAO,MAAM,EAAE;AAAA,UAChB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,GAAG,QAAQ;AAAA,MAIX;AAAA,IACD;AAAA,IACA;AAAA;AAAA,MAEC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,UAAU;AACd,mBAAW,CAAC,IAAI,MAAM,KAAK,iBAAiB,KAAK,GAAG;AACnD,cAAI,OAAO,SAAS,MAAM,wBAAwB,GAAG;AACpD,mBAAO,MAAM,EAAE;AAAA,UAChB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA;AAAA,MAEC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,UAAU;AACd,mBAAW,CAAC,IAAI,MAAM,KAAK,iBAAiB,KAAK,GAAG;AACnD,cAAI,OAAO,SAAS,MAAM,eAAe,GAAG;AAC3C,mBAAO,MAAM,EAAE;AAAA,UAChB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,IAAI,CAAC,WAAW;AACf,YAAI,CAAC,SAAS,MAAM,EAAE,SAAS,OAAO,QAAQ,KAAK,WAAW,QAAQ;AACrE,gBAAM,kBAAkB;AAGxB,cAAI,gBAAgB,MAAM,SAAS,GAAG,KAAK,gBAAgB,UAAU,MAAM;AAC1E,4BAAgB,QAAS,gBAAgB,MAAM,MAAM,GAAG,EAAE,IACzD,uBAAuB,CAAC;AAAA,UAC1B;AAEA,cAAI,OAAO,aAAa,WAAY,gBAA4B,SAAS,QAAQ;AAChF,kBAAM,YAAY;AAClB,uBAAW,CAAC,GAAG,KAAK,KAAK,iBAAiB,UAAU,MAAM,MAAM,GAAG;AAClE,kBAAI,MAAM,MAAM,SAAS,GAAG,KAAK,MAAM,UAAU,MAAM;AACtD,sBAAM,QAAS,MAAM,MAAM,MAAM,GAAG,EAAE,IAAI,uBAAuB,CAAC;AAAA,cACnE;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACA,MAAM,MAAM;AAAA,MAGZ;AAAA,IACD;AAAA,EACD;AACD,CAAC;AAED,MAAM,8BAA8B;AACpC,MAAM,uBAAuB,MAAM;AAClC,SAAO,4BAA4B;AAAA,IAClC,KAAK,MAAM,KAAK,OAAO,IAAI,4BAA4B,MAAM;AAAA,EAC9D;AACD;AAEA,MAAM,yBAAyB,CAAC,MAAc;AAC7C,SAAO,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,oBAAoB,EAAE,KAAK,EAAE;AAC/D;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/tlschema",
3
3
  "description": "tldraw infinite canvas SDK (schema).",
4
- "version": "4.3.0-next.7810f2131b3c",
4
+ "version": "4.3.0-next.7f179bd04d6c",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -51,10 +51,10 @@
51
51
  "vitest": "^3.2.4"
52
52
  },
53
53
  "dependencies": {
54
- "@tldraw/state": "4.3.0-next.7810f2131b3c",
55
- "@tldraw/store": "4.3.0-next.7810f2131b3c",
56
- "@tldraw/utils": "4.3.0-next.7810f2131b3c",
57
- "@tldraw/validate": "4.3.0-next.7810f2131b3c"
54
+ "@tldraw/state": "4.3.0-next.7f179bd04d6c",
55
+ "@tldraw/store": "4.3.0-next.7f179bd04d6c",
56
+ "@tldraw/utils": "4.3.0-next.7f179bd04d6c",
57
+ "@tldraw/validate": "4.3.0-next.7f179bd04d6c"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "react": "^18.2.0 || ^19.0.0",
@@ -1,4 +1,3 @@
1
- import { BaseRecord } from '@tldraw/store'
2
1
  import { JsonObject } from '@tldraw/utils'
3
2
  import { T } from '@tldraw/validate'
4
3
  import { idValidator } from '../misc/id-validator'
@@ -10,33 +9,41 @@ import { shapeIdValidator } from '../shapes/TLBaseShape'
10
9
  * Base interface for all binding types in tldraw. Bindings represent relationships
11
10
  * between shapes, such as arrows connecting to other shapes or organizational connections.
12
11
  *
13
- * All bindings extend this base interface with specific type and property definitions.
12
+ * All default bindings extend this base interface with specific type and property definitions.
14
13
  * The binding system enables shapes to maintain relationships that persist through
15
14
  * transformations, movements, and other operations.
16
15
  *
16
+ * Custom bindings should be defined by augmenting the TLGlobalBindingPropsMap type and getting the binding type from the TLBinding type.
17
+ *
17
18
  * @param Type - String literal type identifying the specific binding type (e.g., 'arrow')
18
19
  * @param Props - Object containing binding-specific properties and configuration
19
20
  *
20
21
  * @example
21
22
  * ```ts
22
- * // Define a custom binding type
23
- * interface MyCustomBinding extends TLBaseBinding<'custom', MyCustomProps> {}
24
- *
25
- * interface MyCustomProps {
26
- * strength: number
27
- * color: string
23
+ * // Define a default binding type
24
+ * interface TLArrowBinding extends TLBaseBinding<'arrow', TLArrowBindingProps> {}
25
+ *
26
+ * interface TLArrowBindingProps {
27
+ * terminal: 'start' | 'end'
28
+ * normalizedAnchor: VecModel
29
+ * isExact: boolean
30
+ * isPrecise: boolean
31
+ * snap: ElbowArrowSnap
28
32
  * }
29
33
  *
30
34
  * // Create a binding instance
31
- * const binding: MyCustomBinding = {
35
+ * const arrowBinding: TLArrowBinding = {
32
36
  * id: 'binding:abc123',
33
37
  * typeName: 'binding',
34
- * type: 'custom',
38
+ * type: 'arrow',
35
39
  * fromId: 'shape:source1',
36
40
  * toId: 'shape:target1',
37
41
  * props: {
38
- * strength: 0.8,
39
- * color: 'red'
42
+ * terminal: 'end',
43
+ * normalizedAnchor: { x: 0.5, y: 0.5 },
44
+ * isExact: false,
45
+ * isPrecise: true,
46
+ * snap: 'edge'
40
47
  * },
41
48
  * meta: {}
42
49
  * }
@@ -44,8 +51,12 @@ import { shapeIdValidator } from '../shapes/TLBaseShape'
44
51
  *
45
52
  * @public
46
53
  */
47
- export interface TLBaseBinding<Type extends string, Props extends object>
48
- extends BaseRecord<'binding', TLBindingId> {
54
+ export interface TLBaseBinding<Type extends string, Props extends object> {
55
+ // using real `extends BaseRecord<'binding', TLBindingId>` introduces a circularity in the types
56
+ // and for that reason those "base members" have to be declared manually here
57
+ readonly id: TLBindingId
58
+ readonly typeName: 'binding'
59
+
49
60
  /** The specific type of this binding (e.g., 'arrow', 'custom') */
50
61
  type: Type
51
62
  /** ID of the source shape in this binding relationship */
@@ -22,8 +22,9 @@ import {
22
22
  getShapePropKeysByStyle,
23
23
  rootShapeMigrations,
24
24
  } from './records/TLShape'
25
- import { TLPropsMigrations, processPropsMigrations } from './recordsWithProps'
25
+ import { RecordProps, TLPropsMigrations, processPropsMigrations } from './recordsWithProps'
26
26
  import { arrowShapeMigrations, arrowShapeProps } from './shapes/TLArrowShape'
27
+ import { TLBaseShape } from './shapes/TLBaseShape'
27
28
  import { bookmarkShapeMigrations, bookmarkShapeProps } from './shapes/TLBookmarkShape'
28
29
  import { drawShapeMigrations, drawShapeProps } from './shapes/TLDrawShape'
29
30
  import { embedShapeMigrations, embedShapeProps } from './shapes/TLEmbedShape'
@@ -147,7 +148,12 @@ export const defaultShapeSchemas = {
147
148
  note: { migrations: noteShapeMigrations, props: noteShapeProps },
148
149
  text: { migrations: textShapeMigrations, props: textShapeProps },
149
150
  video: { migrations: videoShapeMigrations, props: videoShapeProps },
150
- } satisfies { [T in TLDefaultShape['type']]: SchemaPropsInfo }
151
+ } satisfies {
152
+ [T in TLDefaultShape['type']]: {
153
+ migrations: SchemaPropsInfo['migrations']
154
+ props: RecordProps<TLBaseShape<T, Extract<TLDefaultShape, { type: T }>['props']>>
155
+ }
156
+ }
151
157
 
152
158
  /**
153
159
  * Default binding schema configurations for all built-in tldraw binding types.
package/src/index.ts CHANGED
@@ -99,6 +99,8 @@ export {
99
99
  type TLBindingId,
100
100
  type TLBindingUpdate,
101
101
  type TLDefaultBinding,
102
+ type TLGlobalBindingPropsMap,
103
+ type TLIndexedBindings,
102
104
  type TLUnknownBinding,
103
105
  } from './records/TLBinding'
104
106
  export { CameraRecordType, type TLCamera, type TLCameraId } from './records/TLCamera'
@@ -146,7 +148,11 @@ export {
146
148
  isShape,
147
149
  isShapeId,
148
150
  rootShapeMigrations,
151
+ type ExtractShapeByProps,
152
+ type TLCreateShapePartial,
149
153
  type TLDefaultShape,
154
+ type TLGlobalShapePropsMap,
155
+ type TLIndexedShapes,
150
156
  type TLParentId,
151
157
  type TLShape,
152
158
  type TLShapeId,
@@ -9,7 +9,7 @@ import { TLBaseAsset } from '../assets/TLBaseAsset'
9
9
  import { bookmarkAssetValidator, TLBookmarkAsset } from '../assets/TLBookmarkAsset'
10
10
  import { imageAssetValidator, TLImageAsset } from '../assets/TLImageAsset'
11
11
  import { TLVideoAsset, videoAssetValidator } from '../assets/TLVideoAsset'
12
- import { TLShape } from './TLShape'
12
+ import { ExtractShapeByProps } from './TLShape'
13
13
 
14
14
  /**
15
15
  * Union type representing all possible asset types in tldraw.
@@ -222,4 +222,4 @@ export type TLAssetId = RecordId<TLBaseAsset<any, any>>
222
222
  *
223
223
  * @public
224
224
  */
225
- export type TLAssetShape = Extract<TLShape, { props: { assetId: TLAssetId } }>
225
+ export type TLAssetShape = ExtractShapeByProps<{ assetId: TLAssetId }>
@@ -5,7 +5,7 @@ import {
5
5
  createRecordMigrationSequence,
6
6
  createRecordType,
7
7
  } from '@tldraw/store'
8
- import { Expand, mapObjectMapValues, uniqueId } from '@tldraw/utils'
8
+ import { mapObjectMapValues, uniqueId } from '@tldraw/utils'
9
9
  import { T } from '@tldraw/validate'
10
10
  import { TLArrowBinding } from '../bindings/TLArrowBinding'
11
11
  import { TLBaseBinding, createBindingValidator } from '../bindings/TLBaseBinding'
@@ -56,10 +56,42 @@ export type TLDefaultBinding = TLArrowBinding
56
56
  */
57
57
  export type TLUnknownBinding = TLBaseBinding<string, object>
58
58
 
59
+ /** @public */
60
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
61
+ export interface TLGlobalBindingPropsMap {}
62
+
63
+ /** @public */
64
+ // prettier-ignore
65
+ export type TLIndexedBindings = {
66
+ // We iterate over a union of augmented keys and default binding types.
67
+ // This allows us to include (or conditionally exclude or override) the default bindings in one go.
68
+ //
69
+ // In the `as` clause we are filtering out disabled bindings.
70
+ [K in keyof TLGlobalBindingPropsMap | TLDefaultBinding['type'] as K extends TLDefaultBinding['type']
71
+ ? K extends keyof TLGlobalBindingPropsMap
72
+ ? // if it extends a nullish value the user has disabled this binding type so we filter it out with never
73
+ TLGlobalBindingPropsMap[K] extends null | undefined
74
+ ? never
75
+ : K
76
+ : K
77
+ : K]: K extends TLDefaultBinding['type']
78
+ ? // if it's a default binding type we need to check if it's been overridden
79
+ K extends keyof TLGlobalBindingPropsMap
80
+ ? // if it has been overriden then use the custom binding definition
81
+ TLBaseBinding<K, TLGlobalBindingPropsMap[K]>
82
+ : // if it has not been overriden then reuse existing type aliases for better type display
83
+ Extract<TLDefaultBinding, { type: K }>
84
+ : // use the custom binding definition
85
+ TLBaseBinding<K, TLGlobalBindingPropsMap[K & keyof TLGlobalBindingPropsMap]>
86
+ }
87
+
59
88
  /**
60
- * The set of all bindings that are available in the editor, including unknown bindings.
89
+ * The set of all bindings that are available in the editor.
61
90
  * Bindings represent relationships between shapes, such as arrows connecting to other shapes.
62
91
  *
92
+ * You can use this type without a type argument to work with any binding, or pass
93
+ * a specific binding type string (e.g., `'arrow'`) to narrow down to that specific binding type.
94
+ *
63
95
  * @example
64
96
  * ```ts
65
97
  * // Check binding type and handle accordingly
@@ -73,11 +105,17 @@ export type TLUnknownBinding = TLBaseBinding<string, object>
73
105
  * break
74
106
  * }
75
107
  * }
108
+ *
109
+ * // Narrow to a specific binding type by passing the type as a generic argument
110
+ * function getArrowSourceId(binding: TLBinding<'arrow'>) {
111
+ * return binding.fromId // TypeScript knows this is a TLArrowBinding
112
+ * }
76
113
  * ```
77
114
  *
78
115
  * @public
79
116
  */
80
- export type TLBinding = TLDefaultBinding | TLUnknownBinding
117
+ export type TLBinding<K extends keyof TLIndexedBindings = keyof TLIndexedBindings> =
118
+ TLIndexedBindings[K]
81
119
 
82
120
  /**
83
121
  * Type for updating existing bindings with partial properties.
@@ -99,15 +137,17 @@ export type TLBinding = TLDefaultBinding | TLUnknownBinding
99
137
  *
100
138
  * @public
101
139
  */
102
- export type TLBindingUpdate<T extends TLBinding = TLBinding> = Expand<{
103
- id: TLBindingId
104
- type: T['type']
105
- typeName?: T['typeName']
106
- fromId?: T['fromId']
107
- toId?: T['toId']
108
- props?: Partial<T['props']>
109
- meta?: Partial<T['meta']>
110
- }>
140
+ export type TLBindingUpdate<T extends TLBinding = TLBinding> = T extends T
141
+ ? {
142
+ id: TLBindingId
143
+ type: T['type']
144
+ typeName?: T['typeName']
145
+ fromId?: T['fromId']
146
+ toId?: T['toId']
147
+ props?: Partial<T['props']>
148
+ meta?: Partial<T['meta']>
149
+ }
150
+ : never
111
151
 
112
152
  /**
113
153
  * Type for creating new bindings with required fromId and toId.
@@ -133,15 +173,17 @@ export type TLBindingUpdate<T extends TLBinding = TLBinding> = Expand<{
133
173
  *
134
174
  * @public
135
175
  */
136
- export type TLBindingCreate<T extends TLBinding = TLBinding> = Expand<{
137
- id?: TLBindingId
138
- type: T['type']
139
- typeName?: T['typeName']
140
- fromId: T['fromId']
141
- toId: T['toId']
142
- props?: Partial<T['props']>
143
- meta?: Partial<T['meta']>
144
- }>
176
+ export type TLBindingCreate<T extends TLBinding = TLBinding> = T extends T
177
+ ? {
178
+ id?: TLBindingId
179
+ type: T['type']
180
+ typeName?: T['typeName']
181
+ fromId: T['fromId']
182
+ toId: T['toId']
183
+ props?: Partial<T['props']>
184
+ meta?: Partial<T['meta']>
185
+ }
186
+ : never
145
187
 
146
188
  /**
147
189
  * Branded string type for binding record identifiers.
@@ -166,7 +208,7 @@ export type TLBindingCreate<T extends TLBinding = TLBinding> = Expand<{
166
208
  *
167
209
  * @public
168
210
  */
169
- export type TLBindingId = RecordId<TLUnknownBinding>
211
+ export type TLBindingId = RecordId<TLBinding>
170
212
 
171
213
  /**
172
214
  * Migration version identifiers for the root binding record schema.
@@ -375,7 +417,7 @@ export function createBindingPropsMigrationIds<S extends string, T extends Recor
375
417
  * @internal
376
418
  */
377
419
  export function createBindingRecordType(bindings: Record<string, SchemaPropsInfo>) {
378
- return createRecordType<TLBinding>('binding', {
420
+ return createRecordType('binding', {
379
421
  scope: 'document',
380
422
  validator: T.model(
381
423
  'binding',
@@ -1,5 +1,9 @@
1
+ import { IndexKey } from '@tldraw/utils'
1
2
  import { describe, expect, it } from 'vitest'
3
+ import { toRichText } from '../misc/TLRichText'
4
+ import { TLPageId } from './TLPage'
2
5
  import { TLRecord } from './TLRecord'
6
+ import { TLParentId, TLShapeId } from './TLShape'
3
7
 
4
8
  describe('TLRecord', () => {
5
9
  it('should support type discrimination by typeName', () => {
@@ -34,14 +38,14 @@ describe('TLRecord', () => {
34
38
 
35
39
  // Test that the discriminated union works correctly
36
40
  const shapeRecord: TLRecord = {
37
- id: 'shape:test' as any,
41
+ id: 'shape:test' as TLShapeId,
38
42
  typeName: 'shape',
39
43
  type: 'geo',
40
44
  x: 50,
41
45
  y: 100,
42
46
  rotation: 0,
43
- index: 'a1' as any,
44
- parentId: 'page:main' as any,
47
+ index: 'a1' as IndexKey,
48
+ parentId: 'page:main' as TLParentId,
45
49
  isLocked: false,
46
50
  opacity: 1,
47
51
  props: {
@@ -52,15 +56,23 @@ describe('TLRecord', () => {
52
56
  fill: 'none',
53
57
  dash: 'draw',
54
58
  size: 'm',
59
+ growY: 0,
60
+ url: '',
61
+ scale: 1,
62
+ font: 'draw',
63
+ align: 'middle',
64
+ verticalAlign: 'middle',
65
+ labelColor: 'black',
66
+ richText: toRichText(''),
55
67
  },
56
68
  meta: {},
57
69
  }
58
70
 
59
71
  const pageRecord: TLRecord = {
60
- id: 'page:test' as any,
72
+ id: 'page:test' as TLPageId,
61
73
  typeName: 'page',
62
74
  name: 'Test Page',
63
- index: 'a1' as any,
75
+ index: 'a1' as IndexKey,
64
76
  meta: {},
65
77
  }
66
78