@tldraw/editor 4.1.0-canary.8e597b345c40 → 4.1.0-canary.95d46c96eb30
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 +15 -3
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +2 -2
- package/dist-cjs/lib/components/ErrorBoundary.js +1 -1
- package/dist-cjs/lib/components/HTMLContainer.js +1 -1
- package/dist-cjs/lib/components/SVGContainer.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCursor.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultHandle.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultSelectionForeground.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +2 -2
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js +1 -1
- package/dist-cjs/lib/editor/Editor.js +5 -3
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/hooks/useDarkMode.js +1 -1
- package/dist-cjs/lib/hooks/useEditor.js +1 -1
- package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
- package/dist-cjs/lib/hooks/useHandleEvents.js +1 -1
- package/dist-cjs/lib/hooks/useZoomCss.js +1 -1
- package/dist-cjs/lib/license/LicenseManager.js +18 -12
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +4 -4
- package/dist-cjs/lib/license/Watermark.js.map +1 -1
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +5 -0
- package/dist-cjs/lib/primitives/geometry/Geometry2d.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 +15 -3
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +5 -2
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +18 -12
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +4 -4
- package/dist-esm/lib/license/Watermark.mjs.map +1 -1
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +5 -0
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +7 -2
- package/package.json +7 -7
- package/src/index.ts +0 -1
- package/src/lib/editor/Editor.ts +5 -2
- package/src/lib/editor/shapes/ShapeUtil.ts +10 -0
- package/src/lib/license/LicenseManager.test.ts +68 -4
- package/src/lib/license/LicenseManager.ts +19 -20
- package/src/lib/license/Watermark.tsx +4 -4
- package/src/lib/primitives/geometry/Geometry2d.ts +6 -0
- package/src/version.ts +3 -3
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 = '4.1.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 = '4.1.0-canary.95d46c96eb30'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2025-10-07T11:02:50.479Z',\n\tpatch: '2025-10-07T11:02:50.479Z',\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
|
@@ -1321,6 +1321,10 @@ input,
|
|
|
1321
1321
|
flex: 1;
|
|
1322
1322
|
}
|
|
1323
1323
|
|
|
1324
|
+
.tl-bookmark__copy_container:has(.tl-bookmark__link:only-child) {
|
|
1325
|
+
justify-content: center;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1324
1328
|
.tl-bookmark__heading,
|
|
1325
1329
|
.tl-bookmark__description,
|
|
1326
1330
|
.tl-bookmark__link {
|
|
@@ -1353,7 +1357,7 @@ input,
|
|
|
1353
1357
|
line-clamp: 3;
|
|
1354
1358
|
text-overflow: ellipsis;
|
|
1355
1359
|
display: -webkit-box;
|
|
1356
|
-
color: var(--tl-color-text-
|
|
1360
|
+
color: var(--tl-color-text-1);
|
|
1357
1361
|
margin: var(--tl-space-2) 0px;
|
|
1358
1362
|
}
|
|
1359
1363
|
|
|
@@ -1365,11 +1369,12 @@ input,
|
|
|
1365
1369
|
font-size: 12px;
|
|
1366
1370
|
pointer-events: all;
|
|
1367
1371
|
display: flex;
|
|
1368
|
-
color: var(--tl-color-text-
|
|
1372
|
+
color: var(--tl-color-text-1);
|
|
1369
1373
|
align-items: center;
|
|
1370
1374
|
cursor: var(--tl-cursor-pointer);
|
|
1371
1375
|
width: fit-content;
|
|
1372
1376
|
max-width: 100%;
|
|
1377
|
+
text-decoration: none;
|
|
1373
1378
|
}
|
|
1374
1379
|
|
|
1375
1380
|
.tl-bookmark__link > span {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (editor).",
|
|
4
|
-
"version": "4.1.0-canary.
|
|
4
|
+
"version": "4.1.0-canary.95d46c96eb30",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"@tiptap/core": "^2.9.1",
|
|
51
51
|
"@tiptap/pm": "^2.9.1",
|
|
52
52
|
"@tiptap/react": "^2.9.1",
|
|
53
|
-
"@tldraw/state": "4.1.0-canary.
|
|
54
|
-
"@tldraw/state-react": "4.1.0-canary.
|
|
55
|
-
"@tldraw/store": "4.1.0-canary.
|
|
56
|
-
"@tldraw/tlschema": "4.1.0-canary.
|
|
57
|
-
"@tldraw/utils": "4.1.0-canary.
|
|
58
|
-
"@tldraw/validate": "4.1.0-canary.
|
|
53
|
+
"@tldraw/state": "4.1.0-canary.95d46c96eb30",
|
|
54
|
+
"@tldraw/state-react": "4.1.0-canary.95d46c96eb30",
|
|
55
|
+
"@tldraw/store": "4.1.0-canary.95d46c96eb30",
|
|
56
|
+
"@tldraw/tlschema": "4.1.0-canary.95d46c96eb30",
|
|
57
|
+
"@tldraw/utils": "4.1.0-canary.95d46c96eb30",
|
|
58
|
+
"@tldraw/validate": "4.1.0-canary.95d46c96eb30",
|
|
59
59
|
"@types/core-js": "^2.5.8",
|
|
60
60
|
"@use-gesture/react": "^10.3.1",
|
|
61
61
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
JsonObject,
|
|
70
70
|
PerformanceTracker,
|
|
71
71
|
Result,
|
|
72
|
+
ZERO_INDEX_KEY,
|
|
72
73
|
annotateError,
|
|
73
74
|
assert,
|
|
74
75
|
assertExists,
|
|
@@ -2019,7 +2020,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2019
2020
|
}
|
|
2020
2021
|
|
|
2021
2022
|
/**
|
|
2022
|
-
*
|
|
2023
|
+
* Get the page bounds of all the provided shapes.
|
|
2024
|
+
*
|
|
2025
|
+
* @public
|
|
2023
2026
|
*/
|
|
2024
2027
|
getShapesPageBounds(shapeIds: TLShapeId[]): Box | null {
|
|
2025
2028
|
const bounds = compact(shapeIds.map((id) => this.getShapePageBounds(id)))
|
|
@@ -5661,7 +5664,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5661
5664
|
const children = this._parentIdsToChildIds.get()[parentId]
|
|
5662
5665
|
|
|
5663
5666
|
if (!children || children.length === 0) {
|
|
5664
|
-
return
|
|
5667
|
+
return getIndexAbove(ZERO_INDEX_KEY)
|
|
5665
5668
|
}
|
|
5666
5669
|
const shape = this.getShape(children[children.length - 1])!
|
|
5667
5670
|
return getIndexAbove(shape.index)
|
|
@@ -328,6 +328,16 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
328
328
|
*/
|
|
329
329
|
shouldClipChild?(child: TLShape): boolean
|
|
330
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Whether a specific shape should hide in the minimap.
|
|
333
|
+
*
|
|
334
|
+
* If not defined, the default behavior is to show all shapes in the minimap.
|
|
335
|
+
*
|
|
336
|
+
* @returns boolean indicating if this shape should hide in the minimap
|
|
337
|
+
* @public
|
|
338
|
+
*/
|
|
339
|
+
hideInMinimap?(_shape: Shape): boolean
|
|
340
|
+
|
|
331
341
|
/**
|
|
332
342
|
* Whether the shape should hide its resize handles when selected.
|
|
333
343
|
*
|
|
@@ -36,13 +36,15 @@ describe('LicenseManager', () => {
|
|
|
36
36
|
let licenseManager: LicenseManager
|
|
37
37
|
|
|
38
38
|
beforeAll(() => {
|
|
39
|
+
process.env.NODE_ENV = 'production'
|
|
39
40
|
return new Promise((resolve) => {
|
|
40
41
|
generateKeyPair().then((kp) => {
|
|
41
42
|
keyPair = kp
|
|
42
|
-
licenseManager = new LicenseManager('', keyPair.publicKey
|
|
43
|
+
licenseManager = new LicenseManager('', keyPair.publicKey)
|
|
43
44
|
resolve(void 0)
|
|
44
45
|
})
|
|
45
46
|
})
|
|
47
|
+
process.env.NODE_ENV = 'test'
|
|
46
48
|
})
|
|
47
49
|
|
|
48
50
|
beforeEach(() => {
|
|
@@ -58,7 +60,7 @@ describe('LicenseManager', () => {
|
|
|
58
60
|
expect(result).toMatchObject({ isLicenseParseable: false, reason: 'no-key-provided' })
|
|
59
61
|
})
|
|
60
62
|
|
|
61
|
-
it('Signals that it is development mode when
|
|
63
|
+
it('Signals that it is development mode when localhost', async () => {
|
|
62
64
|
const schemes = ['http', 'https']
|
|
63
65
|
for (const scheme of schemes) {
|
|
64
66
|
// @ts-ignore
|
|
@@ -66,7 +68,7 @@ describe('LicenseManager', () => {
|
|
|
66
68
|
// @ts-ignore
|
|
67
69
|
window.location = new URL(`${scheme}://localhost:3000`)
|
|
68
70
|
|
|
69
|
-
const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey
|
|
71
|
+
const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
70
72
|
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
71
73
|
const result = await testEnvLicenseManager.getLicenseFromKey(licenseKey)
|
|
72
74
|
expect(result).toMatchObject({
|
|
@@ -77,6 +79,66 @@ describe('LicenseManager', () => {
|
|
|
77
79
|
}
|
|
78
80
|
})
|
|
79
81
|
|
|
82
|
+
it('Signals that it is development mode when NODE_ENV is not production', async () => {
|
|
83
|
+
process.env.NODE_ENV = 'development'
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
delete window.location
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
window.location = new URL(`https://www.example.com`)
|
|
88
|
+
|
|
89
|
+
const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
90
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
91
|
+
const result = await testEnvLicenseManager.getLicenseFromKey(licenseKey)
|
|
92
|
+
expect(result).toMatchObject({
|
|
93
|
+
isLicenseParseable: true,
|
|
94
|
+
isDomainValid: true,
|
|
95
|
+
isDevelopment: true,
|
|
96
|
+
})
|
|
97
|
+
const licenseState = testEnvLicenseManager.state.get()
|
|
98
|
+
expect(licenseState).toBe('unlicensed')
|
|
99
|
+
process.env.NODE_ENV = 'test'
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('Signals that it is development mode when NODE_ENV is "test"', async () => {
|
|
103
|
+
process.env.NODE_ENV = 'test'
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
delete window.location
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
window.location = new URL(`https://www.example.com`)
|
|
108
|
+
|
|
109
|
+
const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
110
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
111
|
+
const result = await testEnvLicenseManager.getLicenseFromKey(licenseKey)
|
|
112
|
+
expect(result).toMatchObject({
|
|
113
|
+
isLicenseParseable: true,
|
|
114
|
+
isDomainValid: true,
|
|
115
|
+
isDevelopment: true,
|
|
116
|
+
})
|
|
117
|
+
const licenseState = testEnvLicenseManager.state.get()
|
|
118
|
+
expect(licenseState).toBe('unlicensed')
|
|
119
|
+
process.env.NODE_ENV = 'test'
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('Signals that it is not development mode when NODE_ENV is production', async () => {
|
|
123
|
+
process.env.NODE_ENV = 'production'
|
|
124
|
+
// @ts-ignore
|
|
125
|
+
delete window.location
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
window.location = new URL(`https://www.example.com`)
|
|
128
|
+
|
|
129
|
+
const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
130
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
131
|
+
const result = await testEnvLicenseManager.getLicenseFromKey(licenseKey)
|
|
132
|
+
expect(result).toMatchObject({
|
|
133
|
+
isLicenseParseable: true,
|
|
134
|
+
isDomainValid: true,
|
|
135
|
+
isDevelopment: false,
|
|
136
|
+
})
|
|
137
|
+
const licenseState = testEnvLicenseManager.state.get()
|
|
138
|
+
expect(licenseState).toBe('unlicensed-production')
|
|
139
|
+
process.env.NODE_ENV = 'test'
|
|
140
|
+
})
|
|
141
|
+
|
|
80
142
|
it('Cleanses out valid keys that accidentally have zero-width characters or newlines', async () => {
|
|
81
143
|
const cleanLicenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
82
144
|
const dirtyLicenseKey = cleanLicenseKey + '\u200B\u200D\uFEFF\n\r'
|
|
@@ -85,10 +147,12 @@ describe('LicenseManager', () => {
|
|
|
85
147
|
})
|
|
86
148
|
|
|
87
149
|
it('Fails if garbage key provided', async () => {
|
|
88
|
-
|
|
150
|
+
process.env.NODE_ENV = 'production'
|
|
151
|
+
const badPublicKeyLicenseManager = new LicenseManager('', 'badpublickey')
|
|
89
152
|
const invalidLicenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
90
153
|
const result = await badPublicKeyLicenseManager.getLicenseFromKey(invalidLicenseKey)
|
|
91
154
|
expect(result).toMatchObject({ isLicenseParseable: false, reason: 'invalid-license-key' })
|
|
155
|
+
process.env.NODE_ENV = 'test'
|
|
92
156
|
})
|
|
93
157
|
|
|
94
158
|
it('Fails if non-JSON parseable message is provided', async () => {
|
|
@@ -87,9 +87,6 @@ export interface ValidLicenseKeyResult {
|
|
|
87
87
|
daysSinceExpiry: number
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/** @internal */
|
|
91
|
-
export type TestEnvironment = 'development' | 'production'
|
|
92
|
-
|
|
93
90
|
/** @internal */
|
|
94
91
|
export type TrackType = 'unlicensed' | 'with_watermark' | 'evaluation' | null
|
|
95
92
|
|
|
@@ -103,13 +100,9 @@ export class LicenseManager {
|
|
|
103
100
|
state = atom<LicenseState>('license state', 'pending')
|
|
104
101
|
public verbose = true
|
|
105
102
|
|
|
106
|
-
constructor(
|
|
107
|
-
licenseKey: string | undefined,
|
|
108
|
-
testPublicKey?: string,
|
|
109
|
-
testEnvironment?: TestEnvironment
|
|
110
|
-
) {
|
|
103
|
+
constructor(licenseKey: string | undefined, testPublicKey?: string) {
|
|
111
104
|
this.isTest = process.env.NODE_ENV === 'test'
|
|
112
|
-
this.isDevelopment = this.getIsDevelopment(
|
|
105
|
+
this.isDevelopment = this.getIsDevelopment()
|
|
113
106
|
this.publicKey = testPublicKey || this.publicKey
|
|
114
107
|
this.isCryptoAvailable = !!crypto.subtle
|
|
115
108
|
|
|
@@ -131,14 +124,12 @@ export class LicenseManager {
|
|
|
131
124
|
})
|
|
132
125
|
}
|
|
133
126
|
|
|
134
|
-
private getIsDevelopment(
|
|
135
|
-
if (testEnvironment === 'development') return true
|
|
136
|
-
if (testEnvironment === 'production') return false
|
|
137
|
-
|
|
127
|
+
private getIsDevelopment() {
|
|
138
128
|
// If we are using https on a non-localhost domain we assume it's a production env and a development one otherwise
|
|
139
129
|
return (
|
|
140
130
|
!['https:', 'vscode-webview:'].includes(window.location.protocol) ||
|
|
141
|
-
window.location.hostname === 'localhost'
|
|
131
|
+
window.location.hostname === 'localhost' ||
|
|
132
|
+
process.env.NODE_ENV !== 'production'
|
|
142
133
|
)
|
|
143
134
|
}
|
|
144
135
|
|
|
@@ -181,6 +172,9 @@ export class LicenseManager {
|
|
|
181
172
|
if ('license' in result) {
|
|
182
173
|
url.searchParams.set('license_id', result.license.id)
|
|
183
174
|
}
|
|
175
|
+
if (process.env.NODE_ENV) {
|
|
176
|
+
url.searchParams.set('environment', process.env.NODE_ENV)
|
|
177
|
+
}
|
|
184
178
|
|
|
185
179
|
// eslint-disable-next-line no-restricted-globals
|
|
186
180
|
fetch(url.toString())
|
|
@@ -415,22 +409,27 @@ export class LicenseManager {
|
|
|
415
409
|
// If we added a new flag it will be twice the value of the currently highest flag.
|
|
416
410
|
// And if all the current flags are on we would get the `HIGHEST_FLAG * 2 - 1`, so anything higher than that means there are new flags.
|
|
417
411
|
if (result.license.flags >= HIGHEST_FLAG * 2) {
|
|
418
|
-
this.outputMessages(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
412
|
+
this.outputMessages(
|
|
413
|
+
[
|
|
414
|
+
'Warning: This tldraw license contains some unknown flags.',
|
|
415
|
+
'This will still work, however, you may want to update tldraw packages to a newer version to get access to new functionality.',
|
|
416
|
+
],
|
|
417
|
+
'warning'
|
|
418
|
+
)
|
|
422
419
|
}
|
|
423
420
|
}
|
|
424
421
|
|
|
425
|
-
private outputMessages(messages: string[]) {
|
|
422
|
+
private outputMessages(messages: string[], type: 'warning' | 'error' = 'error') {
|
|
426
423
|
if (this.isTest) return
|
|
427
424
|
if (this.verbose) {
|
|
428
425
|
this.outputDelimiter()
|
|
429
426
|
for (const message of messages) {
|
|
427
|
+
const color = type === 'warning' ? 'orange' : 'crimson'
|
|
428
|
+
const bgColor = type === 'warning' ? 'orange' : 'crimson'
|
|
430
429
|
// eslint-disable-next-line no-console
|
|
431
430
|
console.log(
|
|
432
431
|
`%c${message}`,
|
|
433
|
-
`color:
|
|
432
|
+
`color: ${color}; background: ${bgColor}; padding: 2px; border-radius: 3px;`
|
|
434
433
|
)
|
|
435
434
|
}
|
|
436
435
|
this.outputDelimiter()
|
|
@@ -208,22 +208,22 @@ To remove the watermark, please purchase a license at tldraw.dev.
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
@media (hover: hover) {
|
|
211
|
-
.${className}
|
|
211
|
+
.${className} > button {
|
|
212
212
|
pointer-events: none;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
.${className}
|
|
215
|
+
.${className}:hover {
|
|
216
216
|
background-color: var(--tl-color-background);
|
|
217
217
|
transition: background-color 0.2s ease-in-out;
|
|
218
218
|
transition-delay: 0.32s;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
.${className}
|
|
221
|
+
.${className}:hover > button {
|
|
222
222
|
animation: ${className}_delayed_link 0.2s forwards ease-in-out;
|
|
223
223
|
animation-delay: 0.32s;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
.${className}
|
|
226
|
+
.${className} > button:focus-visible {
|
|
227
227
|
opacity: 1;
|
|
228
228
|
}
|
|
229
229
|
}
|
|
@@ -120,6 +120,8 @@ export abstract class Geometry2d {
|
|
|
120
120
|
distanceToLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
121
121
|
if (Vec.Equals(A, B)) return this.distanceToPoint(A, false, filters)
|
|
122
122
|
const { vertices } = this
|
|
123
|
+
if (vertices.length === 0) throw Error('nearest point not found')
|
|
124
|
+
if (vertices.length === 1) return Vec.Dist(A, vertices[0])
|
|
123
125
|
let nearest: Vec | undefined
|
|
124
126
|
let dist = Infinity
|
|
125
127
|
let d: number, p: Vec, q: Vec
|
|
@@ -175,6 +177,8 @@ export abstract class Geometry2d {
|
|
|
175
177
|
interpolateAlongEdge(t: number, _filters?: Geometry2dFilters): Vec {
|
|
176
178
|
const { vertices } = this
|
|
177
179
|
|
|
180
|
+
if (vertices.length === 0) return new Vec(0, 0)
|
|
181
|
+
if (vertices.length === 1) return vertices[0]
|
|
178
182
|
if (t <= 0) return vertices[0]
|
|
179
183
|
|
|
180
184
|
const distanceToTravel = t * this.length
|
|
@@ -209,6 +213,8 @@ export abstract class Geometry2d {
|
|
|
209
213
|
let closestDistance = Infinity
|
|
210
214
|
let distanceTraveled = 0
|
|
211
215
|
|
|
216
|
+
if (vertices.length === 0 || vertices.length === 1) return 0
|
|
217
|
+
|
|
212
218
|
for (let i = 0; i < (this.isClosed ? vertices.length : vertices.length - 1); i++) {
|
|
213
219
|
const curr = vertices[i]
|
|
214
220
|
const next = vertices[(i + 1) % vertices.length]
|
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 = '4.1.0-canary.
|
|
4
|
+
export const version = '4.1.0-canary.95d46c96eb30'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-10-07T11:02:50.479Z',
|
|
8
|
+
patch: '2025-10-07T11:02:50.479Z',
|
|
9
9
|
}
|