@tldraw/tlschema 5.3.0-canary.fa3355c81e86 → 5.3.0-internal.1640468db8fd
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.
- package/dist-cjs/TLStore.js +7 -0
- package/dist-cjs/TLStore.js.map +2 -2
- package/dist-cjs/index.d.ts +196 -1
- package/dist-cjs/index.js +11 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLCursor.js +1 -0
- package/dist-cjs/misc/TLCursor.js.map +2 -2
- package/dist-cjs/records/TLComment.js +187 -0
- package/dist-cjs/records/TLComment.js.map +7 -0
- package/dist-cjs/shapes/TLGeoShape.js +15 -1
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-esm/TLStore.mjs +7 -0
- package/dist-esm/TLStore.mjs.map +2 -2
- package/dist-esm/index.d.mts +196 -1
- package/dist-esm/index.mjs +21 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLCursor.mjs +1 -0
- package/dist-esm/misc/TLCursor.mjs.map +2 -2
- package/dist-esm/records/TLComment.mjs +167 -0
- package/dist-esm/records/TLComment.mjs.map +7 -0
- package/dist-esm/shapes/TLGeoShape.mjs +15 -1
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/package.json +5 -5
- package/src/TLStore.ts +16 -0
- package/src/index.ts +16 -0
- package/src/migrations.test.ts +12 -0
- package/src/misc/TLCursor.ts +1 -0
- package/src/records/TLComment.test.ts +182 -0
- package/src/records/TLComment.ts +330 -0
- package/src/shapes/TLGeoShape.ts +21 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/shapes/TLGeoShape.ts"],
|
|
4
|
-
"sourcesContent": ["import { T } from '@tldraw/validate'\nimport { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText'\nimport { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape'\nimport { RecordProps } from '../recordsWithProps'\nimport { StyleProp } from '../styles/StyleProp'\nimport {\n\tDefaultColorStyle,\n\tDefaultLabelColorStyle,\n\tTLDefaultColorStyle,\n} from '../styles/TLColorStyle'\nimport { DefaultDashStyle, TLDefaultDashStyle } from '../styles/TLDashStyle'\nimport { DefaultFillStyle, TLDefaultFillStyle } from '../styles/TLFillStyle'\nimport { DefaultFontStyle, TLDefaultFontStyle } from '../styles/TLFontStyle'\nimport {\n\tDefaultHorizontalAlignStyle,\n\tTLDefaultHorizontalAlignStyle,\n} from '../styles/TLHorizontalAlignStyle'\nimport { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'\nimport {\n\tDefaultVerticalAlignStyle,\n\tTLDefaultVerticalAlignStyle,\n} from '../styles/TLVerticalAlignStyle'\nimport { TLBaseShape } from './TLBaseShape'\n\n/**\n * Style property defining the geometric shape type for geo shapes.\n * Provides a variety of built-in geometric forms including basic shapes,\n * polygons, arrows, and special shapes.\n *\n * @public\n * @example\n * ```ts\n * // Use in shape props\n * const props = {\n * geo: 'rectangle', // or 'ellipse', 'triangle', etc.\n * // other properties...\n * }\n * ```\n */\nexport const GeoShapeGeoStyle = StyleProp.defineEnum('tldraw:geo', {\n\tdefaultValue: 'rectangle',\n\tvalues: [\n\t\t'cloud',\n\t\t'rectangle',\n\t\t'ellipse',\n\t\t'triangle',\n\t\t'diamond',\n\t\t'pentagon',\n\t\t'hexagon',\n\t\t'octagon',\n\t\t'star',\n\t\t'rhombus',\n\t\t'rhombus-2',\n\t\t'oval',\n\t\t'trapezoid',\n\t\t'arrow-right',\n\t\t'arrow-left',\n\t\t'arrow-up',\n\t\t'arrow-down',\n\t\t'x-box',\n\t\t'check-box',\n\t\t'heart',\n\t],\n})\n\n/**\n * Type representing valid geometric shape styles for geo shapes.\n *\n * @public\n */\nexport type TLGeoShapeGeoStyle = T.TypeOf<typeof GeoShapeGeoStyle>\n\n/**\n * Properties for the geo shape, which renders various geometric forms with styling and text.\n *\n * @public\n */\nexport interface TLGeoShapeProps {\n\t/** Geometric shape type (rectangle, ellipse, triangle, etc.) */\n\tgeo: TLGeoShapeGeoStyle\n\t/** Dash pattern style for the shape outline */\n\tdash: TLDefaultDashStyle\n\t/** URL link associated with the shape */\n\turl: string\n\t/** Width of the shape in pixels */\n\tw: number\n\t/** Height of the shape in pixels */\n\th: number\n\t/** Additional vertical growth for text content */\n\tgrowY: number\n\t/** Scale factor applied to the shape */\n\tscale: number\n\n\t/** Color style for text label */\n\tlabelColor: TLDefaultColorStyle\n\t/** Color style for the shape outline */\n\tcolor: TLDefaultColorStyle\n\t/** Fill style for the shape interior */\n\tfill: TLDefaultFillStyle\n\t/** Size/thickness style for outline and text */\n\tsize: TLDefaultSizeStyle\n\t/** Font style for text content */\n\tfont: TLDefaultFontStyle\n\t/** Horizontal alignment for text content */\n\talign: TLDefaultHorizontalAlignStyle\n\t/** Vertical alignment for text content */\n\tverticalAlign: TLDefaultVerticalAlignStyle\n\t/** Rich text content displayed within the shape */\n\trichText: TLRichText\n}\n\n/**\n * A geo shape represents geometric forms like rectangles, ellipses, triangles, and other\n * predefined shapes. Geo shapes support styling, text content, and can act as containers.\n *\n * @public\n * @example\n * ```ts\n * const geoShape: TLGeoShape = {\n * id: createShapeId(),\n * typeName: 'shape',\n * type: 'geo',\n * x: 100,\n * y: 100,\n * rotation: 0,\n * index: 'a1',\n * parentId: 'page:page1',\n * isLocked: false,\n * opacity: 1,\n * props: {\n * geo: 'rectangle',\n * w: 200,\n * h: 100,\n * color: 'black',\n * fill: 'solid',\n * dash: 'solid',\n * size: 'm',\n * font: 'draw',\n * align: 'middle',\n * verticalAlign: 'middle',\n * richText: toRichText('Hello World'),\n * labelColor: 'black',\n * url: '',\n * growY: 0,\n * scale: 1\n * },\n * meta: {}\n * }\n * ```\n */\nexport type TLGeoShape = TLBaseShape<'geo', TLGeoShapeProps>\n\n/**\n * Validation schema for geo shape properties.\n *\n * @public\n * @example\n * ```ts\n * // Validate geo shape properties\n * const isValidGeo = geoShapeProps.geo.isValid('rectangle')\n * const isValidSize = geoShapeProps.w.isValid(100)\n * const isValidText = geoShapeProps.richText.isValid(toRichText('Hello'))\n * ```\n */\nexport const geoShapeProps: RecordProps<TLGeoShape> = {\n\tgeo: GeoShapeGeoStyle,\n\tdash: DefaultDashStyle,\n\turl: T.linkUrl,\n\tw: T.nonZeroNumber,\n\th: T.nonZeroNumber,\n\tgrowY: T.positiveNumber,\n\tscale: T.nonZeroNumber,\n\n\t// Text properties\n\tlabelColor: DefaultLabelColorStyle,\n\tcolor: DefaultColorStyle,\n\tfill: DefaultFillStyle,\n\tsize: DefaultSizeStyle,\n\tfont: DefaultFontStyle,\n\talign: DefaultHorizontalAlignStyle,\n\tverticalAlign: DefaultVerticalAlignStyle,\n\trichText: richTextValidator,\n}\n\nconst geoShapeVersions = createShapePropsMigrationIds('geo', {\n\tAddUrlProp: 1,\n\tAddLabelColor: 2,\n\tRemoveJustify: 3,\n\tAddCheckBox: 4,\n\tAddVerticalAlign: 5,\n\tMigrateLegacyAlign: 6,\n\tAddCloud: 7,\n\tMakeUrlsValid: 8,\n\tAddScale: 9,\n\tAddRichText: 10,\n\tAddRichTextAttrs: 11,\n})\n\n/**\n * Version identifiers for geo shape migrations.\n *\n * @public\n */\nexport { geoShapeVersions }\n\n/**\n * Migration sequence for geo shape properties across different schema versions.\n * Handles evolution of geo shapes including URL support, label colors, alignment changes,\n * the transition from plain text to rich text, and support for attrs property on richText.\n *\n * @public\n */\nexport const geoShapeMigrations = createShapePropsMigrationSequence({\n\tsequence: [\n\t\t{\n\t\t\tid: geoShapeVersions.AddUrlProp,\n\t\t\tup: (props) => {\n\t\t\t\tprops.url = ''\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddLabelColor,\n\t\t\tup: (props) => {\n\t\t\t\tprops.labelColor = 'black'\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.RemoveJustify,\n\t\t\tup: (props) => {\n\t\t\t\tif (props.align === 'justify') {\n\t\t\t\t\tprops.align = 'start'\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddCheckBox,\n\t\t\tup: (_props) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddVerticalAlign,\n\t\t\tup: (props) => {\n\t\t\t\tprops.verticalAlign = 'middle'\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.MigrateLegacyAlign,\n\t\t\tup: (props) => {\n\t\t\t\tlet newAlign: TLDefaultHorizontalAlignStyle\n\t\t\t\tswitch (props.align) {\n\t\t\t\t\tcase 'start':\n\t\t\t\t\t\tnewAlign = 'start-legacy'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'end':\n\t\t\t\t\t\tnewAlign = 'end-legacy'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tnewAlign = 'middle-legacy'\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tprops.align = newAlign\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddCloud,\n\t\t\tup: (_props) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.MakeUrlsValid,\n\t\t\tup: (props) => {\n\t\t\t\tif (!T.linkUrl.isValid(props.url)) {\n\t\t\t\t\tprops.url = ''\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: (_props) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddScale,\n\t\t\tup: (props) => {\n\t\t\t\tprops.scale = 1\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.scale\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddRichText,\n\t\t\tup: (props) => {\n\t\t\t\tprops.richText = toRichText(props.text)\n\t\t\t\tdelete props.text\n\t\t\t},\n\t\t\t// N.B. Explicitly no down state so that we force clients to update.\n\t\t\t// down: (props) => {\n\t\t\t// \tdelete props.richText\n\t\t\t// },\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddRichTextAttrs,\n\t\t\tup: (_props) => {\n\t\t\t\t// noop - attrs is optional so old records are valid\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\t// Remove attrs from richText when migrating down\n\t\t\t\tif (props.richText && 'attrs' in props.richText) {\n\t\t\t\t\tdelete props.richText.attrs\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t],\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAqB,mBAAmB,kBAAkB;AAC1D,SAAS,8BAA8B,yCAAyC;AAEhF,SAAS,iBAAiB;AAC1B;AAAA,EACC;AAAA,EACA;AAAA,OAEM;AACP,SAAS,wBAA4C;AACrD,SAAS,wBAA4C;AACrD,SAAS,wBAA4C;AACrD;AAAA,EACC;AAAA,OAEM;AACP,SAAS,wBAA4C;AACrD;AAAA,EACC;AAAA,OAEM;AAkBA,MAAM,mBAAmB,UAAU,WAAW,cAAc;AAAA,EAClE,cAAc;AAAA,EACd,QAAQ;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD,CAAC;
|
|
4
|
+
"sourcesContent": ["import { T } from '@tldraw/validate'\nimport { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText'\nimport { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape'\nimport { RecordProps } from '../recordsWithProps'\nimport { StyleProp } from '../styles/StyleProp'\nimport {\n\tDefaultColorStyle,\n\tDefaultLabelColorStyle,\n\tTLDefaultColorStyle,\n} from '../styles/TLColorStyle'\nimport { DefaultDashStyle, TLDefaultDashStyle } from '../styles/TLDashStyle'\nimport { DefaultFillStyle, TLDefaultFillStyle } from '../styles/TLFillStyle'\nimport { DefaultFontStyle, TLDefaultFontStyle } from '../styles/TLFontStyle'\nimport {\n\tDefaultHorizontalAlignStyle,\n\tTLDefaultHorizontalAlignStyle,\n} from '../styles/TLHorizontalAlignStyle'\nimport { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'\nimport {\n\tDefaultVerticalAlignStyle,\n\tTLDefaultVerticalAlignStyle,\n} from '../styles/TLVerticalAlignStyle'\nimport { TLBaseShape } from './TLBaseShape'\n\n/**\n * Style property defining the geometric shape type for geo shapes.\n * Provides a variety of built-in geometric forms including basic shapes,\n * polygons, arrows, and special shapes.\n *\n * @public\n * @example\n * ```ts\n * // Use in shape props\n * const props = {\n * geo: 'rectangle', // or 'ellipse', 'triangle', etc.\n * // other properties...\n * }\n * ```\n */\nexport const GeoShapeGeoStyle = StyleProp.defineEnum('tldraw:geo', {\n\tdefaultValue: 'rectangle',\n\tvalues: [\n\t\t'cloud',\n\t\t'rectangle',\n\t\t'ellipse',\n\t\t'triangle',\n\t\t'diamond',\n\t\t'pentagon',\n\t\t'hexagon',\n\t\t'octagon',\n\t\t'star',\n\t\t'rhombus',\n\t\t'rhombus-2',\n\t\t'oval',\n\t\t'trapezoid',\n\t\t'arrow-right',\n\t\t'arrow-left',\n\t\t'arrow-up',\n\t\t'arrow-down',\n\t\t'x-box',\n\t\t'check-box',\n\t\t'heart',\n\t],\n})\n\n/**\n * Type representing valid geometric shape styles for geo shapes.\n *\n * @public\n */\nexport type TLGeoShapeGeoStyle = T.TypeOf<typeof GeoShapeGeoStyle>\n\n/**\n * Properties for the geo shape, which renders various geometric forms with styling and text.\n *\n * @public\n */\nexport interface TLGeoShapeProps {\n\t/** Geometric shape type (rectangle, ellipse, triangle, etc.) */\n\tgeo: TLGeoShapeGeoStyle\n\t/** Dash pattern style for the shape outline */\n\tdash: TLDefaultDashStyle\n\t/** URL link associated with the shape */\n\turl: string\n\t/** Width of the shape in pixels */\n\tw: number\n\t/** Height of the shape in pixels */\n\th: number\n\t/** Additional vertical growth for text content */\n\tgrowY: number\n\t/** Scale factor applied to the shape */\n\tscale: number\n\t/** Whether to mirror the shape horizontally */\n\tflipX: boolean\n\t/** Whether to mirror the shape vertically */\n\tflipY: boolean\n\n\t/** Color style for text label */\n\tlabelColor: TLDefaultColorStyle\n\t/** Color style for the shape outline */\n\tcolor: TLDefaultColorStyle\n\t/** Fill style for the shape interior */\n\tfill: TLDefaultFillStyle\n\t/** Size/thickness style for outline and text */\n\tsize: TLDefaultSizeStyle\n\t/** Font style for text content */\n\tfont: TLDefaultFontStyle\n\t/** Horizontal alignment for text content */\n\talign: TLDefaultHorizontalAlignStyle\n\t/** Vertical alignment for text content */\n\tverticalAlign: TLDefaultVerticalAlignStyle\n\t/** Rich text content displayed within the shape */\n\trichText: TLRichText\n}\n\n/**\n * A geo shape represents geometric forms like rectangles, ellipses, triangles, and other\n * predefined shapes. Geo shapes support styling, text content, and can act as containers.\n *\n * @public\n * @example\n * ```ts\n * const geoShape: TLGeoShape = {\n * id: createShapeId(),\n * typeName: 'shape',\n * type: 'geo',\n * x: 100,\n * y: 100,\n * rotation: 0,\n * index: 'a1',\n * parentId: 'page:page1',\n * isLocked: false,\n * opacity: 1,\n * props: {\n * geo: 'rectangle',\n * w: 200,\n * h: 100,\n * color: 'black',\n * fill: 'solid',\n * dash: 'solid',\n * size: 'm',\n * font: 'draw',\n * align: 'middle',\n * verticalAlign: 'middle',\n * richText: toRichText('Hello World'),\n * labelColor: 'black',\n * url: '',\n * growY: 0,\n * scale: 1,\n * flipX: false,\n * flipY: false\n * },\n * meta: {}\n * }\n * ```\n */\nexport type TLGeoShape = TLBaseShape<'geo', TLGeoShapeProps>\n\n/**\n * Validation schema for geo shape properties.\n *\n * @public\n * @example\n * ```ts\n * // Validate geo shape properties\n * const isValidGeo = geoShapeProps.geo.isValid('rectangle')\n * const isValidSize = geoShapeProps.w.isValid(100)\n * const isValidText = geoShapeProps.richText.isValid(toRichText('Hello'))\n * ```\n */\nexport const geoShapeProps: RecordProps<TLGeoShape> = {\n\tgeo: GeoShapeGeoStyle,\n\tdash: DefaultDashStyle,\n\turl: T.linkUrl,\n\tw: T.nonZeroNumber,\n\th: T.nonZeroNumber,\n\tgrowY: T.positiveNumber,\n\tscale: T.nonZeroNumber,\n\tflipX: T.boolean,\n\tflipY: T.boolean,\n\n\t// Text properties\n\tlabelColor: DefaultLabelColorStyle,\n\tcolor: DefaultColorStyle,\n\tfill: DefaultFillStyle,\n\tsize: DefaultSizeStyle,\n\tfont: DefaultFontStyle,\n\talign: DefaultHorizontalAlignStyle,\n\tverticalAlign: DefaultVerticalAlignStyle,\n\trichText: richTextValidator,\n}\n\nconst geoShapeVersions = createShapePropsMigrationIds('geo', {\n\tAddUrlProp: 1,\n\tAddLabelColor: 2,\n\tRemoveJustify: 3,\n\tAddCheckBox: 4,\n\tAddVerticalAlign: 5,\n\tMigrateLegacyAlign: 6,\n\tAddCloud: 7,\n\tMakeUrlsValid: 8,\n\tAddScale: 9,\n\tAddRichText: 10,\n\tAddRichTextAttrs: 11,\n\tAddFlipProps: 12,\n})\n\n/**\n * Version identifiers for geo shape migrations.\n *\n * @public\n */\nexport { geoShapeVersions }\n\n/**\n * Migration sequence for geo shape properties across different schema versions.\n * Handles evolution of geo shapes including URL support, label colors, alignment changes,\n * the transition from plain text to rich text, and support for attrs property on richText.\n *\n * @public\n */\nexport const geoShapeMigrations = createShapePropsMigrationSequence({\n\tsequence: [\n\t\t{\n\t\t\tid: geoShapeVersions.AddUrlProp,\n\t\t\tup: (props) => {\n\t\t\t\tprops.url = ''\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddLabelColor,\n\t\t\tup: (props) => {\n\t\t\t\tprops.labelColor = 'black'\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.RemoveJustify,\n\t\t\tup: (props) => {\n\t\t\t\tif (props.align === 'justify') {\n\t\t\t\t\tprops.align = 'start'\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddCheckBox,\n\t\t\tup: (_props) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddVerticalAlign,\n\t\t\tup: (props) => {\n\t\t\t\tprops.verticalAlign = 'middle'\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.MigrateLegacyAlign,\n\t\t\tup: (props) => {\n\t\t\t\tlet newAlign: TLDefaultHorizontalAlignStyle\n\t\t\t\tswitch (props.align) {\n\t\t\t\t\tcase 'start':\n\t\t\t\t\t\tnewAlign = 'start-legacy'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'end':\n\t\t\t\t\t\tnewAlign = 'end-legacy'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tnewAlign = 'middle-legacy'\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tprops.align = newAlign\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddCloud,\n\t\t\tup: (_props) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.MakeUrlsValid,\n\t\t\tup: (props) => {\n\t\t\t\tif (!T.linkUrl.isValid(props.url)) {\n\t\t\t\t\tprops.url = ''\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: (_props) => {\n\t\t\t\t// noop\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddScale,\n\t\t\tup: (props) => {\n\t\t\t\tprops.scale = 1\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.scale\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddRichText,\n\t\t\tup: (props) => {\n\t\t\t\tprops.richText = toRichText(props.text)\n\t\t\t\tdelete props.text\n\t\t\t},\n\t\t\t// N.B. Explicitly no down state so that we force clients to update.\n\t\t\t// down: (props) => {\n\t\t\t// \tdelete props.richText\n\t\t\t// },\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddRichTextAttrs,\n\t\t\tup: (_props) => {\n\t\t\t\t// noop - attrs is optional so old records are valid\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\t// Remove attrs from richText when migrating down\n\t\t\t\tif (props.richText && 'attrs' in props.richText) {\n\t\t\t\t\tdelete props.richText.attrs\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: geoShapeVersions.AddFlipProps,\n\t\t\tup: (props) => {\n\t\t\t\tprops.flipX = false\n\t\t\t\tprops.flipY = false\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.flipX\n\t\t\t\tdelete props.flipY\n\t\t\t},\n\t\t},\n\t],\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
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": "5.3.0-
|
|
4
|
+
"version": "5.3.0-internal.1640468db8fd",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"vitest": "^4.1.7"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@tldraw/state": "5.3.0-
|
|
55
|
-
"@tldraw/store": "5.3.0-
|
|
56
|
-
"@tldraw/utils": "5.3.0-
|
|
57
|
-
"@tldraw/validate": "5.3.0-
|
|
54
|
+
"@tldraw/state": "5.3.0-internal.1640468db8fd",
|
|
55
|
+
"@tldraw/store": "5.3.0-internal.1640468db8fd",
|
|
56
|
+
"@tldraw/utils": "5.3.0-internal.1640468db8fd",
|
|
57
|
+
"@tldraw/validate": "5.3.0-internal.1640468db8fd"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"react": "^18.2.0 || ^19.2.1",
|
package/src/TLStore.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { PageRecordType, TLPageId } from './records/TLPage'
|
|
|
15
15
|
import { InstancePageStateRecordType, TLInstancePageStateId } from './records/TLPageState'
|
|
16
16
|
import { PointerRecordType, TLPOINTER_ID } from './records/TLPointer'
|
|
17
17
|
import { TLRecord } from './records/TLRecord'
|
|
18
|
+
import { isShapeId } from './records/TLShape'
|
|
18
19
|
import { TLUser } from './records/TLUser'
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -462,6 +463,7 @@ function getDefaultPages() {
|
|
|
462
463
|
export function createIntegrityChecker(store: Store<TLRecord, TLStoreProps>): () => void {
|
|
463
464
|
const $pageIds = store.query.ids('page')
|
|
464
465
|
const $pageStates = store.query.records('instance_page_state')
|
|
466
|
+
const $shapes = store.query.records('shape')
|
|
465
467
|
|
|
466
468
|
const ensureStoreIsUsable = (): void => {
|
|
467
469
|
// make sure we have exactly one document
|
|
@@ -531,6 +533,20 @@ export function createIntegrityChecker(store: Store<TLRecord, TLStoreProps>): ()
|
|
|
531
533
|
store.put([...missingCameraIds].map((id) => CameraRecordType.create({ id })))
|
|
532
534
|
}
|
|
533
535
|
|
|
536
|
+
// remove any shape whose parent is another shape that no longer exists. This can happen
|
|
537
|
+
// when a change is undone that references a shape (e.g. a frame) which has since been
|
|
538
|
+
// deleted by another collaborator: the undo would otherwise leave a shape parented to a
|
|
539
|
+
// record that doesn't exist. Such a shape has no valid place in the hierarchy, so we
|
|
540
|
+
// discard it. Removing a shape can orphan its own children, which the re-run then handles.
|
|
541
|
+
const orphanedShapeIds = $shapes
|
|
542
|
+
.get()
|
|
543
|
+
.filter((shape) => isShapeId(shape.parentId) && !store.has(shape.parentId))
|
|
544
|
+
.map((shape) => shape.id)
|
|
545
|
+
if (orphanedShapeIds.length > 0) {
|
|
546
|
+
store.remove(orphanedShapeIds)
|
|
547
|
+
return ensureStoreIsUsable()
|
|
548
|
+
}
|
|
549
|
+
|
|
534
550
|
const pageStates = $pageStates.get()
|
|
535
551
|
for (const pageState of pageStates) {
|
|
536
552
|
if (!pageIds.has(pageState.pageId)) {
|
package/src/index.ts
CHANGED
|
@@ -116,6 +116,22 @@ export {
|
|
|
116
116
|
type TLUnknownBinding,
|
|
117
117
|
} from './records/TLBinding'
|
|
118
118
|
export { CameraRecordType, type TLCamera, type TLCameraId } from './records/TLCamera'
|
|
119
|
+
export {
|
|
120
|
+
commentRecordConfig,
|
|
121
|
+
commentSchemaRecords,
|
|
122
|
+
commentThreadRecordConfig,
|
|
123
|
+
createComment,
|
|
124
|
+
createCommentId,
|
|
125
|
+
createCommentThread,
|
|
126
|
+
createCommentThreadId,
|
|
127
|
+
isCommentId,
|
|
128
|
+
isCommentThreadId,
|
|
129
|
+
type TLComment,
|
|
130
|
+
type TLCommentAnchor,
|
|
131
|
+
type TLCommentId,
|
|
132
|
+
type TLCommentThread,
|
|
133
|
+
type TLCommentThreadId,
|
|
134
|
+
} from './records/TLComment'
|
|
119
135
|
export {
|
|
120
136
|
createCustomRecordId,
|
|
121
137
|
createCustomRecordMigrationIds,
|
package/src/migrations.test.ts
CHANGED
|
@@ -2152,6 +2152,18 @@ describe('Add flipX, flipY to image shape', () => {
|
|
|
2152
2152
|
})
|
|
2153
2153
|
})
|
|
2154
2154
|
|
|
2155
|
+
describe('Add flipX, flipY to geo shape', () => {
|
|
2156
|
+
const { up, down } = getTestMigration(geoShapeVersions.AddFlipProps)
|
|
2157
|
+
|
|
2158
|
+
test('up works as expected', () => {
|
|
2159
|
+
expect(up({ props: {} })).toEqual({ props: { flipX: false, flipY: false } })
|
|
2160
|
+
})
|
|
2161
|
+
|
|
2162
|
+
test('down works as expected', () => {
|
|
2163
|
+
expect(down({ props: { flipX: false, flipY: false } })).toEqual({ props: {} })
|
|
2164
|
+
})
|
|
2165
|
+
})
|
|
2166
|
+
|
|
2155
2167
|
describe('Add alt text to image shape', () => {
|
|
2156
2168
|
const { up, down } = getTestMigration(imageShapeVersions.AddAltText)
|
|
2157
2169
|
|
package/src/misc/TLCursor.ts
CHANGED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createTLSchema } from '../createTLSchema'
|
|
3
|
+
import { toRichText } from '../misc/TLRichText'
|
|
4
|
+
import {
|
|
5
|
+
commentRecordConfig,
|
|
6
|
+
commentSchemaRecords,
|
|
7
|
+
commentThreadRecordConfig,
|
|
8
|
+
createComment,
|
|
9
|
+
createCommentId,
|
|
10
|
+
createCommentThread,
|
|
11
|
+
createCommentThreadId,
|
|
12
|
+
isCommentId,
|
|
13
|
+
isCommentThreadId,
|
|
14
|
+
TLCommentAnchor,
|
|
15
|
+
} from './TLComment'
|
|
16
|
+
import { TLPageId } from './TLPage'
|
|
17
|
+
import { TLShapeId } from './TLShape'
|
|
18
|
+
|
|
19
|
+
const pageId = 'page:page1' as TLPageId
|
|
20
|
+
const shapeId = 'shape:box1' as TLShapeId
|
|
21
|
+
|
|
22
|
+
const anchors: TLCommentAnchor[] = [
|
|
23
|
+
{ type: 'shape', shapeId, x: 1, y: 0, isPrecise: false },
|
|
24
|
+
{ type: 'point', x: 100, y: 200 },
|
|
25
|
+
{ type: 'region', x: 0, y: 0, w: 300, h: 150 },
|
|
26
|
+
{ type: 'page' },
|
|
27
|
+
{ type: 'text-range', shapeId, from: 3, to: 12 },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
describe('TLCommentThread', () => {
|
|
31
|
+
it.each(anchors)('creates a valid thread anchored to %j', (anchor) => {
|
|
32
|
+
const thread = createCommentThread({ pageId, anchor, createdBy: 'user1', now: 1000 })
|
|
33
|
+
expect(commentThreadRecordConfig.validator.validate(thread)).toEqual(thread)
|
|
34
|
+
expect(thread).toMatchObject({
|
|
35
|
+
typeName: 'comment-thread',
|
|
36
|
+
pageId,
|
|
37
|
+
anchor,
|
|
38
|
+
createdAt: 1000,
|
|
39
|
+
resolved: null,
|
|
40
|
+
meta: {},
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('validates resolution state', () => {
|
|
45
|
+
const thread = createCommentThread({
|
|
46
|
+
pageId,
|
|
47
|
+
anchor: { type: 'page' },
|
|
48
|
+
createdBy: 'user1',
|
|
49
|
+
now: 1000,
|
|
50
|
+
})
|
|
51
|
+
const resolved = { ...thread, resolved: { at: 2000, by: 'user2' } }
|
|
52
|
+
expect(commentThreadRecordConfig.validator.validate(resolved)).toEqual(resolved)
|
|
53
|
+
expect(() =>
|
|
54
|
+
commentThreadRecordConfig.validator.validate({ ...thread, resolved: { at: 2000 } })
|
|
55
|
+
).toThrow()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('rejects unknown anchor kinds and malformed anchors', () => {
|
|
59
|
+
const thread = createCommentThread({
|
|
60
|
+
pageId,
|
|
61
|
+
anchor: { type: 'shape', shapeId, x: 1, y: 0, isPrecise: false },
|
|
62
|
+
createdBy: 'user1',
|
|
63
|
+
now: 1000,
|
|
64
|
+
})
|
|
65
|
+
expect(() =>
|
|
66
|
+
commentThreadRecordConfig.validator.validate({
|
|
67
|
+
...thread,
|
|
68
|
+
anchor: { type: 'volume', level: 11 },
|
|
69
|
+
})
|
|
70
|
+
).toThrow()
|
|
71
|
+
expect(() =>
|
|
72
|
+
commentThreadRecordConfig.validator.validate({
|
|
73
|
+
...thread,
|
|
74
|
+
// shape anchors must reference a shape id
|
|
75
|
+
anchor: { type: 'shape', shapeId: 'page:nope', x: 1, y: 0, isPrecise: false },
|
|
76
|
+
})
|
|
77
|
+
).toThrow()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('TLComment', () => {
|
|
82
|
+
const threadId = createCommentThreadId('thread1')
|
|
83
|
+
|
|
84
|
+
it('creates a valid comment with a rich text body', () => {
|
|
85
|
+
const comment = createComment({
|
|
86
|
+
threadId,
|
|
87
|
+
pageId,
|
|
88
|
+
authorId: 'user1',
|
|
89
|
+
body: toRichText('hello'),
|
|
90
|
+
now: 1000,
|
|
91
|
+
})
|
|
92
|
+
expect(commentRecordConfig.validator.validate(comment)).toEqual(comment)
|
|
93
|
+
expect(comment).toMatchObject({
|
|
94
|
+
typeName: 'comment',
|
|
95
|
+
threadId,
|
|
96
|
+
pageId,
|
|
97
|
+
createdAt: 1000,
|
|
98
|
+
editedAt: null,
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('accepts an editedAt timestamp after edits', () => {
|
|
103
|
+
const comment = createComment({
|
|
104
|
+
threadId,
|
|
105
|
+
pageId,
|
|
106
|
+
authorId: 'user1',
|
|
107
|
+
body: toRichText('hello'),
|
|
108
|
+
now: 1000,
|
|
109
|
+
})
|
|
110
|
+
const edited = { ...comment, editedAt: 2000, body: toRichText('hello, edited') }
|
|
111
|
+
expect(commentRecordConfig.validator.validate(edited)).toEqual(edited)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('rejects a threadId that is not a comment-thread id', () => {
|
|
115
|
+
const comment = createComment({
|
|
116
|
+
threadId,
|
|
117
|
+
pageId,
|
|
118
|
+
authorId: 'user1',
|
|
119
|
+
body: toRichText('hello'),
|
|
120
|
+
now: 1000,
|
|
121
|
+
})
|
|
122
|
+
expect(() =>
|
|
123
|
+
commentRecordConfig.validator.validate({ ...comment, threadId: 'comment:not-a-thread' })
|
|
124
|
+
).toThrow()
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('schema registration', () => {
|
|
129
|
+
it('registers both record types via commentSchemaRecords', () => {
|
|
130
|
+
const schema = createTLSchema({ records: commentSchemaRecords })
|
|
131
|
+
// custom record types aren't part of the default TLRecord union, so index untyped
|
|
132
|
+
const types = schema.types as Record<string, { scope: string } | undefined>
|
|
133
|
+
expect(types['comment-thread']?.scope).toBe('document')
|
|
134
|
+
expect(types['comment']?.scope).toBe('document')
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('isCommentThreadId / isCommentId', () => {
|
|
139
|
+
it('isCommentThreadId accepts comment-thread ids and rejects everything else', () => {
|
|
140
|
+
expect(isCommentThreadId(createCommentThreadId())).toBe(true)
|
|
141
|
+
expect(isCommentThreadId(createCommentId())).toBe(false)
|
|
142
|
+
expect(isCommentThreadId('shape:box1')).toBe(false)
|
|
143
|
+
expect(isCommentThreadId('not-an-id')).toBe(false)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('isCommentId accepts comment ids and rejects everything else, including comment-thread ids', () => {
|
|
147
|
+
expect(isCommentId(createCommentId())).toBe(true)
|
|
148
|
+
expect(isCommentId(createCommentThreadId())).toBe(false)
|
|
149
|
+
expect(isCommentId('shape:box1')).toBe(false)
|
|
150
|
+
expect(isCommentId('not-an-id')).toBe(false)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe('comment-thread migrations', () => {
|
|
155
|
+
const migration = (commentThreadRecordConfig.migrations as any).sequence.find(
|
|
156
|
+
(m: any) => m.id === 'com.tldraw.comment-thread/2'
|
|
157
|
+
) as { up(r: any): any; down(r: any): any }
|
|
158
|
+
|
|
159
|
+
it('stamps existing shape anchors with the top-right position (x:1, y:0)', () => {
|
|
160
|
+
expect(migration.up({ anchor: { type: 'shape', shapeId } })).toEqual({
|
|
161
|
+
anchor: { type: 'shape', shapeId, x: 1, y: 0, isPrecise: false },
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('leaves shape anchors that already have x/y unchanged', () => {
|
|
166
|
+
const anchor = { type: 'shape', shapeId, x: 0.25, y: 0.75, isPrecise: true }
|
|
167
|
+
expect(migration.up({ anchor })).toEqual({ anchor })
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('leaves non-shape anchors untouched', () => {
|
|
171
|
+
const anchor = { type: 'point', x: 5, y: 6 }
|
|
172
|
+
expect(migration.up({ anchor })).toEqual({ anchor })
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('down-migrates by dropping x/y', () => {
|
|
176
|
+
expect(
|
|
177
|
+
migration.down({ anchor: { type: 'shape', shapeId, x: 0.3, y: 0.4, isPrecise: true } })
|
|
178
|
+
).toEqual({
|
|
179
|
+
anchor: { type: 'shape', shapeId },
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
})
|