@tldraw/editor 3.13.0-canary.6cb770c78958 → 3.13.0-canary.70e156551c5c
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 +84 -97
- package/dist-cjs/index.js +7 -22
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +12 -8
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +21 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +17 -11
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +33 -14
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager.js +10 -0
- package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +16 -0
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Mat.js +1 -1
- package/dist-cjs/lib/primitives/Mat.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +20 -0
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Circle2d.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/CubicSpline2d.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/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +91 -20
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +55 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/utils/areShapesContentEqual.js +25 -0
- package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
- package/dist-cjs/lib/utils/debug-flags.js +5 -2
- package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
- package/dist-cjs/lib/utils/nearestMultiple.js +34 -0
- package/dist-cjs/lib/utils/nearestMultiple.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +84 -97
- package/dist-esm/index.mjs +9 -41
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +12 -8
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +21 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +33 -14
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager.mjs +10 -0
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +16 -0
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Mat.mjs +1 -1
- package/dist-esm/lib/primitives/Mat.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +20 -0
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Circle2d.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/CubicSpline2d.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/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +92 -21
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/utils/areShapesContentEqual.mjs +5 -0
- package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
- package/dist-esm/lib/utils/debug-flags.mjs +5 -2
- package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
- package/dist-esm/lib/utils/nearestMultiple.mjs +14 -0
- package/dist-esm/lib/utils/nearestMultiple.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +29 -0
- package/package.json +7 -7
- package/src/index.ts +16 -31
- package/src/lib/components/Shape.tsx +14 -10
- package/src/lib/components/default-components/DefaultCanvas.tsx +21 -2
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
- package/src/lib/editor/Editor.test.ts +1 -1
- package/src/lib/editor/Editor.ts +33 -14
- package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +0 -1
- package/src/lib/editor/managers/TextManager.ts +12 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +9 -1
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +9 -9
- package/src/lib/exports/getSvgJsx.tsx +16 -7
- package/src/lib/primitives/Box.ts +20 -0
- package/src/lib/primitives/Mat.ts +5 -4
- package/src/lib/primitives/Vec.ts +23 -0
- package/src/lib/primitives/geometry/Arc2d.ts +5 -5
- package/src/lib/primitives/geometry/Circle2d.ts +4 -4
- package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -4
- package/src/lib/primitives/geometry/CubicSpline2d.ts +3 -3
- package/src/lib/primitives/geometry/Edge2d.ts +3 -3
- package/src/lib/primitives/geometry/Ellipse2d.ts +3 -3
- package/src/lib/primitives/geometry/Geometry2d.test.ts +42 -0
- package/src/lib/primitives/geometry/Geometry2d.ts +123 -35
- package/src/lib/primitives/geometry/Group2d.ts +70 -7
- package/src/lib/primitives/geometry/Point2d.ts +2 -2
- package/src/lib/primitives/geometry/Polyline2d.ts +3 -3
- package/src/lib/primitives/geometry/Stadium2d.ts +3 -3
- package/src/lib/test/currentToolIdMask.test.ts +1 -1
- package/src/lib/test/user.test.ts +1 -1
- package/src/lib/utils/areShapesContentEqual.ts +4 -0
- package/src/lib/utils/debug-flags.ts +7 -2
- package/src/lib/utils/nearestMultiple.ts +13 -0
- package/src/lib/utils/sync/LocalIndexedDb.test.ts +1 -1
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
- package/src/version.ts +3 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function gcd(a, b) {
|
|
2
|
+
return b === 0 ? a : gcd(b, a % b);
|
|
3
|
+
}
|
|
4
|
+
function nearestMultiple(float) {
|
|
5
|
+
const decimal = float.toString().split(".")[1];
|
|
6
|
+
if (!decimal) return 1;
|
|
7
|
+
const denominator = Math.pow(10, decimal.length);
|
|
8
|
+
const numerator = parseInt(decimal, 10);
|
|
9
|
+
return denominator / gcd(numerator, denominator);
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
nearestMultiple
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=nearestMultiple.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/utils/nearestMultiple.ts"],
|
|
4
|
+
"sourcesContent": ["// Euclidean algorithm to find the GCD\nfunction gcd(a: number, b: number): number {\n\treturn b === 0 ? a : gcd(b, a % b)\n}\n\n// Returns the lowest value that the given number can be multiplied by to reach an integer\nexport function nearestMultiple(float: number) {\n\tconst decimal = float.toString().split('.')[1]\n\tif (!decimal) return 1\n\tconst denominator = Math.pow(10, decimal.length)\n\tconst numerator = parseInt(decimal, 10)\n\treturn denominator / gcd(numerator, denominator)\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,IAAI,GAAW,GAAmB;AAC1C,SAAO,MAAM,IAAI,IAAI,IAAI,GAAG,IAAI,CAAC;AAClC;AAGO,SAAS,gBAAgB,OAAe;AAC9C,QAAM,UAAU,MAAM,SAAS,EAAE,MAAM,GAAG,EAAE,CAAC;AAC7C,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,cAAc,KAAK,IAAI,IAAI,QAAQ,MAAM;AAC/C,QAAM,YAAY,SAAS,SAAS,EAAE;AACtC,SAAO,cAAc,IAAI,WAAW,WAAW;AAChD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "3.13.0-canary.
|
|
1
|
+
const version = "3.13.0-canary.70e156551c5c";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-
|
|
5
|
-
patch: "2025-
|
|
4
|
+
minor: "2025-05-08T14:57:13.497Z",
|
|
5
|
+
patch: "2025-05-08T14:57:13.497Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
package/dist-esm/version.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.13.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.13.0-canary.70e156551c5c'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-05-08T14:57:13.497Z',\n\tpatch: '2025-05-08T14:57:13.497Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/editor.css
CHANGED
|
@@ -600,6 +600,31 @@ input,
|
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
+
/* --------------------- Arrow Hints -------------------- */
|
|
604
|
+
|
|
605
|
+
.tl-arrow-hint-handle {
|
|
606
|
+
fill: var(--color-selected-contrast);
|
|
607
|
+
stroke: var(--color-selection-stroke);
|
|
608
|
+
stroke-width: calc(1.5px * var(--tl-scale));
|
|
609
|
+
r: calc(4px * var(--tl-scale));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.tl-arrow-hint-snap {
|
|
613
|
+
stroke: transparent;
|
|
614
|
+
fill: var(--color-selection-fill);
|
|
615
|
+
r: calc(12px * var(--tl-scale));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.tl-arrow-hint-snap__none,
|
|
619
|
+
.tl-arrow-hint-snap__center,
|
|
620
|
+
.tl-arrow-hint-snap__axis {
|
|
621
|
+
display: none;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.tl-arrow-hint-snap__edge {
|
|
625
|
+
r: calc(8px * var(--tl-scale));
|
|
626
|
+
}
|
|
627
|
+
|
|
603
628
|
/* ------------------ Bounds Detail ----------------- */
|
|
604
629
|
|
|
605
630
|
.tl-image,
|
|
@@ -969,6 +994,8 @@ input,
|
|
|
969
994
|
|
|
970
995
|
.tl-rich-text p {
|
|
971
996
|
margin: 0;
|
|
997
|
+
/* Depending on the extensions, <p> tags can be empty, without a <br />. */
|
|
998
|
+
min-height: 1lh;
|
|
972
999
|
}
|
|
973
1000
|
|
|
974
1001
|
.tl-rich-text ul,
|
|
@@ -976,6 +1003,8 @@ input,
|
|
|
976
1003
|
text-align: left;
|
|
977
1004
|
margin: 0;
|
|
978
1005
|
padding-left: 3.25ch;
|
|
1006
|
+
/* Some resets, like Tailwind, nix the list styling. */
|
|
1007
|
+
list-style: revert;
|
|
979
1008
|
}
|
|
980
1009
|
|
|
981
1010
|
.tl-rich-text ol:has(> li:nth-child(10)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "A tiny little drawing app (editor).",
|
|
4
|
-
"version": "3.13.0-canary.
|
|
4
|
+
"version": "3.13.0-canary.70e156551c5c",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@tiptap/core": "^2.9.1",
|
|
49
49
|
"@tiptap/pm": "^2.9.1",
|
|
50
50
|
"@tiptap/react": "^2.9.1",
|
|
51
|
-
"@tldraw/state": "3.13.0-canary.
|
|
52
|
-
"@tldraw/state-react": "3.13.0-canary.
|
|
53
|
-
"@tldraw/store": "3.13.0-canary.
|
|
54
|
-
"@tldraw/tlschema": "3.13.0-canary.
|
|
55
|
-
"@tldraw/utils": "3.13.0-canary.
|
|
56
|
-
"@tldraw/validate": "3.13.0-canary.
|
|
51
|
+
"@tldraw/state": "3.13.0-canary.70e156551c5c",
|
|
52
|
+
"@tldraw/state-react": "3.13.0-canary.70e156551c5c",
|
|
53
|
+
"@tldraw/store": "3.13.0-canary.70e156551c5c",
|
|
54
|
+
"@tldraw/tlschema": "3.13.0-canary.70e156551c5c",
|
|
55
|
+
"@tldraw/utils": "3.13.0-canary.70e156551c5c",
|
|
56
|
+
"@tldraw/validate": "3.13.0-canary.70e156551c5c",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
|
@@ -4,37 +4,11 @@ import 'core-js/stable/array/flat-map.js'
|
|
|
4
4
|
import 'core-js/stable/array/flat.js'
|
|
5
5
|
import 'core-js/stable/string/at.js'
|
|
6
6
|
import 'core-js/stable/string/replace-all.js'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
react,
|
|
13
|
-
transact,
|
|
14
|
-
transaction,
|
|
15
|
-
whyAmIRunning,
|
|
16
|
-
type Atom,
|
|
17
|
-
type Signal,
|
|
18
|
-
} from '@tldraw/state'
|
|
19
|
-
export {
|
|
20
|
-
track,
|
|
21
|
-
useAtom,
|
|
22
|
-
useComputed,
|
|
23
|
-
useQuickReactor,
|
|
24
|
-
useReactor,
|
|
25
|
-
useStateTracking,
|
|
26
|
-
useValue,
|
|
27
|
-
} from '@tldraw/state-react'
|
|
28
|
-
export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
|
|
29
|
-
export {
|
|
30
|
-
getFontsFromRichText,
|
|
31
|
-
type RichTextFontVisitor,
|
|
32
|
-
type RichTextFontVisitorState,
|
|
33
|
-
type TLTextOptions,
|
|
34
|
-
type TiptapEditor,
|
|
35
|
-
type TiptapNode,
|
|
36
|
-
} from './lib/utils/richText'
|
|
37
|
-
export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line local/no-export-star
|
|
9
|
+
export * from '@tldraw/state'
|
|
10
|
+
// eslint-disable-next-line local/no-export-star
|
|
11
|
+
export * from '@tldraw/state-react'
|
|
38
12
|
// eslint-disable-next-line local/no-export-star
|
|
39
13
|
export * from '@tldraw/store'
|
|
40
14
|
// eslint-disable-next-line local/no-export-star
|
|
@@ -43,6 +17,7 @@ export * from '@tldraw/tlschema'
|
|
|
43
17
|
export * from '@tldraw/utils'
|
|
44
18
|
// eslint-disable-next-line local/no-export-star
|
|
45
19
|
export * from '@tldraw/validate'
|
|
20
|
+
|
|
46
21
|
export {
|
|
47
22
|
ErrorScreen,
|
|
48
23
|
LoadingScreen,
|
|
@@ -212,6 +187,7 @@ export {
|
|
|
212
187
|
export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
|
|
213
188
|
export { getPerfectDashProps } from './lib/editor/shapes/shared/getPerfectDashProps'
|
|
214
189
|
export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox'
|
|
190
|
+
export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
|
|
215
191
|
export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
|
|
216
192
|
export { maybeSnapToGrid } from './lib/editor/tools/BaseBoxShapeTool/children/Pointing'
|
|
217
193
|
export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode'
|
|
@@ -459,12 +435,21 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
|
|
|
459
435
|
export { isAccelKey } from './lib/utils/keyboard'
|
|
460
436
|
export { normalizeWheel } from './lib/utils/normalizeWheel'
|
|
461
437
|
export { refreshPage } from './lib/utils/refreshPage'
|
|
438
|
+
export {
|
|
439
|
+
getFontsFromRichText,
|
|
440
|
+
type RichTextFontVisitor,
|
|
441
|
+
type RichTextFontVisitorState,
|
|
442
|
+
type TLTextOptions,
|
|
443
|
+
type TiptapEditor,
|
|
444
|
+
type TiptapNode,
|
|
445
|
+
} from './lib/utils/richText'
|
|
462
446
|
export {
|
|
463
447
|
applyRotationToSnapshotShapes,
|
|
464
448
|
getRotationSnapshot,
|
|
465
449
|
type TLRotationSnapshot,
|
|
466
450
|
} from './lib/utils/rotation'
|
|
467
451
|
export { runtime, setRuntimeOverrides } from './lib/utils/runtime'
|
|
452
|
+
export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
|
|
468
453
|
export { type TLStoreWithStatus } from './lib/utils/sync/StoreWithStatus'
|
|
469
454
|
export { hardReset } from './lib/utils/sync/hardReset'
|
|
470
455
|
export { uniq } from './lib/utils/uniq'
|
|
@@ -6,6 +6,7 @@ import { ShapeUtil } from '../editor/shapes/ShapeUtil'
|
|
|
6
6
|
import { useEditor } from '../hooks/useEditor'
|
|
7
7
|
import { useEditorComponents } from '../hooks/useEditorComponents'
|
|
8
8
|
import { Mat } from '../primitives/Mat'
|
|
9
|
+
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
9
10
|
import { setStyleProperty } from '../utils/dom'
|
|
10
11
|
import { OptionalErrorBoundary } from './ErrorBoundary'
|
|
11
12
|
|
|
@@ -27,6 +28,7 @@ export const Shape = memo(function Shape({
|
|
|
27
28
|
index,
|
|
28
29
|
backgroundIndex,
|
|
29
30
|
opacity,
|
|
31
|
+
dprMultiple,
|
|
30
32
|
}: {
|
|
31
33
|
id: TLShapeId
|
|
32
34
|
shape: TLShape
|
|
@@ -34,6 +36,7 @@ export const Shape = memo(function Shape({
|
|
|
34
36
|
index: number
|
|
35
37
|
backgroundIndex: number
|
|
36
38
|
opacity: number
|
|
39
|
+
dprMultiple: number
|
|
37
40
|
}) {
|
|
38
41
|
const editor = useEditor()
|
|
39
42
|
|
|
@@ -88,14 +91,18 @@ export const Shape = memo(function Shape({
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
// Width / Height
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
// We round the shape width and height up to the nearest multiple of dprMultiple
|
|
95
|
+
// to avoid the browser making miscalculations when applying the transform.
|
|
96
|
+
const widthRemainder = bounds.w % dprMultiple
|
|
97
|
+
const heightRemainder = bounds.h % dprMultiple
|
|
98
|
+
const width = widthRemainder === 0 ? bounds.w : bounds.w + (dprMultiple - widthRemainder)
|
|
99
|
+
const height = heightRemainder === 0 ? bounds.h : bounds.h + (dprMultiple - heightRemainder)
|
|
93
100
|
|
|
94
101
|
if (width !== prev.width || height !== prev.height) {
|
|
95
|
-
setStyleProperty(containerRef.current, 'width', width + 'px')
|
|
96
|
-
setStyleProperty(containerRef.current, 'height', height + 'px')
|
|
97
|
-
setStyleProperty(bgContainerRef.current, 'width', width + 'px')
|
|
98
|
-
setStyleProperty(bgContainerRef.current, 'height', height + 'px')
|
|
102
|
+
setStyleProperty(containerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
|
|
103
|
+
setStyleProperty(containerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
|
|
104
|
+
setStyleProperty(bgContainerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
|
|
105
|
+
setStyleProperty(bgContainerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
|
|
99
106
|
prev.width = width
|
|
100
107
|
prev.height = height
|
|
101
108
|
}
|
|
@@ -184,10 +191,7 @@ export const InnerShape = memo(
|
|
|
184
191
|
[util, shape.id]
|
|
185
192
|
)
|
|
186
193
|
},
|
|
187
|
-
(prev, next) =>
|
|
188
|
-
prev.shape.props === next.shape.props &&
|
|
189
|
-
prev.shape.meta === next.shape.meta &&
|
|
190
|
-
prev.util === next.util
|
|
194
|
+
(prev, next) => areShapesContentEqual(prev.shape, next.shape) && prev.util === next.util
|
|
191
195
|
)
|
|
192
196
|
|
|
193
197
|
export const InnerShapeBackground = memo(
|
|
@@ -22,6 +22,7 @@ import { Vec } from '../../primitives/Vec'
|
|
|
22
22
|
import { toDomPrecision } from '../../primitives/utils'
|
|
23
23
|
import { debugFlags } from '../../utils/debug-flags'
|
|
24
24
|
import { setStyleProperty } from '../../utils/dom'
|
|
25
|
+
import { nearestMultiple } from '../../utils/nearestMultiple'
|
|
25
26
|
import { GeometryDebuggingView } from '../GeometryDebuggingView'
|
|
26
27
|
import { LiveCollaborators } from '../LiveCollaborators'
|
|
27
28
|
import { MenuClickCapture } from '../MenuClickCapture'
|
|
@@ -388,9 +389,18 @@ function ShapesWithSVGs() {
|
|
|
388
389
|
|
|
389
390
|
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
|
|
390
391
|
|
|
392
|
+
const dprMultiple = useValue(
|
|
393
|
+
'dpr multiple',
|
|
394
|
+
() =>
|
|
395
|
+
// dprMultiple is the smallest number we can multiply dpr by to get an integer
|
|
396
|
+
// it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
|
|
397
|
+
nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
|
|
398
|
+
[editor]
|
|
399
|
+
)
|
|
400
|
+
|
|
391
401
|
return renderingShapes.map((result) => (
|
|
392
402
|
<Fragment key={result.id + '_fragment'}>
|
|
393
|
-
<Shape {...result} />
|
|
403
|
+
<Shape {...result} dprMultiple={dprMultiple} />
|
|
394
404
|
<DebugSvgCopy id={result.id} mode="iframe" />
|
|
395
405
|
</Fragment>
|
|
396
406
|
))
|
|
@@ -425,10 +435,19 @@ function ShapesToDisplay() {
|
|
|
425
435
|
|
|
426
436
|
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
|
|
427
437
|
|
|
438
|
+
const dprMultiple = useValue(
|
|
439
|
+
'dpr multiple',
|
|
440
|
+
() =>
|
|
441
|
+
// dprMultiple is the smallest number we can multiply dpr by to get an integer
|
|
442
|
+
// it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
|
|
443
|
+
nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
|
|
444
|
+
[editor]
|
|
445
|
+
)
|
|
446
|
+
|
|
428
447
|
return (
|
|
429
448
|
<>
|
|
430
449
|
{renderingShapes.map((result) => (
|
|
431
|
-
<Shape key={result.id + '_shape'} {...result} />
|
|
450
|
+
<Shape key={result.id + '_shape'} {...result} dprMultiple={dprMultiple} />
|
|
432
451
|
))}
|
|
433
452
|
{tlenv.isSafari && <ReflowIfNeeded />}
|
|
434
453
|
</>
|
|
@@ -9,13 +9,21 @@ import { useEditorComponents } from '../../hooks/useEditorComponents'
|
|
|
9
9
|
import { OptionalErrorBoundary } from '../ErrorBoundary'
|
|
10
10
|
|
|
11
11
|
// need an extra layer of indirection here to allow hooks to be used inside the indicator render
|
|
12
|
-
const EvenInnererIndicator = memo(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const EvenInnererIndicator = memo(
|
|
13
|
+
({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
|
|
14
|
+
return useStateTracking('Indicator: ' + shape.type, () =>
|
|
15
|
+
// always fetch the latest shape from the store even if the props/meta have not changed, to avoid
|
|
16
|
+
// calling the render method with stale data.
|
|
17
|
+
util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
(prevProps, nextProps) => {
|
|
21
|
+
return (
|
|
22
|
+
prevProps.shape.props === nextProps.shape.props &&
|
|
23
|
+
prevProps.shape.meta === nextProps.shape.meta
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
)
|
|
19
27
|
|
|
20
28
|
const InnerIndicator = memo(({ editor, id }: { editor: Editor; id: TLShapeId }) => {
|
|
21
29
|
const shape = useValue('shape for indicator', () => editor.store.get(id), [editor, id])
|
|
@@ -61,13 +69,14 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
|
|
|
61
69
|
useQuickReactor(
|
|
62
70
|
'indicator transform',
|
|
63
71
|
() => {
|
|
72
|
+
if (hidden) return
|
|
64
73
|
const elm = rIndicator.current
|
|
65
74
|
if (!elm) return
|
|
66
75
|
const pageTransform = editor.getShapePageTransform(shapeId)
|
|
67
76
|
if (!pageTransform) return
|
|
68
77
|
elm.style.setProperty('transform', pageTransform.toCssString())
|
|
69
78
|
},
|
|
70
|
-
[editor, shapeId]
|
|
79
|
+
[editor, shapeId, hidden]
|
|
71
80
|
)
|
|
72
81
|
|
|
73
82
|
useLayoutEffect(() => {
|
|
@@ -52,7 +52,7 @@ beforeEach(() => {
|
|
|
52
52
|
shapeUtils: [CustomShape],
|
|
53
53
|
bindingUtils: [],
|
|
54
54
|
tools: [],
|
|
55
|
-
store: createTLStore({ shapeUtils: [CustomShape] }),
|
|
55
|
+
store: createTLStore({ shapeUtils: [CustomShape], bindingUtils: [] }),
|
|
56
56
|
getContainer: () => document.body,
|
|
57
57
|
})
|
|
58
58
|
editor.setCameraOptions({ isLocked: true })
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -129,6 +129,7 @@ import { Group2d } from '../primitives/geometry/Group2d'
|
|
|
129
129
|
import { intersectPolygonPolygon } from '../primitives/intersect'
|
|
130
130
|
import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'
|
|
131
131
|
import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
|
|
132
|
+
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
132
133
|
import { dataUrlToFile } from '../utils/assets'
|
|
133
134
|
import { debugFlags } from '../utils/debug-flags'
|
|
134
135
|
import {
|
|
@@ -2275,13 +2276,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2275
2276
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2276
2277
|
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2277
2278
|
this.setRichTextEditor(null)
|
|
2278
|
-
|
|
2279
|
+
const prevEditingShapeId = this.getEditingShapeId()
|
|
2280
|
+
if (id !== prevEditingShapeId) {
|
|
2279
2281
|
if (id) {
|
|
2280
2282
|
const shape = this.getShape(id)
|
|
2281
2283
|
if (shape && this.getShapeUtil(shape).canEdit(shape)) {
|
|
2282
2284
|
this.run(
|
|
2283
2285
|
() => {
|
|
2284
2286
|
this._updateCurrentPageState({ editingShapeId: id })
|
|
2287
|
+
this.getShapeUtil(shape).onEditStart?.(shape)
|
|
2285
2288
|
},
|
|
2286
2289
|
{ history: 'ignore' }
|
|
2287
2290
|
)
|
|
@@ -2294,6 +2297,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2294
2297
|
() => {
|
|
2295
2298
|
this._updateCurrentPageState({ editingShapeId: null })
|
|
2296
2299
|
this._currentRichTextEditor.set(null)
|
|
2300
|
+
if (prevEditingShapeId) {
|
|
2301
|
+
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2302
|
+
if (prevEditingShape) {
|
|
2303
|
+
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2297
2306
|
},
|
|
2298
2307
|
{ history: 'ignore' }
|
|
2299
2308
|
)
|
|
@@ -4574,7 +4583,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4574
4583
|
this.fonts.trackFontsForShape(shape)
|
|
4575
4584
|
return this.getShapeUtil(shape).getGeometry(shape, opts)
|
|
4576
4585
|
},
|
|
4577
|
-
{ areRecordsEqual:
|
|
4586
|
+
{ areRecordsEqual: areShapesContentEqual }
|
|
4578
4587
|
)
|
|
4579
4588
|
}
|
|
4580
4589
|
return this._shapeGeometryCaches[context].get(
|
|
@@ -4622,9 +4631,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4622
4631
|
|
|
4623
4632
|
/** @internal */
|
|
4624
4633
|
@computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
|
|
4625
|
-
return this.store.createComputedCache(
|
|
4626
|
-
|
|
4627
|
-
|
|
4634
|
+
return this.store.createComputedCache(
|
|
4635
|
+
'handles',
|
|
4636
|
+
(shape) => {
|
|
4637
|
+
return this.getShapeUtil(shape).getHandles?.(shape)
|
|
4638
|
+
},
|
|
4639
|
+
{
|
|
4640
|
+
areRecordsEqual: areShapesContentEqual,
|
|
4641
|
+
}
|
|
4642
|
+
)
|
|
4628
4643
|
}
|
|
4629
4644
|
|
|
4630
4645
|
/**
|
|
@@ -5845,9 +5860,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5845
5860
|
@computed
|
|
5846
5861
|
private _getBindingsIndexCache() {
|
|
5847
5862
|
const index = bindingsIndex(this)
|
|
5848
|
-
return this.store.createComputedCache<TLBinding[], TLShape>(
|
|
5849
|
-
|
|
5850
|
-
|
|
5863
|
+
return this.store.createComputedCache<TLBinding[], TLShape>(
|
|
5864
|
+
'bindingsIndex',
|
|
5865
|
+
(shape) => {
|
|
5866
|
+
return index.get().get(shape.id)
|
|
5867
|
+
},
|
|
5868
|
+
// we can ignore the shape equality check here because the index is
|
|
5869
|
+
// computed incrementally based on what bindings are in the store
|
|
5870
|
+
{ areRecordsEqual: () => true }
|
|
5871
|
+
)
|
|
5851
5872
|
}
|
|
5852
5873
|
|
|
5853
5874
|
/**
|
|
@@ -10214,7 +10235,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10214
10235
|
|
|
10215
10236
|
// If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
|
|
10216
10237
|
// If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
|
|
10217
|
-
if (
|
|
10238
|
+
if (info.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
|
|
10218
10239
|
|
|
10219
10240
|
switch (behavior) {
|
|
10220
10241
|
case 'zoom': {
|
|
@@ -10330,12 +10351,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10330
10351
|
if (this.inputs.isPanning && this.inputs.isPointing) {
|
|
10331
10352
|
// Handle spacebar / middle mouse button panning
|
|
10332
10353
|
const { currentScreenPoint, previousScreenPoint } = this.inputs
|
|
10333
|
-
const { panSpeed } = cameraOptions
|
|
10334
10354
|
const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
|
|
10335
|
-
this.setCamera(
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
)
|
|
10355
|
+
this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
|
|
10356
|
+
immediate: true,
|
|
10357
|
+
})
|
|
10339
10358
|
this.maybeTrackPerformance('Panning')
|
|
10340
10359
|
return
|
|
10341
10360
|
}
|
|
@@ -32,6 +32,7 @@ export interface TLMeasureTextSpanOpts {
|
|
|
32
32
|
fontStyle: string
|
|
33
33
|
lineHeight: number
|
|
34
34
|
textAlign: TLDefaultHorizontalAlignStyle
|
|
35
|
+
otherStyles?: Record<string, string>
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const spaceCharacterRegex = /\s/
|
|
@@ -86,6 +87,7 @@ export class TextManager {
|
|
|
86
87
|
*/
|
|
87
88
|
maxWidth: null | number
|
|
88
89
|
minWidth?: null | number
|
|
90
|
+
otherStyles?: Record<string, string>
|
|
89
91
|
padding: string
|
|
90
92
|
disableOverflowWrapBreaking?: boolean
|
|
91
93
|
}
|
|
@@ -112,6 +114,11 @@ export class TextManager {
|
|
|
112
114
|
'overflow-wrap',
|
|
113
115
|
opts.disableOverflowWrapBreaking ? 'normal' : 'break-word'
|
|
114
116
|
)
|
|
117
|
+
if (opts.otherStyles) {
|
|
118
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
119
|
+
wrapperElm.style.setProperty(key, value)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
115
122
|
|
|
116
123
|
const scrollWidth = wrapperElm.scrollWidth
|
|
117
124
|
const rect = wrapperElm.getBoundingClientRect()
|
|
@@ -256,6 +263,11 @@ export class TextManager {
|
|
|
256
263
|
elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
|
|
257
264
|
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
258
265
|
elm.style.setProperty('font-style', opts.fontStyle)
|
|
266
|
+
if (opts.otherStyles) {
|
|
267
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
268
|
+
elm.style.setProperty(key, value)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
259
271
|
|
|
260
272
|
const shouldTruncateToFirstLine =
|
|
261
273
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -707,7 +707,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
707
707
|
onClick?(shape: Shape): TLShapePartial<Shape> | void
|
|
708
708
|
|
|
709
709
|
/**
|
|
710
|
-
* A callback called when a shape
|
|
710
|
+
* A callback called when a shape starts being edited.
|
|
711
|
+
*
|
|
712
|
+
* @param shape - The shape.
|
|
713
|
+
* @public
|
|
714
|
+
*/
|
|
715
|
+
onEditStart?(shape: Shape): void
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* A callback called when a shape finishes being edited.
|
|
711
719
|
*
|
|
712
720
|
* @param shape - The shape.
|
|
713
721
|
* @public
|
|
@@ -4,15 +4,15 @@ import { TLDefaultDashStyle } from '@tldraw/tlschema'
|
|
|
4
4
|
export function getPerfectDashProps(
|
|
5
5
|
totalLength: number,
|
|
6
6
|
strokeWidth: number,
|
|
7
|
-
opts
|
|
8
|
-
style
|
|
9
|
-
snap
|
|
10
|
-
end
|
|
11
|
-
start
|
|
12
|
-
lengthRatio
|
|
13
|
-
closed
|
|
14
|
-
forceSolid
|
|
15
|
-
}
|
|
7
|
+
opts: {
|
|
8
|
+
style?: TLDefaultDashStyle
|
|
9
|
+
snap?: number
|
|
10
|
+
end?: 'skip' | 'outset' | 'none'
|
|
11
|
+
start?: 'skip' | 'outset' | 'none'
|
|
12
|
+
lengthRatio?: number
|
|
13
|
+
closed?: boolean
|
|
14
|
+
forceSolid?: boolean
|
|
15
|
+
} = {}
|
|
16
16
|
): {
|
|
17
17
|
strokeDasharray: string
|
|
18
18
|
strokeDashoffset: string
|
|
@@ -365,6 +365,21 @@ function SvgExport({
|
|
|
365
365
|
onMount()
|
|
366
366
|
}, [onMount, shapeElements])
|
|
367
367
|
|
|
368
|
+
let backgroundColor = background ? theme.background : 'transparent'
|
|
369
|
+
|
|
370
|
+
if (singleFrameShapeId && background) {
|
|
371
|
+
const frameShapeUtil = editor.getShapeUtil('frame') as any as
|
|
372
|
+
| undefined
|
|
373
|
+
| { options: { showColors: boolean } }
|
|
374
|
+
if (frameShapeUtil?.options.showColors) {
|
|
375
|
+
const shape = editor.getShape(singleFrameShapeId)! as TLFrameShape
|
|
376
|
+
const color = theme[shape.props.color]
|
|
377
|
+
backgroundColor = color.frame.fill
|
|
378
|
+
} else {
|
|
379
|
+
backgroundColor = theme.solid
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
368
383
|
return (
|
|
369
384
|
<SvgExportContextProvider editor={editor} context={exportContext}>
|
|
370
385
|
<svg
|
|
@@ -375,13 +390,7 @@ function SvgExport({
|
|
|
375
390
|
viewBox={`${bbox.minX} ${bbox.minY} ${bbox.width} ${bbox.height}`}
|
|
376
391
|
strokeLinecap="round"
|
|
377
392
|
strokeLinejoin="round"
|
|
378
|
-
style={{
|
|
379
|
-
backgroundColor: background
|
|
380
|
-
? singleFrameShapeId
|
|
381
|
-
? theme.solid
|
|
382
|
-
: theme.background
|
|
383
|
-
: 'transparent',
|
|
384
|
-
}}
|
|
393
|
+
style={{ backgroundColor }}
|
|
385
394
|
data-color-mode={isDarkMode ? 'dark' : 'light'}
|
|
386
395
|
className={`tl-container tl-theme__force-sRGB ${isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'}`}
|
|
387
396
|
>
|
|
@@ -57,6 +57,11 @@ export class Box {
|
|
|
57
57
|
this.x = n
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
61
|
+
get left() {
|
|
62
|
+
return this.x
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
// eslint-disable-next-line no-restricted-syntax
|
|
61
66
|
get midX() {
|
|
62
67
|
return this.x + this.w / 2
|
|
@@ -67,6 +72,11 @@ export class Box {
|
|
|
67
72
|
return this.x + this.w
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
76
|
+
get right() {
|
|
77
|
+
return this.x + this.w
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
// eslint-disable-next-line no-restricted-syntax
|
|
71
81
|
get minY() {
|
|
72
82
|
return this.y
|
|
@@ -77,6 +87,11 @@ export class Box {
|
|
|
77
87
|
this.y = n
|
|
78
88
|
}
|
|
79
89
|
|
|
90
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
91
|
+
get top() {
|
|
92
|
+
return this.y
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
// eslint-disable-next-line no-restricted-syntax
|
|
81
96
|
get midY() {
|
|
82
97
|
return this.y + this.h / 2
|
|
@@ -87,6 +102,11 @@ export class Box {
|
|
|
87
102
|
return this.y + this.h
|
|
88
103
|
}
|
|
89
104
|
|
|
105
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
106
|
+
get bottom() {
|
|
107
|
+
return this.y + this.h
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
// eslint-disable-next-line no-restricted-syntax
|
|
91
111
|
get width() {
|
|
92
112
|
return this.w
|