@tldraw/tlschema 4.2.2 → 4.2.3
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/bindings/TLBaseBinding.js.map +2 -2
- package/dist-cjs/createTLSchema.js.map +2 -2
- package/dist-cjs/index.d.ts +71 -242
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js +5 -1
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js +1 -5
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- package/dist-cjs/records/TLAsset.js.map +1 -1
- package/dist-cjs/records/TLBinding.js.map +2 -2
- package/dist-cjs/records/TLShape.js.map +2 -2
- package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
- package/dist-cjs/shapes/TLArrowShape.js +13 -26
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js +4 -37
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +0 -17
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js +1 -12
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js +2 -29
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js +1 -12
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js +1 -12
- package/dist-cjs/shapes/TLTextShape.js.map +2 -2
- package/dist-cjs/store-migrations.js +15 -15
- package/dist-cjs/store-migrations.js.map +2 -2
- package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
- package/dist-esm/createTLSchema.mjs.map +2 -2
- package/dist-esm/index.d.mts +71 -242
- package/dist-esm/index.mjs +1 -5
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs +5 -1
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs +1 -5
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- package/dist-esm/records/TLAsset.mjs.map +1 -1
- package/dist-esm/records/TLBinding.mjs.map +2 -2
- package/dist-esm/records/TLShape.mjs.map +2 -2
- package/dist-esm/shapes/TLArrowShape.mjs +13 -26
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs +4 -37
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +0 -17
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs +1 -12
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs +2 -29
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs +1 -12
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs +1 -12
- package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
- package/dist-esm/store-migrations.mjs +15 -15
- package/dist-esm/store-migrations.mjs.map +2 -2
- package/package.json +8 -8
- package/src/__tests__/migrationTestUtils.ts +3 -9
- package/src/assets/TLBookmarkAsset.test.ts +96 -0
- package/src/assets/TLImageAsset.test.ts +213 -0
- package/src/assets/TLVideoAsset.test.ts +105 -0
- package/src/bindings/TLArrowBinding.test.ts +55 -0
- package/src/bindings/TLBaseBinding.ts +14 -25
- package/src/createTLSchema.ts +2 -8
- package/src/index.ts +0 -9
- package/src/migrations.test.ts +1 -149
- package/src/misc/TLOpacity.ts +5 -1
- package/src/misc/TLRichText.ts +1 -6
- package/src/misc/id-validator.test.ts +50 -0
- package/src/records/TLAsset.test.ts +234 -0
- package/src/records/TLAsset.ts +2 -2
- package/src/records/TLBinding.test.ts +22 -0
- package/src/records/TLBinding.ts +23 -65
- package/src/records/TLCamera.test.ts +19 -0
- package/src/records/TLDocument.test.ts +35 -0
- package/src/records/TLInstance.test.ts +201 -0
- package/src/records/TLPage.test.ts +110 -0
- package/src/records/TLPageState.test.ts +228 -0
- package/src/records/TLPointer.test.ts +63 -0
- package/src/records/TLPresence.test.ts +190 -0
- package/src/records/TLRecord.test.ts +70 -0
- package/src/records/TLShape.test.ts +232 -0
- package/src/records/TLShape.ts +5 -100
- package/src/shapes/ShapeWithCrop.test.ts +18 -0
- package/src/shapes/ShapeWithCrop.ts +2 -2
- package/src/shapes/TLArrowShape.test.ts +505 -0
- package/src/shapes/TLArrowShape.ts +14 -28
- package/src/shapes/TLBaseShape.test.ts +142 -0
- package/src/shapes/TLBaseShape.ts +10 -34
- package/src/shapes/TLBookmarkShape.test.ts +122 -0
- package/src/shapes/TLDrawShape.test.ts +177 -0
- package/src/shapes/TLDrawShape.ts +12 -59
- package/src/shapes/TLEmbedShape.test.ts +286 -0
- package/src/shapes/TLEmbedShape.ts +0 -17
- package/src/shapes/TLFrameShape.test.ts +71 -0
- package/src/shapes/TLGeoShape.test.ts +247 -0
- package/src/shapes/TLGeoShape.ts +1 -14
- package/src/shapes/TLGroupShape.test.ts +59 -0
- package/src/shapes/TLHighlightShape.test.ts +325 -0
- package/src/shapes/TLHighlightShape.ts +0 -37
- package/src/shapes/TLImageShape.test.ts +534 -0
- package/src/shapes/TLLineShape.test.ts +269 -0
- package/src/shapes/TLNoteShape.test.ts +1568 -0
- package/src/shapes/TLNoteShape.ts +1 -15
- package/src/shapes/TLTextShape.test.ts +407 -0
- package/src/shapes/TLTextShape.ts +2 -16
- package/src/shapes/TLVideoShape.test.ts +112 -0
- package/src/store-migrations.ts +16 -17
- package/src/styles/TLColorStyle.test.ts +439 -0
- package/dist-cjs/misc/b64Vecs.js +0 -224
- package/dist-cjs/misc/b64Vecs.js.map +0 -7
- package/dist-esm/misc/b64Vecs.mjs +0 -204
- package/dist-esm/misc/b64Vecs.mjs.map +0 -7
- package/src/misc/b64Vecs.ts +0 -308
|
@@ -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: '
|
|
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,
|
|
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;",
|
|
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.2.
|
|
4
|
+
"version": "4.2.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -47,18 +47,18 @@
|
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"kleur": "^4.1.5",
|
|
49
49
|
"lazyrepo": "0.0.0-alpha.27",
|
|
50
|
-
"react": "^
|
|
50
|
+
"react": "^18.3.1",
|
|
51
51
|
"vitest": "^3.2.4"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@tldraw/state": "4.2.
|
|
55
|
-
"@tldraw/store": "4.2.
|
|
56
|
-
"@tldraw/utils": "4.2.
|
|
57
|
-
"@tldraw/validate": "4.2.
|
|
54
|
+
"@tldraw/state": "4.2.3",
|
|
55
|
+
"@tldraw/store": "4.2.3",
|
|
56
|
+
"@tldraw/utils": "4.2.3",
|
|
57
|
+
"@tldraw/validate": "4.2.3"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"react": "^18.2.0 || ^19.
|
|
61
|
-
"react-dom": "^18.2.0 || ^19.
|
|
60
|
+
"react": "^18.2.0 || ^19.0.0",
|
|
61
|
+
"react-dom": "^18.2.0 || ^19.0.0"
|
|
62
62
|
},
|
|
63
63
|
"module": "dist-esm/index.mjs",
|
|
64
64
|
"source": "src/index.ts",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Migration, MigrationId } from '@tldraw/store'
|
|
2
2
|
import { mockUniqueId, structuredClone } from '@tldraw/utils'
|
|
3
3
|
import { vi } from 'vitest'
|
|
4
4
|
import { createTLSchema } from '../createTLSchema'
|
|
@@ -25,14 +25,8 @@ export function getTestMigration(migrationId: MigrationId) {
|
|
|
25
25
|
id: migrationId,
|
|
26
26
|
up: (stuff: any) => {
|
|
27
27
|
nextNanoId = 0
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return migration.up(result) ?? result
|
|
31
|
-
}
|
|
32
|
-
const storage =
|
|
33
|
-
typeof stuff.entries === 'function' ? stuff : new Map(Object.entries(stuff).map(devFreeze))
|
|
34
|
-
migration.up(storage)
|
|
35
|
-
return typeof stuff.entries === 'function' ? storage : Object.fromEntries(storage.entries())
|
|
28
|
+
const result = structuredClone(stuff)
|
|
29
|
+
return migration.up(result) ?? result
|
|
36
30
|
},
|
|
37
31
|
down: (stuff: any) => {
|
|
38
32
|
nextNanoId = 0
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
3
|
+
import { bookmarkAssetVersions } from './TLBookmarkAsset'
|
|
4
|
+
|
|
5
|
+
describe('TLBookmarkAsset', () => {
|
|
6
|
+
describe('MakeUrlsValid migration', () => {
|
|
7
|
+
const { up, down } = getTestMigration(bookmarkAssetVersions.MakeUrlsValid)
|
|
8
|
+
|
|
9
|
+
it('should clean invalid src URLs and preserve valid ones', () => {
|
|
10
|
+
const assetWithInvalidSrc = {
|
|
11
|
+
id: 'asset:bookmark1',
|
|
12
|
+
type: 'bookmark',
|
|
13
|
+
props: {
|
|
14
|
+
title: 'Test Bookmark',
|
|
15
|
+
description: 'Test Description',
|
|
16
|
+
image: 'https://example.com/image.jpg',
|
|
17
|
+
src: 'invalid-url-format',
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = up(assetWithInvalidSrc)
|
|
22
|
+
expect(result.props.src).toBe('')
|
|
23
|
+
|
|
24
|
+
// Test valid URL is preserved
|
|
25
|
+
const assetWithValidSrc = {
|
|
26
|
+
...assetWithInvalidSrc,
|
|
27
|
+
props: { ...assetWithInvalidSrc.props, src: 'https://example.com' },
|
|
28
|
+
}
|
|
29
|
+
const validResult = up(assetWithValidSrc)
|
|
30
|
+
expect(validResult.props.src).toBe('https://example.com')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should be a no-op down migration', () => {
|
|
34
|
+
const asset = {
|
|
35
|
+
id: 'asset:bookmark1',
|
|
36
|
+
type: 'bookmark',
|
|
37
|
+
props: {
|
|
38
|
+
title: 'Test Bookmark',
|
|
39
|
+
description: 'Test Description',
|
|
40
|
+
image: 'https://example.com/image.jpg',
|
|
41
|
+
src: 'https://example.com',
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = down(asset)
|
|
46
|
+
expect(result).toEqual(asset)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('AddFavicon migration', () => {
|
|
51
|
+
const { up, down } = getTestMigration(bookmarkAssetVersions.AddFavicon)
|
|
52
|
+
|
|
53
|
+
it('should add favicon property and clean invalid URLs', () => {
|
|
54
|
+
// Test adding favicon property to asset without one
|
|
55
|
+
const assetWithoutFavicon = {
|
|
56
|
+
id: 'asset:bookmark1',
|
|
57
|
+
type: 'bookmark',
|
|
58
|
+
props: {
|
|
59
|
+
title: 'Test Bookmark',
|
|
60
|
+
description: 'Test Description',
|
|
61
|
+
image: 'https://example.com/image.jpg',
|
|
62
|
+
src: 'https://example.com',
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = up(assetWithoutFavicon)
|
|
67
|
+
expect(result.props.favicon).toBe('')
|
|
68
|
+
|
|
69
|
+
// Test cleaning invalid favicon URL
|
|
70
|
+
const assetWithInvalidFavicon = {
|
|
71
|
+
...assetWithoutFavicon,
|
|
72
|
+
props: { ...assetWithoutFavicon.props, favicon: 'invalid-url' },
|
|
73
|
+
}
|
|
74
|
+
const cleanResult = up(assetWithInvalidFavicon)
|
|
75
|
+
expect(cleanResult.props.favicon).toBe('')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should remove favicon property in down migration', () => {
|
|
79
|
+
const assetWithFavicon = {
|
|
80
|
+
id: 'asset:bookmark1',
|
|
81
|
+
type: 'bookmark',
|
|
82
|
+
props: {
|
|
83
|
+
title: 'Test Bookmark',
|
|
84
|
+
description: 'Test Description',
|
|
85
|
+
image: 'https://example.com/image.jpg',
|
|
86
|
+
src: 'https://example.com',
|
|
87
|
+
favicon: 'https://example.com/favicon.ico',
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const result = down(assetWithFavicon)
|
|
92
|
+
expect(result.props).not.toHaveProperty('favicon')
|
|
93
|
+
expect(result.props.title).toBe('Test Bookmark')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
3
|
+
import { imageAssetVersions } from './TLImageAsset'
|
|
4
|
+
|
|
5
|
+
describe('TLImageAsset', () => {
|
|
6
|
+
describe('AddIsAnimated migration', () => {
|
|
7
|
+
const { up, down } = getTestMigration(imageAssetVersions.AddIsAnimated)
|
|
8
|
+
|
|
9
|
+
it('should add isAnimated property in up migration', () => {
|
|
10
|
+
const assetWithoutIsAnimated = {
|
|
11
|
+
id: 'asset:image1',
|
|
12
|
+
type: 'image',
|
|
13
|
+
props: {
|
|
14
|
+
w: 100,
|
|
15
|
+
h: 100,
|
|
16
|
+
name: 'test.jpg',
|
|
17
|
+
mimeType: 'image/jpeg',
|
|
18
|
+
src: 'https://example.com/test.jpg',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = up(assetWithoutIsAnimated)
|
|
23
|
+
expect(result.props.isAnimated).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should remove isAnimated property in down migration', () => {
|
|
27
|
+
const assetWithIsAnimated = {
|
|
28
|
+
id: 'asset:image3',
|
|
29
|
+
type: 'image',
|
|
30
|
+
props: {
|
|
31
|
+
w: 100,
|
|
32
|
+
h: 100,
|
|
33
|
+
name: 'test.jpg',
|
|
34
|
+
mimeType: 'image/jpeg',
|
|
35
|
+
src: 'https://example.com/test.jpg',
|
|
36
|
+
isAnimated: false,
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = down(assetWithIsAnimated)
|
|
41
|
+
expect(result.props).not.toHaveProperty('isAnimated')
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('RenameWidthHeight migration', () => {
|
|
46
|
+
const { up, down } = getTestMigration(imageAssetVersions.RenameWidthHeight)
|
|
47
|
+
|
|
48
|
+
it('should rename width and height to w and h in up migration', () => {
|
|
49
|
+
const assetWithWidthHeight = {
|
|
50
|
+
id: 'asset:image1',
|
|
51
|
+
type: 'image',
|
|
52
|
+
props: {
|
|
53
|
+
width: 800,
|
|
54
|
+
height: 600,
|
|
55
|
+
name: 'test.jpg',
|
|
56
|
+
isAnimated: false,
|
|
57
|
+
mimeType: 'image/jpeg',
|
|
58
|
+
src: 'https://example.com/test.jpg',
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = up(assetWithWidthHeight)
|
|
63
|
+
expect(result.props.w).toBe(800)
|
|
64
|
+
expect(result.props.h).toBe(600)
|
|
65
|
+
expect(result.props).not.toHaveProperty('width')
|
|
66
|
+
expect(result.props).not.toHaveProperty('height')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should rename w and h to width and height in down migration', () => {
|
|
70
|
+
const assetWithWH = {
|
|
71
|
+
id: 'asset:image3',
|
|
72
|
+
type: 'image',
|
|
73
|
+
props: {
|
|
74
|
+
w: 1024,
|
|
75
|
+
h: 768,
|
|
76
|
+
name: 'test.png',
|
|
77
|
+
isAnimated: false,
|
|
78
|
+
mimeType: 'image/png',
|
|
79
|
+
src: 'https://example.com/test.png',
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result = down(assetWithWH)
|
|
84
|
+
expect(result.props.width).toBe(1024)
|
|
85
|
+
expect(result.props.height).toBe(768)
|
|
86
|
+
expect(result.props).not.toHaveProperty('w')
|
|
87
|
+
expect(result.props).not.toHaveProperty('h')
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('MakeUrlsValid migration', () => {
|
|
92
|
+
const { up, down: _down } = getTestMigration(imageAssetVersions.MakeUrlsValid)
|
|
93
|
+
|
|
94
|
+
it('should clean invalid src URLs in up migration', () => {
|
|
95
|
+
const assetWithInvalidSrc = {
|
|
96
|
+
id: 'asset:image1',
|
|
97
|
+
type: 'image',
|
|
98
|
+
props: {
|
|
99
|
+
w: 100,
|
|
100
|
+
h: 100,
|
|
101
|
+
name: 'test.jpg',
|
|
102
|
+
isAnimated: false,
|
|
103
|
+
mimeType: 'image/jpeg',
|
|
104
|
+
src: 'invalid-url-format',
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const result = up(assetWithInvalidSrc)
|
|
109
|
+
expect(result.props.src).toBe('')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should preserve valid src URLs in up migration', () => {
|
|
113
|
+
const assetWithValidSrc = {
|
|
114
|
+
id: 'asset:image2',
|
|
115
|
+
type: 'image',
|
|
116
|
+
props: {
|
|
117
|
+
w: 100,
|
|
118
|
+
h: 100,
|
|
119
|
+
name: 'test.jpg',
|
|
120
|
+
isAnimated: false,
|
|
121
|
+
mimeType: 'image/jpeg',
|
|
122
|
+
src: 'https://example.com/test.jpg',
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = up(assetWithValidSrc)
|
|
127
|
+
expect(result.props.src).toBe('https://example.com/test.jpg')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('AddFileSize migration', () => {
|
|
132
|
+
const { up, down } = getTestMigration(imageAssetVersions.AddFileSize)
|
|
133
|
+
|
|
134
|
+
it('should add fileSize property with -1 default in up migration', () => {
|
|
135
|
+
const assetWithoutFileSize = {
|
|
136
|
+
id: 'asset:image1',
|
|
137
|
+
type: 'image',
|
|
138
|
+
props: {
|
|
139
|
+
w: 100,
|
|
140
|
+
h: 100,
|
|
141
|
+
name: 'test.jpg',
|
|
142
|
+
isAnimated: false,
|
|
143
|
+
mimeType: 'image/jpeg',
|
|
144
|
+
src: 'https://example.com/test.jpg',
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = up(assetWithoutFileSize)
|
|
149
|
+
expect(result.props.fileSize).toBe(-1)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('should remove fileSize property in down migration', () => {
|
|
153
|
+
const assetWithFileSize = {
|
|
154
|
+
id: 'asset:image3',
|
|
155
|
+
type: 'image',
|
|
156
|
+
props: {
|
|
157
|
+
w: 100,
|
|
158
|
+
h: 100,
|
|
159
|
+
name: 'test.jpg',
|
|
160
|
+
isAnimated: false,
|
|
161
|
+
mimeType: 'image/jpeg',
|
|
162
|
+
src: 'https://example.com/test.jpg',
|
|
163
|
+
fileSize: 50000,
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = down(assetWithFileSize)
|
|
168
|
+
expect(result.props).not.toHaveProperty('fileSize')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
describe('MakeFileSizeOptional migration', () => {
|
|
173
|
+
const { up, down } = getTestMigration(imageAssetVersions.MakeFileSizeOptional)
|
|
174
|
+
|
|
175
|
+
it('should convert fileSize -1 to undefined in up migration', () => {
|
|
176
|
+
const assetWithNegativeFileSize = {
|
|
177
|
+
id: 'asset:image1',
|
|
178
|
+
type: 'image',
|
|
179
|
+
props: {
|
|
180
|
+
w: 100,
|
|
181
|
+
h: 100,
|
|
182
|
+
name: 'test.jpg',
|
|
183
|
+
isAnimated: false,
|
|
184
|
+
mimeType: 'image/jpeg',
|
|
185
|
+
src: 'https://example.com/test.jpg',
|
|
186
|
+
fileSize: -1,
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = up(assetWithNegativeFileSize)
|
|
191
|
+
expect(result.props.fileSize).toBeUndefined()
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should convert undefined fileSize to -1 in down migration', () => {
|
|
195
|
+
const assetWithUndefinedFileSize = {
|
|
196
|
+
id: 'asset:image5',
|
|
197
|
+
type: 'image',
|
|
198
|
+
props: {
|
|
199
|
+
w: 100,
|
|
200
|
+
h: 100,
|
|
201
|
+
name: 'test.jpg',
|
|
202
|
+
isAnimated: false,
|
|
203
|
+
mimeType: 'image/jpeg',
|
|
204
|
+
src: 'https://example.com/test.jpg',
|
|
205
|
+
fileSize: undefined,
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const result = down(assetWithUndefinedFileSize)
|
|
210
|
+
expect(result.props.fileSize).toBe(-1)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
3
|
+
import { videoAssetVersions } from './TLVideoAsset'
|
|
4
|
+
|
|
5
|
+
describe('TLVideoAsset', () => {
|
|
6
|
+
describe('migrations', () => {
|
|
7
|
+
it('should handle AddIsAnimated migration', () => {
|
|
8
|
+
const { up, down } = getTestMigration(videoAssetVersions.AddIsAnimated)
|
|
9
|
+
|
|
10
|
+
const assetWithoutIsAnimated = {
|
|
11
|
+
id: 'asset:video1',
|
|
12
|
+
type: 'video',
|
|
13
|
+
props: {
|
|
14
|
+
w: 640,
|
|
15
|
+
h: 480,
|
|
16
|
+
name: 'test.mp4',
|
|
17
|
+
mimeType: 'video/mp4',
|
|
18
|
+
src: 'https://example.com/test.mp4',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const upResult = up(assetWithoutIsAnimated)
|
|
23
|
+
expect(upResult.props.isAnimated).toBe(false)
|
|
24
|
+
|
|
25
|
+
const downResult = down(upResult)
|
|
26
|
+
expect(downResult.props).not.toHaveProperty('isAnimated')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should handle RenameWidthHeight migration', () => {
|
|
30
|
+
const { up, down } = getTestMigration(videoAssetVersions.RenameWidthHeight)
|
|
31
|
+
|
|
32
|
+
const assetWithWidthHeight = {
|
|
33
|
+
id: 'asset:video1',
|
|
34
|
+
type: 'video',
|
|
35
|
+
props: {
|
|
36
|
+
width: 1920,
|
|
37
|
+
height: 1080,
|
|
38
|
+
name: 'test.mp4',
|
|
39
|
+
isAnimated: true,
|
|
40
|
+
mimeType: 'video/mp4',
|
|
41
|
+
src: 'https://example.com/test.mp4',
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const upResult = up(assetWithWidthHeight)
|
|
46
|
+
expect(upResult.props.w).toBe(1920)
|
|
47
|
+
expect(upResult.props.h).toBe(1080)
|
|
48
|
+
expect(upResult.props).not.toHaveProperty('width')
|
|
49
|
+
expect(upResult.props).not.toHaveProperty('height')
|
|
50
|
+
|
|
51
|
+
const downResult = down(upResult)
|
|
52
|
+
expect(downResult.props.width).toBe(1920)
|
|
53
|
+
expect(downResult.props.height).toBe(1080)
|
|
54
|
+
expect(downResult.props).not.toHaveProperty('w')
|
|
55
|
+
expect(downResult.props).not.toHaveProperty('h')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should handle MakeUrlsValid migration', () => {
|
|
59
|
+
const { up } = getTestMigration(videoAssetVersions.MakeUrlsValid)
|
|
60
|
+
|
|
61
|
+
const assetWithInvalidSrc = {
|
|
62
|
+
id: 'asset:video1',
|
|
63
|
+
type: 'video',
|
|
64
|
+
props: {
|
|
65
|
+
w: 640,
|
|
66
|
+
h: 480,
|
|
67
|
+
name: 'test.mp4',
|
|
68
|
+
isAnimated: true,
|
|
69
|
+
mimeType: 'video/mp4',
|
|
70
|
+
src: 'invalid-url-format',
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result = up(assetWithInvalidSrc)
|
|
75
|
+
expect(result.props.src).toBe('')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should handle MakeFileSizeOptional migration', () => {
|
|
79
|
+
const { up, down } = getTestMigration(videoAssetVersions.MakeFileSizeOptional)
|
|
80
|
+
|
|
81
|
+
const assetWithNegativeFileSize = {
|
|
82
|
+
id: 'asset:video1',
|
|
83
|
+
type: 'video',
|
|
84
|
+
props: {
|
|
85
|
+
w: 640,
|
|
86
|
+
h: 480,
|
|
87
|
+
name: 'test.mp4',
|
|
88
|
+
isAnimated: true,
|
|
89
|
+
mimeType: 'video/mp4',
|
|
90
|
+
src: 'https://example.com/test.mp4',
|
|
91
|
+
fileSize: -1,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const upResult = up(assetWithNegativeFileSize)
|
|
96
|
+
expect(upResult.props.fileSize).toBeUndefined()
|
|
97
|
+
|
|
98
|
+
const downResult = down({
|
|
99
|
+
...assetWithNegativeFileSize,
|
|
100
|
+
props: { ...assetWithNegativeFileSize.props, fileSize: undefined },
|
|
101
|
+
})
|
|
102
|
+
expect(downResult.props.fileSize).toBe(-1)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
3
|
+
import { arrowBindingVersions } from './TLArrowBinding'
|
|
4
|
+
|
|
5
|
+
describe('TLArrowBinding', () => {
|
|
6
|
+
describe('AddSnap migration', () => {
|
|
7
|
+
const { up, down } = getTestMigration(arrowBindingVersions.AddSnap)
|
|
8
|
+
|
|
9
|
+
it('should add snap property with default value "none"', () => {
|
|
10
|
+
const oldRecord = {
|
|
11
|
+
props: {
|
|
12
|
+
terminal: 'end',
|
|
13
|
+
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
14
|
+
isExact: true,
|
|
15
|
+
isPrecise: false,
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const result = up(oldRecord)
|
|
20
|
+
expect(result.props.snap).toBe('none')
|
|
21
|
+
expect(result.props.terminal).toBe('end')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should remove snap property on down migration', () => {
|
|
25
|
+
const newRecord = {
|
|
26
|
+
props: {
|
|
27
|
+
terminal: 'end',
|
|
28
|
+
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
29
|
+
isExact: true,
|
|
30
|
+
isPrecise: false,
|
|
31
|
+
snap: 'center',
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = down(newRecord)
|
|
36
|
+
expect(result.props.snap).toBeUndefined()
|
|
37
|
+
expect(result.props.terminal).toBe('end')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should be reversible', () => {
|
|
41
|
+
const originalRecord = {
|
|
42
|
+
props: {
|
|
43
|
+
terminal: 'start',
|
|
44
|
+
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
45
|
+
isExact: true,
|
|
46
|
+
isPrecise: false,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const upResult = up(originalRecord)
|
|
51
|
+
const downResult = down(upResult)
|
|
52
|
+
expect(downResult.props).toEqual(originalRecord.props)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BaseRecord } from '@tldraw/store'
|
|
1
2
|
import { JsonObject } from '@tldraw/utils'
|
|
2
3
|
import { T } from '@tldraw/validate'
|
|
3
4
|
import { idValidator } from '../misc/id-validator'
|
|
@@ -9,41 +10,33 @@ import { shapeIdValidator } from '../shapes/TLBaseShape'
|
|
|
9
10
|
* Base interface for all binding types in tldraw. Bindings represent relationships
|
|
10
11
|
* between shapes, such as arrows connecting to other shapes or organizational connections.
|
|
11
12
|
*
|
|
12
|
-
* All
|
|
13
|
+
* All bindings extend this base interface with specific type and property definitions.
|
|
13
14
|
* The binding system enables shapes to maintain relationships that persist through
|
|
14
15
|
* transformations, movements, and other operations.
|
|
15
16
|
*
|
|
16
|
-
* Custom bindings should be defined by augmenting the TLGlobalBindingPropsMap type and getting the binding type from the TLBinding type.
|
|
17
|
-
*
|
|
18
17
|
* @param Type - String literal type identifying the specific binding type (e.g., 'arrow')
|
|
19
18
|
* @param Props - Object containing binding-specific properties and configuration
|
|
20
19
|
*
|
|
21
20
|
* @example
|
|
22
21
|
* ```ts
|
|
23
|
-
* // Define a
|
|
24
|
-
* interface
|
|
25
|
-
*
|
|
26
|
-
* interface
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* isExact: boolean
|
|
30
|
-
* isPrecise: boolean
|
|
31
|
-
* snap: ElbowArrowSnap
|
|
22
|
+
* // Define a custom binding type
|
|
23
|
+
* interface MyCustomBinding extends TLBaseBinding<'custom', MyCustomProps> {}
|
|
24
|
+
*
|
|
25
|
+
* interface MyCustomProps {
|
|
26
|
+
* strength: number
|
|
27
|
+
* color: string
|
|
32
28
|
* }
|
|
33
29
|
*
|
|
34
30
|
* // Create a binding instance
|
|
35
|
-
* const
|
|
31
|
+
* const binding: MyCustomBinding = {
|
|
36
32
|
* id: 'binding:abc123',
|
|
37
33
|
* typeName: 'binding',
|
|
38
|
-
* type: '
|
|
34
|
+
* type: 'custom',
|
|
39
35
|
* fromId: 'shape:source1',
|
|
40
36
|
* toId: 'shape:target1',
|
|
41
37
|
* props: {
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* isExact: false,
|
|
45
|
-
* isPrecise: true,
|
|
46
|
-
* snap: 'edge'
|
|
38
|
+
* strength: 0.8,
|
|
39
|
+
* color: 'red'
|
|
47
40
|
* },
|
|
48
41
|
* meta: {}
|
|
49
42
|
* }
|
|
@@ -51,12 +44,8 @@ import { shapeIdValidator } from '../shapes/TLBaseShape'
|
|
|
51
44
|
*
|
|
52
45
|
* @public
|
|
53
46
|
*/
|
|
54
|
-
export interface TLBaseBinding<Type extends string, Props extends object>
|
|
55
|
-
|
|
56
|
-
// and for that reason those "base members" have to be declared manually here
|
|
57
|
-
readonly id: TLBindingId
|
|
58
|
-
readonly typeName: 'binding'
|
|
59
|
-
|
|
47
|
+
export interface TLBaseBinding<Type extends string, Props extends object>
|
|
48
|
+
extends BaseRecord<'binding', TLBindingId> {
|
|
60
49
|
/** The specific type of this binding (e.g., 'arrow', 'custom') */
|
|
61
50
|
type: Type
|
|
62
51
|
/** ID of the source shape in this binding relationship */
|
package/src/createTLSchema.ts
CHANGED
|
@@ -22,9 +22,8 @@ import {
|
|
|
22
22
|
getShapePropKeysByStyle,
|
|
23
23
|
rootShapeMigrations,
|
|
24
24
|
} from './records/TLShape'
|
|
25
|
-
import {
|
|
25
|
+
import { TLPropsMigrations, processPropsMigrations } from './recordsWithProps'
|
|
26
26
|
import { arrowShapeMigrations, arrowShapeProps } from './shapes/TLArrowShape'
|
|
27
|
-
import { TLBaseShape } from './shapes/TLBaseShape'
|
|
28
27
|
import { bookmarkShapeMigrations, bookmarkShapeProps } from './shapes/TLBookmarkShape'
|
|
29
28
|
import { drawShapeMigrations, drawShapeProps } from './shapes/TLDrawShape'
|
|
30
29
|
import { embedShapeMigrations, embedShapeProps } from './shapes/TLEmbedShape'
|
|
@@ -148,12 +147,7 @@ export const defaultShapeSchemas = {
|
|
|
148
147
|
note: { migrations: noteShapeMigrations, props: noteShapeProps },
|
|
149
148
|
text: { migrations: textShapeMigrations, props: textShapeProps },
|
|
150
149
|
video: { migrations: videoShapeMigrations, props: videoShapeProps },
|
|
151
|
-
} satisfies {
|
|
152
|
-
[T in TLDefaultShape['type']]: {
|
|
153
|
-
migrations: SchemaPropsInfo['migrations']
|
|
154
|
-
props: RecordProps<TLBaseShape<T, Extract<TLDefaultShape, { type: T }>['props']>>
|
|
155
|
-
}
|
|
156
|
-
}
|
|
150
|
+
} satisfies { [T in TLDefaultShape['type']]: SchemaPropsInfo }
|
|
157
151
|
|
|
158
152
|
/**
|
|
159
153
|
* Default binding schema configurations for all built-in tldraw binding types.
|