snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
181
packages/tlschema/src/shapes/TLBaseShape.ts
Normal file
181
packages/tlschema/src/shapes/TLBaseShape.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { IndexKey, JsonObject } from '@tldraw/utils'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { idValidator } from '../misc/id-validator'
|
||||
import { TLOpacityType, opacityValidator } from '../misc/TLOpacity'
|
||||
import { TLParentId, TLShapeId } from '../records/TLShape'
|
||||
|
||||
/**
|
||||
* Base interface for all shapes in tldraw.
|
||||
*
|
||||
* This interface defines the common properties that all shapes share, regardless of their
|
||||
* specific type. Every default shape extends this base with additional type-specific properties.
|
||||
*
|
||||
* Custom shapes should be defined by augmenting the TLGlobalShapePropsMap type and getting the shape type from the TLShape type.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Define a default shape type
|
||||
* interface TLArrowShape extends TLBaseShape<'arrow', {
|
||||
* kind: TLArrowShapeKind
|
||||
* labelColor: TLDefaultColorStyle
|
||||
* color: TLDefaultColorStyle
|
||||
* fill: TLDefaultFillStyle
|
||||
* dash: TLDefaultDashStyle
|
||||
* size: TLDefaultSizeStyle
|
||||
* arrowheadStart: TLArrowShapeArrowheadStyle
|
||||
* arrowheadEnd: TLArrowShapeArrowheadStyle
|
||||
* font: TLDefaultFontStyle
|
||||
* start: VecModel
|
||||
* end: VecModel
|
||||
* bend: number
|
||||
* richText: TLRichText
|
||||
* labelPosition: number
|
||||
* scale: number
|
||||
* elbowMidPoint: number
|
||||
* }> {}
|
||||
*
|
||||
* // Create a shape instance
|
||||
* const arrowShape: TLArrowShape = {
|
||||
* id: 'shape:abc123',
|
||||
* typeName: 'shape',
|
||||
* type: 'arrow',
|
||||
* x: 100,
|
||||
* y: 200,
|
||||
* rotation: 0,
|
||||
* index: 'a1',
|
||||
* parentId: 'page:main',
|
||||
* isLocked: false,
|
||||
* opacity: 1,
|
||||
* props: {
|
||||
* kind: 'arc',
|
||||
* start: { x: 0, y: 0 },
|
||||
* end: { x: 100, y: 100 },
|
||||
* // ... other props
|
||||
* },
|
||||
* meta: {}
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TLBaseShape<Type extends string, Props extends object> {
|
||||
// using real `extends BaseRecord<'shape', TLShapeId>` introduces a circularity in the types
|
||||
// and for that reason those "base members" have to be declared manually here
|
||||
readonly id: TLShapeId
|
||||
readonly typeName: 'shape'
|
||||
|
||||
type: Type
|
||||
x: number
|
||||
y: number
|
||||
rotation: number
|
||||
index: IndexKey
|
||||
parentId: TLParentId
|
||||
isLocked: boolean
|
||||
opacity: TLOpacityType
|
||||
props: Props
|
||||
meta: JsonObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator for parent IDs, ensuring they follow the correct format.
|
||||
*
|
||||
* Parent IDs must start with either "page:" (for shapes directly on a page)
|
||||
* or "shape:" (for shapes inside other shapes like frames or groups).
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Valid parent IDs
|
||||
* const pageParent = parentIdValidator.validate('page:main') // ✓
|
||||
* const shapeParent = parentIdValidator.validate('shape:frame1') // ✓
|
||||
*
|
||||
* // Invalid parent ID (throws error)
|
||||
* const invalid = parentIdValidator.validate('invalid:123') // ✗
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const parentIdValidator = T.string.refine((id) => {
|
||||
if (!id.startsWith('page:') && !id.startsWith('shape:')) {
|
||||
throw new Error('Parent ID must start with "page:" or "shape:"')
|
||||
}
|
||||
return id as TLParentId
|
||||
})
|
||||
|
||||
/**
|
||||
* Validator for shape IDs, ensuring they follow the "shape:" format.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const validId = shapeIdValidator.validate('shape:abc123') // ✓
|
||||
* const invalidId = shapeIdValidator.validate('page:abc123') // ✗ throws error
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const shapeIdValidator = idValidator<TLShapeId>('shape')
|
||||
|
||||
/**
|
||||
* Creates a validator for a specific shape type.
|
||||
*
|
||||
* This function generates a complete validator that can validate shape records
|
||||
* of the specified type, including both the base shape properties and any
|
||||
* custom properties and metadata specific to that shape type.
|
||||
*
|
||||
* @param type - The string literal type for this shape (e.g., 'geo', 'arrow')
|
||||
* @param props - Optional validator configuration for shape-specific properties
|
||||
* @param meta - Optional validator configuration for shape-specific metadata
|
||||
* @returns A validator that can validate complete shape records of the specified type
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Create a validator for a custom shape type
|
||||
* const customShapeValidator = createShapeValidator('custom', {
|
||||
* width: T.number,
|
||||
* height: T.number,
|
||||
* color: T.string
|
||||
* })
|
||||
*
|
||||
* // Use the validator to validate shape data
|
||||
* const shapeData = {
|
||||
* id: 'shape:abc123',
|
||||
* typeName: 'shape',
|
||||
* type: 'custom',
|
||||
* x: 100,
|
||||
* y: 200,
|
||||
* // ... other base properties
|
||||
* props: {
|
||||
* width: 150,
|
||||
* height: 100,
|
||||
* color: 'red'
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const validatedShape = customShapeValidator.validate(shapeData)
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createShapeValidator<
|
||||
Type extends string,
|
||||
Props extends JsonObject,
|
||||
Meta extends JsonObject,
|
||||
>(
|
||||
type: Type,
|
||||
props?: { [K in keyof Props]: T.Validatable<Props[K]> },
|
||||
meta?: { [K in keyof Meta]: T.Validatable<Meta[K]> }
|
||||
) {
|
||||
return T.object<TLBaseShape<Type, Props>>({
|
||||
id: shapeIdValidator,
|
||||
typeName: T.literal('shape'),
|
||||
x: T.number,
|
||||
y: T.number,
|
||||
rotation: T.number,
|
||||
index: T.indexKey,
|
||||
parentId: parentIdValidator,
|
||||
type: T.literal(type),
|
||||
isLocked: T.boolean,
|
||||
opacity: opacityValidator,
|
||||
props: props ? T.object(props) : (T.jsonValue as any),
|
||||
meta: meta ? T.object(meta) : (T.jsonValue as any),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user