@tldraw/editor 3.14.0-canary.fd2ad122b803 → 3.14.0-canary.fdbfe5bf2604
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 +220 -117
- package/dist-cjs/index.js +11 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
- package/dist-cjs/lib/editor/Editor.js +133 -102
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js +22 -22
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -20
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js +16 -16
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
- package/dist-cjs/lib/editor/managers/{ClickManager.js → ClickManager/ClickManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{EdgeScrollManager.js → EdgeScrollManager/EdgeScrollManager.js} +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{FontManager.js → FontManager/FontManager.js} +4 -1
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{HistoryManager.js → HistoryManager/HistoryManager.js} +67 -7
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{ScribbleManager.js → ScribbleManager/ScribbleManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{TextManager.js → TextManager/TextManager.js} +73 -42
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{TickManager.js → TickManager/TickManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{UserPreferencesManager.js → UserPreferencesManager/UserPreferencesManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +7 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +8 -10
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +6 -0
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +10 -6
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
- package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/exports/getSvgJsx.js.map +1 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +33 -33
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +13 -8
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +41 -21
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +11 -11
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +13 -16
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +4 -4
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +14 -17
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +10 -10
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +11 -6
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +6 -6
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js +3 -0
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js +8 -5
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js +22 -11
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js +22 -22
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +1 -1
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/reorderShapes.js +11 -10
- package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +232 -0
- package/dist-cjs/lib/utils/reparenting.js.map +7 -0
- package/dist-cjs/lib/utils/richText.js +7 -2
- package/dist-cjs/lib/utils/richText.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 +220 -117
- package/dist-esm/index.mjs +15 -8
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +133 -102
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +22 -22
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -20
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +16 -16
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/{ClickManager.mjs → ClickManager/ClickManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{EdgeScrollManager.mjs → EdgeScrollManager/EdgeScrollManager.mjs} +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{FontManager.mjs → FontManager/FontManager.mjs} +4 -1
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{HistoryManager.mjs → HistoryManager/HistoryManager.mjs} +63 -3
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{ScribbleManager.mjs → ScribbleManager/ScribbleManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{TextManager.mjs → TextManager/TextManager.mjs} +73 -42
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{TickManager.mjs → TickManager/TickManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{UserPreferencesManager.mjs → UserPreferencesManager/UserPreferencesManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +7 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +8 -10
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +6 -0
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +10 -6
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
- package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +1 -1
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +33 -33
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +13 -8
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +41 -21
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +13 -16
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +4 -4
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +14 -17
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +11 -6
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +6 -6
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs +3 -0
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +8 -5
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs +22 -11
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +22 -22
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +1 -1
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/reorderShapes.mjs +11 -10
- package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +216 -0
- package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
- package/dist-esm/lib/utils/richText.mjs +8 -3
- package/dist-esm/lib/utils/richText.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +446 -489
- package/package.json +8 -9
- package/src/index.ts +20 -7
- package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
- package/src/lib/editor/Editor.test.ts +252 -3
- package/src/lib/editor/Editor.ts +152 -111
- package/src/lib/editor/bindings/BindingUtil.ts +6 -0
- package/src/lib/editor/derivations/bindingsIndex.ts +27 -26
- package/src/lib/editor/derivations/notVisibleShapes.ts +24 -25
- package/src/lib/editor/derivations/parentsToChildren.ts +28 -25
- package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +442 -0
- package/src/lib/editor/managers/{ClickManager.ts → ClickManager/ClickManager.ts} +3 -3
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +374 -0
- package/src/lib/editor/managers/{EdgeScrollManager.ts → EdgeScrollManager/EdgeScrollManager.ts} +3 -3
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +455 -0
- package/src/lib/editor/managers/{FocusManager.ts → FocusManager/FocusManager.ts} +1 -1
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +263 -0
- package/src/lib/editor/managers/{FontManager.ts → FontManager/FontManager.ts} +5 -2
- package/src/lib/editor/managers/{HistoryManager.test.ts → HistoryManager/HistoryManager.test.ts} +388 -1
- package/src/lib/editor/managers/{HistoryManager.ts → HistoryManager/HistoryManager.ts} +76 -3
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +624 -0
- package/src/lib/editor/managers/{ScribbleManager.ts → ScribbleManager/ScribbleManager.ts} +2 -2
- package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +485 -0
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +407 -0
- package/src/lib/editor/managers/{TextManager.ts → TextManager/TextManager.ts} +119 -87
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +314 -0
- package/src/lib/editor/managers/{TickManager.ts → TickManager/TickManager.ts} +2 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +591 -0
- package/src/lib/editor/managers/{UserPreferencesManager.ts → UserPreferencesManager/UserPreferencesManager.ts} +2 -2
- package/src/lib/editor/shapes/ShapeUtil.ts +57 -16
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +8 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +22 -17
- package/src/lib/editor/tools/StateNode.ts +3 -3
- package/src/lib/editor/types/emit-types.ts +4 -0
- package/src/lib/editor/types/external-content.ts +11 -2
- package/src/lib/exports/getSvgJsx.tsx +1 -1
- package/src/lib/hooks/useCanvasEvents.ts +0 -1
- package/src/lib/primitives/Box.test.ts +588 -7
- package/src/lib/primitives/Box.ts +33 -33
- package/src/lib/primitives/Vec.test.ts +2 -2
- package/src/lib/primitives/Vec.ts +13 -8
- package/src/lib/primitives/geometry/Arc2d.ts +42 -23
- package/src/lib/primitives/geometry/Circle2d.ts +12 -12
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +5 -0
- package/src/lib/primitives/geometry/CubicBezier2d.ts +13 -17
- package/src/lib/primitives/geometry/CubicSpline2d.ts +5 -5
- package/src/lib/primitives/geometry/Edge2d.ts +14 -18
- package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
- package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
- package/src/lib/primitives/geometry/Group2d.ts +11 -5
- package/src/lib/primitives/geometry/Point2d.ts +6 -6
- package/src/lib/primitives/geometry/Polygon2d.ts +4 -0
- package/src/lib/primitives/geometry/Polyline2d.ts +10 -7
- package/src/lib/primitives/geometry/Rectangle2d.ts +24 -11
- package/src/lib/primitives/geometry/Stadium2d.ts +22 -23
- package/src/lib/utils/dom.ts +1 -1
- package/src/lib/utils/reorderShapes.ts +10 -13
- package/src/lib/utils/reparenting.ts +383 -0
- package/src/lib/utils/richText.ts +10 -4
- package/src/version.ts +3 -3
- package/dist-cjs/lib/editor/managers/ClickManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/EdgeScrollManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/FocusManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/FontManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/HistoryManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/ScribbleManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/Stack.js +0 -82
- package/dist-cjs/lib/editor/managers/Stack.js.map +0 -7
- package/dist-cjs/lib/editor/managers/TextManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/TickManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/UserPreferencesManager.js.map +0 -7
- package/dist-esm/lib/editor/managers/ClickManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/EdgeScrollManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/FocusManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/FontManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/HistoryManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/ScribbleManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/Stack.mjs +0 -62
- package/dist-esm/lib/editor/managers/Stack.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/TickManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/UserPreferencesManager.mjs.map +0 -7
- package/src/lib/editor/managers/ScribbleManager.test.ts +0 -32
- package/src/lib/editor/managers/Stack.ts +0 -71
- /package/dist-cjs/lib/editor/managers/{FocusManager.js → FocusManager/FocusManager.js} +0 -0
- /package/dist-esm/lib/editor/managers/{FocusManager.mjs → FocusManager/FocusManager.mjs} +0 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { Editor } from '../../Editor'
|
|
2
|
+
import { TextManager, TLMeasureTextSpanOpts } from './TextManager'
|
|
3
|
+
|
|
4
|
+
// Create a simple mock DOM environment
|
|
5
|
+
const mockElement = {
|
|
6
|
+
classList: { add: jest.fn() },
|
|
7
|
+
tabIndex: -1,
|
|
8
|
+
cloneNode: jest.fn(),
|
|
9
|
+
innerHTML: '',
|
|
10
|
+
textContent: '',
|
|
11
|
+
setAttribute: jest.fn(),
|
|
12
|
+
style: { setProperty: jest.fn() },
|
|
13
|
+
scrollWidth: 100,
|
|
14
|
+
getBoundingClientRect: jest.fn(() => ({
|
|
15
|
+
width: 100,
|
|
16
|
+
height: 20,
|
|
17
|
+
left: 0,
|
|
18
|
+
top: 0,
|
|
19
|
+
right: 100,
|
|
20
|
+
bottom: 20,
|
|
21
|
+
})),
|
|
22
|
+
remove: jest.fn(),
|
|
23
|
+
insertAdjacentElement: jest.fn(),
|
|
24
|
+
childNodes: [],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Mock document.createElement to return our mock element
|
|
28
|
+
const mockCreateElement = jest.fn(() => {
|
|
29
|
+
const element = { ...mockElement }
|
|
30
|
+
element.cloneNode = jest.fn(() => ({ ...element }))
|
|
31
|
+
return element
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Mock editor
|
|
35
|
+
const mockEditor = {
|
|
36
|
+
getContainer: jest.fn(() => ({
|
|
37
|
+
appendChild: jest.fn(),
|
|
38
|
+
})),
|
|
39
|
+
} as unknown as Editor
|
|
40
|
+
|
|
41
|
+
// Setup global mocks
|
|
42
|
+
global.document = {
|
|
43
|
+
createElement: mockCreateElement,
|
|
44
|
+
} as any
|
|
45
|
+
|
|
46
|
+
global.Range = jest.fn(() => ({
|
|
47
|
+
setStart: jest.fn(),
|
|
48
|
+
setEnd: jest.fn(),
|
|
49
|
+
getClientRects: jest.fn(() => [
|
|
50
|
+
{
|
|
51
|
+
width: 10,
|
|
52
|
+
height: 16,
|
|
53
|
+
left: 0,
|
|
54
|
+
top: 0,
|
|
55
|
+
right: 10,
|
|
56
|
+
bottom: 16,
|
|
57
|
+
},
|
|
58
|
+
]),
|
|
59
|
+
})) as any
|
|
60
|
+
|
|
61
|
+
describe('TextManager', () => {
|
|
62
|
+
let textManager: TextManager
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
jest.clearAllMocks()
|
|
66
|
+
textManager = new TextManager(mockEditor)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('constructor', () => {
|
|
70
|
+
it('should create a TextManager instance', () => {
|
|
71
|
+
expect(textManager).toBeInstanceOf(TextManager)
|
|
72
|
+
expect(textManager.editor).toBe(mockEditor)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('measureText', () => {
|
|
77
|
+
const defaultOpts = {
|
|
78
|
+
fontStyle: 'normal',
|
|
79
|
+
fontWeight: '400',
|
|
80
|
+
fontFamily: 'Arial',
|
|
81
|
+
fontSize: 16,
|
|
82
|
+
lineHeight: 1.2,
|
|
83
|
+
maxWidth: 200,
|
|
84
|
+
minWidth: null,
|
|
85
|
+
padding: '0px',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
it('should call measureHtml with normalized text', () => {
|
|
89
|
+
const spy = jest.spyOn(textManager, 'measureHtml')
|
|
90
|
+
textManager.measureText('Hello World', defaultOpts)
|
|
91
|
+
expect(spy).toHaveBeenCalledWith('Hello World', defaultOpts)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should normalize line breaks', () => {
|
|
95
|
+
const spy = jest.spyOn(textManager, 'measureHtml')
|
|
96
|
+
textManager.measureText('Hello\nWorld\r\nTest', defaultOpts)
|
|
97
|
+
// The text should be normalized to use consistent line breaks
|
|
98
|
+
expect(spy).toHaveBeenCalled()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should handle empty text', () => {
|
|
102
|
+
const result = textManager.measureText('', { ...defaultOpts, measureScrollWidth: true })
|
|
103
|
+
expect(result).toHaveProperty('x', 0)
|
|
104
|
+
expect(result).toHaveProperty('y', 0)
|
|
105
|
+
expect(result).toHaveProperty('w')
|
|
106
|
+
expect(result).toHaveProperty('h')
|
|
107
|
+
expect(result).toHaveProperty('scrollWidth')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('measureHtml', () => {
|
|
112
|
+
const defaultOpts = {
|
|
113
|
+
fontStyle: 'normal',
|
|
114
|
+
fontWeight: '400',
|
|
115
|
+
fontFamily: 'Arial',
|
|
116
|
+
fontSize: 16,
|
|
117
|
+
lineHeight: 1.2,
|
|
118
|
+
maxWidth: 200,
|
|
119
|
+
minWidth: null,
|
|
120
|
+
padding: '0px',
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
it('should return measurement object with correct structure', () => {
|
|
124
|
+
const result = textManager.measureHtml('<span>Test</span>', defaultOpts)
|
|
125
|
+
|
|
126
|
+
expect(result).toMatchObject({
|
|
127
|
+
x: 0,
|
|
128
|
+
y: 0,
|
|
129
|
+
w: expect.any(Number),
|
|
130
|
+
h: expect.any(Number),
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should handle null maxWidth', () => {
|
|
135
|
+
const opts = { ...defaultOpts, maxWidth: null }
|
|
136
|
+
const result = textManager.measureHtml('Test', opts)
|
|
137
|
+
|
|
138
|
+
expect(result).toMatchObject({
|
|
139
|
+
x: 0,
|
|
140
|
+
y: 0,
|
|
141
|
+
w: expect.any(Number),
|
|
142
|
+
h: expect.any(Number),
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should handle overflow wrap breaking', () => {
|
|
147
|
+
const opts = { ...defaultOpts, disableOverflowWrapBreaking: true }
|
|
148
|
+
const result = textManager.measureHtml('Test', opts)
|
|
149
|
+
|
|
150
|
+
expect(result).toMatchObject({
|
|
151
|
+
x: 0,
|
|
152
|
+
y: 0,
|
|
153
|
+
w: expect.any(Number),
|
|
154
|
+
h: expect.any(Number),
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should handle other styles', () => {
|
|
159
|
+
const opts = {
|
|
160
|
+
...defaultOpts,
|
|
161
|
+
otherStyles: {
|
|
162
|
+
'text-decoration': 'underline',
|
|
163
|
+
color: 'red',
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = textManager.measureHtml('Test', opts)
|
|
168
|
+
expect(result).toMatchObject({
|
|
169
|
+
x: 0,
|
|
170
|
+
y: 0,
|
|
171
|
+
w: expect.any(Number),
|
|
172
|
+
h: expect.any(Number),
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('measureElementTextNodeSpans', () => {
|
|
178
|
+
it('should handle elements with text nodes', () => {
|
|
179
|
+
const mockTextNode = {
|
|
180
|
+
nodeType: 3, // TEXT_NODE
|
|
181
|
+
textContent: 'Hello',
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const mockElementWithText = {
|
|
185
|
+
childNodes: [mockTextNode],
|
|
186
|
+
getBoundingClientRect: () => ({ left: 0, top: 0 }),
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const result = textManager.measureElementTextNodeSpans(mockElementWithText as any)
|
|
190
|
+
|
|
191
|
+
expect(result).toHaveProperty('spans')
|
|
192
|
+
expect(result).toHaveProperty('didTruncate')
|
|
193
|
+
expect(Array.isArray(result.spans)).toBe(true)
|
|
194
|
+
expect(typeof result.didTruncate).toBe('boolean')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('should handle empty elements', () => {
|
|
198
|
+
const mockEmptyElement = {
|
|
199
|
+
childNodes: [],
|
|
200
|
+
getBoundingClientRect: () => ({ left: 0, top: 0 }),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = textManager.measureElementTextNodeSpans(mockEmptyElement as any)
|
|
204
|
+
|
|
205
|
+
expect(result.didTruncate).toBe(false)
|
|
206
|
+
expect(result.spans).toHaveLength(0)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should handle truncation option', () => {
|
|
210
|
+
const mockTextNode = {
|
|
211
|
+
nodeType: 3, // TEXT_NODE
|
|
212
|
+
textContent: 'Hello World',
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const mockElementWithText = {
|
|
216
|
+
childNodes: [mockTextNode],
|
|
217
|
+
getBoundingClientRect: () => ({ left: 0, top: 0 }),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = textManager.measureElementTextNodeSpans(mockElementWithText as any, {
|
|
221
|
+
shouldTruncateToFirstLine: true,
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
expect(result).toHaveProperty('spans')
|
|
225
|
+
expect(result).toHaveProperty('didTruncate')
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
describe('measureTextSpans', () => {
|
|
230
|
+
const defaultOpts: TLMeasureTextSpanOpts = {
|
|
231
|
+
overflow: 'wrap',
|
|
232
|
+
width: 200,
|
|
233
|
+
height: 100,
|
|
234
|
+
padding: 10,
|
|
235
|
+
fontSize: 16,
|
|
236
|
+
fontWeight: '400',
|
|
237
|
+
fontFamily: 'Arial',
|
|
238
|
+
fontStyle: 'normal',
|
|
239
|
+
lineHeight: 1.2,
|
|
240
|
+
textAlign: 'start',
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
it('should return empty array for empty text', () => {
|
|
244
|
+
const result = textManager.measureTextSpans('', defaultOpts)
|
|
245
|
+
expect(result).toEqual([])
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should return array of text spans for non-empty text', () => {
|
|
249
|
+
// Mock measureElementTextNodeSpans to return some spans
|
|
250
|
+
jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
|
|
251
|
+
spans: [
|
|
252
|
+
{
|
|
253
|
+
text: 'Hello World',
|
|
254
|
+
box: { x: 0, y: 0, w: 100, h: 16 },
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
didTruncate: false,
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const result = textManager.measureTextSpans('Hello World', defaultOpts)
|
|
261
|
+
|
|
262
|
+
expect(Array.isArray(result)).toBe(true)
|
|
263
|
+
expect(result.length).toBeGreaterThan(0)
|
|
264
|
+
expect(result[0]).toHaveProperty('text')
|
|
265
|
+
expect(result[0]).toHaveProperty('box')
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('should handle wrap overflow', () => {
|
|
269
|
+
jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
|
|
270
|
+
spans: [
|
|
271
|
+
{
|
|
272
|
+
text: 'Hello World',
|
|
273
|
+
box: { x: 0, y: 0, w: 100, h: 16 },
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
didTruncate: false,
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const opts = { ...defaultOpts, overflow: 'wrap' as const }
|
|
280
|
+
const result = textManager.measureTextSpans('Hello World', opts)
|
|
281
|
+
|
|
282
|
+
expect(Array.isArray(result)).toBe(true)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should handle truncate-ellipsis overflow', () => {
|
|
286
|
+
// Mock the calls for ellipsis handling
|
|
287
|
+
jest
|
|
288
|
+
.spyOn(textManager, 'measureElementTextNodeSpans')
|
|
289
|
+
.mockReturnValueOnce({
|
|
290
|
+
spans: [
|
|
291
|
+
{
|
|
292
|
+
text: 'Hello Wo',
|
|
293
|
+
box: { x: 0, y: 0, w: 80, h: 16 },
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
didTruncate: true,
|
|
297
|
+
})
|
|
298
|
+
.mockReturnValueOnce({
|
|
299
|
+
spans: [
|
|
300
|
+
{
|
|
301
|
+
text: '…',
|
|
302
|
+
box: { x: 0, y: 0, w: 10, h: 16 },
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
didTruncate: false,
|
|
306
|
+
})
|
|
307
|
+
.mockReturnValueOnce({
|
|
308
|
+
spans: [
|
|
309
|
+
{
|
|
310
|
+
text: 'Hello W',
|
|
311
|
+
box: { x: 0, y: 0, w: 70, h: 16 },
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
didTruncate: false,
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
const opts = { ...defaultOpts, overflow: 'truncate-ellipsis' as const }
|
|
318
|
+
const result = textManager.measureTextSpans('Hello World', opts)
|
|
319
|
+
|
|
320
|
+
expect(Array.isArray(result)).toBe(true)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('should handle truncate-clip overflow', () => {
|
|
324
|
+
jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
|
|
325
|
+
spans: [
|
|
326
|
+
{
|
|
327
|
+
text: 'Hello Wo',
|
|
328
|
+
box: { x: 0, y: 0, w: 80, h: 16 },
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
didTruncate: true,
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
const opts = { ...defaultOpts, overflow: 'truncate-clip' as const }
|
|
335
|
+
const result = textManager.measureTextSpans('Hello World', opts)
|
|
336
|
+
|
|
337
|
+
expect(Array.isArray(result)).toBe(true)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('should handle different text alignments', () => {
|
|
341
|
+
jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
|
|
342
|
+
spans: [
|
|
343
|
+
{
|
|
344
|
+
text: 'Test',
|
|
345
|
+
box: { x: 0, y: 0, w: 40, h: 16 },
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
didTruncate: false,
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
const alignments: Array<TLMeasureTextSpanOpts['textAlign']> = ['start', 'middle', 'end']
|
|
352
|
+
|
|
353
|
+
alignments.forEach((textAlign) => {
|
|
354
|
+
const opts = { ...defaultOpts, textAlign }
|
|
355
|
+
const result = textManager.measureTextSpans('Test', opts)
|
|
356
|
+
expect(Array.isArray(result)).toBe(true)
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should handle custom font properties', () => {
|
|
361
|
+
jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
|
|
362
|
+
spans: [
|
|
363
|
+
{
|
|
364
|
+
text: 'Test',
|
|
365
|
+
box: { x: 0, y: 0, w: 40, h: 16 },
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
didTruncate: false,
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
const opts = {
|
|
372
|
+
...defaultOpts,
|
|
373
|
+
fontSize: 18,
|
|
374
|
+
fontFamily: 'Times',
|
|
375
|
+
fontWeight: 'bold',
|
|
376
|
+
fontStyle: 'italic',
|
|
377
|
+
lineHeight: 1.5,
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const result = textManager.measureTextSpans('Test', opts)
|
|
381
|
+
expect(Array.isArray(result)).toBe(true)
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('should handle other styles', () => {
|
|
385
|
+
jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
|
|
386
|
+
spans: [
|
|
387
|
+
{
|
|
388
|
+
text: 'Test',
|
|
389
|
+
box: { x: 0, y: 0, w: 40, h: 16 },
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
didTruncate: false,
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const opts = {
|
|
396
|
+
...defaultOpts,
|
|
397
|
+
otherStyles: {
|
|
398
|
+
'text-shadow': '1px 1px 1px black',
|
|
399
|
+
'letter-spacing': '1px',
|
|
400
|
+
},
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const result = textManager.measureTextSpans('Test', opts)
|
|
404
|
+
expect(Array.isArray(result)).toBe(true)
|
|
405
|
+
})
|
|
406
|
+
})
|
|
407
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
|
|
2
|
-
import { Editor } from '
|
|
2
|
+
import { Editor } from '../../Editor'
|
|
3
3
|
|
|
4
4
|
const fixNewLines = /\r?\n|\r/g
|
|
5
5
|
|
|
@@ -20,6 +20,28 @@ const textAlignmentsForLtr = {
|
|
|
20
20
|
'end-legacy': 'right',
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** @public */
|
|
24
|
+
export interface TLMeasureTextOpts {
|
|
25
|
+
fontStyle: string
|
|
26
|
+
fontWeight: string
|
|
27
|
+
fontFamily: string
|
|
28
|
+
fontSize: number
|
|
29
|
+
/** This must be a number, e.g. 1.35, not a pixel value. */
|
|
30
|
+
lineHeight: number
|
|
31
|
+
/**
|
|
32
|
+
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
33
|
+
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
34
|
+
* space are preserved.
|
|
35
|
+
*/
|
|
36
|
+
maxWidth: null | number
|
|
37
|
+
minWidth?: null | number
|
|
38
|
+
// todo: make this a number so that it is consistent with other TLMeasureTextSpanOpts
|
|
39
|
+
padding: string
|
|
40
|
+
otherStyles?: Record<string, string>
|
|
41
|
+
disableOverflowWrapBreaking?: boolean
|
|
42
|
+
measureScrollWidth?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
/** @public */
|
|
24
46
|
export interface TLMeasureTextSpanOpts {
|
|
25
47
|
overflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'
|
|
@@ -33,96 +55,99 @@ export interface TLMeasureTextSpanOpts {
|
|
|
33
55
|
lineHeight: number
|
|
34
56
|
textAlign: TLDefaultHorizontalAlignStyle
|
|
35
57
|
otherStyles?: Record<string, string>
|
|
58
|
+
measureScrollWidth?: boolean
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
const spaceCharacterRegex = /\s/
|
|
39
62
|
|
|
40
63
|
/** @public */
|
|
41
64
|
export class TextManager {
|
|
42
|
-
private
|
|
65
|
+
private elm: HTMLDivElement
|
|
66
|
+
private defaultStyles: Record<string, string | null>
|
|
43
67
|
|
|
44
68
|
constructor(public editor: Editor) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
const elm = document.createElement('div')
|
|
70
|
+
elm.classList.add('tl-text')
|
|
71
|
+
elm.classList.add('tl-text-measure')
|
|
72
|
+
elm.setAttribute('dir', 'auto')
|
|
73
|
+
elm.tabIndex = -1
|
|
74
|
+
this.editor.getContainer().appendChild(elm)
|
|
75
|
+
|
|
76
|
+
// we need to save the default styles so that we can restore them when we're done
|
|
77
|
+
// these must be the css names, not the js names for the styles
|
|
78
|
+
this.defaultStyles = {
|
|
79
|
+
'overflow-wrap': 'break-word',
|
|
80
|
+
'word-break': 'auto',
|
|
81
|
+
width: null,
|
|
82
|
+
height: null,
|
|
83
|
+
'max-width': null,
|
|
84
|
+
'min-width': null,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.elm = elm
|
|
49
88
|
}
|
|
50
89
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
61
|
-
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
62
|
-
* space are preserved.
|
|
63
|
-
*/
|
|
64
|
-
maxWidth: null | number
|
|
65
|
-
minWidth?: null | number
|
|
66
|
-
padding: string
|
|
67
|
-
disableOverflowWrapBreaking?: boolean
|
|
90
|
+
dispose() {
|
|
91
|
+
return this.elm.remove()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private resetElmStyles() {
|
|
95
|
+
const { elm, defaultStyles } = this
|
|
96
|
+
for (const key in defaultStyles) {
|
|
97
|
+
elm.style.setProperty(key, defaultStyles[key])
|
|
68
98
|
}
|
|
69
|
-
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
70
102
|
const div = document.createElement('div')
|
|
71
103
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
72
104
|
return this.measureHtml(div.innerHTML, opts)
|
|
73
105
|
}
|
|
74
106
|
|
|
75
|
-
measureHtml(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
86
|
-
* space are preserved.
|
|
87
|
-
*/
|
|
88
|
-
maxWidth: null | number
|
|
89
|
-
minWidth?: null | number
|
|
90
|
-
otherStyles?: Record<string, string>
|
|
91
|
-
padding: string
|
|
92
|
-
disableOverflowWrapBreaking?: boolean
|
|
107
|
+
measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
108
|
+
const { elm } = this
|
|
109
|
+
|
|
110
|
+
if (opts.otherStyles) {
|
|
111
|
+
for (const key in opts.otherStyles) {
|
|
112
|
+
if (!this.defaultStyles[key]) {
|
|
113
|
+
// we need to save the original style so that we can restore it when we're done
|
|
114
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
93
117
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
|
|
119
|
+
elm.innerHTML = html
|
|
120
|
+
|
|
121
|
+
// Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
|
|
122
|
+
this.resetElmStyles()
|
|
123
|
+
|
|
124
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
125
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
126
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
127
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
128
|
+
elm.style.setProperty('line-height', opts.lineHeight.toString())
|
|
129
|
+
elm.style.setProperty('padding', opts.padding)
|
|
130
|
+
|
|
131
|
+
if (opts.maxWidth) {
|
|
132
|
+
elm.style.setProperty('max-width', opts.maxWidth + 'px')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (opts.minWidth) {
|
|
136
|
+
elm.style.setProperty('min-width', opts.minWidth + 'px')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (opts.disableOverflowWrapBreaking) {
|
|
140
|
+
elm.style.setProperty('overflow-wrap', 'normal')
|
|
141
|
+
}
|
|
142
|
+
|
|
117
143
|
if (opts.otherStyles) {
|
|
118
144
|
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
119
|
-
|
|
145
|
+
elm.style.setProperty(key, value)
|
|
120
146
|
}
|
|
121
147
|
}
|
|
122
148
|
|
|
123
|
-
const scrollWidth =
|
|
124
|
-
const rect =
|
|
125
|
-
wrapperElm.remove()
|
|
149
|
+
const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
|
|
150
|
+
const rect = elm.getBoundingClientRect()
|
|
126
151
|
|
|
127
152
|
return {
|
|
128
153
|
x: 0,
|
|
@@ -247,27 +272,29 @@ export class TextManager {
|
|
|
247
272
|
): { text: string; box: BoxModel }[] {
|
|
248
273
|
if (textToMeasure === '') return []
|
|
249
274
|
|
|
250
|
-
const elm = this
|
|
251
|
-
|
|
275
|
+
const { elm } = this
|
|
276
|
+
|
|
277
|
+
if (opts.otherStyles) {
|
|
278
|
+
for (const key in opts.otherStyles) {
|
|
279
|
+
if (!this.defaultStyles[key]) {
|
|
280
|
+
// we need to save the original style so that we can restore it when we're done
|
|
281
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.resetElmStyles()
|
|
287
|
+
|
|
288
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
289
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
290
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
291
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
292
|
+
elm.style.setProperty('line-height', opts.lineHeight.toString())
|
|
252
293
|
|
|
253
294
|
const elementWidth = Math.ceil(opts.width - opts.padding * 2)
|
|
254
|
-
elm.setAttribute('dir', 'auto')
|
|
255
|
-
// N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers")
|
|
256
|
-
// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.
|
|
257
|
-
elm.style.setProperty('unicode-bidi', 'plaintext')
|
|
258
295
|
elm.style.setProperty('width', `${elementWidth}px`)
|
|
259
296
|
elm.style.setProperty('height', 'min-content')
|
|
260
|
-
elm.style.setProperty('font-size', `${opts.fontSize}px`)
|
|
261
|
-
elm.style.setProperty('font-family', opts.fontFamily)
|
|
262
|
-
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
263
|
-
elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
|
|
264
297
|
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
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
|
-
}
|
|
271
298
|
|
|
272
299
|
const shouldTruncateToFirstLine =
|
|
273
300
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -277,6 +304,12 @@ export class TextManager {
|
|
|
277
304
|
elm.style.setProperty('word-break', 'break-all')
|
|
278
305
|
}
|
|
279
306
|
|
|
307
|
+
if (opts.otherStyles) {
|
|
308
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
309
|
+
elm.style.setProperty(key, value)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
280
313
|
const normalizedText = normalizeTextForDom(textToMeasure)
|
|
281
314
|
|
|
282
315
|
// Render the text into the measurement element:
|
|
@@ -313,11 +346,10 @@ export class TextManager {
|
|
|
313
346
|
h: lastSpan.box.h,
|
|
314
347
|
},
|
|
315
348
|
})
|
|
349
|
+
|
|
316
350
|
return truncatedSpans
|
|
317
351
|
}
|
|
318
352
|
|
|
319
|
-
elm.remove()
|
|
320
|
-
|
|
321
353
|
return spans
|
|
322
354
|
}
|
|
323
355
|
}
|