@tldraw/editor 3.16.0-canary.bf9fb05eb8ef → 3.16.0-canary.c2c4563957ce

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.
Files changed (72) hide show
  1. package/dist-cjs/index.d.ts +24 -101
  2. package/dist-cjs/index.js +1 -5
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +0 -4
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/editor/Editor.js +14 -109
  7. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  8. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  9. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  10. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +4 -1
  11. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  12. package/dist-cjs/lib/license/LicenseManager.js +17 -22
  13. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  14. package/dist-cjs/lib/license/LicenseProvider.js +5 -0
  15. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  16. package/dist-cjs/lib/license/Watermark.js +4 -4
  17. package/dist-cjs/lib/license/Watermark.js.map +1 -1
  18. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  19. package/dist-cjs/lib/primitives/Vec.js +0 -4
  20. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  21. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +26 -18
  22. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  23. package/dist-cjs/lib/primitives/geometry/Group2d.js +3 -0
  24. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  25. package/dist-cjs/lib/utils/reparenting.js +2 -35
  26. package/dist-cjs/lib/utils/reparenting.js.map +3 -3
  27. package/dist-cjs/version.js +3 -3
  28. package/dist-cjs/version.js.map +1 -1
  29. package/dist-esm/index.d.mts +24 -101
  30. package/dist-esm/index.mjs +1 -5
  31. package/dist-esm/index.mjs.map +2 -2
  32. package/dist-esm/lib/TldrawEditor.mjs +0 -4
  33. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  34. package/dist-esm/lib/editor/Editor.mjs +14 -109
  35. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  36. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  37. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +4 -1
  38. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  39. package/dist-esm/lib/license/LicenseManager.mjs +17 -22
  40. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  41. package/dist-esm/lib/license/LicenseProvider.mjs +5 -0
  42. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  43. package/dist-esm/lib/license/Watermark.mjs +4 -4
  44. package/dist-esm/lib/license/Watermark.mjs.map +1 -1
  45. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  46. package/dist-esm/lib/primitives/Vec.mjs +0 -4
  47. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  48. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +29 -19
  49. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  50. package/dist-esm/lib/primitives/geometry/Group2d.mjs +3 -0
  51. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  52. package/dist-esm/lib/utils/reparenting.mjs +3 -40
  53. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  54. package/dist-esm/version.mjs +3 -3
  55. package/dist-esm/version.mjs.map +1 -1
  56. package/package.json +7 -7
  57. package/src/index.ts +1 -9
  58. package/src/lib/TldrawEditor.tsx +0 -11
  59. package/src/lib/editor/Editor.ts +20 -146
  60. package/src/lib/editor/shapes/ShapeUtil.ts +21 -0
  61. package/src/lib/editor/types/misc-types.ts +0 -6
  62. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +4 -1
  63. package/src/lib/license/LicenseManager.test.ts +58 -51
  64. package/src/lib/license/LicenseManager.ts +32 -24
  65. package/src/lib/license/LicenseProvider.tsx +8 -0
  66. package/src/lib/license/Watermark.tsx +4 -4
  67. package/src/lib/license/useLicenseManagerState.ts +2 -2
  68. package/src/lib/primitives/Vec.ts +0 -5
  69. package/src/lib/primitives/geometry/Geometry2d.ts +49 -19
  70. package/src/lib/primitives/geometry/Group2d.ts +4 -0
  71. package/src/lib/utils/reparenting.ts +3 -69
  72. package/src/version.ts +3 -3
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": "3.16.0-canary.bf9fb05eb8ef",
4
+ "version": "3.16.0-canary.c2c4563957ce",
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": "3.16.0-canary.bf9fb05eb8ef",
54
- "@tldraw/state-react": "3.16.0-canary.bf9fb05eb8ef",
55
- "@tldraw/store": "3.16.0-canary.bf9fb05eb8ef",
56
- "@tldraw/tlschema": "3.16.0-canary.bf9fb05eb8ef",
57
- "@tldraw/utils": "3.16.0-canary.bf9fb05eb8ef",
58
- "@tldraw/validate": "3.16.0-canary.bf9fb05eb8ef",
53
+ "@tldraw/state": "3.16.0-canary.c2c4563957ce",
54
+ "@tldraw/state-react": "3.16.0-canary.c2c4563957ce",
55
+ "@tldraw/store": "3.16.0-canary.c2c4563957ce",
56
+ "@tldraw/tlschema": "3.16.0-canary.c2c4563957ce",
57
+ "@tldraw/utils": "3.16.0-canary.c2c4563957ce",
58
+ "@tldraw/validate": "3.16.0-canary.c2c4563957ce",
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
@@ -268,7 +268,6 @@ export {
268
268
  type TLGetShapeAtPointOptions,
269
269
  type TLImageExportOptions,
270
270
  type TLSvgExportOptions,
271
- type TLSvgOptions,
272
271
  type TLUpdatePointerOptions,
273
272
  } from './lib/editor/types/misc-types'
274
273
  export {
@@ -331,6 +330,7 @@ export {
331
330
  type InvalidLicenseReason,
332
331
  type LicenseFromKeyResult,
333
332
  type LicenseInfo,
333
+ type LicenseState,
334
334
  type TestEnvironment,
335
335
  type ValidLicenseKeyResult,
336
336
  } from './lib/license/LicenseManager'
@@ -485,14 +485,6 @@ export { type TLStoreWithStatus } from './lib/utils/sync/StoreWithStatus'
485
485
  export { uniq } from './lib/utils/uniq'
486
486
  export { openWindow } from './lib/utils/window-open'
487
487
 
488
- /**
489
- * @deprecated Licensing is now enabled in the tldraw SDK.
490
- * @public */
491
- export function debugEnableLicensing() {
492
- // noop
493
- return
494
- }
495
-
496
488
  registerTldrawLibraryVersion(
497
489
  (globalThis as any).TLDRAW_LIBRARY_NAME,
498
490
  (globalThis as any).TLDRAW_LIBRARY_VERSION,
@@ -189,13 +189,6 @@ export interface TldrawEditorBaseProps {
189
189
  */
190
190
  deepLinks?: true | TLDeepLinkOptions
191
191
 
192
- /**
193
- * Predicate for whether or not a shape should be hidden.
194
- *
195
- * @deprecated Use {@link TldrawEditorBaseProps#getShapeVisibility} instead.
196
- */
197
- isShapeHidden?(shape: TLShape, editor: Editor): boolean
198
-
199
192
  /**
200
193
  * Provides a way to hide shapes.
201
194
  *
@@ -412,8 +405,6 @@ function TldrawEditorWithReadyStore({
412
405
  options,
413
406
  licenseKey,
414
407
  deepLinks: _deepLinks,
415
- // eslint-disable-next-line @typescript-eslint/no-deprecated
416
- isShapeHidden,
417
408
  getShapeVisibility,
418
409
  assetUrls,
419
410
  }: Required<
@@ -473,7 +464,6 @@ function TldrawEditorWithReadyStore({
473
464
  textOptions,
474
465
  options,
475
466
  licenseKey,
476
- isShapeHidden,
477
467
  getShapeVisibility,
478
468
  fontAssetUrls: assetUrls?.fonts,
479
469
  })
@@ -509,7 +499,6 @@ function TldrawEditorWithReadyStore({
509
499
  user,
510
500
  setEditor,
511
501
  licenseKey,
512
- isShapeHidden,
513
502
  getShapeVisibility,
514
503
  textOptions,
515
504
  assetUrls,
@@ -116,7 +116,6 @@ import {
116
116
  } from '../constants'
117
117
  import { exportToSvg } from '../exports/exportToSvg'
118
118
  import { getSvgAsImage } from '../exports/getSvgAsImage'
119
- import { tlenv } from '../globals/environment'
120
119
  import { tlmenus } from '../globals/menus'
121
120
  import { tltime } from '../globals/time'
122
121
  import { TldrawOptions, defaultTldrawOptions } from '../options'
@@ -244,16 +243,6 @@ export interface TLEditorOptions {
244
243
  options?: Partial<TldrawOptions>
245
244
  licenseKey?: string
246
245
  fontAssetUrls?: { [key: string]: string | undefined }
247
- /**
248
- * A predicate that should return true if the given shape should be hidden.
249
- *
250
- * @deprecated Use {@link Editor#getShapeVisibility} instead.
251
- *
252
- * @param shape - The shape to check.
253
- * @param editor - The editor instance.
254
- */
255
- isShapeHidden?(shape: TLShape, editor: Editor): boolean
256
-
257
246
  /**
258
247
  * Provides a way to hide shapes.
259
248
  *
@@ -309,21 +298,12 @@ export class Editor extends EventEmitter<TLEventMap> {
309
298
  autoFocus,
310
299
  inferDarkMode,
311
300
  options,
312
- // eslint-disable-next-line @typescript-eslint/no-deprecated
313
- isShapeHidden,
314
301
  getShapeVisibility,
315
302
  fontAssetUrls,
316
303
  }: TLEditorOptions) {
317
304
  super()
318
- assert(
319
- !(isShapeHidden && getShapeVisibility),
320
- 'Cannot use both isShapeHidden and getShapeVisibility'
321
- )
322
305
 
323
- this._getShapeVisibility = isShapeHidden
324
- ? // eslint-disable-next-line @typescript-eslint/no-deprecated
325
- (shape: TLShape, editor: Editor) => (isShapeHidden(shape, editor) ? 'hidden' : 'inherit')
326
- : getShapeVisibility
306
+ this._getShapeVisibility = getShapeVisibility
327
307
 
328
308
  this.options = { ...defaultTldrawOptions, ...options }
329
309
 
@@ -907,14 +887,6 @@ export class Editor extends EventEmitter<TLEventMap> {
907
887
  */
908
888
  readonly fonts: FontManager
909
889
 
910
- /**
911
- * A manager for the editor's environment.
912
- *
913
- * @deprecated This is deprecated and will be removed in a future version. Use the `tlenv` global export instead.
914
- * @public
915
- */
916
- readonly environment = tlenv
917
-
918
890
  /**
919
891
  * A manager for the editor's scribbles.
920
892
  *
@@ -1119,35 +1091,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1119
1091
  return this.history.getNumRedos() > 0
1120
1092
  }
1121
1093
 
1122
- /**
1123
- * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1124
- * any redos.
1125
- *
1126
- * @example
1127
- * ```ts
1128
- * editor.mark()
1129
- * editor.mark('flip shapes')
1130
- * ```
1131
- *
1132
- * @param markId - The mark's id, usually the reason for adding the mark.
1133
- *
1134
- * @public
1135
- * @deprecated use {@link Editor.markHistoryStoppingPoint} instead
1136
- */
1137
- mark(markId?: string): this {
1138
- if (typeof markId === 'string') {
1139
- console.warn(
1140
- `[tldraw] \`editor.history.mark("${markId}")\` is deprecated. Please use \`const myMarkId = editor.markHistoryStoppingPoint()\` instead.`
1141
- )
1142
- } else {
1143
- console.warn(
1144
- '[tldraw] `editor.mark()` is deprecated. Use `editor.markHistoryStoppingPoint()` instead.'
1145
- )
1146
- }
1147
- this.history._mark(markId ?? uniqueId())
1148
- return this
1149
- }
1150
-
1151
1094
  /**
1152
1095
  * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1153
1096
  * any redos. You typically want to do this just before a user interaction begins or is handled.
@@ -1272,13 +1215,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1272
1215
  return this
1273
1216
  }
1274
1217
 
1275
- /**
1276
- * @deprecated Use `Editor.run` instead.
1277
- */
1278
- batch(fn: () => void, opts?: TLEditorRunOptions): this {
1279
- return this.run(fn, opts)
1280
- }
1281
-
1282
1218
  /* --------------------- Errors --------------------- */
1283
1219
 
1284
1220
  /** @internal */
@@ -1580,54 +1516,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1580
1516
 
1581
1517
  menus = tlmenus.forContext(this.contextId)
1582
1518
 
1583
- /**
1584
- * @deprecated Use `editor.menus.getOpenMenus` instead.
1585
- *
1586
- * @public
1587
- */
1588
- @computed getOpenMenus(): string[] {
1589
- return this.menus.getOpenMenus()
1590
- }
1591
-
1592
- /**
1593
- * @deprecated Use `editor.menus.addOpenMenu` instead.
1594
- *
1595
- * @public
1596
- */
1597
- addOpenMenu(id: string): this {
1598
- this.menus.addOpenMenu(id)
1599
- return this
1600
- }
1601
-
1602
- /**
1603
- * @deprecated Use `editor.menus.deleteOpenMenu` instead.
1604
- *
1605
- * @public
1606
- */
1607
- deleteOpenMenu(id: string): this {
1608
- this.menus.deleteOpenMenu(id)
1609
- return this
1610
- }
1611
-
1612
- /**
1613
- * @deprecated Use `editor.menus.clearOpenMenus` instead.
1614
- *
1615
- * @public
1616
- */
1617
- clearOpenMenus(): this {
1618
- this.menus.clearOpenMenus()
1619
- return this
1620
- }
1621
-
1622
- /**
1623
- * @deprecated Use `editor.menus.hasAnyOpenMenus` instead.
1624
- *
1625
- * @public
1626
- */
1627
- @computed getIsMenuOpen(): boolean {
1628
- return this.menus.hasAnyOpenMenus()
1629
- }
1630
-
1631
1519
  /* --------------------- Cursor --------------------- */
1632
1520
 
1633
1521
  /**
@@ -4860,27 +4748,25 @@ export class Editor extends EventEmitter<TLEventMap> {
4860
4748
  return this.store.createComputedCache('pageMaskCache', (shape) => {
4861
4749
  if (isPageId(shape.parentId)) return undefined
4862
4750
 
4863
- const frameAncestors = this.getShapeAncestors(shape.id).filter((shape) =>
4864
- this.isShapeOfType<TLFrameShape>(shape, 'frame')
4865
- )
4866
-
4867
- if (frameAncestors.length === 0) return undefined
4868
-
4869
- const pageMask = frameAncestors
4870
- .map<Vec[] | undefined>((s) => {
4871
- // Apply the frame transform to the frame outline to get the frame outline in the current page space
4872
- const geometry = this.getShapeGeometry(s.id)
4873
- const pageTransform = this.getShapePageTransform(s.id)
4874
- return pageTransform.applyToPoints(geometry.vertices)
4875
- })
4876
- .reduce((acc, b) => {
4877
- if (!(b && acc)) return undefined
4878
- const intersection = intersectPolygonPolygon(acc, b)
4879
- if (intersection) {
4880
- return intersection.map(Vec.Cast)
4881
- }
4882
- return []
4883
- })
4751
+ const clipPaths: Vec[][] = []
4752
+ // Get all ancestors that can potentially clip this shape
4753
+ for (const ancestor of this.getShapeAncestors(shape.id)) {
4754
+ const util = this.getShapeUtil(ancestor)
4755
+ const clipPath = util.getClipPath?.(ancestor)
4756
+ if (!clipPath) continue
4757
+ if (util.shouldClipChild?.(shape) === false) continue
4758
+ const pageTransform = this.getShapePageTransform(ancestor.id)
4759
+ clipPaths.push(pageTransform.applyToPoints(clipPath))
4760
+ }
4761
+ if (clipPaths.length === 0) return undefined
4762
+
4763
+ const pageMask = clipPaths.reduce((acc, b) => {
4764
+ const intersection = intersectPolygonPolygon(acc, b)
4765
+ if (intersection) {
4766
+ return intersection.map(Vec.Cast)
4767
+ }
4768
+ return []
4769
+ })
4884
4770
 
4885
4771
  return pageMask
4886
4772
  })
@@ -5841,11 +5727,6 @@ export class Editor extends EventEmitter<TLEventMap> {
5841
5727
  return shapeIds
5842
5728
  }
5843
5729
 
5844
- /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5845
- getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5846
- return this.getDraggingOverShape(point, droppingShapes)
5847
- }
5848
-
5849
5730
  /**
5850
5731
  * Get the shape that some shapes should be dropped on at a given point.
5851
5732
  *
@@ -9461,13 +9342,6 @@ export class Editor extends EventEmitter<TLEventMap> {
9461
9342
  }
9462
9343
  }
9463
9344
 
9464
- /** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
9465
- async getSvg(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
9466
- const result = await this.getSvgElement(shapes, opts)
9467
- if (!result) return undefined
9468
- return result.svg
9469
- }
9470
-
9471
9345
  /**
9472
9346
  * Get an exported image of the given shapes.
9473
9347
  *
@@ -296,6 +296,27 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
296
296
  return false
297
297
  }
298
298
 
299
+ /**
300
+ * Get the clip path to apply to this shape's children.
301
+ *
302
+ * @param shape - The shape to get the clip path for
303
+ * @returns Array of points defining the clipping polygon in local coordinates, or undefined if no clipping
304
+ * @public
305
+ */
306
+ getClipPath?(shape: Shape): Vec[] | undefined
307
+
308
+ /**
309
+ * Whether a specific child shape should be clipped by this shape.
310
+ * Only called if getClipPath returns a valid polygon.
311
+ *
312
+ * If not defined, the default behavior is to clip all children.
313
+ *
314
+ * @param child - The child shape to check
315
+ * @returns boolean indicating if this child should be clipped
316
+ * @public
317
+ */
318
+ shouldClipChild?(child: TLShape): boolean
319
+
299
320
  /**
300
321
  * Whether the shape should hide its resize handles when selected.
301
322
  *
@@ -72,12 +72,6 @@ export interface TLImageExportOptions extends TLSvgExportOptions {
72
72
  format?: TLExportType
73
73
  }
74
74
 
75
- /**
76
- * @public
77
- * @deprecated use {@link TLImageExportOptions} instead
78
- */
79
- export type TLSvgOptions = TLImageExportOptions
80
-
81
75
  /** @public */
82
76
  export interface TLCameraMoveOptions {
83
77
  /** Whether to move the camera immediately, rather than on the next tick. */
@@ -1,14 +1,17 @@
1
1
  import { RefObject, useEffect } from 'react'
2
2
  import { preventDefault } from '../utils/dom'
3
3
  import { useContainer } from './useContainer'
4
+ import { useMaybeEditor } from './useEditor'
4
5
 
5
6
  /** @public */
6
7
  export function usePassThroughMouseOverEvents(ref: RefObject<HTMLElement>) {
7
8
  if (!ref) throw Error('usePassThroughWheelEvents must be passed a ref')
8
9
  const container = useContainer()
10
+ const editor = useMaybeEditor()
9
11
 
10
12
  useEffect(() => {
11
13
  function onMouseOver(e: MouseEvent) {
14
+ if (!editor?.getInstanceState().isFocused) return
12
15
  if ((e as any).isSpecialRedispatchedEvent) return
13
16
  preventDefault(e)
14
17
  const cvs = container.querySelector('.tl-canvas')
@@ -25,5 +28,5 @@ export function usePassThroughMouseOverEvents(ref: RefObject<HTMLElement>) {
25
28
  return () => {
26
29
  elm.removeEventListener('mouseover', onMouseOver)
27
30
  }
28
- }, [container, ref])
31
+ }, [container, editor, ref])
29
32
  }
@@ -4,7 +4,7 @@ import { publishDates } from '../../version'
4
4
  import { str2ab } from '../utils/licensing'
5
5
  import {
6
6
  FLAGS,
7
- isEditorUnlicensed,
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(isEditorUnlicensed, () => {
491
- it('shows watermark when license is not parseable', () => {
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(isEditorUnlicensed(licenseResult)).toBe(true)
496
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
497
497
  })
498
498
 
499
- it('shows watermark when domain is not valid', () => {
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(isEditorUnlicensed(licenseResult)).toBe(true)
504
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
504
505
  })
505
506
 
506
- it('shows watermark when annual license has expired', () => {
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(isEditorUnlicensed(licenseResult)).toBe(true)
521
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
512
522
  })
513
523
 
514
- it('shows watermark when annual license has expired, even if dev mode', () => {
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(isEditorUnlicensed(licenseResult)).toBe(true)
531
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
521
532
  })
522
533
 
523
- it('shows watermark when perpetual license has expired', () => {
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(isEditorUnlicensed(licenseResult)).toBe(true)
540
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
529
541
  })
530
542
 
531
- it('does not show watermark when license is valid and not expired', () => {
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: false,
535
- isInternalLicense: false,
547
+ isAnnualLicenseExpired: true,
548
+ isInternalLicense: true,
549
+ isDomainValid: true,
550
+ expiryDate,
536
551
  })
537
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
552
+ expect(getLicenseState(licenseResult)).toBe('internal-expired')
538
553
  })
539
554
 
540
- it('does not show watermark when perpetual license is valid and not expired', () => {
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: false,
544
- isInternalLicense: false,
545
- })
546
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
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(isEditorUnlicensed(licenseResult)).toBe(false)
564
+ expect(getLicenseState(licenseResult)).toBe('internal-expired')
554
565
  })
555
566
 
556
- it('does not show watermark when license is parseable and domain is valid', () => {
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
- isLicenseParseable: true,
559
- isDomainValid: true,
570
+ isAnnualLicense: true,
571
+ isAnnualLicenseExpired: true,
572
+ isInternalLicense: true,
573
+ isDomainValid: false,
574
+ expiryDate,
560
575
  })
561
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
576
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
562
577
  })
563
578
 
564
- it('does not show watermark when license is parseable and domain is not valid and dev mode', () => {
579
+ it('returns "licensed-with-watermark" for watermarked license', () => {
565
580
  const licenseResult = getDefaultLicenseResult({
566
- isLicenseParseable: true,
567
- isDomainValid: false,
568
- isDevelopment: true,
581
+ isLicensedWithWatermark: true,
569
582
  })
570
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
583
+ expect(getLicenseState(licenseResult)).toBe('licensed-with-watermark')
571
584
  })
572
585
 
573
- it('throws when an internal annual license has expired', () => {
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: true,
578
- isInternalLicense: true,
579
- expiryDate,
589
+ isAnnualLicenseExpired: false,
580
590
  })
581
- expect(() => isEditorUnlicensed(licenseResult)).toThrow(/License: Internal license expired/)
591
+ expect(getLicenseState(licenseResult)).toBe('licensed')
582
592
  })
583
593
 
584
- it('throws when an internal perpetual license has expired', () => {
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: true,
589
- isInternalLicense: true,
590
- expiryDate,
597
+ isPerpetualLicenseExpired: false,
591
598
  })
592
- expect(() => isEditorUnlicensed(licenseResult)).toThrow(/License: Internal license expired/)
599
+ expect(getLicenseState(licenseResult)).toBe('licensed')
593
600
  })
594
601
 
595
- it('shows watermark when license has that flag specified', () => {
602
+ it('returns "licensed" for valid license in development mode', () => {
596
603
  const licenseResult = getDefaultLicenseResult({
597
- isLicensedWithWatermark: true,
604
+ isDevelopment: true,
598
605
  })
599
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
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<'pending' | 'licensed' | 'licensed-with-watermark' | 'unlicensed'>(
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).then((result) => {
93
- const isUnlicensed = isEditorUnlicensed(result)
97
+ this.getLicenseFromKey(licenseKey)
98
+ .then((result) => {
99
+ const licenseState = getLicenseState(result)
94
100
 
95
- if (!this.isDevelopment && isUnlicensed) {
96
- fetch(WATERMARK_TRACK_SRC)
97
- }
101
+ if (!this.isDevelopment && licenseState === 'unlicensed') {
102
+ fetch(WATERMARK_TRACK_SRC)
103
+ }
98
104
 
99
- if (isUnlicensed) {
105
+ this.state.set(licenseState)
106
+ })
107
+ .catch((error) => {
108
+ console.error('License validation failed:', error)
100
109
  this.state.set('unlicensed')
101
- } else if ((result as ValidLicenseKeyResult).isLicensedWithWatermark) {
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 isEditorUnlicensed(result: LicenseFromKeyResult) {
371
- if (!result.isLicenseParseable) return true
372
- if (!result.isDomainValid && !result.isDevelopment) return true
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 (result.isInternalLicense) {
375
- throw new Error('License: Internal license expired.')
376
- }
377
- return true
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 false
388
+ return 'licensed'
381
389
  }