@tldraw/editor 5.4.2 → 5.5.0-canary.0241f11f2e99
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/index.d.ts +7 -2
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/constants.js +2 -0
- package/dist-cjs/lib/constants.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +131 -151
- package/dist-cjs/lib/editor/Editor.js.map +3 -3
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -0
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
- package/dist-cjs/lib/exports/ExportDelay.js +10 -2
- package/dist-cjs/lib/exports/ExportDelay.js.map +2 -2
- package/dist-cjs/lib/exports/FontEmbedder.js +25 -11
- package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/StyleEmbedder.js +9 -4
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/fetchCache.js +3 -0
- package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgAsImage.js +10 -4
- package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +18 -9
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/exports/parseCss.js +1 -1
- package/dist-cjs/lib/exports/parseCss.js.map +1 -1
- package/dist-cjs/lib/license/LicenseManager.js +9 -1
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +12 -4
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -5
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +13 -17
- package/dist-cjs/lib/utils/sync/LocalIndexedDb.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +78 -40
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +7 -2
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/constants.mjs +2 -0
- package/dist-esm/lib/constants.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +132 -151
- package/dist-esm/lib/editor/Editor.mjs.map +3 -3
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -0
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
- package/dist-esm/lib/exports/ExportDelay.mjs +11 -3
- package/dist-esm/lib/exports/ExportDelay.mjs.map +2 -2
- package/dist-esm/lib/exports/FontEmbedder.mjs +25 -11
- package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +9 -4
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/fetchCache.mjs +3 -0
- package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgAsImage.mjs +10 -4
- package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +18 -9
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/exports/parseCss.mjs +1 -1
- package/dist-esm/lib/exports/parseCss.mjs.map +1 -1
- package/dist-esm/lib/license/LicenseManager.mjs +9 -1
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +13 -5
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -6
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +13 -17
- package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +78 -40
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/constants.ts +8 -0
- package/src/lib/editor/Editor.test.ts +20 -0
- package/src/lib/editor/Editor.ts +202 -255
- package/src/lib/editor/derivations/bindingsIndex.ts +3 -0
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
- package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
- package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
- package/src/lib/exports/ExportDelay.tsx +16 -4
- package/src/lib/exports/FontEmbedder.test.ts +99 -0
- package/src/lib/exports/FontEmbedder.ts +32 -11
- package/src/lib/exports/StyleEmbedder.ts +13 -4
- package/src/lib/exports/fetchCache.test.ts +22 -0
- package/src/lib/exports/fetchCache.ts +4 -0
- package/src/lib/exports/getSvgAsImage.ts +13 -5
- package/src/lib/exports/getSvgJsx.test.ts +43 -1
- package/src/lib/exports/getSvgJsx.tsx +28 -16
- package/src/lib/exports/parseCss.test.ts +4 -0
- package/src/lib/exports/parseCss.ts +2 -2
- package/src/lib/license/LicenseManager.ts +13 -1
- package/src/lib/license/LicenseProvider.test.tsx +76 -0
- package/src/lib/license/LicenseProvider.tsx +19 -5
- package/src/lib/primitives/geometry/Arc2d.test.ts +9 -0
- package/src/lib/primitives/geometry/Arc2d.ts +1 -1
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +13 -0
- package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
- package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
- package/src/lib/primitives/geometry/Edge2d.ts +1 -1
- package/src/lib/primitives/geometry/Group2d.test.ts +26 -5
- package/src/lib/primitives/geometry/Group2d.ts +18 -7
- package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
- package/src/lib/primitives/geometry/Point2d.ts +1 -1
- package/src/lib/utils/sync/LocalIndexedDb.test.ts +28 -0
- package/src/lib/utils/sync/LocalIndexedDb.ts +18 -20
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +236 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +106 -54
- package/src/version.ts +3 -3
|
@@ -11,6 +11,7 @@ function fromScratch(bindingsQuery) {
|
|
|
11
11
|
} else {
|
|
12
12
|
bindingsForFromShape.push(binding);
|
|
13
13
|
}
|
|
14
|
+
if (toId === fromId) continue;
|
|
14
15
|
const bindingsForToShape = shapesToBindings.get(toId);
|
|
15
16
|
if (!bindingsForToShape) {
|
|
16
17
|
shapesToBindings.set(toId, [binding]);
|
|
@@ -65,6 +66,7 @@ function bindingsIndex(editor) {
|
|
|
65
66
|
}
|
|
66
67
|
function addBinding(binding) {
|
|
67
68
|
ensureNewArray(binding.fromId).push(binding);
|
|
69
|
+
if (binding.toId === binding.fromId) return;
|
|
68
70
|
ensureNewArray(binding.toId).push(binding);
|
|
69
71
|
}
|
|
70
72
|
for (const changes of diff) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/editor/derivations/bindingsIndex.ts"],
|
|
4
|
-
"sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport { TLBinding, TLShapeId } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport type { Editor } from '../Editor'\n\ntype TLBindingsIndex = Map<TLShapeId, TLBinding[]>\n\nfunction fromScratch(bindingsQuery: Computed<TLBinding[], unknown>) {\n\tconst allBindings = bindingsQuery.get() as TLBinding[]\n\n\tconst shapesToBindings: TLBindingsIndex = new Map()\n\n\tfor (const binding of allBindings) {\n\t\tconst { fromId, toId } = binding\n\t\tconst bindingsForFromShape = shapesToBindings.get(fromId)\n\t\tif (!bindingsForFromShape) {\n\t\t\tshapesToBindings.set(fromId, [binding])\n\t\t} else {\n\t\t\tbindingsForFromShape.push(binding)\n\t\t}\n\t\tconst bindingsForToShape = shapesToBindings.get(toId)\n\t\tif (!bindingsForToShape) {\n\t\t\tshapesToBindings.set(toId, [binding])\n\t\t} else {\n\t\t\tbindingsForToShape.push(binding)\n\t\t}\n\t}\n\n\treturn shapesToBindings\n}\n\nexport function bindingsIndex(editor: Editor): Computed<TLBindingsIndex> {\n\tconst { store } = editor\n\tconst bindingsHistory = store.query.filterHistory('binding')\n\tconst bindingsQuery = store.query.records('binding')\n\n\treturn computed<TLBindingsIndex>('arrowBindingsIndex', (_lastValue, lastComputedEpoch) => {\n\t\tif (isUninitialized(_lastValue)) {\n\t\t\treturn fromScratch(bindingsQuery)\n\t\t}\n\n\t\tconst lastValue = _lastValue\n\n\t\tconst diff = bindingsHistory.getDiffSince(lastComputedEpoch)\n\n\t\tif (diff === RESET_VALUE) {\n\t\t\treturn fromScratch(bindingsQuery)\n\t\t}\n\n\t\tlet nextValue: TLBindingsIndex | undefined = undefined\n\n\t\tfunction removingBinding(binding: TLBinding) {\n\t\t\tnextValue ??= new Map(lastValue)\n\t\t\tconst prevFrom = nextValue.get(binding.fromId)\n\t\t\tconst nextFrom = prevFrom?.filter((b) => b.id !== binding.id)\n\t\t\tif (!nextFrom?.length) {\n\t\t\t\tnextValue.delete(binding.fromId)\n\t\t\t} else {\n\t\t\t\tnextValue.set(binding.fromId, nextFrom)\n\t\t\t}\n\t\t\tconst prevTo = nextValue.get(binding.toId)\n\t\t\tconst nextTo = prevTo?.filter((b) => b.id !== binding.id)\n\t\t\tif (!nextTo?.length) {\n\t\t\t\tnextValue.delete(binding.toId)\n\t\t\t} else {\n\t\t\t\tnextValue.set(binding.toId, nextTo)\n\t\t\t}\n\t\t}\n\n\t\tfunction ensureNewArray(shapeId: TLShapeId) {\n\t\t\tnextValue ??= new Map(lastValue)\n\n\t\t\tlet result = nextValue.get(shapeId)\n\t\t\tif (!result) {\n\t\t\t\tresult = []\n\t\t\t\tnextValue.set(shapeId, result)\n\t\t\t} else if (result === lastValue.get(shapeId)) {\n\t\t\t\tresult = result.slice(0)\n\t\t\t\tnextValue.set(shapeId, result)\n\t\t\t}\n\t\t\treturn result\n\t\t}\n\n\t\tfunction addBinding(binding: TLBinding) {\n\t\t\tensureNewArray(binding.fromId).push(binding)\n\t\t\tensureNewArray(binding.toId).push(binding)\n\t\t}\n\n\t\tfor (const changes of diff) {\n\t\t\tfor (const newBinding of objectMapValues(changes.added)) {\n\t\t\t\taddBinding(newBinding)\n\t\t\t}\n\n\t\t\tfor (const [prev, next] of objectMapValues(changes.updated)) {\n\t\t\t\tremovingBinding(prev)\n\t\t\t\taddBinding(next)\n\t\t\t}\n\n\t\t\tfor (const prev of objectMapValues(changes.removed)) {\n\t\t\t\tremovingBinding(prev)\n\t\t\t}\n\t\t}\n\n\t\t// TODO: add diff entries if we need them\n\t\treturn nextValue ?? lastValue\n\t})\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAmB,aAAa,UAAU,uBAAuB;AAEjE,SAAS,uBAAuB;AAKhC,SAAS,YAAY,eAA+C;AACnE,QAAM,cAAc,cAAc,IAAI;AAEtC,QAAM,mBAAoC,oBAAI,IAAI;AAElD,aAAW,WAAW,aAAa;AAClC,UAAM,EAAE,QAAQ,KAAK,IAAI;AACzB,UAAM,uBAAuB,iBAAiB,IAAI,MAAM;AACxD,QAAI,CAAC,sBAAsB;AAC1B,uBAAiB,IAAI,QAAQ,CAAC,OAAO,CAAC;AAAA,IACvC,OAAO;AACN,2BAAqB,KAAK,OAAO;AAAA,IAClC;
|
|
4
|
+
"sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport { TLBinding, TLShapeId } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport type { Editor } from '../Editor'\n\ntype TLBindingsIndex = Map<TLShapeId, TLBinding[]>\n\nfunction fromScratch(bindingsQuery: Computed<TLBinding[], unknown>) {\n\tconst allBindings = bindingsQuery.get() as TLBinding[]\n\n\tconst shapesToBindings: TLBindingsIndex = new Map()\n\n\tfor (const binding of allBindings) {\n\t\tconst { fromId, toId } = binding\n\t\tconst bindingsForFromShape = shapesToBindings.get(fromId)\n\t\tif (!bindingsForFromShape) {\n\t\t\tshapesToBindings.set(fromId, [binding])\n\t\t} else {\n\t\t\tbindingsForFromShape.push(binding)\n\t\t}\n\t\t// A binding from a shape to itself belongs in that shape's array once, not twice\n\t\tif (toId === fromId) continue\n\t\tconst bindingsForToShape = shapesToBindings.get(toId)\n\t\tif (!bindingsForToShape) {\n\t\t\tshapesToBindings.set(toId, [binding])\n\t\t} else {\n\t\t\tbindingsForToShape.push(binding)\n\t\t}\n\t}\n\n\treturn shapesToBindings\n}\n\nexport function bindingsIndex(editor: Editor): Computed<TLBindingsIndex> {\n\tconst { store } = editor\n\tconst bindingsHistory = store.query.filterHistory('binding')\n\tconst bindingsQuery = store.query.records('binding')\n\n\treturn computed<TLBindingsIndex>('arrowBindingsIndex', (_lastValue, lastComputedEpoch) => {\n\t\tif (isUninitialized(_lastValue)) {\n\t\t\treturn fromScratch(bindingsQuery)\n\t\t}\n\n\t\tconst lastValue = _lastValue\n\n\t\tconst diff = bindingsHistory.getDiffSince(lastComputedEpoch)\n\n\t\tif (diff === RESET_VALUE) {\n\t\t\treturn fromScratch(bindingsQuery)\n\t\t}\n\n\t\tlet nextValue: TLBindingsIndex | undefined = undefined\n\n\t\tfunction removingBinding(binding: TLBinding) {\n\t\t\tnextValue ??= new Map(lastValue)\n\t\t\tconst prevFrom = nextValue.get(binding.fromId)\n\t\t\tconst nextFrom = prevFrom?.filter((b) => b.id !== binding.id)\n\t\t\tif (!nextFrom?.length) {\n\t\t\t\tnextValue.delete(binding.fromId)\n\t\t\t} else {\n\t\t\t\tnextValue.set(binding.fromId, nextFrom)\n\t\t\t}\n\t\t\tconst prevTo = nextValue.get(binding.toId)\n\t\t\tconst nextTo = prevTo?.filter((b) => b.id !== binding.id)\n\t\t\tif (!nextTo?.length) {\n\t\t\t\tnextValue.delete(binding.toId)\n\t\t\t} else {\n\t\t\t\tnextValue.set(binding.toId, nextTo)\n\t\t\t}\n\t\t}\n\n\t\tfunction ensureNewArray(shapeId: TLShapeId) {\n\t\t\tnextValue ??= new Map(lastValue)\n\n\t\t\tlet result = nextValue.get(shapeId)\n\t\t\tif (!result) {\n\t\t\t\tresult = []\n\t\t\t\tnextValue.set(shapeId, result)\n\t\t\t} else if (result === lastValue.get(shapeId)) {\n\t\t\t\tresult = result.slice(0)\n\t\t\t\tnextValue.set(shapeId, result)\n\t\t\t}\n\t\t\treturn result\n\t\t}\n\n\t\tfunction addBinding(binding: TLBinding) {\n\t\t\tensureNewArray(binding.fromId).push(binding)\n\t\t\tif (binding.toId === binding.fromId) return\n\t\t\tensureNewArray(binding.toId).push(binding)\n\t\t}\n\n\t\tfor (const changes of diff) {\n\t\t\tfor (const newBinding of objectMapValues(changes.added)) {\n\t\t\t\taddBinding(newBinding)\n\t\t\t}\n\n\t\t\tfor (const [prev, next] of objectMapValues(changes.updated)) {\n\t\t\t\tremovingBinding(prev)\n\t\t\t\taddBinding(next)\n\t\t\t}\n\n\t\t\tfor (const prev of objectMapValues(changes.removed)) {\n\t\t\t\tremovingBinding(prev)\n\t\t\t}\n\t\t}\n\n\t\t// TODO: add diff entries if we need them\n\t\treturn nextValue ?? lastValue\n\t})\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAmB,aAAa,UAAU,uBAAuB;AAEjE,SAAS,uBAAuB;AAKhC,SAAS,YAAY,eAA+C;AACnE,QAAM,cAAc,cAAc,IAAI;AAEtC,QAAM,mBAAoC,oBAAI,IAAI;AAElD,aAAW,WAAW,aAAa;AAClC,UAAM,EAAE,QAAQ,KAAK,IAAI;AACzB,UAAM,uBAAuB,iBAAiB,IAAI,MAAM;AACxD,QAAI,CAAC,sBAAsB;AAC1B,uBAAiB,IAAI,QAAQ,CAAC,OAAO,CAAC;AAAA,IACvC,OAAO;AACN,2BAAqB,KAAK,OAAO;AAAA,IAClC;AAEA,QAAI,SAAS,OAAQ;AACrB,UAAM,qBAAqB,iBAAiB,IAAI,IAAI;AACpD,QAAI,CAAC,oBAAoB;AACxB,uBAAiB,IAAI,MAAM,CAAC,OAAO,CAAC;AAAA,IACrC,OAAO;AACN,yBAAmB,KAAK,OAAO;AAAA,IAChC;AAAA,EACD;AAEA,SAAO;AACR;AAEO,SAAS,cAAc,QAA2C;AACxE,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,kBAAkB,MAAM,MAAM,cAAc,SAAS;AAC3D,QAAM,gBAAgB,MAAM,MAAM,QAAQ,SAAS;AAEnD,SAAO,SAA0B,sBAAsB,CAAC,YAAY,sBAAsB;AACzF,QAAI,gBAAgB,UAAU,GAAG;AAChC,aAAO,YAAY,aAAa;AAAA,IACjC;AAEA,UAAM,YAAY;AAElB,UAAM,OAAO,gBAAgB,aAAa,iBAAiB;AAE3D,QAAI,SAAS,aAAa;AACzB,aAAO,YAAY,aAAa;AAAA,IACjC;AAEA,QAAI,YAAyC;AAE7C,aAAS,gBAAgB,SAAoB;AAC5C,oBAAc,IAAI,IAAI,SAAS;AAC/B,YAAM,WAAW,UAAU,IAAI,QAAQ,MAAM;AAC7C,YAAM,WAAW,UAAU,OAAO,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE;AAC5D,UAAI,CAAC,UAAU,QAAQ;AACtB,kBAAU,OAAO,QAAQ,MAAM;AAAA,MAChC,OAAO;AACN,kBAAU,IAAI,QAAQ,QAAQ,QAAQ;AAAA,MACvC;AACA,YAAM,SAAS,UAAU,IAAI,QAAQ,IAAI;AACzC,YAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE;AACxD,UAAI,CAAC,QAAQ,QAAQ;AACpB,kBAAU,OAAO,QAAQ,IAAI;AAAA,MAC9B,OAAO;AACN,kBAAU,IAAI,QAAQ,MAAM,MAAM;AAAA,MACnC;AAAA,IACD;AAEA,aAAS,eAAe,SAAoB;AAC3C,oBAAc,IAAI,IAAI,SAAS;AAE/B,UAAI,SAAS,UAAU,IAAI,OAAO;AAClC,UAAI,CAAC,QAAQ;AACZ,iBAAS,CAAC;AACV,kBAAU,IAAI,SAAS,MAAM;AAAA,MAC9B,WAAW,WAAW,UAAU,IAAI,OAAO,GAAG;AAC7C,iBAAS,OAAO,MAAM,CAAC;AACvB,kBAAU,IAAI,SAAS,MAAM;AAAA,MAC9B;AACA,aAAO;AAAA,IACR;AAEA,aAAS,WAAW,SAAoB;AACvC,qBAAe,QAAQ,MAAM,EAAE,KAAK,OAAO;AAC3C,UAAI,QAAQ,SAAS,QAAQ,OAAQ;AACrC,qBAAe,QAAQ,IAAI,EAAE,KAAK,OAAO;AAAA,IAC1C;AAEA,eAAW,WAAW,MAAM;AAC3B,iBAAW,cAAc,gBAAgB,QAAQ,KAAK,GAAG;AACxD,mBAAW,UAAU;AAAA,MACtB;AAEA,iBAAW,CAAC,MAAM,IAAI,KAAK,gBAAgB,QAAQ,OAAO,GAAG;AAC5D,wBAAgB,IAAI;AACpB,mBAAW,IAAI;AAAA,MAChB;AAEA,iBAAW,QAAQ,gBAAgB,QAAQ,OAAO,GAAG;AACpD,wBAAgB,IAAI;AAAA,MACrB;AAAA,IACD;AAGA,WAAO,aAAa;AAAA,EACrB,CAAC;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FRAME_MS_60HZ } from "../../../constants.mjs";
|
|
1
2
|
import { EASINGS } from "../../../primitives/easings.mjs";
|
|
2
3
|
import { Vec } from "../../../primitives/Vec.mjs";
|
|
3
4
|
const EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX = 1e3;
|
|
@@ -40,10 +41,13 @@ class EdgeScrollManager {
|
|
|
40
41
|
this._edgeScrollDuration / (editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)
|
|
41
42
|
)
|
|
42
43
|
) : 1;
|
|
43
|
-
this.moveCameraWhenCloseToEdge(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
this.moveCameraWhenCloseToEdge(
|
|
45
|
+
{
|
|
46
|
+
x: edgeScrollProximityFactor.x * eased,
|
|
47
|
+
y: edgeScrollProximityFactor.y * eased
|
|
48
|
+
},
|
|
49
|
+
elapsed
|
|
50
|
+
);
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -90,14 +94,14 @@ class EdgeScrollManager {
|
|
|
90
94
|
* Moves the camera when the mouse is close to the edge of the screen.
|
|
91
95
|
* @public
|
|
92
96
|
*/
|
|
93
|
-
moveCameraWhenCloseToEdge(proximityFactor) {
|
|
97
|
+
moveCameraWhenCloseToEdge(proximityFactor, elapsed) {
|
|
94
98
|
if (proximityFactor.x === 0 && proximityFactor.y === 0) return;
|
|
95
99
|
const { editor } = this;
|
|
96
100
|
const screenBounds = editor.getViewportScreenBounds();
|
|
97
101
|
const screenSizeFactorX = screenBounds.w < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX ? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR : 1;
|
|
98
102
|
const screenSizeFactorY = screenBounds.h < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX ? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR : 1;
|
|
99
103
|
const zoomLevel = editor.getZoomLevel();
|
|
100
|
-
const pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed;
|
|
104
|
+
const pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed * (elapsed / FRAME_MS_60HZ);
|
|
101
105
|
const scrollDeltaX = pxSpeed * proximityFactor.x * screenSizeFactorX / zoomLevel;
|
|
102
106
|
const scrollDeltaY = pxSpeed * proximityFactor.y * screenSizeFactorY / zoomLevel;
|
|
103
107
|
const { x, y, z } = editor.getCamera();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts"],
|
|
4
|
-
"sourcesContent": ["import { EASINGS } from '../../../primitives/easings'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\n\n// Tuned for touch/small-screen feel; adjust together with coarsePointerWidth.\nconst EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX = 1000\nconst EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR = 0.612\n\n/** @public */\nexport class EdgeScrollManager {\n\tconstructor(public editor: Editor) {}\n\n\tprivate _isEdgeScrolling = false\n\tprivate _edgeScrollDuration = -1\n\n\tgetIsEdgeScrolling() {\n\t\treturn this._isEdgeScrolling\n\t}\n\n\t/**\n\t * Update the camera position when the mouse is close to the edge of the screen.\n\t * Run this on every tick when in a state where edge scrolling is enabled.\n\t *\n\t * @public\n\t */\n\tupdateEdgeScrolling(elapsed: number) {\n\t\tconst { editor } = this\n\n\t\tif (editor.getCameraOptions().isLocked) return\n\n\t\tconst edgeScrollProximityFactor = this.getEdgeScroll()\n\t\tif (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {\n\t\t\tif (this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = false\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t} else {\n\t\t\tif (!this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = true\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t\tthis._edgeScrollDuration += elapsed\n\t\t\tif (this._edgeScrollDuration > editor.options.edgeScrollDelay) {\n\t\t\t\tconst eased =\n\t\t\t\t\teditor.options.edgeScrollEaseDuration > 0\n\t\t\t\t\t\t? EASINGS.easeInCubic(\n\t\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tthis._edgeScrollDuration /\n\t\t\t\t\t\t\t\t\t\t(editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: 1\n\t\t\t\tthis.moveCameraWhenCloseToEdge({\n\t\t\t\t\tx: edgeScrollProximityFactor.x * eased,\n\t\t\t\t\ty: edgeScrollProximityFactor.y * eased,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to get the scroll proximity factor for a given position.\n\t * @param position - The mouse position on the axis.\n\t * @param dimension - The component dimension on the axis.\n\t * @param isCoarse - Whether the pointer is coarse.\n\t * @param insetStart - Whether the pointer is inset at the start of the axis.\n\t * @param insetEnd - Whether the pointer is inset at the end of the axis.\n\t * @internal\n\t */\n\tprivate getEdgeProximityFactors(\n\t\tposition: number,\n\t\tdimension: number,\n\t\tisCoarse: boolean,\n\t\tinsetStart: boolean,\n\t\tinsetEnd: boolean\n\t) {\n\t\tconst { editor } = this\n\t\tconst dist = editor.options.edgeScrollDistance\n\t\tconst pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width\n\t\tconst pMin = position - pw\n\t\tconst pMax = position + pw\n\t\tconst min = insetStart ? 0 : dist\n\t\tconst max = insetEnd ? dimension : dimension - dist\n\t\tif (pMin < min) {\n\t\t\treturn Math.min(1, (min - pMin) / dist)\n\t\t} else if (pMax > max) {\n\t\t\treturn -Math.min(1, (pMax - max) / dist)\n\t\t}\n\t\treturn 0\n\t}\n\n\tprivate getEdgeScroll() {\n\t\tconst { editor } = this\n\t\tconst { x, y } = editor.inputs.getCurrentScreenPoint()\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\tconst {\n\t\t\tisCoarsePointer,\n\t\t\tinsets: [t, r, b, l],\n\t\t} = editor.getInstanceState()\n\t\tconst proximityFactorX = this.getEdgeProximityFactors(x, screenBounds.w, isCoarsePointer, l, r)\n\t\tconst proximityFactorY = this.getEdgeProximityFactors(y, screenBounds.h, isCoarsePointer, t, b)\n\n\t\treturn {\n\t\t\tx: proximityFactorX,\n\t\t\ty: proximityFactorY,\n\t\t}\n\t}\n\n\t/**\n\t * Moves the camera when the mouse is close to the edge of the screen.\n\t * @public\n\t */\n\tprivate moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }) {\n\t\tif (proximityFactor.x === 0 && proximityFactor.y === 0) return\n\t\tconst { editor } = this\n\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\t// Determines how much the speed is affected by the screen size\n\t\tconst screenSizeFactorX =\n\t\t\tscreenBounds.w < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\t\tconst screenSizeFactorY =\n\t\t\tscreenBounds.h < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\n\t\t// Determines the base speed of the scroll\n\t\tconst zoomLevel = editor.getZoomLevel()\n\t\tconst pxSpeed
|
|
5
|
-
"mappings": "AAAA,SAAS,eAAe;AACxB,SAAS,WAAW;AAIpB,MAAM,wCAAwC;AAC9C,MAAM,wCAAwC;AAGvC,MAAM,kBAAkB;AAAA,EAC9B,YAAmB,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EAAjB;AAAA,EAEX,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EAE9B,qBAAqB;AACpB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,oBAAoB,SAAiB;AACpC,UAAM,EAAE,OAAO,IAAI;AAEnB,QAAI,OAAO,iBAAiB,EAAE,SAAU;AAExC,UAAM,4BAA4B,KAAK,cAAc;AACrD,QAAI,0BAA0B,MAAM,KAAK,0BAA0B,MAAM,GAAG;AAC3E,UAAI,KAAK,kBAAkB;AAC1B,aAAK,mBAAmB;AACxB,aAAK,sBAAsB;AAAA,MAC5B;AAAA,IACD,OAAO;AACN,UAAI,CAAC,KAAK,kBAAkB;AAC3B,aAAK,mBAAmB;AACxB,aAAK,sBAAsB;AAAA,MAC5B;AACA,WAAK,uBAAuB;AAC5B,UAAI,KAAK,sBAAsB,OAAO,QAAQ,iBAAiB;AAC9D,cAAM,QACL,OAAO,QAAQ,yBAAyB,IACrC,QAAQ;AAAA,UACR,KAAK;AAAA,YACJ;AAAA,YACA,KAAK,uBACH,OAAO,QAAQ,kBAAkB,OAAO,QAAQ;AAAA,UACnD;AAAA,QACD,IACC;AACJ,aAAK,
|
|
4
|
+
"sourcesContent": ["import { FRAME_MS_60HZ } from '../../../constants'\nimport { EASINGS } from '../../../primitives/easings'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\n\n// Tuned for touch/small-screen feel; adjust together with coarsePointerWidth.\nconst EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX = 1000\nconst EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR = 0.612\n\n/** @public */\nexport class EdgeScrollManager {\n\tconstructor(public editor: Editor) {}\n\n\tprivate _isEdgeScrolling = false\n\tprivate _edgeScrollDuration = -1\n\n\tgetIsEdgeScrolling() {\n\t\treturn this._isEdgeScrolling\n\t}\n\n\t/**\n\t * Update the camera position when the mouse is close to the edge of the screen.\n\t * Run this on every tick when in a state where edge scrolling is enabled.\n\t *\n\t * @public\n\t */\n\tupdateEdgeScrolling(elapsed: number) {\n\t\tconst { editor } = this\n\n\t\tif (editor.getCameraOptions().isLocked) return\n\n\t\tconst edgeScrollProximityFactor = this.getEdgeScroll()\n\t\tif (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {\n\t\t\tif (this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = false\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t} else {\n\t\t\tif (!this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = true\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t\tthis._edgeScrollDuration += elapsed\n\t\t\tif (this._edgeScrollDuration > editor.options.edgeScrollDelay) {\n\t\t\t\tconst eased =\n\t\t\t\t\teditor.options.edgeScrollEaseDuration > 0\n\t\t\t\t\t\t? EASINGS.easeInCubic(\n\t\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tthis._edgeScrollDuration /\n\t\t\t\t\t\t\t\t\t\t(editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: 1\n\t\t\t\tthis.moveCameraWhenCloseToEdge(\n\t\t\t\t\t{\n\t\t\t\t\t\tx: edgeScrollProximityFactor.x * eased,\n\t\t\t\t\t\ty: edgeScrollProximityFactor.y * eased,\n\t\t\t\t\t},\n\t\t\t\t\telapsed\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to get the scroll proximity factor for a given position.\n\t * @param position - The mouse position on the axis.\n\t * @param dimension - The component dimension on the axis.\n\t * @param isCoarse - Whether the pointer is coarse.\n\t * @param insetStart - Whether the pointer is inset at the start of the axis.\n\t * @param insetEnd - Whether the pointer is inset at the end of the axis.\n\t * @internal\n\t */\n\tprivate getEdgeProximityFactors(\n\t\tposition: number,\n\t\tdimension: number,\n\t\tisCoarse: boolean,\n\t\tinsetStart: boolean,\n\t\tinsetEnd: boolean\n\t) {\n\t\tconst { editor } = this\n\t\tconst dist = editor.options.edgeScrollDistance\n\t\tconst pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width\n\t\tconst pMin = position - pw\n\t\tconst pMax = position + pw\n\t\tconst min = insetStart ? 0 : dist\n\t\tconst max = insetEnd ? dimension : dimension - dist\n\t\tif (pMin < min) {\n\t\t\treturn Math.min(1, (min - pMin) / dist)\n\t\t} else if (pMax > max) {\n\t\t\treturn -Math.min(1, (pMax - max) / dist)\n\t\t}\n\t\treturn 0\n\t}\n\n\tprivate getEdgeScroll() {\n\t\tconst { editor } = this\n\t\tconst { x, y } = editor.inputs.getCurrentScreenPoint()\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\tconst {\n\t\t\tisCoarsePointer,\n\t\t\tinsets: [t, r, b, l],\n\t\t} = editor.getInstanceState()\n\t\tconst proximityFactorX = this.getEdgeProximityFactors(x, screenBounds.w, isCoarsePointer, l, r)\n\t\tconst proximityFactorY = this.getEdgeProximityFactors(y, screenBounds.h, isCoarsePointer, t, b)\n\n\t\treturn {\n\t\t\tx: proximityFactorX,\n\t\t\ty: proximityFactorY,\n\t\t}\n\t}\n\n\t/**\n\t * Moves the camera when the mouse is close to the edge of the screen.\n\t * @public\n\t */\n\tprivate moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }, elapsed: number) {\n\t\tif (proximityFactor.x === 0 && proximityFactor.y === 0) return\n\t\tconst { editor } = this\n\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\t// Determines how much the speed is affected by the screen size\n\t\tconst screenSizeFactorX =\n\t\t\tscreenBounds.w < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\t\tconst screenSizeFactorY =\n\t\t\tscreenBounds.h < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\n\t\t// Determines the base speed of the scroll. edgeScrollSpeed is px per 60 Hz frame, so scale\n\t\t// by elapsed time or a 120 Hz display scrolls twice as fast.\n\t\tconst zoomLevel = editor.getZoomLevel()\n\t\tconst pxSpeed =\n\t\t\teditor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed * (elapsed / FRAME_MS_60HZ)\n\t\tconst scrollDeltaX = (pxSpeed * proximityFactor.x * screenSizeFactorX) / zoomLevel\n\t\tconst scrollDeltaY = (pxSpeed * proximityFactor.y * screenSizeFactorY) / zoomLevel\n\n\t\t// update the camera\n\t\tconst { x, y, z } = editor.getCamera()\n\t\teditor.setCamera(new Vec(x + scrollDeltaX, y + scrollDeltaY, z))\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -41,14 +41,18 @@ class ThemeManager {
|
|
|
41
41
|
getCurrentTheme() {
|
|
42
42
|
return this._themes.get()[this.getCurrentThemeId()];
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Set the current theme by id. The theme must have been previously registered; an
|
|
46
|
+
* unregistered id is ignored and the current theme is kept.
|
|
47
|
+
*/
|
|
45
48
|
setCurrentTheme(id) {
|
|
46
|
-
if (
|
|
47
|
-
if (
|
|
49
|
+
if (!(id in this._themes.get())) {
|
|
50
|
+
if (process.env.NODE_ENV !== "production") {
|
|
48
51
|
console.warn(
|
|
49
52
|
`Theme '${id}' not found. Available themes: ${Object.keys(this._themes.get()).join(", ")}`
|
|
50
53
|
);
|
|
51
54
|
}
|
|
55
|
+
return;
|
|
52
56
|
}
|
|
53
57
|
this._currentThemeId.set(id);
|
|
54
58
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/editor/managers/ThemeManager/ThemeManager.ts"],
|
|
4
|
-
"sourcesContent": ["import { Atom, atom, computed } from '@tldraw/state'\nimport { TLTheme, TLThemeId, TLThemes } from '@tldraw/tlschema'\nimport { structuredClone } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { DEFAULT_THEME } from './defaultThemes'\n\n/**\n * Resolve a partial set of user-provided themes into a complete `TLThemes`\n * record by merging with `DEFAULT_THEME`. The result is suitable for passing to\n * `registerColorsFromThemes`, `registerFontsFromThemes`, and the\n * `ThemeManager` constructor.\n *\n * @public\n */\nexport function resolveThemes(themes?: Partial<TLThemes>): TLThemes {\n\treturn { default: DEFAULT_THEME, ...themes } as TLThemes\n}\n\n/**\n * Manages the editor's color themes.\n *\n * Stores named theme definitions (each containing light and dark color palettes\n * alongside shared properties like font size). The current theme is resolved by\n * combining the current theme name with the user's color mode preference.\n *\n * **Terminology:**\n * - **Theme** (`TLTheme`): A named set of colors and typographic values for both light and dark modes.\n * - **Color mode** (`'light' | 'dark'`): The resolved appearance mode, derived from the user's\n * `colorScheme` preference (`'light' | 'dark' | 'system'`). Access via `getColorMode()`.\n *\n * @public\n */\nexport class ThemeManager {\n\tprivate readonly _themes: Atom<TLThemes>\n\tprivate readonly _currentThemeId: Atom<TLThemeId>\n\n\tconstructor(\n\t\tprivate readonly editor: Editor,\n\t\toptions: {\n\t\t\tthemes: TLThemes\n\t\t\tinitial: TLThemeId\n\t\t}\n\t) {\n\t\tthis._themes = atom('ThemeManager._definitions', options.themes)\n\t\tthis._currentThemeId = atom('ThemeManager._currentThemeName', options.initial)\n\t}\n\n\t/** Get the current color mode based on the user's dark mode preference. */\n\t@computed getColorMode(): 'light' | 'dark' {\n\t\treturn this.editor.user.getIsDarkMode() ? 'dark' : 'light'\n\t}\n\n\t/** Get all registered theme definitions. */\n\tgetThemes(): TLThemes {\n\t\treturn this._themes.get()\n\t}\n\n\t/** Get a single theme definition by id. */\n\tgetTheme(id: TLThemeId): TLTheme | undefined {\n\t\treturn this._themes.get()[id]\n\t}\n\n\t/** Get the id of the current theme. */\n\tgetCurrentThemeId(): TLThemeId {\n\t\treturn this._currentThemeId.get()\n\t}\n\n\tgetCurrentTheme(): TLTheme {\n\t\treturn this._themes.get()[this.getCurrentThemeId()]!\n\t}\n\n\t
|
|
5
|
-
"mappings": ";;;;;;;;;;AAAA,SAAe,MAAM,gBAAgB;AAErC,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAUvB,SAAS,cAAc,QAAsC;AACnE,SAAO,EAAE,SAAS,eAAe,GAAG,OAAO;AAC5C;AAgBO,MAAM,aAAa;AAAA,EAIzB,YACkB,QACjB,SAIC;AALgB;AAMjB,SAAK,UAAU,KAAK,6BAA6B,QAAQ,MAAM;AAC/D,SAAK,kBAAkB,KAAK,kCAAkC,QAAQ,OAAO;AAAA,EAC9E;AAAA,EARkB;AAAA,EAJD;AAAA,EACA;AAAA,EAcP,eAAiC;AAC1C,WAAO,KAAK,OAAO,KAAK,cAAc,IAAI,SAAS;AAAA,EACpD;AAAA;AAAA,EAGA,YAAsB;AACrB,WAAO,KAAK,QAAQ,IAAI;AAAA,EACzB;AAAA;AAAA,EAGA,SAAS,IAAoC;AAC5C,WAAO,KAAK,QAAQ,IAAI,EAAE,EAAE;AAAA,EAC7B;AAAA;AAAA,EAGA,oBAA+B;AAC9B,WAAO,KAAK,gBAAgB,IAAI;AAAA,EACjC;AAAA,EAEA,kBAA2B;AAC1B,WAAO,KAAK,QAAQ,IAAI,EAAE,KAAK,kBAAkB,CAAC;AAAA,EACnD;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { Atom, atom, computed } from '@tldraw/state'\nimport { TLTheme, TLThemeId, TLThemes } from '@tldraw/tlschema'\nimport { structuredClone } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { DEFAULT_THEME } from './defaultThemes'\n\n/**\n * Resolve a partial set of user-provided themes into a complete `TLThemes`\n * record by merging with `DEFAULT_THEME`. The result is suitable for passing to\n * `registerColorsFromThemes`, `registerFontsFromThemes`, and the\n * `ThemeManager` constructor.\n *\n * @public\n */\nexport function resolveThemes(themes?: Partial<TLThemes>): TLThemes {\n\treturn { default: DEFAULT_THEME, ...themes } as TLThemes\n}\n\n/**\n * Manages the editor's color themes.\n *\n * Stores named theme definitions (each containing light and dark color palettes\n * alongside shared properties like font size). The current theme is resolved by\n * combining the current theme name with the user's color mode preference.\n *\n * **Terminology:**\n * - **Theme** (`TLTheme`): A named set of colors and typographic values for both light and dark modes.\n * - **Color mode** (`'light' | 'dark'`): The resolved appearance mode, derived from the user's\n * `colorScheme` preference (`'light' | 'dark' | 'system'`). Access via `getColorMode()`.\n *\n * @public\n */\nexport class ThemeManager {\n\tprivate readonly _themes: Atom<TLThemes>\n\tprivate readonly _currentThemeId: Atom<TLThemeId>\n\n\tconstructor(\n\t\tprivate readonly editor: Editor,\n\t\toptions: {\n\t\t\tthemes: TLThemes\n\t\t\tinitial: TLThemeId\n\t\t}\n\t) {\n\t\tthis._themes = atom('ThemeManager._definitions', options.themes)\n\t\tthis._currentThemeId = atom('ThemeManager._currentThemeName', options.initial)\n\t}\n\n\t/** Get the current color mode based on the user's dark mode preference. */\n\t@computed getColorMode(): 'light' | 'dark' {\n\t\treturn this.editor.user.getIsDarkMode() ? 'dark' : 'light'\n\t}\n\n\t/** Get all registered theme definitions. */\n\tgetThemes(): TLThemes {\n\t\treturn this._themes.get()\n\t}\n\n\t/** Get a single theme definition by id. */\n\tgetTheme(id: TLThemeId): TLTheme | undefined {\n\t\treturn this._themes.get()[id]\n\t}\n\n\t/** Get the id of the current theme. */\n\tgetCurrentThemeId(): TLThemeId {\n\t\treturn this._currentThemeId.get()\n\t}\n\n\tgetCurrentTheme(): TLTheme {\n\t\treturn this._themes.get()[this.getCurrentThemeId()]!\n\t}\n\n\t/**\n\t * Set the current theme by id. The theme must have been previously registered; an\n\t * unregistered id is ignored and the current theme is kept.\n\t */\n\tsetCurrentTheme(id: TLThemeId): void {\n\t\t// Storing an unregistered id would make getCurrentTheme() return\n\t\t// undefined despite its TLTheme return type.\n\t\tif (!(id in this._themes.get())) {\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Theme '${id}' not found. Available themes: ${Object.keys(this._themes.get()).join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tthis._currentThemeId.set(id)\n\t}\n\n\t/** Replace all theme definitions, or update them via a callback that receives a deep copy. */\n\tupdateThemes(themes: TLThemes | ((themes: TLThemes) => TLThemes)): void {\n\t\tthis._themes.update((prev) => {\n\t\t\tconst next = typeof themes === 'function' ? themes(structuredClone(prev)) : themes\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\tif (!('default' in next)) {\n\t\t\t\t\tconsole.warn(\"The 'default' theme cannot be removed.\")\n\t\t\t\t\treturn prev\n\t\t\t\t}\n\t\t\t}\n\t\t\t// If the current theme was removed, fall back to 'default'\n\t\t\tif (!(this._currentThemeId.get() in next)) {\n\t\t\t\tthis._currentThemeId.set('default')\n\t\t\t}\n\t\t\treturn next\n\t\t})\n\t}\n\n\t/** Register or update a named theme definition. */\n\tupdateTheme(theme: TLTheme): void {\n\t\tthis._themes.update((prev) => ({\n\t\t\t...prev,\n\t\t\t[theme.id]: theme,\n\t\t}))\n\t}\n\n\t/** Clean up any resources held by the manager. */\n\tdispose() {\n\t\t// currently no subscriptions to tear down, but here for consistency\n\t\t// with the manager pattern and for future use\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;AAAA,SAAe,MAAM,gBAAgB;AAErC,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAUvB,SAAS,cAAc,QAAsC;AACnE,SAAO,EAAE,SAAS,eAAe,GAAG,OAAO;AAC5C;AAgBO,MAAM,aAAa;AAAA,EAIzB,YACkB,QACjB,SAIC;AALgB;AAMjB,SAAK,UAAU,KAAK,6BAA6B,QAAQ,MAAM;AAC/D,SAAK,kBAAkB,KAAK,kCAAkC,QAAQ,OAAO;AAAA,EAC9E;AAAA,EARkB;AAAA,EAJD;AAAA,EACA;AAAA,EAcP,eAAiC;AAC1C,WAAO,KAAK,OAAO,KAAK,cAAc,IAAI,SAAS;AAAA,EACpD;AAAA;AAAA,EAGA,YAAsB;AACrB,WAAO,KAAK,QAAQ,IAAI;AAAA,EACzB;AAAA;AAAA,EAGA,SAAS,IAAoC;AAC5C,WAAO,KAAK,QAAQ,IAAI,EAAE,EAAE;AAAA,EAC7B;AAAA;AAAA,EAGA,oBAA+B;AAC9B,WAAO,KAAK,gBAAgB,IAAI;AAAA,EACjC;AAAA,EAEA,kBAA2B;AAC1B,WAAO,KAAK,QAAQ,IAAI,EAAE,KAAK,kBAAkB,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,IAAqB;AAGpC,QAAI,EAAE,MAAM,KAAK,QAAQ,IAAI,IAAI;AAChC,UAAI,QAAQ,IAAI,aAAa,cAAc;AAC1C,gBAAQ;AAAA,UACP,UAAU,EAAE,kCAAkC,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,QACzF;AAAA,MACD;AACA;AAAA,IACD;AAEA,SAAK,gBAAgB,IAAI,EAAE;AAAA,EAC5B;AAAA;AAAA,EAGA,aAAa,QAA2D;AACvE,SAAK,QAAQ,OAAO,CAAC,SAAS;AAC7B,YAAM,OAAO,OAAO,WAAW,aAAa,OAAO,gBAAgB,IAAI,CAAC,IAAI;AAC5E,UAAI,QAAQ,IAAI,aAAa,cAAc;AAC1C,YAAI,EAAE,aAAa,OAAO;AACzB,kBAAQ,KAAK,wCAAwC;AACrD,iBAAO;AAAA,QACR;AAAA,MACD;AAEA,UAAI,EAAE,KAAK,gBAAgB,IAAI,KAAK,OAAO;AAC1C,aAAK,gBAAgB,IAAI,SAAS;AAAA,MACnC;AACA,aAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,YAAY,OAAsB;AACjC,SAAK,QAAQ,OAAO,CAAC,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH,CAAC,MAAM,EAAE,GAAG;AAAA,IACb,EAAE;AAAA,EACH;AAAA;AAAA,EAGA,UAAU;AAAA,EAGV;AACD;AAzEW;AAAA,EAAT;AAAA,GAhBW,aAgBF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -30,12 +30,20 @@ class GroupShapeUtil extends ShapeUtil {
|
|
|
30
30
|
if (children.length === 0) {
|
|
31
31
|
return new Rectangle2d({ width: 1, height: 1, isFilled: false });
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
const geometries = [];
|
|
34
|
+
for (const childId of children) {
|
|
35
|
+
const child = this.editor.getShape(childId);
|
|
36
|
+
if (!child) continue;
|
|
37
|
+
const geometry = this.editor.getShapeGeometry(childId);
|
|
38
|
+
if (!geometry) continue;
|
|
39
|
+
const transform = this.editor.getShapeLocalTransform(child);
|
|
40
|
+
if (!transform) continue;
|
|
41
|
+
geometries.push(geometry.transform(transform, { isLabel: false }));
|
|
42
|
+
}
|
|
43
|
+
if (geometries.length === 0) {
|
|
44
|
+
return new Rectangle2d({ width: 1, height: 1, isFilled: false });
|
|
45
|
+
}
|
|
46
|
+
return new Group2d({ children: geometries });
|
|
39
47
|
}
|
|
40
48
|
component(shape) {
|
|
41
49
|
const isErasing = this.editor.getErasingShapeIds().includes(shape.id);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/editor/shapes/group/GroupShapeUtil.tsx"],
|
|
4
|
-
"sourcesContent": ["import { TLGroupShape, groupShapeMigrations, groupShapeProps } from '@tldraw/tlschema'\nimport { SVGContainer } from '../../../components/SVGContainer'\nimport { Geometry2d } from '../../../primitives/geometry/Geometry2d'\nimport { Group2d } from '../../../primitives/geometry/Group2d'\nimport { Rectangle2d } from '../../../primitives/geometry/Rectangle2d'\nimport { ShapeUtil } from '../ShapeUtil'\nimport { getPerfectDashProps } from '../shared/getPerfectDashProps'\nimport { DashedOutlineBox } from './DashedOutlineBox'\n\n/** @public */\nexport class GroupShapeUtil extends ShapeUtil<TLGroupShape> {\n\tstatic override type = 'group' as const\n\tstatic override props = groupShapeProps\n\tstatic override migrations = groupShapeMigrations\n\n\toverride hideSelectionBoundsFg(shape: TLGroupShape) {\n\t\treturn true\n\t}\n\n\toverride canBind() {\n\t\treturn false\n\t}\n\n\tcanResize() {\n\t\treturn true\n\t}\n\n\tcanResizeChildren() {\n\t\treturn true\n\t}\n\n\tgetDefaultProps(): TLGroupShape['props'] {\n\t\treturn {}\n\t}\n\n\tgetGeometry(shape: TLGroupShape): Geometry2d {\n\t\tconst children = this.editor.getSortedChildIdsForParent(shape.id)\n\t\tif (children.length === 0) {\n\t\t\treturn new Rectangle2d({ width: 1, height: 1, isFilled: false })\n\t\t}\n\n\t\
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["import { TLGroupShape, groupShapeMigrations, groupShapeProps } from '@tldraw/tlschema'\nimport { SVGContainer } from '../../../components/SVGContainer'\nimport { Geometry2d } from '../../../primitives/geometry/Geometry2d'\nimport { Group2d } from '../../../primitives/geometry/Group2d'\nimport { Rectangle2d } from '../../../primitives/geometry/Rectangle2d'\nimport { ShapeUtil } from '../ShapeUtil'\nimport { getPerfectDashProps } from '../shared/getPerfectDashProps'\nimport { DashedOutlineBox } from './DashedOutlineBox'\n\n/** @public */\nexport class GroupShapeUtil extends ShapeUtil<TLGroupShape> {\n\tstatic override type = 'group' as const\n\tstatic override props = groupShapeProps\n\tstatic override migrations = groupShapeMigrations\n\n\toverride hideSelectionBoundsFg(shape: TLGroupShape) {\n\t\treturn true\n\t}\n\n\toverride canBind() {\n\t\treturn false\n\t}\n\n\tcanResize() {\n\t\treturn true\n\t}\n\n\tcanResizeChildren() {\n\t\treturn true\n\t}\n\n\tgetDefaultProps(): TLGroupShape['props'] {\n\t\treturn {}\n\t}\n\n\tgetGeometry(shape: TLGroupShape): Geometry2d {\n\t\tconst children = this.editor.getSortedChildIdsForParent(shape.id)\n\t\tif (children.length === 0) {\n\t\t\treturn new Rectangle2d({ width: 1, height: 1, isFilled: false })\n\t\t}\n\n\t\t// A child id can outlive its record. The children index and the geometry cache are separate\n\t\t// derivations, so inside a transaction that removes a child the index can still name it while\n\t\t// `getShapeGeometry` already answers nothing \u2014 its `!` hides that it returns `undefined` for a\n\t\t// record the store no longer holds. Asserting instead of checking threw a `TypeError` out of a\n\t\t// reactive recompute, where no caller could catch it.\n\t\tconst geometries: Geometry2d[] = []\n\t\tfor (const childId of children) {\n\t\t\tconst child = this.editor.getShape(childId)\n\t\t\tif (!child) continue\n\t\t\tconst geometry = this.editor.getShapeGeometry(childId)\n\t\t\tif (!geometry) continue\n\t\t\tconst transform = this.editor.getShapeLocalTransform(child)\n\t\t\tif (!transform) continue\n\t\t\tgeometries.push(geometry.transform(transform, { isLabel: false }))\n\t\t}\n\n\t\t// Every child resolved to nothing, so there is no meaningful extent to report. Matches the\n\t\t// no-children case above rather than building an empty `Group2d`, whose bounds are `NaN`.\n\t\tif (geometries.length === 0) {\n\t\t\treturn new Rectangle2d({ width: 1, height: 1, isFilled: false })\n\t\t}\n\n\t\treturn new Group2d({ children: geometries })\n\t}\n\n\tcomponent(shape: TLGroupShape) {\n\t\tconst isErasing = this.editor.getErasingShapeIds().includes(shape.id)\n\n\t\tconst { hintingShapeIds } = this.editor.getCurrentPageState()\n\t\tconst isHintingOtherGroup =\n\t\t\thintingShapeIds.length > 0 &&\n\t\t\thintingShapeIds.some(\n\t\t\t\t(id) => id !== shape.id && this.editor.isShapeOfType(this.editor.getShape(id)!, 'group')\n\t\t\t)\n\n\t\tconst isFocused = this.editor.getCurrentPageState().focusedGroupId !== shape.id\n\n\t\tif (\n\t\t\t!isErasing && // always show the outline while we're erasing the group\n\t\t\t// show the outline while the group is focused unless something outside of the group is being hinted\n\t\t\t// this happens dropping shapes from a group onto some outside group\n\t\t\t(isFocused || isHintingOtherGroup)\n\t\t) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\n\t\treturn (\n\t\t\t<SVGContainer>\n\t\t\t\t<DashedOutlineBox className=\"tl-group\" bounds={bounds} />\n\t\t\t</SVGContainer>\n\t\t)\n\t}\n\n\toverride getIndicatorPath(shape: TLGroupShape): Path2D {\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst zoomLevel = this.editor.getEfficientZoomLevel()\n\t\tconst path = new Path2D()\n\n\t\tfor (const side of bounds.sides) {\n\t\t\tconst [start, end] = side\n\t\t\tconst length = start.dist(end)\n\t\t\tif (length <= 0) continue\n\n\t\t\tconst { strokeDasharray, strokeDashoffset } = getPerfectDashProps(length, 1 / zoomLevel, {\n\t\t\t\tstyle: 'dashed',\n\t\t\t\tlengthRatio: 4,\n\t\t\t})\n\n\t\t\tif (strokeDasharray === 'none') {\n\t\t\t\tpath.moveTo(start.x, start.y)\n\t\t\t\tpath.lineTo(end.x, end.y)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst [dashLength, gapLength] = strokeDasharray.split(' ').map(Number)\n\t\t\tconst dashOffset = Number(strokeDashoffset)\n\t\t\tconst period = dashLength + gapLength\n\t\t\tif (!Number.isFinite(period) || period <= 0) continue\n\n\t\t\tconst dx = (end.x - start.x) / length\n\t\t\tconst dy = (end.y - start.y) / length\n\n\t\t\tfor (let dashStart = -dashOffset; dashStart < length; dashStart += period) {\n\t\t\t\tconst dashEnd = Math.min(length, dashStart + dashLength)\n\t\t\t\tconst clippedDashStart = Math.max(0, dashStart)\n\t\t\t\tif (dashEnd <= clippedDashStart) continue\n\n\t\t\t\tpath.moveTo(start.x + dx * clippedDashStart, start.y + dy * clippedDashStart)\n\t\t\t\tpath.lineTo(start.x + dx * dashEnd, start.y + dy * dashEnd)\n\t\t\t}\n\t\t}\n\n\t\treturn path\n\t}\n\n\toverride onChildrenChange(group: TLGroupShape) {\n\t\tconst children = this.editor.getSortedChildIdsForParent(group.id)\n\t\tif (children.length === 0) {\n\t\t\tif (this.editor.getCurrentPageState().focusedGroupId === group.id) {\n\t\t\t\tthis.editor.popFocusedGroupId()\n\t\t\t}\n\t\t\tthis.editor.deleteShapes([group.id])\n\t\t\treturn\n\t\t} else if (children.length === 1) {\n\t\t\tif (this.editor.getCurrentPageState().focusedGroupId === group.id) {\n\t\t\t\tthis.editor.popFocusedGroupId()\n\t\t\t}\n\t\t\tthis.editor.reparentShapes(children, group.parentId, group.index)\n\t\t\tthis.editor.deleteShapes([group.id])\n\t\t\treturn\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
|
@@ -8,14 +8,19 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
8
8
|
if (kind && result) __defProp(target, key, result);
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
|
-
import { bind, sleep } from "@tldraw/utils";
|
|
11
|
+
import { bind, noop, promiseWithResolve, sleep } from "@tldraw/utils";
|
|
12
12
|
class ExportDelay {
|
|
13
13
|
constructor(maxDelayTimeMs) {
|
|
14
14
|
this.maxDelayTimeMs = maxDelayTimeMs;
|
|
15
|
+
this.failure.catch(noop);
|
|
15
16
|
}
|
|
16
17
|
maxDelayTimeMs;
|
|
17
18
|
isResolved = false;
|
|
18
19
|
promisesToWaitFor = [];
|
|
20
|
+
failure = promiseWithResolve();
|
|
21
|
+
fail(error) {
|
|
22
|
+
this.failure.reject(error);
|
|
23
|
+
}
|
|
19
24
|
waitUntil(promise) {
|
|
20
25
|
if (this.isResolved) {
|
|
21
26
|
throw new Error(
|
|
@@ -37,13 +42,16 @@ class ExportDelay {
|
|
|
37
42
|
async resolve() {
|
|
38
43
|
const timeoutPromise = sleep(this.maxDelayTimeMs).then(() => "timeout");
|
|
39
44
|
const resolvePromise = this.resolvePromises().then(() => "resolved");
|
|
40
|
-
const result = await Promise.race([timeoutPromise, resolvePromise]);
|
|
45
|
+
const result = await Promise.race([timeoutPromise, resolvePromise, this.failure]);
|
|
41
46
|
if (result === "timeout") {
|
|
42
|
-
console.warn(
|
|
47
|
+
console.warn(`[tldraw] Export delay timed out after ${this.maxDelayTimeMs}ms`);
|
|
43
48
|
}
|
|
44
49
|
this.isResolved = true;
|
|
45
50
|
}
|
|
46
51
|
}
|
|
52
|
+
__decorateClass([
|
|
53
|
+
bind
|
|
54
|
+
], ExportDelay.prototype, "fail", 1);
|
|
47
55
|
__decorateClass([
|
|
48
56
|
bind
|
|
49
57
|
], ExportDelay.prototype, "waitUntil", 1);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/exports/ExportDelay.tsx"],
|
|
4
|
-
"sourcesContent": ["import { bind, sleep } from '@tldraw/utils'\n\n/**\n * Export delay is a helper class that allows you to wait for a set of promises to resolve before\n * proceeding with an export. Over time, promises can be added by calling `waitUntil`.\n *\n * When `resolve` is called, we'll wait for all the promises already added (and any new ones added\n * in the mean time) to resolve before proceeding. The class is designed to be used once: after\n * `resolve` has been called and finished, new promises cannot be added.\n */\nexport class ExportDelay {\n\tprivate isResolved = false\n\tprivate readonly promisesToWaitFor: Promise<void>[] = []\n\n\tconstructor(private readonly maxDelayTimeMs: number) {}\n\n\t@bind waitUntil(promise: Promise<void>): void {\n\t\tif (this.isResolved) {\n\t\t\tthrow new Error(\n\t\t\t\t'Cannot `waitUntil` - the export has already been resolved. Make sure to call `waitUntil` as soon as possible during an export - ie within the first react effect after rendering.'\n\t\t\t)\n\t\t}\n\t\tthis.promisesToWaitFor.push(\n\t\t\tpromise.catch((err) => console.error('Error while waiting for export:', err))\n\t\t)\n\t}\n\n\tprivate async resolvePromises() {\n\t\tlet lastLength = null\n\t\twhile (this.promisesToWaitFor.length !== lastLength) {\n\t\t\tlastLength = this.promisesToWaitFor.length\n\t\t\tawait Promise.allSettled(this.promisesToWaitFor)\n\n\t\t\t// wait for a cycle of the event loop to allow any of those promises to add more if needed.\n\t\t\tawait sleep(0)\n\t\t}\n\t}\n\n\tasync resolve() {\n\t\tconst timeoutPromise = sleep(this.maxDelayTimeMs).then(() => 'timeout' as const)\n\t\tconst resolvePromise = this.resolvePromises().then(() => 'resolved' as const)\n\n\t\tconst result = await Promise.race([timeoutPromise, resolvePromise])\n\t\tif (result === 'timeout') {\n\t\t\tconsole.warn(
|
|
5
|
-
"mappings": ";;;;;;;;;;AAAA,SAAS,MAAM,aAAa;
|
|
4
|
+
"sourcesContent": ["import { bind, noop, promiseWithResolve, sleep } from '@tldraw/utils'\n\n/**\n * Export delay is a helper class that allows you to wait for a set of promises to resolve before\n * proceeding with an export. Over time, promises can be added by calling `waitUntil`.\n *\n * When `resolve` is called, we'll wait for all the promises already added (and any new ones added\n * in the mean time) to resolve before proceeding. The class is designed to be used once: after\n * `resolve` has been called and finished, new promises cannot be added.\n */\nexport class ExportDelay {\n\tprivate isResolved = false\n\tprivate readonly promisesToWaitFor: Promise<void>[] = []\n\tprivate readonly failure = promiseWithResolve<never>()\n\n\tconstructor(private readonly maxDelayTimeMs: number) {\n\t\t// only observed inside `resolve`, so a `fail` before then would otherwise be an unhandled rejection\n\t\tthis.failure.catch(noop)\n\t}\n\n\t/**\n\t * Abort the export: `resolve` rejects with this error instead of waiting out the delay and\n\t * snapshotting a broken svg.\n\t */\n\t@bind fail(error: unknown): void {\n\t\tthis.failure.reject(error)\n\t}\n\n\t@bind waitUntil(promise: Promise<void>): void {\n\t\tif (this.isResolved) {\n\t\t\tthrow new Error(\n\t\t\t\t'Cannot `waitUntil` - the export has already been resolved. Make sure to call `waitUntil` as soon as possible during an export - ie within the first react effect after rendering.'\n\t\t\t)\n\t\t}\n\t\tthis.promisesToWaitFor.push(\n\t\t\tpromise.catch((err) => console.error('Error while waiting for export:', err))\n\t\t)\n\t}\n\n\tprivate async resolvePromises() {\n\t\tlet lastLength = null\n\t\twhile (this.promisesToWaitFor.length !== lastLength) {\n\t\t\tlastLength = this.promisesToWaitFor.length\n\t\t\tawait Promise.allSettled(this.promisesToWaitFor)\n\n\t\t\t// wait for a cycle of the event loop to allow any of those promises to add more if needed.\n\t\t\tawait sleep(0)\n\t\t}\n\t}\n\n\tasync resolve() {\n\t\tconst timeoutPromise = sleep(this.maxDelayTimeMs).then(() => 'timeout' as const)\n\t\tconst resolvePromise = this.resolvePromises().then(() => 'resolved' as const)\n\n\t\tconst result = await Promise.race([timeoutPromise, resolvePromise, this.failure])\n\t\tif (result === 'timeout') {\n\t\t\tconsole.warn(`[tldraw] Export delay timed out after ${this.maxDelayTimeMs}ms`)\n\t\t}\n\n\t\tthis.isResolved = true\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;AAAA,SAAS,MAAM,MAAM,oBAAoB,aAAa;AAU/C,MAAM,YAAY;AAAA,EAKxB,YAA6B,gBAAwB;AAAxB;AAE5B,SAAK,QAAQ,MAAM,IAAI;AAAA,EACxB;AAAA,EAH6B;AAAA,EAJrB,aAAa;AAAA,EACJ,oBAAqC,CAAC;AAAA,EACtC,UAAU,mBAA0B;AAAA,EAW/C,KAAK,OAAsB;AAChC,SAAK,QAAQ,OAAO,KAAK;AAAA,EAC1B;AAAA,EAEM,UAAU,SAA8B;AAC7C,QAAI,KAAK,YAAY;AACpB,YAAM,IAAI;AAAA,QACT;AAAA,MACD;AAAA,IACD;AACA,SAAK,kBAAkB;AAAA,MACtB,QAAQ,MAAM,CAAC,QAAQ,QAAQ,MAAM,mCAAmC,GAAG,CAAC;AAAA,IAC7E;AAAA,EACD;AAAA,EAEA,MAAc,kBAAkB;AAC/B,QAAI,aAAa;AACjB,WAAO,KAAK,kBAAkB,WAAW,YAAY;AACpD,mBAAa,KAAK,kBAAkB;AACpC,YAAM,QAAQ,WAAW,KAAK,iBAAiB;AAG/C,YAAM,MAAM,CAAC;AAAA,IACd;AAAA,EACD;AAAA,EAEA,MAAM,UAAU;AACf,UAAM,iBAAiB,MAAM,KAAK,cAAc,EAAE,KAAK,MAAM,SAAkB;AAC/E,UAAM,iBAAiB,KAAK,gBAAgB,EAAE,KAAK,MAAM,UAAmB;AAE5E,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,gBAAgB,gBAAgB,KAAK,OAAO,CAAC;AAChF,QAAI,WAAW,WAAW;AACzB,cAAQ,KAAK,yCAAyC,KAAK,cAAc,IAAI;AAAA,IAC9E;AAEA,SAAK,aAAa;AAAA,EACnB;AACD;AArCO;AAAA,EAAL;AAAA,GAdW,YAcN;AAIA;AAAA,EAAL;AAAA,GAlBW,YAkBN;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,6 +16,9 @@ class FontEmbedder {
|
|
|
16
16
|
fontFacesPromise = null;
|
|
17
17
|
foundFontNames = /* @__PURE__ */ new Set();
|
|
18
18
|
fontFacesToEmbed = /* @__PURE__ */ new Set();
|
|
19
|
+
// per export, keyed by resolved url: parsed font faces are shared across exports through the
|
|
20
|
+
// fetch cache, so a failed fetch stored on them would never be retried
|
|
21
|
+
embeddedUrls = /* @__PURE__ */ new Map();
|
|
19
22
|
pendingPromises = [];
|
|
20
23
|
startFindingDocumentFontFaces(doc) {
|
|
21
24
|
assert(!this.fontFacesPromise, "FontEmbedder already started");
|
|
@@ -25,7 +28,7 @@ class FontEmbedder {
|
|
|
25
28
|
assert(this.fontFacesPromise, "FontEmbedder not started");
|
|
26
29
|
const fonts = parseCssFontFamilyValue(fontFamilyValue);
|
|
27
30
|
for (const font of fonts) {
|
|
28
|
-
if (this.foundFontNames.has(font))
|
|
31
|
+
if (this.foundFontNames.has(font)) continue;
|
|
29
32
|
this.foundFontNames.add(font);
|
|
30
33
|
this.pendingPromises.push(
|
|
31
34
|
this.fontFacesPromise.then((fontFaces) => {
|
|
@@ -34,8 +37,8 @@ class FontEmbedder {
|
|
|
34
37
|
if (this.fontFacesToEmbed.has(fontFace)) continue;
|
|
35
38
|
this.fontFacesToEmbed.add(fontFace);
|
|
36
39
|
for (const url of fontFace.urls) {
|
|
37
|
-
if (!url.resolved || url.
|
|
38
|
-
url.
|
|
40
|
+
if (!url.resolved || this.embeddedUrls.has(url.resolved)) continue;
|
|
41
|
+
this.embeddedUrls.set(url.resolved, resourceToDataUrl(url.resolved));
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
})
|
|
@@ -48,10 +51,10 @@ class FontEmbedder {
|
|
|
48
51
|
for (const fontFace of this.fontFacesToEmbed) {
|
|
49
52
|
let fontFaceString = `@font-face {${fontFace.fontFace}}`;
|
|
50
53
|
for (const url of fontFace.urls) {
|
|
51
|
-
if (!url.
|
|
52
|
-
const dataUrl = await url.
|
|
54
|
+
if (!url.resolved) continue;
|
|
55
|
+
const dataUrl = await this.embeddedUrls.get(url.resolved);
|
|
53
56
|
if (!dataUrl) continue;
|
|
54
|
-
fontFaceString = fontFaceString.replace(url.original, dataUrl);
|
|
57
|
+
fontFaceString = fontFaceString.replace(url.original, () => dataUrl);
|
|
55
58
|
}
|
|
56
59
|
css += fontFaceString;
|
|
57
60
|
}
|
|
@@ -88,15 +91,26 @@ async function getDocumentFontFaces(doc) {
|
|
|
88
91
|
}
|
|
89
92
|
return compact(await Promise.all(fontFaces)).flat();
|
|
90
93
|
}
|
|
91
|
-
const
|
|
94
|
+
const fetchParsedCss = fetchCache(async (response) => {
|
|
92
95
|
const parsed = parseCss(await response.text(), response.url);
|
|
96
|
+
return {
|
|
97
|
+
fontFaces: parsed.fontFaces,
|
|
98
|
+
importUrls: parsed.imports.map(({ url }) => new URL(url, response.url).href)
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
async function fetchCssFontFaces(url, seen = /* @__PURE__ */ new Set()) {
|
|
102
|
+
if (seen.has(url)) return [];
|
|
103
|
+
seen.add(url);
|
|
104
|
+
const parsed = await fetchParsedCss(url);
|
|
105
|
+
if (!parsed) return [];
|
|
93
106
|
const importedFontFaces = await Promise.all(
|
|
94
|
-
parsed.
|
|
107
|
+
parsed.importUrls.map((importUrl) => fetchCssFontFaces(importUrl, new Set(seen)))
|
|
95
108
|
);
|
|
96
|
-
return [...parsed.fontFaces, ...
|
|
97
|
-
}
|
|
109
|
+
return [...parsed.fontFaces, ...importedFontFaces.flat()];
|
|
110
|
+
}
|
|
98
111
|
export {
|
|
99
112
|
FontEmbedder,
|
|
100
|
-
SVG_EXPORT_CLASSNAME
|
|
113
|
+
SVG_EXPORT_CLASSNAME,
|
|
114
|
+
fetchCssFontFaces
|
|
101
115
|
};
|
|
102
116
|
//# sourceMappingURL=FontEmbedder.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/exports/FontEmbedder.ts"],
|
|
4
|
-
"sourcesContent": ["import { assert, bind, compact } from '@tldraw/utils'\nimport { fetchCache, resourceToDataUrl } from './fetchCache'\nimport { ParsedFontFace, parseCss, parseCssFontFaces, parseCssFontFamilyValue } from './parseCss'\n\nexport const SVG_EXPORT_CLASSNAME = 'tldraw-svg-export'\n\n/**\n * Because SVGs cannot refer to external CSS/font resources, any web fonts used in the SVG must be\n * embedded as data URLs in inlined @font-face declarations. This class is responsible for\n * collecting used font faces and creating a CSS string with embedded fonts that can be used in the\n * SVG.\n *\n * It works in three steps:\n * 1. `startFindingDocumentFontFaces` - this traverses the given document, finding all the\n * stylesheets in use (including those imported via `@import` rules etc) and extracting the\n * @font-face declarations from them.\n * 2. `onFontFamilyValue` - as `StyleEmbedder` traverses the SVG, it will call this method with the\n * value of the `font-family` property for each element. We parse out the font names in use, and\n * mark them as needing to be embedded.\n * 3. `createCss` - once all the font families have been collected, this method will return a CSS\n * string with embedded fonts.\n */\nexport class FontEmbedder {\n\tprivate fontFacesPromise: Promise<ParsedFontFace[]> | null = null\n\tprivate readonly foundFontNames = new Set<string>()\n\tprivate readonly fontFacesToEmbed = new Set<ParsedFontFace>()\n\tprivate readonly pendingPromises: Promise<void>[] = []\n\n\tstartFindingDocumentFontFaces(doc: Document) {\n\t\tassert(!this.fontFacesPromise, 'FontEmbedder already started')\n\t\tthis.fontFacesPromise = getDocumentFontFaces(doc)\n\t}\n\n\t@bind onFontFamilyValue(fontFamilyValue: string) {\n\t\tassert(this.fontFacesPromise, 'FontEmbedder not started')\n\n\t\tconst fonts = parseCssFontFamilyValue(fontFamilyValue)\n\t\tfor (const font of fonts) {\n\t\t\tif (this.foundFontNames.has(font))
|
|
5
|
-
"mappings": ";;;;;;;;;;AAAA,SAAS,QAAQ,MAAM,eAAe;AACtC,SAAS,YAAY,yBAAyB;AAC9C,SAAyB,UAAU,mBAAmB,+BAA+B;AAE9E,MAAM,uBAAuB;AAkB7B,MAAM,aAAa;AAAA,EACjB,mBAAqD;AAAA,EAC5C,iBAAiB,oBAAI,IAAY;AAAA,EACjC,mBAAmB,oBAAI,IAAoB;AAAA,
|
|
4
|
+
"sourcesContent": ["import { assert, bind, compact } from '@tldraw/utils'\nimport { fetchCache, resourceToDataUrl } from './fetchCache'\nimport { ParsedFontFace, parseCss, parseCssFontFaces, parseCssFontFamilyValue } from './parseCss'\n\nexport const SVG_EXPORT_CLASSNAME = 'tldraw-svg-export'\n\n/**\n * Because SVGs cannot refer to external CSS/font resources, any web fonts used in the SVG must be\n * embedded as data URLs in inlined @font-face declarations. This class is responsible for\n * collecting used font faces and creating a CSS string with embedded fonts that can be used in the\n * SVG.\n *\n * It works in three steps:\n * 1. `startFindingDocumentFontFaces` - this traverses the given document, finding all the\n * stylesheets in use (including those imported via `@import` rules etc) and extracting the\n * @font-face declarations from them.\n * 2. `onFontFamilyValue` - as `StyleEmbedder` traverses the SVG, it will call this method with the\n * value of the `font-family` property for each element. We parse out the font names in use, and\n * mark them as needing to be embedded.\n * 3. `createCss` - once all the font families have been collected, this method will return a CSS\n * string with embedded fonts.\n */\nexport class FontEmbedder {\n\tprivate fontFacesPromise: Promise<ParsedFontFace[]> | null = null\n\tprivate readonly foundFontNames = new Set<string>()\n\tprivate readonly fontFacesToEmbed = new Set<ParsedFontFace>()\n\t// per export, keyed by resolved url: parsed font faces are shared across exports through the\n\t// fetch cache, so a failed fetch stored on them would never be retried\n\tprivate readonly embeddedUrls = new Map<string, Promise<string | null>>()\n\tprivate readonly pendingPromises: Promise<void>[] = []\n\n\tstartFindingDocumentFontFaces(doc: Document) {\n\t\tassert(!this.fontFacesPromise, 'FontEmbedder already started')\n\t\tthis.fontFacesPromise = getDocumentFontFaces(doc)\n\t}\n\n\t@bind onFontFamilyValue(fontFamilyValue: string) {\n\t\tassert(this.fontFacesPromise, 'FontEmbedder not started')\n\n\t\tconst fonts = parseCssFontFamilyValue(fontFamilyValue)\n\t\tfor (const font of fonts) {\n\t\t\tif (this.foundFontNames.has(font)) continue\n\t\t\tthis.foundFontNames.add(font)\n\n\t\t\tthis.pendingPromises.push(\n\t\t\t\tthis.fontFacesPromise.then((fontFaces) => {\n\t\t\t\t\tconst relevantFontFaces = fontFaces.filter((fontFace) => fontFace.fontFamilies.has(font))\n\t\t\t\t\tfor (const fontFace of relevantFontFaces) {\n\t\t\t\t\t\tif (this.fontFacesToEmbed.has(fontFace)) continue\n\n\t\t\t\t\t\tthis.fontFacesToEmbed.add(fontFace)\n\t\t\t\t\t\tfor (const url of fontFace.urls) {\n\t\t\t\t\t\t\tif (!url.resolved || this.embeddedUrls.has(url.resolved)) continue\n\t\t\t\t\t\t\t// kick off fetching this font\n\t\t\t\t\t\t\tthis.embeddedUrls.set(url.resolved, resourceToDataUrl(url.resolved))\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}\n\t}\n\n\tasync createCss() {\n\t\tawait Promise.all(this.pendingPromises)\n\n\t\tlet css = ''\n\n\t\tfor (const fontFace of this.fontFacesToEmbed) {\n\t\t\tlet fontFaceString = `@font-face {${fontFace.fontFace}}`\n\n\t\t\tfor (const url of fontFace.urls) {\n\t\t\t\tif (!url.resolved) continue\n\t\t\t\tconst dataUrl = await this.embeddedUrls.get(url.resolved)\n\t\t\t\tif (!dataUrl) continue\n\n\t\t\t\tfontFaceString = fontFaceString.replace(url.original, () => dataUrl)\n\t\t\t}\n\n\t\t\tcss += fontFaceString\n\t\t}\n\n\t\treturn css\n\t}\n}\n\nasync function getDocumentFontFaces(doc: Document) {\n\tconst win = doc.defaultView ?? globalThis\n\tconst fontFaces: (ParsedFontFace[] | Promise<ParsedFontFace[] | null>)[] = []\n\n\t// In exportToSvg we add the exported node to the DOM temporarily.\n\t// Because of this, and because we do a setTimeout to delay removing that node from the\n\t// DOM, when looking at document.styleSheets the number of nodes and stylesheets\n\t// can grow unbounded (especially when using \"Debug svg\" and moving shapes around).\n\t// To avoid this, we filter out the stylesheets that are part of the SVG export.\n\tconst styleSheetsWithoutSvgExports = Array.from(doc.styleSheets).filter(\n\t\t(styleSheet) =>\n\t\t\t!(styleSheet.ownerNode as HTMLElement | null)?.closest(`.${SVG_EXPORT_CLASSNAME}`)\n\t)\n\n\tfor (const styleSheet of styleSheetsWithoutSvgExports) {\n\t\tlet cssRules\n\t\ttry {\n\t\t\tcssRules = styleSheet.cssRules\n\t\t} catch {\n\t\t\t// some stylesheets don't allow access through the DOM. We'll try to fetch them instead.\n\t\t}\n\n\t\tif (cssRules) {\n\t\t\tfor (const rule of styleSheet.cssRules) {\n\t\t\t\tif (rule instanceof win.CSSFontFaceRule) {\n\t\t\t\t\tfontFaces.push(parseCssFontFaces(rule.cssText, styleSheet.href ?? doc.baseURI))\n\t\t\t\t} else if (rule instanceof win.CSSImportRule) {\n\t\t\t\t\tconst absoluteUrl = new URL(rule.href, rule.parentStyleSheet?.href ?? doc.baseURI)\n\t\t\t\t\tfontFaces.push(fetchCssFontFaces(absoluteUrl.href))\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (styleSheet.href) {\n\t\t\tfontFaces.push(fetchCssFontFaces(styleSheet.href))\n\t\t}\n\t}\n\n\treturn compact(await Promise.all(fontFaces)).flat()\n}\n\nconst fetchParsedCss = fetchCache(async (response: Response) => {\n\tconst parsed = parseCss(await response.text(), response.url)\n\treturn {\n\t\tfontFaces: parsed.fontFaces,\n\t\timportUrls: parsed.imports.map(({ url }) => new URL(url, response.url).href),\n\t}\n})\n\n/** @internal */\nexport async function fetchCssFontFaces(\n\turl: string,\n\t// the chain of sheets that imported this one: sheets that @import each other would otherwise\n\t// await their own in-flight fetch forever. Each branch gets its own copy so a sheet imported by\n\t// two siblings lands under both in source order, not under whichever fetch resolved first.\n\tseen = new Set<string>()\n): Promise<ParsedFontFace[]> {\n\tif (seen.has(url)) return []\n\tseen.add(url)\n\n\tconst parsed = await fetchParsedCss(url)\n\tif (!parsed) return []\n\n\tconst importedFontFaces = await Promise.all(\n\t\tparsed.importUrls.map((importUrl) => fetchCssFontFaces(importUrl, new Set(seen)))\n\t)\n\treturn [...parsed.fontFaces, ...importedFontFaces.flat()]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -68,10 +68,15 @@ class StyleEmbedder {
|
|
|
68
68
|
const urlMatches = parseCssValueUrls(value);
|
|
69
69
|
if (urlMatches.length === 0) continue;
|
|
70
70
|
promises.push(
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
(async () => {
|
|
72
|
+
const dataUrls = await Promise.all(
|
|
73
|
+
urlMatches.map(({ url }) => resourceToDataUrl(url))
|
|
74
|
+
);
|
|
75
|
+
styles[property] = urlMatches.reduce(
|
|
76
|
+
(result, { original }, i) => result.replace(original, () => `url("${dataUrls[i] ?? "data:"}")`),
|
|
77
|
+
value
|
|
78
|
+
);
|
|
79
|
+
})()
|
|
75
80
|
);
|
|
76
81
|
}
|
|
77
82
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/exports/StyleEmbedder.ts"],
|
|
4
|
-
"sourcesContent": ["import { assertExists, getOwnProperty, objectMapValues, uniqueId } from '@tldraw/utils'\nimport { ReadonlyStyles, Styles, cssRules } from './cssRules'\nimport {\n\telementStyle,\n\tgetComputedStyle,\n\tgetRenderedChildNodes,\n\tgetRenderedChildren,\n\tisElement,\n} from './domUtils'\nimport { resourceToDataUrl } from './fetchCache'\nimport { FontEmbedder } from './FontEmbedder'\nimport { parseCssValueUrls, shouldIncludeCssProperty } from './parseCss'\n\nconst NO_STYLES = {} as const\n\ninterface ElementStyleInfo {\n\tself: Styles\n\tbefore: Styles | undefined\n\tafter: Styles | undefined\n}\n\nexport class StyleEmbedder {\n\tconstructor(private readonly root: Element) {}\n\tprivate readonly styles = new Map<Element, ElementStyleInfo>()\n\treadonly fonts = new FontEmbedder()\n\n\treadRootElementStyles(rootElement: Element) {\n\t\t// when reading a root, we always apply _all_ the styles, even if they match the defaults\n\t\tthis.readElementStyles(rootElement, {\n\t\t\tshouldRespectDefaults: false,\n\t\t\tshouldSkipInheritedParentStyles: false,\n\t\t})\n\n\t\tconst children = Array.from(getRenderedChildren(rootElement))\n\t\twhile (children.length) {\n\t\t\tconst child = children.pop()!\n\t\t\tchildren.push(...getRenderedChildren(child))\n\n\t\t\t// when reading children, we don't apply styles that match the defaults for that\n\t\t\t// element, or that would be inherited from the parent\n\t\t\tthis.readElementStyles(child, {\n\t\t\t\tshouldRespectDefaults: true,\n\t\t\t\tshouldSkipInheritedParentStyles: true,\n\t\t\t})\n\t\t}\n\t}\n\n\tprivate readElementStyles(\n\t\telement: Element,\n\t\t{ shouldRespectDefaults = true, shouldSkipInheritedParentStyles = true }\n\t) {\n\t\tconst defaultStyles = shouldRespectDefaults\n\t\t\t? getDefaultStylesForTagName(element.ownerDocument, element.tagName.toLowerCase())\n\t\t\t: NO_STYLES\n\n\t\tconst parentStyles = Object.assign({}, NO_STYLES) as Styles\n\t\tif (shouldSkipInheritedParentStyles) {\n\t\t\tlet el = element.parentElement\n\t\t\t// Keep going up the tree to find all the relevant styles\n\t\t\twhile (el) {\n\t\t\t\tconst currentStyles = this.styles.get(el)?.self\n\t\t\t\tfor (const style in currentStyles) {\n\t\t\t\t\tif (!parentStyles[style]) {\n\t\t\t\t\t\tparentStyles[style] = currentStyles[style]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tel = el.parentElement\n\t\t\t}\n\t\t}\n\n\t\tconst info: ElementStyleInfo = {\n\t\t\tself: styleFromElement(element, { defaultStyles, parentStyles }),\n\t\t\tbefore: styleFromPseudoElement(element, '::before'),\n\t\t\tafter: styleFromPseudoElement(element, '::after'),\n\t\t}\n\t\tthis.styles.set(element, info)\n\t}\n\n\tfetchResources() {\n\t\tconst promises: Promise<void>[] = []\n\n\t\tfor (const info of this.styles.values()) {\n\t\t\tfor (const styles of objectMapValues(info)) {\n\t\t\t\tif (!styles) continue\n\t\t\t\tfor (const [property, value] of Object.entries(styles)) {\n\t\t\t\t\tif (!value) continue\n\t\t\t\t\tif (property === 'font-family') {\n\t\t\t\t\t\tthis.fonts.onFontFamilyValue(value)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst urlMatches = parseCssValueUrls(value)\n\t\t\t\t\tif (urlMatches.length === 0) continue\n\n\t\t\t\t\tpromises.push(\n\t\t\t\t\t\t...urlMatches.map(async ({ url, original }) => {\n\t\t\t\t\t\t\tconst dataUrl = (await resourceToDataUrl(url)) ?? 'data:'\n\t\t\t\t\t\t\tstyles[property] = value.replace(original, `url(\"${dataUrl}\")`)\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}\n\n\t\treturn Promise.all(promises)\n\t}\n\n\t// custom elements are tricky. if we serialize the dom as-is, the custom elements wont have\n\t// their shadow-dom contents serialized. after we've read all the styles, we need to unwrap the\n\t// contents of each custom elements shadow dom directly into the parent element itself.\n\tunwrapCustomElements() {\n\t\tconst visited = new Set<Node>()\n\n\t\tconst visit = (element: Element, clonedParent: Element | null) => {\n\t\t\tif (visited.has(element)) return\n\t\t\tvisited.add(element)\n\n\t\t\tconst shadowRoot = element.shadowRoot\n\n\t\t\tif (shadowRoot) {\n\t\t\t\tconst clonedCustomEl = element.ownerDocument.createElement('div')\n\t\t\t\tthis.styles.set(clonedCustomEl, this.styles.get(element)!)\n\n\t\t\t\tclonedCustomEl.setAttribute('data-tl-custom-element', element.tagName)\n\t\t\t\t;(clonedParent ?? element.parentElement!).appendChild(clonedCustomEl)\n\n\t\t\t\tfor (const child of shadowRoot.childNodes) {\n\t\t\t\t\tif (isElement(child)) {\n\t\t\t\t\t\tvisit(child, clonedCustomEl)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclonedCustomEl.appendChild(child.cloneNode(true))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\telement.remove()\n\t\t\t} else if (clonedParent) {\n\t\t\t\tif (element.tagName.toLowerCase() === 'style') {\n\t\t\t\t\t// we don't clone style tags at that would break the style scoping. instead we\n\t\t\t\t\t// rely on the computed styles we've already read\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst clonedEl = element.cloneNode(false) as Element\n\t\t\t\tthis.styles.set(clonedEl, this.styles.get(element)!)\n\n\t\t\t\tclonedParent.appendChild(clonedEl)\n\n\t\t\t\tfor (const child of getRenderedChildNodes(element)) {\n\t\t\t\t\tif (isElement(child)) {\n\t\t\t\t\t\tvisit(child, clonedEl)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclonedEl.appendChild(child.cloneNode(true))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const element of this.styles.keys()) {\n\t\t\tvisit(element, null)\n\t\t}\n\t}\n\n\tembedStyles(): string {\n\t\tlet css = ''\n\n\t\tfor (const [element, info] of this.styles) {\n\t\t\tif (info.after || info.before) {\n\t\t\t\tconst className = `pseudo-${uniqueId()}`\n\t\t\t\telement.classList.add(className)\n\n\t\t\t\tif (info.before) {\n\t\t\t\t\tcss += `.${className}::before {${formatCss(info.before)}}\\n`\n\t\t\t\t}\n\t\t\t\tif (info.after) {\n\t\t\t\t\tcss += `.${className}::after {${formatCss(info.after)}}\\n`\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst style = elementStyle(element)\n\t\t\tfor (const [property, value] of Object.entries(info.self)) {\n\t\t\t\tif (!value) continue\n\t\t\t\tstyle.setProperty(property, value)\n\t\t\t}\n\n\t\t\t// in HTML, font-kerning: auto is equivalent to font-kerning: normal. But in SVG, it's\n\t\t\t// none. We set it to normal here to match the HTML behavior, as otherwise this can\n\t\t\t// cause rendering differences.\n\t\t\tif (style.fontKerning === 'auto') {\n\t\t\t\tstyle.fontKerning = 'normal'\n\t\t\t}\n\t\t}\n\n\t\treturn css\n\t}\n\n\tasync getFontFaceCss() {\n\t\treturn await this.fonts.createCss()\n\t}\n\n\tdispose() {\n\t\tdestroyDefaultStyleFrame()\n\t}\n}\n\ninterface ReadStyleOpts {\n\tdefaultStyles: ReadonlyStyles\n\tparentStyles: ReadonlyStyles\n}\n\nfunction styleFromElement(element: Element, { defaultStyles, parentStyles }: ReadStyleOpts) {\n\t// `computedStyleMap` produces a more accurate representation of the styles, but it's not\n\t// supported in firefox at the time of writing. So we fall back to `getComputedStyle` if it's\n\t// not available.\n\tif (element.computedStyleMap) {\n\t\treturn styleFromComputedStyleMap(element.computedStyleMap(), { defaultStyles, parentStyles })\n\t}\n\treturn styleFromComputedStyle(getComputedStyle(element), { defaultStyles, parentStyles })\n}\n\nfunction styleFromPseudoElement(element: Element, pseudo: string) {\n\t// the equivalent of `computedStyleMap` for pseudo-elements isn't even fully specced out yet, so\n\t// for those we have to use `getComputedStyle` in all browsers.\n\tconst style = getComputedStyle(element, pseudo)\n\n\tconst content = style.getPropertyValue('content')\n\tif (content === '' || content === 'none') {\n\t\treturn undefined\n\t}\n\n\treturn styleFromComputedStyle(style, { defaultStyles: NO_STYLES, parentStyles: NO_STYLES })\n}\n\nfunction styleFromComputedStyleMap(\n\tstyle: StylePropertyMapReadOnly,\n\t{ defaultStyles, parentStyles }: ReadStyleOpts\n) {\n\tconst styles: Record<string, string> = {}\n\tconst currentColor = style.get('color')?.toString() || ''\n\tconst ruleOptions = {\n\t\tcurrentColor,\n\t\tparentStyles,\n\t\tdefaultStyles,\n\t\tgetStyle: (property: string) => style.get(property)?.toString() ?? '',\n\t}\n\tfor (const property of style.keys()) {\n\t\tif (!shouldIncludeCssProperty(property)) continue\n\n\t\tconst value = style.get(property)!.toString()\n\n\t\tif (defaultStyles[property] === value) continue\n\n\t\tconst rule = getOwnProperty(cssRules, property)\n\t\tif (rule && rule(value, property, ruleOptions)) continue\n\n\t\tstyles[property] = value\n\t}\n\n\treturn styles\n}\n\nfunction styleFromComputedStyle(\n\tstyle: CSSStyleDeclaration,\n\t{ defaultStyles, parentStyles }: ReadStyleOpts\n) {\n\tconst styles: Record<string, string> = {}\n\tconst currentColor = style.color\n\tconst ruleOptions = {\n\t\tcurrentColor,\n\t\tparentStyles,\n\t\tdefaultStyles,\n\t\tgetStyle: (property: string) => style.getPropertyValue(property),\n\t}\n\n\tfor (const property in style) {\n\t\tif (!shouldIncludeCssProperty(property)) continue\n\n\t\tconst value = style.getPropertyValue(property)\n\n\t\tif (defaultStyles[property] === value) continue\n\n\t\tconst rule = getOwnProperty(cssRules, property)\n\t\tif (rule && rule(value, property, ruleOptions)) continue\n\n\t\tstyles[property] = value\n\t}\n\treturn styles\n}\n\nfunction formatCss(style: ReadonlyStyles) {\n\tlet cssText = ''\n\tfor (const [property, value] of Object.entries(style)) {\n\t\tcssText += `${property}: ${value};`\n\t}\n\treturn cssText\n}\n\n// when we're figuring out the default values for a tag, we need read them from a separate document\n// so they're not affected by the current document's styles\nlet defaultStyleFrame:\n\t| {\n\t\t\tiframe: HTMLIFrameElement\n\t\t\tforeignObject: SVGForeignObjectElement\n\t\t\tdocument: Document\n\t\t\townerDocument: Document\n\t }\n\t| undefined\nconst defaultStylesByTagName: Record<string, ReadonlyStyles> = {}\nfunction getDefaultStyleFrame(ownerDoc: Document) {\n\tif (!defaultStyleFrame || defaultStyleFrame.ownerDocument !== ownerDoc) {\n\t\tdestroyDefaultStyleFrame()\n\t\tconst frame = ownerDoc.createElement('iframe')\n\t\tframe.style.display = 'none'\n\t\townerDoc.body.appendChild(frame)\n\t\tconst frameDocument = assertExists(frame.contentDocument, 'frame must have a document')\n\t\tconst svg = frameDocument.createElementNS('http://www.w3.org/2000/svg', 'svg')\n\t\tconst foreignObject = frameDocument.createElementNS(\n\t\t\t'http://www.w3.org/2000/svg',\n\t\t\t'foreignObject'\n\t\t)\n\t\tsvg.appendChild(foreignObject)\n\t\tframeDocument.body.appendChild(svg)\n\t\tdefaultStyleFrame = {\n\t\t\tiframe: frame,\n\t\t\tforeignObject,\n\t\t\tdocument: frameDocument,\n\t\t\townerDocument: ownerDoc,\n\t\t}\n\t}\n\treturn defaultStyleFrame\n}\n\nfunction destroyDefaultStyleFrame() {\n\tif (defaultStyleFrame) {\n\t\tdefaultStyleFrame.iframe.remove()\n\t\tdefaultStyleFrame = undefined\n\t}\n\tfor (const tagName in defaultStylesByTagName) {\n\t\tdelete defaultStylesByTagName[tagName]\n\t}\n}\n\nconst defaultStyleReadOptions: ReadStyleOpts = { defaultStyles: NO_STYLES, parentStyles: NO_STYLES }\nfunction getDefaultStylesForTagName(ownerDoc: Document, tagName: string) {\n\tlet existing = defaultStylesByTagName[tagName]\n\tif (!existing) {\n\t\tconst { foreignObject, document } = getDefaultStyleFrame(ownerDoc)\n\t\tconst element = document.createElement(tagName)\n\t\tforeignObject.appendChild(element)\n\t\texisting = element.computedStyleMap\n\t\t\t? styleFromComputedStyleMap(element.computedStyleMap(), defaultStyleReadOptions)\n\t\t\t: styleFromComputedStyle(getComputedStyle(element), defaultStyleReadOptions)\n\t\tforeignObject.removeChild(element)\n\t\tdefaultStylesByTagName[tagName] = existing\n\t}\n\treturn existing\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,cAAc,gBAAgB,iBAAiB,gBAAgB;AACxE,SAAiC,gBAAgB;AACjD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB,gCAAgC;AAE5D,MAAM,YAAY,CAAC;AAQZ,MAAM,cAAc;AAAA,EAC1B,YAA6B,MAAe;AAAf;AAAA,EAAgB;AAAA,EAAhB;AAAA,EACZ,SAAS,oBAAI,IAA+B;AAAA,EACpD,QAAQ,IAAI,aAAa;AAAA,EAElC,sBAAsB,aAAsB;AAE3C,SAAK,kBAAkB,aAAa;AAAA,MACnC,uBAAuB;AAAA,MACvB,iCAAiC;AAAA,IAClC,CAAC;AAED,UAAM,WAAW,MAAM,KAAK,oBAAoB,WAAW,CAAC;AAC5D,WAAO,SAAS,QAAQ;AACvB,YAAM,QAAQ,SAAS,IAAI;AAC3B,eAAS,KAAK,GAAG,oBAAoB,KAAK,CAAC;AAI3C,WAAK,kBAAkB,OAAO;AAAA,QAC7B,uBAAuB;AAAA,QACvB,iCAAiC;AAAA,MAClC,CAAC;AAAA,IACF;AAAA,EACD;AAAA,EAEQ,kBACP,SACA,EAAE,wBAAwB,MAAM,kCAAkC,KAAK,GACtE;AACD,UAAM,gBAAgB,wBACnB,2BAA2B,QAAQ,eAAe,QAAQ,QAAQ,YAAY,CAAC,IAC/E;AAEH,UAAM,eAAe,OAAO,OAAO,CAAC,GAAG,SAAS;AAChD,QAAI,iCAAiC;AACpC,UAAI,KAAK,QAAQ;AAEjB,aAAO,IAAI;AACV,cAAM,gBAAgB,KAAK,OAAO,IAAI,EAAE,GAAG;AAC3C,mBAAW,SAAS,eAAe;AAClC,cAAI,CAAC,aAAa,KAAK,GAAG;AACzB,yBAAa,KAAK,IAAI,cAAc,KAAK;AAAA,UAC1C;AAAA,QACD;AACA,aAAK,GAAG;AAAA,MACT;AAAA,IACD;AAEA,UAAM,OAAyB;AAAA,MAC9B,MAAM,iBAAiB,SAAS,EAAE,eAAe,aAAa,CAAC;AAAA,MAC/D,QAAQ,uBAAuB,SAAS,UAAU;AAAA,MAClD,OAAO,uBAAuB,SAAS,SAAS;AAAA,IACjD;AACA,SAAK,OAAO,IAAI,SAAS,IAAI;AAAA,EAC9B;AAAA,EAEA,iBAAiB;AAChB,UAAM,WAA4B,CAAC;AAEnC,eAAW,QAAQ,KAAK,OAAO,OAAO,GAAG;AACxC,iBAAW,UAAU,gBAAgB,IAAI,GAAG;AAC3C,YAAI,CAAC,OAAQ;AACb,mBAAW,CAAC,UAAU,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,cAAI,CAAC,MAAO;AACZ,cAAI,aAAa,eAAe;AAC/B,iBAAK,MAAM,kBAAkB,KAAK;AAAA,UACnC;AAEA,gBAAM,aAAa,kBAAkB,KAAK;AAC1C,cAAI,WAAW,WAAW,EAAG;AAE7B,mBAAS;AAAA,
|
|
4
|
+
"sourcesContent": ["import { assertExists, getOwnProperty, objectMapValues, uniqueId } from '@tldraw/utils'\nimport { ReadonlyStyles, Styles, cssRules } from './cssRules'\nimport {\n\telementStyle,\n\tgetComputedStyle,\n\tgetRenderedChildNodes,\n\tgetRenderedChildren,\n\tisElement,\n} from './domUtils'\nimport { resourceToDataUrl } from './fetchCache'\nimport { FontEmbedder } from './FontEmbedder'\nimport { parseCssValueUrls, shouldIncludeCssProperty } from './parseCss'\n\nconst NO_STYLES = {} as const\n\ninterface ElementStyleInfo {\n\tself: Styles\n\tbefore: Styles | undefined\n\tafter: Styles | undefined\n}\n\nexport class StyleEmbedder {\n\tconstructor(private readonly root: Element) {}\n\tprivate readonly styles = new Map<Element, ElementStyleInfo>()\n\treadonly fonts = new FontEmbedder()\n\n\treadRootElementStyles(rootElement: Element) {\n\t\t// when reading a root, we always apply _all_ the styles, even if they match the defaults\n\t\tthis.readElementStyles(rootElement, {\n\t\t\tshouldRespectDefaults: false,\n\t\t\tshouldSkipInheritedParentStyles: false,\n\t\t})\n\n\t\tconst children = Array.from(getRenderedChildren(rootElement))\n\t\twhile (children.length) {\n\t\t\tconst child = children.pop()!\n\t\t\tchildren.push(...getRenderedChildren(child))\n\n\t\t\t// when reading children, we don't apply styles that match the defaults for that\n\t\t\t// element, or that would be inherited from the parent\n\t\t\tthis.readElementStyles(child, {\n\t\t\t\tshouldRespectDefaults: true,\n\t\t\t\tshouldSkipInheritedParentStyles: true,\n\t\t\t})\n\t\t}\n\t}\n\n\tprivate readElementStyles(\n\t\telement: Element,\n\t\t{ shouldRespectDefaults = true, shouldSkipInheritedParentStyles = true }\n\t) {\n\t\tconst defaultStyles = shouldRespectDefaults\n\t\t\t? getDefaultStylesForTagName(element.ownerDocument, element.tagName.toLowerCase())\n\t\t\t: NO_STYLES\n\n\t\tconst parentStyles = Object.assign({}, NO_STYLES) as Styles\n\t\tif (shouldSkipInheritedParentStyles) {\n\t\t\tlet el = element.parentElement\n\t\t\t// Keep going up the tree to find all the relevant styles\n\t\t\twhile (el) {\n\t\t\t\tconst currentStyles = this.styles.get(el)?.self\n\t\t\t\tfor (const style in currentStyles) {\n\t\t\t\t\tif (!parentStyles[style]) {\n\t\t\t\t\t\tparentStyles[style] = currentStyles[style]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tel = el.parentElement\n\t\t\t}\n\t\t}\n\n\t\tconst info: ElementStyleInfo = {\n\t\t\tself: styleFromElement(element, { defaultStyles, parentStyles }),\n\t\t\tbefore: styleFromPseudoElement(element, '::before'),\n\t\t\tafter: styleFromPseudoElement(element, '::after'),\n\t\t}\n\t\tthis.styles.set(element, info)\n\t}\n\n\tfetchResources() {\n\t\tconst promises: Promise<void>[] = []\n\n\t\tfor (const info of this.styles.values()) {\n\t\t\tfor (const styles of objectMapValues(info)) {\n\t\t\t\tif (!styles) continue\n\t\t\t\tfor (const [property, value] of Object.entries(styles)) {\n\t\t\t\t\tif (!value) continue\n\t\t\t\t\tif (property === 'font-family') {\n\t\t\t\t\t\tthis.fonts.onFontFamilyValue(value)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst urlMatches = parseCssValueUrls(value)\n\t\t\t\t\tif (urlMatches.length === 0) continue\n\n\t\t\t\t\tpromises.push(\n\t\t\t\t\t\t(async () => {\n\t\t\t\t\t\t\t// resolve every url first, then rewrite the value in one pass - replacing\n\t\t\t\t\t\t\t// per-url from the original value would keep only whichever resolved last.\n\t\t\t\t\t\t\t// The replacer callback keeps `$` in a data url's mime segment literal.\n\t\t\t\t\t\t\tconst dataUrls = await Promise.all(\n\t\t\t\t\t\t\t\turlMatches.map(({ url }) => resourceToDataUrl(url))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tstyles[property] = urlMatches.reduce(\n\t\t\t\t\t\t\t\t(result, { original }, i) =>\n\t\t\t\t\t\t\t\t\tresult.replace(original, () => `url(\"${dataUrls[i] ?? 'data:'}\")`),\n\t\t\t\t\t\t\t\tvalue\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}\n\n\t\treturn Promise.all(promises)\n\t}\n\n\t// custom elements are tricky. if we serialize the dom as-is, the custom elements wont have\n\t// their shadow-dom contents serialized. after we've read all the styles, we need to unwrap the\n\t// contents of each custom elements shadow dom directly into the parent element itself.\n\tunwrapCustomElements() {\n\t\tconst visited = new Set<Node>()\n\n\t\tconst visit = (element: Element, clonedParent: Element | null) => {\n\t\t\tif (visited.has(element)) return\n\t\t\tvisited.add(element)\n\n\t\t\tconst shadowRoot = element.shadowRoot\n\n\t\t\tif (shadowRoot) {\n\t\t\t\tconst clonedCustomEl = element.ownerDocument.createElement('div')\n\t\t\t\tthis.styles.set(clonedCustomEl, this.styles.get(element)!)\n\n\t\t\t\tclonedCustomEl.setAttribute('data-tl-custom-element', element.tagName)\n\t\t\t\t;(clonedParent ?? element.parentElement!).appendChild(clonedCustomEl)\n\n\t\t\t\tfor (const child of shadowRoot.childNodes) {\n\t\t\t\t\tif (isElement(child)) {\n\t\t\t\t\t\tvisit(child, clonedCustomEl)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclonedCustomEl.appendChild(child.cloneNode(true))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\telement.remove()\n\t\t\t} else if (clonedParent) {\n\t\t\t\tif (element.tagName.toLowerCase() === 'style') {\n\t\t\t\t\t// we don't clone style tags at that would break the style scoping. instead we\n\t\t\t\t\t// rely on the computed styles we've already read\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst clonedEl = element.cloneNode(false) as Element\n\t\t\t\tthis.styles.set(clonedEl, this.styles.get(element)!)\n\n\t\t\t\tclonedParent.appendChild(clonedEl)\n\n\t\t\t\tfor (const child of getRenderedChildNodes(element)) {\n\t\t\t\t\tif (isElement(child)) {\n\t\t\t\t\t\tvisit(child, clonedEl)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclonedEl.appendChild(child.cloneNode(true))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const element of this.styles.keys()) {\n\t\t\tvisit(element, null)\n\t\t}\n\t}\n\n\tembedStyles(): string {\n\t\tlet css = ''\n\n\t\tfor (const [element, info] of this.styles) {\n\t\t\tif (info.after || info.before) {\n\t\t\t\tconst className = `pseudo-${uniqueId()}`\n\t\t\t\telement.classList.add(className)\n\n\t\t\t\tif (info.before) {\n\t\t\t\t\tcss += `.${className}::before {${formatCss(info.before)}}\\n`\n\t\t\t\t}\n\t\t\t\tif (info.after) {\n\t\t\t\t\tcss += `.${className}::after {${formatCss(info.after)}}\\n`\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst style = elementStyle(element)\n\t\t\tfor (const [property, value] of Object.entries(info.self)) {\n\t\t\t\tif (!value) continue\n\t\t\t\tstyle.setProperty(property, value)\n\t\t\t}\n\n\t\t\t// in HTML, font-kerning: auto is equivalent to font-kerning: normal. But in SVG, it's\n\t\t\t// none. We set it to normal here to match the HTML behavior, as otherwise this can\n\t\t\t// cause rendering differences.\n\t\t\tif (style.fontKerning === 'auto') {\n\t\t\t\tstyle.fontKerning = 'normal'\n\t\t\t}\n\t\t}\n\n\t\treturn css\n\t}\n\n\tasync getFontFaceCss() {\n\t\treturn await this.fonts.createCss()\n\t}\n\n\tdispose() {\n\t\tdestroyDefaultStyleFrame()\n\t}\n}\n\ninterface ReadStyleOpts {\n\tdefaultStyles: ReadonlyStyles\n\tparentStyles: ReadonlyStyles\n}\n\nfunction styleFromElement(element: Element, { defaultStyles, parentStyles }: ReadStyleOpts) {\n\t// `computedStyleMap` produces a more accurate representation of the styles, but it's not\n\t// supported in firefox at the time of writing. So we fall back to `getComputedStyle` if it's\n\t// not available.\n\tif (element.computedStyleMap) {\n\t\treturn styleFromComputedStyleMap(element.computedStyleMap(), { defaultStyles, parentStyles })\n\t}\n\treturn styleFromComputedStyle(getComputedStyle(element), { defaultStyles, parentStyles })\n}\n\nfunction styleFromPseudoElement(element: Element, pseudo: string) {\n\t// the equivalent of `computedStyleMap` for pseudo-elements isn't even fully specced out yet, so\n\t// for those we have to use `getComputedStyle` in all browsers.\n\tconst style = getComputedStyle(element, pseudo)\n\n\tconst content = style.getPropertyValue('content')\n\tif (content === '' || content === 'none') {\n\t\treturn undefined\n\t}\n\n\treturn styleFromComputedStyle(style, { defaultStyles: NO_STYLES, parentStyles: NO_STYLES })\n}\n\nfunction styleFromComputedStyleMap(\n\tstyle: StylePropertyMapReadOnly,\n\t{ defaultStyles, parentStyles }: ReadStyleOpts\n) {\n\tconst styles: Record<string, string> = {}\n\tconst currentColor = style.get('color')?.toString() || ''\n\tconst ruleOptions = {\n\t\tcurrentColor,\n\t\tparentStyles,\n\t\tdefaultStyles,\n\t\tgetStyle: (property: string) => style.get(property)?.toString() ?? '',\n\t}\n\tfor (const property of style.keys()) {\n\t\tif (!shouldIncludeCssProperty(property)) continue\n\n\t\tconst value = style.get(property)!.toString()\n\n\t\tif (defaultStyles[property] === value) continue\n\n\t\tconst rule = getOwnProperty(cssRules, property)\n\t\tif (rule && rule(value, property, ruleOptions)) continue\n\n\t\tstyles[property] = value\n\t}\n\n\treturn styles\n}\n\nfunction styleFromComputedStyle(\n\tstyle: CSSStyleDeclaration,\n\t{ defaultStyles, parentStyles }: ReadStyleOpts\n) {\n\tconst styles: Record<string, string> = {}\n\tconst currentColor = style.color\n\tconst ruleOptions = {\n\t\tcurrentColor,\n\t\tparentStyles,\n\t\tdefaultStyles,\n\t\tgetStyle: (property: string) => style.getPropertyValue(property),\n\t}\n\n\tfor (const property in style) {\n\t\tif (!shouldIncludeCssProperty(property)) continue\n\n\t\tconst value = style.getPropertyValue(property)\n\n\t\tif (defaultStyles[property] === value) continue\n\n\t\tconst rule = getOwnProperty(cssRules, property)\n\t\tif (rule && rule(value, property, ruleOptions)) continue\n\n\t\tstyles[property] = value\n\t}\n\treturn styles\n}\n\nfunction formatCss(style: ReadonlyStyles) {\n\tlet cssText = ''\n\tfor (const [property, value] of Object.entries(style)) {\n\t\tcssText += `${property}: ${value};`\n\t}\n\treturn cssText\n}\n\n// when we're figuring out the default values for a tag, we need read them from a separate document\n// so they're not affected by the current document's styles\nlet defaultStyleFrame:\n\t| {\n\t\t\tiframe: HTMLIFrameElement\n\t\t\tforeignObject: SVGForeignObjectElement\n\t\t\tdocument: Document\n\t\t\townerDocument: Document\n\t }\n\t| undefined\nconst defaultStylesByTagName: Record<string, ReadonlyStyles> = {}\nfunction getDefaultStyleFrame(ownerDoc: Document) {\n\tif (!defaultStyleFrame || defaultStyleFrame.ownerDocument !== ownerDoc) {\n\t\tdestroyDefaultStyleFrame()\n\t\tconst frame = ownerDoc.createElement('iframe')\n\t\tframe.style.display = 'none'\n\t\townerDoc.body.appendChild(frame)\n\t\tconst frameDocument = assertExists(frame.contentDocument, 'frame must have a document')\n\t\tconst svg = frameDocument.createElementNS('http://www.w3.org/2000/svg', 'svg')\n\t\tconst foreignObject = frameDocument.createElementNS(\n\t\t\t'http://www.w3.org/2000/svg',\n\t\t\t'foreignObject'\n\t\t)\n\t\tsvg.appendChild(foreignObject)\n\t\tframeDocument.body.appendChild(svg)\n\t\tdefaultStyleFrame = {\n\t\t\tiframe: frame,\n\t\t\tforeignObject,\n\t\t\tdocument: frameDocument,\n\t\t\townerDocument: ownerDoc,\n\t\t}\n\t}\n\treturn defaultStyleFrame\n}\n\nfunction destroyDefaultStyleFrame() {\n\tif (defaultStyleFrame) {\n\t\tdefaultStyleFrame.iframe.remove()\n\t\tdefaultStyleFrame = undefined\n\t}\n\tfor (const tagName in defaultStylesByTagName) {\n\t\tdelete defaultStylesByTagName[tagName]\n\t}\n}\n\nconst defaultStyleReadOptions: ReadStyleOpts = { defaultStyles: NO_STYLES, parentStyles: NO_STYLES }\nfunction getDefaultStylesForTagName(ownerDoc: Document, tagName: string) {\n\tlet existing = defaultStylesByTagName[tagName]\n\tif (!existing) {\n\t\tconst { foreignObject, document } = getDefaultStyleFrame(ownerDoc)\n\t\tconst element = document.createElement(tagName)\n\t\tforeignObject.appendChild(element)\n\t\texisting = element.computedStyleMap\n\t\t\t? styleFromComputedStyleMap(element.computedStyleMap(), defaultStyleReadOptions)\n\t\t\t: styleFromComputedStyle(getComputedStyle(element), defaultStyleReadOptions)\n\t\tforeignObject.removeChild(element)\n\t\tdefaultStylesByTagName[tagName] = existing\n\t}\n\treturn existing\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/lib/exports/fetchCache.ts"],
|
|
4
|
-
"sourcesContent": ["import { FileHelpers, LruCache, assert, fetch } from '@tldraw/utils'\n\nexport function fetchCache<T>(cb: (response: Response) => Promise<T>, init?: RequestInit) {\n\tconst cache = new LruCache<string, Promise<T | null>>(100)\n\n\treturn async function fetchCached(url: string): Promise<T | null> {\n\t\tconst existing = cache.get(url)\n\t\tif (existing) return existing\n\n\t\tconst promise = (async () => {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(url, init)\n\t\t\t\tassert(response.ok)\n\t\t\t\treturn await cb(response)\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(err)\n\t\t\t\treturn null\n\t\t\t}\n\t\t})()\n\t\tcache.set(url, promise)\n\t\treturn promise\n\t}\n}\n\nexport const resourceToDataUrl = fetchCache(async (response) => {\n\treturn await FileHelpers.blobToDataUrl(await response.blob())\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,aAAa,UAAU,QAAQ,aAAa;AAE9C,SAAS,WAAc,IAAwC,MAAoB;AACzF,QAAM,QAAQ,IAAI,SAAoC,GAAG;AAEzD,SAAO,eAAe,YAAY,KAAgC;AACjE,UAAM,WAAW,MAAM,IAAI,GAAG;AAC9B,QAAI,SAAU,QAAO;AAErB,UAAM,WAAW,YAAY;AAC5B,UAAI;AACH,cAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AACtC,eAAO,SAAS,EAAE;AAClB,eAAO,MAAM,GAAG,QAAQ;AAAA,MACzB,SAAS,KAAK;AACb,gBAAQ,MAAM,GAAG;AACjB,eAAO;AAAA,MACR;AAAA,IACD,GAAG;AACH,UAAM,IAAI,KAAK,OAAO;
|
|
4
|
+
"sourcesContent": ["import { FileHelpers, LruCache, assert, fetch } from '@tldraw/utils'\n\nexport function fetchCache<T>(cb: (response: Response) => Promise<T>, init?: RequestInit) {\n\tconst cache = new LruCache<string, Promise<T | null>>(100)\n\n\treturn async function fetchCached(url: string): Promise<T | null> {\n\t\tconst existing = cache.get(url)\n\t\tif (existing) return existing\n\n\t\tconst promise = (async () => {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(url, init)\n\t\t\t\tassert(response.ok)\n\t\t\t\treturn await cb(response)\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(err)\n\t\t\t\treturn null\n\t\t\t}\n\t\t})()\n\t\tcache.set(url, promise)\n\t\t// only keep successes, otherwise a transient failure poisons this url for the whole session\n\t\tpromise.then((result) => {\n\t\t\tif (result === null && cache.get(url) === promise) cache.delete(url)\n\t\t})\n\t\treturn promise\n\t}\n}\n\nexport const resourceToDataUrl = fetchCache(async (response) => {\n\treturn await FileHelpers.blobToDataUrl(await response.blob())\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,aAAa,UAAU,QAAQ,aAAa;AAE9C,SAAS,WAAc,IAAwC,MAAoB;AACzF,QAAM,QAAQ,IAAI,SAAoC,GAAG;AAEzD,SAAO,eAAe,YAAY,KAAgC;AACjE,UAAM,WAAW,MAAM,IAAI,GAAG;AAC9B,QAAI,SAAU,QAAO;AAErB,UAAM,WAAW,YAAY;AAC5B,UAAI;AACH,cAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AACtC,eAAO,SAAS,EAAE;AAClB,eAAO,MAAM,GAAG,QAAQ;AAAA,MACzB,SAAS,KAAK;AACb,gBAAQ,MAAM,GAAG;AACjB,eAAO;AAAA,MACR;AAAA,IACD,GAAG;AACH,UAAM,IAAI,KAAK,OAAO;AAEtB,YAAQ,KAAK,CAAC,WAAW;AACxB,UAAI,WAAW,QAAQ,MAAM,IAAI,GAAG,MAAM,QAAS,OAAM,OAAO,GAAG;AAAA,IACpE,CAAC;AACD,WAAO;AAAA,EACR;AACD;AAEO,MAAM,oBAAoB,WAAW,OAAO,aAAa;AAC/D,SAAO,MAAM,YAAY,cAAc,MAAM,SAAS,KAAK,CAAC;AAC7D,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|