@tldraw/editor 3.16.0-canary.6074088f67bd → 3.16.0-canary.62bc202550a3
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 +5 -101
- package/dist-cjs/index.js +1 -5
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +0 -4
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +3 -99
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +4 -1
- package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +17 -22
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +5 -0
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +0 -4
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +26 -18
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +3 -0
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +2 -35
- package/dist-cjs/lib/utils/reparenting.js.map +3 -3
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +5 -101
- package/dist-esm/index.mjs +1 -5
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +0 -4
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +3 -99
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +4 -1
- package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +17 -22
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +5 -0
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +0 -4
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +29 -19
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +3 -0
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +3 -40
- package/dist-esm/lib/utils/reparenting.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/index.ts +1 -9
- package/src/lib/TldrawEditor.tsx +0 -11
- package/src/lib/editor/Editor.ts +1 -125
- package/src/lib/editor/types/misc-types.ts +0 -6
- package/src/lib/hooks/usePassThroughMouseOverEvents.ts +4 -1
- package/src/lib/license/LicenseManager.test.ts +58 -51
- package/src/lib/license/LicenseManager.ts +32 -24
- package/src/lib/license/LicenseProvider.tsx +8 -0
- package/src/lib/license/useLicenseManagerState.ts +2 -2
- package/src/lib/primitives/Vec.ts +0 -5
- package/src/lib/primitives/geometry/Geometry2d.ts +49 -19
- package/src/lib/primitives/geometry/Group2d.ts +4 -0
- package/src/lib/utils/reparenting.ts +3 -69
- package/src/version.ts +3 -3
|
@@ -4,7 +4,7 @@ import { publishDates } from '../../version'
|
|
|
4
4
|
import { str2ab } from '../utils/licensing'
|
|
5
5
|
import {
|
|
6
6
|
FLAGS,
|
|
7
|
-
|
|
7
|
+
getLicenseState,
|
|
8
8
|
LicenseManager,
|
|
9
9
|
PROPERTIES,
|
|
10
10
|
ValidLicenseKeyResult,
|
|
@@ -487,115 +487,122 @@ function getDefaultLicenseResult(overrides: Partial<ValidLicenseKeyResult>): Val
|
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
-
describe(
|
|
491
|
-
it('
|
|
490
|
+
describe('getLicenseState', () => {
|
|
491
|
+
it('returns "unlicensed" for unparseable license', () => {
|
|
492
492
|
const licenseResult = getDefaultLicenseResult({
|
|
493
493
|
// @ts-ignore
|
|
494
494
|
isLicenseParseable: false,
|
|
495
495
|
})
|
|
496
|
-
expect(
|
|
496
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
497
497
|
})
|
|
498
498
|
|
|
499
|
-
it('
|
|
499
|
+
it('returns "unlicensed" for invalid domain in production', () => {
|
|
500
500
|
const licenseResult = getDefaultLicenseResult({
|
|
501
501
|
isDomainValid: false,
|
|
502
|
+
isDevelopment: false,
|
|
502
503
|
})
|
|
503
|
-
expect(
|
|
504
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
504
505
|
})
|
|
505
506
|
|
|
506
|
-
it('
|
|
507
|
+
it('returns "licensed" for invalid domain in development mode', () => {
|
|
508
|
+
const licenseResult = getDefaultLicenseResult({
|
|
509
|
+
isDomainValid: false,
|
|
510
|
+
isDevelopment: true,
|
|
511
|
+
})
|
|
512
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('returns "unlicensed" for expired annual license', () => {
|
|
507
516
|
const licenseResult = getDefaultLicenseResult({
|
|
508
517
|
isAnnualLicense: true,
|
|
509
518
|
isAnnualLicenseExpired: true,
|
|
519
|
+
isInternalLicense: false,
|
|
510
520
|
})
|
|
511
|
-
expect(
|
|
521
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
512
522
|
})
|
|
513
523
|
|
|
514
|
-
it('
|
|
524
|
+
it('returns "unlicensed" for expired annual license even in dev mode', () => {
|
|
515
525
|
const licenseResult = getDefaultLicenseResult({
|
|
516
526
|
isAnnualLicense: true,
|
|
517
527
|
isAnnualLicenseExpired: true,
|
|
518
528
|
isDevelopment: true,
|
|
529
|
+
isInternalLicense: false,
|
|
519
530
|
})
|
|
520
|
-
expect(
|
|
531
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
521
532
|
})
|
|
522
533
|
|
|
523
|
-
it('
|
|
534
|
+
it('returns "unlicensed" for expired perpetual license', () => {
|
|
524
535
|
const licenseResult = getDefaultLicenseResult({
|
|
525
536
|
isPerpetualLicense: true,
|
|
526
537
|
isPerpetualLicenseExpired: true,
|
|
538
|
+
isInternalLicense: false,
|
|
527
539
|
})
|
|
528
|
-
expect(
|
|
540
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
529
541
|
})
|
|
530
542
|
|
|
531
|
-
it('
|
|
543
|
+
it('returns "internal-expired" for expired internal annual license with valid domain', () => {
|
|
544
|
+
const expiryDate = new Date(2023, 1, 1)
|
|
532
545
|
const licenseResult = getDefaultLicenseResult({
|
|
533
546
|
isAnnualLicense: true,
|
|
534
|
-
isAnnualLicenseExpired:
|
|
535
|
-
isInternalLicense:
|
|
547
|
+
isAnnualLicenseExpired: true,
|
|
548
|
+
isInternalLicense: true,
|
|
549
|
+
isDomainValid: true,
|
|
550
|
+
expiryDate,
|
|
536
551
|
})
|
|
537
|
-
expect(
|
|
552
|
+
expect(getLicenseState(licenseResult)).toBe('internal-expired')
|
|
538
553
|
})
|
|
539
554
|
|
|
540
|
-
it('
|
|
555
|
+
it('returns "internal-expired" for expired internal perpetual license with valid domain', () => {
|
|
556
|
+
const expiryDate = new Date(2023, 1, 1)
|
|
541
557
|
const licenseResult = getDefaultLicenseResult({
|
|
542
558
|
isPerpetualLicense: true,
|
|
543
|
-
isPerpetualLicenseExpired:
|
|
544
|
-
isInternalLicense:
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
})
|
|
548
|
-
|
|
549
|
-
it('does not show watermark when in development mode', () => {
|
|
550
|
-
const licenseResult = getDefaultLicenseResult({
|
|
551
|
-
isDevelopment: true,
|
|
559
|
+
isPerpetualLicenseExpired: true,
|
|
560
|
+
isInternalLicense: true,
|
|
561
|
+
isDomainValid: true,
|
|
562
|
+
expiryDate,
|
|
552
563
|
})
|
|
553
|
-
expect(
|
|
564
|
+
expect(getLicenseState(licenseResult)).toBe('internal-expired')
|
|
554
565
|
})
|
|
555
566
|
|
|
556
|
-
it('
|
|
567
|
+
it('returns "unlicensed" for expired internal license with invalid domain', () => {
|
|
568
|
+
const expiryDate = new Date(2023, 1, 1)
|
|
557
569
|
const licenseResult = getDefaultLicenseResult({
|
|
558
|
-
|
|
559
|
-
|
|
570
|
+
isAnnualLicense: true,
|
|
571
|
+
isAnnualLicenseExpired: true,
|
|
572
|
+
isInternalLicense: true,
|
|
573
|
+
isDomainValid: false,
|
|
574
|
+
expiryDate,
|
|
560
575
|
})
|
|
561
|
-
expect(
|
|
576
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
562
577
|
})
|
|
563
578
|
|
|
564
|
-
it('
|
|
579
|
+
it('returns "licensed-with-watermark" for watermarked license', () => {
|
|
565
580
|
const licenseResult = getDefaultLicenseResult({
|
|
566
|
-
|
|
567
|
-
isDomainValid: false,
|
|
568
|
-
isDevelopment: true,
|
|
581
|
+
isLicensedWithWatermark: true,
|
|
569
582
|
})
|
|
570
|
-
expect(
|
|
583
|
+
expect(getLicenseState(licenseResult)).toBe('licensed-with-watermark')
|
|
571
584
|
})
|
|
572
585
|
|
|
573
|
-
it('
|
|
574
|
-
const expiryDate = new Date(2023, 1, 1)
|
|
586
|
+
it('returns "licensed" for valid annual license', () => {
|
|
575
587
|
const licenseResult = getDefaultLicenseResult({
|
|
576
588
|
isAnnualLicense: true,
|
|
577
|
-
isAnnualLicenseExpired:
|
|
578
|
-
isInternalLicense: true,
|
|
579
|
-
expiryDate,
|
|
589
|
+
isAnnualLicenseExpired: false,
|
|
580
590
|
})
|
|
581
|
-
expect((
|
|
591
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
582
592
|
})
|
|
583
593
|
|
|
584
|
-
it('
|
|
585
|
-
const expiryDate = new Date(2023, 1, 1)
|
|
594
|
+
it('returns "licensed" for valid perpetual license', () => {
|
|
586
595
|
const licenseResult = getDefaultLicenseResult({
|
|
587
596
|
isPerpetualLicense: true,
|
|
588
|
-
isPerpetualLicenseExpired:
|
|
589
|
-
isInternalLicense: true,
|
|
590
|
-
expiryDate,
|
|
597
|
+
isPerpetualLicenseExpired: false,
|
|
591
598
|
})
|
|
592
|
-
expect((
|
|
599
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
593
600
|
})
|
|
594
601
|
|
|
595
|
-
it('
|
|
602
|
+
it('returns "licensed" for valid license in development mode', () => {
|
|
596
603
|
const licenseResult = getDefaultLicenseResult({
|
|
597
|
-
|
|
604
|
+
isDevelopment: true,
|
|
598
605
|
})
|
|
599
|
-
expect(
|
|
606
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
600
607
|
})
|
|
601
608
|
})
|
|
@@ -33,6 +33,14 @@ export interface LicenseInfo {
|
|
|
33
33
|
flags: number
|
|
34
34
|
expiryDate: string
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
/** @internal */
|
|
38
|
+
export type LicenseState =
|
|
39
|
+
| 'pending'
|
|
40
|
+
| 'licensed'
|
|
41
|
+
| 'licensed-with-watermark'
|
|
42
|
+
| 'unlicensed'
|
|
43
|
+
| 'internal-expired'
|
|
36
44
|
/** @internal */
|
|
37
45
|
export type InvalidLicenseReason =
|
|
38
46
|
| 'invalid-license-key'
|
|
@@ -73,10 +81,7 @@ export class LicenseManager {
|
|
|
73
81
|
public isDevelopment: boolean
|
|
74
82
|
public isTest: boolean
|
|
75
83
|
public isCryptoAvailable: boolean
|
|
76
|
-
state = atom<'
|
|
77
|
-
'license state',
|
|
78
|
-
'pending'
|
|
79
|
-
)
|
|
84
|
+
state = atom<LicenseState>('license state', 'pending')
|
|
80
85
|
public verbose = true
|
|
81
86
|
|
|
82
87
|
constructor(
|
|
@@ -89,21 +94,20 @@ export class LicenseManager {
|
|
|
89
94
|
this.publicKey = testPublicKey || this.publicKey
|
|
90
95
|
this.isCryptoAvailable = !!crypto.subtle
|
|
91
96
|
|
|
92
|
-
this.getLicenseFromKey(licenseKey)
|
|
93
|
-
|
|
97
|
+
this.getLicenseFromKey(licenseKey)
|
|
98
|
+
.then((result) => {
|
|
99
|
+
const licenseState = getLicenseState(result)
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
if (!this.isDevelopment && licenseState === 'unlicensed') {
|
|
102
|
+
fetch(WATERMARK_TRACK_SRC)
|
|
103
|
+
}
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
this.state.set(licenseState)
|
|
106
|
+
})
|
|
107
|
+
.catch((error) => {
|
|
108
|
+
console.error('License validation failed:', error)
|
|
100
109
|
this.state.set('unlicensed')
|
|
101
|
-
}
|
|
102
|
-
this.state.set('licensed-with-watermark')
|
|
103
|
-
} else {
|
|
104
|
-
this.state.set('licensed')
|
|
105
|
-
}
|
|
106
|
-
})
|
|
110
|
+
})
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
private getIsDevelopment(testEnvironment?: TestEnvironment) {
|
|
@@ -367,15 +371,19 @@ export class LicenseManager {
|
|
|
367
371
|
static className = 'tl-watermark_SEE-LICENSE'
|
|
368
372
|
}
|
|
369
373
|
|
|
370
|
-
export function
|
|
371
|
-
if (!result.isLicenseParseable) return
|
|
372
|
-
if (!result.isDomainValid && !result.isDevelopment) return
|
|
374
|
+
export function getLicenseState(result: LicenseFromKeyResult): LicenseState {
|
|
375
|
+
if (!result.isLicenseParseable) return 'unlicensed'
|
|
376
|
+
if (!result.isDomainValid && !result.isDevelopment) return 'unlicensed'
|
|
373
377
|
if (result.isPerpetualLicenseExpired || result.isAnnualLicenseExpired) {
|
|
374
|
-
if
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
+
// Check if it's an expired internal license with valid domain
|
|
379
|
+
const internalExpired = result.isInternalLicense && result.isDomainValid
|
|
380
|
+
return internalExpired ? 'internal-expired' : 'unlicensed'
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// License is valid, determine if it has watermark
|
|
384
|
+
if (result.isLicensedWithWatermark) {
|
|
385
|
+
return 'licensed-with-watermark'
|
|
378
386
|
}
|
|
379
387
|
|
|
380
|
-
return
|
|
388
|
+
return 'licensed'
|
|
381
389
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useValue } from '@tldraw/state-react'
|
|
1
2
|
import { createContext, ReactNode, useContext, useState } from 'react'
|
|
2
3
|
import { LicenseManager } from './LicenseManager'
|
|
3
4
|
|
|
@@ -16,5 +17,12 @@ export function LicenseProvider({
|
|
|
16
17
|
children: ReactNode
|
|
17
18
|
}) {
|
|
18
19
|
const [licenseManager] = useState(() => new LicenseManager(licenseKey))
|
|
20
|
+
const licenseState = useValue(licenseManager.state)
|
|
21
|
+
|
|
22
|
+
// If internal license has expired, don't render the editor at all
|
|
23
|
+
if (licenseState === 'internal-expired') {
|
|
24
|
+
return <div data-testid="tl-license-expired" style={{ display: 'none' }} />
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
return <LicenseContext.Provider value={licenseManager}>{children}</LicenseContext.Provider>
|
|
20
28
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
|
-
import { LicenseManager } from './LicenseManager'
|
|
2
|
+
import { LicenseManager, LicenseState } from './LicenseManager'
|
|
3
3
|
|
|
4
4
|
/** @internal */
|
|
5
|
-
export function useLicenseManagerState(licenseManager: LicenseManager) {
|
|
5
|
+
export function useLicenseManagerState(licenseManager: LicenseManager): LicenseState {
|
|
6
6
|
return useValue('watermarkState', () => licenseManager.state.get(), [licenseManager])
|
|
7
7
|
}
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
intersectLineSegmentPolyline,
|
|
10
10
|
intersectPolys,
|
|
11
11
|
linesIntersect,
|
|
12
|
+
polygonIntersectsPolyline,
|
|
13
|
+
polygonsIntersect,
|
|
12
14
|
} from '../intersect'
|
|
13
15
|
import { approximately, pointInPolygon } from '../utils'
|
|
14
16
|
|
|
@@ -227,25 +229,6 @@ export abstract class Geometry2d {
|
|
|
227
229
|
return distanceAlongRoute / length
|
|
228
230
|
}
|
|
229
231
|
|
|
230
|
-
/** @deprecated Iterate the vertices instead. */
|
|
231
|
-
nearestPointOnLineSegment(A: VecLike, B: VecLike): Vec {
|
|
232
|
-
const { vertices } = this
|
|
233
|
-
let nearest: Vec | undefined
|
|
234
|
-
let dist = Infinity
|
|
235
|
-
let d: number, p: Vec, q: Vec
|
|
236
|
-
for (let i = 0; i < vertices.length; i++) {
|
|
237
|
-
p = vertices[i]
|
|
238
|
-
q = Vec.NearestPointOnLineSegment(A, B, p, true)
|
|
239
|
-
d = Vec.Dist2(p, q)
|
|
240
|
-
if (d < dist) {
|
|
241
|
-
dist = d
|
|
242
|
-
nearest = q
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (!nearest) throw Error('nearest point not found')
|
|
246
|
-
return nearest
|
|
247
|
-
}
|
|
248
|
-
|
|
249
232
|
isPointInBounds(point: VecLike, margin = 0) {
|
|
250
233
|
const { bounds } = this
|
|
251
234
|
return !(
|
|
@@ -256,6 +239,53 @@ export abstract class Geometry2d {
|
|
|
256
239
|
)
|
|
257
240
|
}
|
|
258
241
|
|
|
242
|
+
overlapsPolygon(_polygon: VecLike[]): boolean {
|
|
243
|
+
const polygon = _polygon.map((v) => Vec.From(v))
|
|
244
|
+
|
|
245
|
+
// Otherwise, check if the geometry itself overlaps the polygon
|
|
246
|
+
const { vertices, center, isFilled, isEmptyLabel, isClosed } = this
|
|
247
|
+
|
|
248
|
+
// We'll do things in order of cheapest to most expensive checks
|
|
249
|
+
|
|
250
|
+
// Skip empty labels
|
|
251
|
+
if (isEmptyLabel) return false
|
|
252
|
+
|
|
253
|
+
// If any of the geometry's vertices are inside the polygon, it's inside
|
|
254
|
+
if (vertices.some((v) => pointInPolygon(v, polygon))) {
|
|
255
|
+
return true
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// If the geometry is filled and closed and its center is inside the polygon, it's inside
|
|
259
|
+
if (isClosed) {
|
|
260
|
+
if (isFilled) {
|
|
261
|
+
// If closed and filled, check if the center is inside the polygon
|
|
262
|
+
if (pointInPolygon(center, polygon)) {
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ..then, slightly more expensive check, see the geometry covers the entire polygon but not its center
|
|
267
|
+
if (polygon.every((v) => pointInPolygon(v, vertices))) {
|
|
268
|
+
return true
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// If any the geometry's vertices intersect the edge of the polygon, it's inside.
|
|
273
|
+
// for example when a rotated rectangle is moved over the corner of a parent rectangle
|
|
274
|
+
// If the geometry is closed, intersect as a polygon
|
|
275
|
+
if (polygonsIntersect(polygon, vertices)) {
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
// If the geometry is not closed, intersect as a polyline
|
|
280
|
+
if (polygonIntersectsPolyline(polygon, vertices)) {
|
|
281
|
+
return true
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// If none of the above checks passed, the geometry is outside the polygon
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
|
|
259
289
|
transform(transform: MatModel, opts?: TransformedGeometry2dOptions): Geometry2d {
|
|
260
290
|
return new TransformedGeometry2d(this, transform, opts)
|
|
261
291
|
}
|
|
@@ -236,4 +236,8 @@ export class Group2d extends Geometry2d {
|
|
|
236
236
|
getSvgPathData(): string {
|
|
237
237
|
return this.children.map((c, i) => (c.isLabel ? '' : c.getSvgPathData(i === 0))).join(' ')
|
|
238
238
|
}
|
|
239
|
+
|
|
240
|
+
overlapsPolygon(polygon: VecLike[]): boolean {
|
|
241
|
+
return this.children.some((child) => child.overlapsPolygon(polygon))
|
|
242
|
+
}
|
|
239
243
|
}
|
|
@@ -2,15 +2,7 @@ import { EMPTY_ARRAY } from '@tldraw/state'
|
|
|
2
2
|
import { TLGroupShape, TLParentId, TLShape, TLShapeId } from '@tldraw/tlschema'
|
|
3
3
|
import { IndexKey, compact, getIndexAbove, getIndexBetween } from '@tldraw/utils'
|
|
4
4
|
import { Editor } from '../editor/Editor'
|
|
5
|
-
import {
|
|
6
|
-
import { Geometry2d } from '../primitives/geometry/Geometry2d'
|
|
7
|
-
import { Group2d } from '../primitives/geometry/Group2d'
|
|
8
|
-
import {
|
|
9
|
-
intersectPolygonPolygon,
|
|
10
|
-
polygonIntersectsPolyline,
|
|
11
|
-
polygonsIntersect,
|
|
12
|
-
} from '../primitives/intersect'
|
|
13
|
-
import { pointInPolygon } from '../primitives/utils'
|
|
5
|
+
import { intersectPolygonPolygon } from '../primitives/intersect'
|
|
14
6
|
|
|
15
7
|
/**
|
|
16
8
|
* Reparents shapes that are no longer contained within their parent shapes.
|
|
@@ -189,68 +181,10 @@ function getOverlappingShapes<T extends TLShape[] | TLShapeId[]>(
|
|
|
189
181
|
|
|
190
182
|
const geometry = editor.getShapeGeometry(childId)
|
|
191
183
|
|
|
192
|
-
return
|
|
184
|
+
return geometry.overlapsPolygon(parentPolygonInShapeShape)
|
|
193
185
|
})
|
|
194
186
|
}
|
|
195
187
|
|
|
196
|
-
/**
|
|
197
|
-
* @public
|
|
198
|
-
*/
|
|
199
|
-
export function doesGeometryOverlapPolygon(
|
|
200
|
-
geometry: Geometry2d,
|
|
201
|
-
parentCornersInShapeSpace: Vec[]
|
|
202
|
-
): boolean {
|
|
203
|
-
// If the child is a group, check if any of its children overlap the box
|
|
204
|
-
if (geometry instanceof Group2d) {
|
|
205
|
-
return geometry.children.some((childGeometry) =>
|
|
206
|
-
doesGeometryOverlapPolygon(childGeometry, parentCornersInShapeSpace)
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Otherwise, check if the geometry overlaps the box
|
|
211
|
-
const { vertices, center, isFilled, isEmptyLabel, isClosed } = geometry
|
|
212
|
-
|
|
213
|
-
// We'll do things in order of cheapest to most expensive checks
|
|
214
|
-
|
|
215
|
-
// Skip empty labels
|
|
216
|
-
if (isEmptyLabel) return false
|
|
217
|
-
|
|
218
|
-
// If any of the shape's vertices are inside the occluder, it's inside
|
|
219
|
-
if (vertices.some((v) => pointInPolygon(v, parentCornersInShapeSpace))) {
|
|
220
|
-
return true
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// If the shape is filled and closed and its center is inside the parent, it's inside
|
|
224
|
-
if (isClosed) {
|
|
225
|
-
if (isFilled) {
|
|
226
|
-
// If closed and filled, check if the center is inside the parent
|
|
227
|
-
if (pointInPolygon(center, parentCornersInShapeSpace)) {
|
|
228
|
-
return true
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// ..then, slightly more expensive check, see the shape covers the entire parent but not its center
|
|
232
|
-
if (parentCornersInShapeSpace.every((v) => pointInPolygon(v, vertices))) {
|
|
233
|
-
return true
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// If any the shape's vertices intersect the edge of the occluder, it's inside.
|
|
238
|
-
// for example when a rotated rectangle is moved over the corner of a parent rectangle
|
|
239
|
-
// If the child shape is closed, intersect as a polygon
|
|
240
|
-
if (polygonsIntersect(parentCornersInShapeSpace, vertices)) {
|
|
241
|
-
return true
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
// if the child shape is not closed, intersect as a polyline
|
|
245
|
-
if (polygonIntersectsPolyline(parentCornersInShapeSpace, vertices)) {
|
|
246
|
-
return true
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// If none of the above checks passed, the shape is outside the parent
|
|
251
|
-
return false
|
|
252
|
-
}
|
|
253
|
-
|
|
254
188
|
/**
|
|
255
189
|
* Get the shapes that will be reparented to new parents when the shapes are dropped.
|
|
256
190
|
*
|
|
@@ -354,7 +288,7 @@ export function getDroppedShapesToNewParents(
|
|
|
354
288
|
.applyToPoints(parentPagePolygon)
|
|
355
289
|
|
|
356
290
|
// If the shape overlaps the parent polygon, reparent it to that parent
|
|
357
|
-
if (
|
|
291
|
+
if (editor.getShapeGeometry(shape).overlapsPolygon(parentPolygonInShapeSpace)) {
|
|
358
292
|
// Use the util to check if the shape can be reparented to the parent
|
|
359
293
|
if (
|
|
360
294
|
!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type)
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '3.16.0-canary.
|
|
4
|
+
export const version = '3.16.0-canary.62bc202550a3'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-09-03T09:39:32.548Z',
|
|
8
|
+
patch: '2025-09-03T09:39:32.548Z',
|
|
9
9
|
}
|