js-draw 1.3.0 → 1.4.0

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 (164) hide show
  1. package/README.md +1 -1
  2. package/dist/Editor.css +55 -13
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +36 -3
  6. package/dist/cjs/Editor.js +63 -26
  7. package/dist/cjs/SVGLoader.js +37 -22
  8. package/dist/cjs/commands/Erase.js +1 -1
  9. package/dist/cjs/commands/UnresolvedCommand.d.ts +1 -1
  10. package/dist/cjs/components/AbstractComponent.d.ts +1 -1
  11. package/dist/cjs/components/AbstractComponent.js +1 -1
  12. package/dist/cjs/components/BackgroundComponent.d.ts +1 -1
  13. package/dist/cjs/components/BackgroundComponent.js +3 -2
  14. package/dist/cjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  15. package/dist/cjs/{EditorImage.js → image/EditorImage.js} +51 -7
  16. package/dist/cjs/image/export/editorImageToSVG.d.ts +8 -0
  17. package/dist/cjs/image/export/editorImageToSVG.js +49 -0
  18. package/dist/cjs/image/export/setExportedSVGSize.d.ts +6 -0
  19. package/dist/cjs/image/export/setExportedSVGSize.js +25 -0
  20. package/dist/cjs/image/lib.d.ts +1 -0
  21. package/dist/cjs/image/lib.js +8 -0
  22. package/dist/cjs/lib.d.ts +1 -1
  23. package/dist/cjs/lib.js +2 -3
  24. package/dist/cjs/localizations/comments.d.ts +6 -0
  25. package/dist/cjs/localizations/comments.js +10 -0
  26. package/dist/cjs/localizations/es.js +68 -48
  27. package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -1
  28. package/dist/cjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  29. package/dist/cjs/rendering/caching/RenderingCacheNode.js +4 -3
  30. package/dist/cjs/rendering/renderers/SVGRenderer.js +8 -19
  31. package/dist/cjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
  32. package/dist/cjs/toolbar/AbstractToolbar.d.ts +11 -3
  33. package/dist/cjs/toolbar/AbstractToolbar.js +20 -6
  34. package/dist/cjs/toolbar/EdgeToolbar.js +5 -6
  35. package/dist/cjs/toolbar/IconProvider.d.ts +1 -0
  36. package/dist/cjs/toolbar/IconProvider.js +43 -0
  37. package/dist/cjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  38. package/dist/cjs/toolbar/widgets/ActionButtonWidget.js +19 -1
  39. package/dist/cjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  40. package/dist/cjs/toolbar/widgets/BaseToolWidget.js +3 -0
  41. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  42. package/dist/cjs/toolbar/widgets/BaseWidget.js +30 -2
  43. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
  44. package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  45. package/dist/cjs/toolbar/widgets/HandToolWidget.js +6 -0
  46. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -1
  47. package/dist/cjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  48. package/dist/cjs/toolbar/widgets/OverflowWidget.js +3 -0
  49. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  50. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +3 -0
  51. package/dist/cjs/tools/BaseTool.d.ts +3 -0
  52. package/dist/cjs/tools/BaseTool.js +13 -2
  53. package/dist/cjs/tools/FindTool.d.ts +1 -0
  54. package/dist/cjs/tools/FindTool.js +4 -1
  55. package/dist/cjs/tools/PanZoom.d.ts +1 -0
  56. package/dist/cjs/tools/PanZoom.js +4 -0
  57. package/dist/cjs/tools/Pen.d.ts +0 -1
  58. package/dist/cjs/tools/Pen.js +1 -4
  59. package/dist/cjs/tools/PipetteTool.d.ts +1 -0
  60. package/dist/cjs/tools/PipetteTool.js +3 -0
  61. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  62. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.js +3 -0
  63. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -0
  64. package/dist/cjs/tools/SelectionTool/Selection.js +44 -8
  65. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  66. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +26 -8
  67. package/dist/cjs/tools/SelectionTool/SelectionTool.js +5 -0
  68. package/dist/cjs/tools/SoundUITool.d.ts +1 -0
  69. package/dist/cjs/tools/SoundUITool.js +4 -1
  70. package/dist/cjs/tools/TextTool.js +2 -2
  71. package/dist/cjs/tools/ToolController.d.ts +2 -0
  72. package/dist/cjs/tools/ToolController.js +13 -2
  73. package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  74. package/dist/cjs/tools/ToolSwitcherShortcut.js +3 -0
  75. package/dist/cjs/types.d.ts +9 -4
  76. package/dist/cjs/types.js +4 -3
  77. package/dist/cjs/util/ReactiveValue.d.ts +1 -1
  78. package/dist/cjs/util/ReactiveValue.js +2 -2
  79. package/dist/cjs/version.js +1 -1
  80. package/dist/mjs/Editor.d.ts +36 -3
  81. package/dist/mjs/Editor.mjs +64 -27
  82. package/dist/mjs/Editor.toSVGAsync.test.d.ts +1 -0
  83. package/dist/mjs/SVGLoader.mjs +37 -22
  84. package/dist/mjs/commands/Erase.mjs +1 -1
  85. package/dist/mjs/commands/UnresolvedCommand.d.ts +1 -1
  86. package/dist/mjs/components/AbstractComponent.d.ts +1 -1
  87. package/dist/mjs/components/AbstractComponent.mjs +1 -1
  88. package/dist/mjs/components/BackgroundComponent.d.ts +1 -1
  89. package/dist/mjs/components/BackgroundComponent.mjs +3 -2
  90. package/dist/mjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  91. package/dist/mjs/{EditorImage.mjs → image/EditorImage.mjs} +51 -7
  92. package/dist/mjs/image/EditorImage.test.d.ts +1 -0
  93. package/dist/mjs/image/export/editorImageToSVG.d.ts +8 -0
  94. package/dist/mjs/image/export/editorImageToSVG.mjs +41 -0
  95. package/dist/mjs/image/export/setExportedSVGSize.d.ts +6 -0
  96. package/dist/mjs/image/export/setExportedSVGSize.mjs +23 -0
  97. package/dist/mjs/image/lib.d.ts +1 -0
  98. package/dist/mjs/image/lib.mjs +1 -0
  99. package/dist/mjs/lib.d.ts +1 -1
  100. package/dist/mjs/lib.mjs +1 -1
  101. package/dist/mjs/localizations/comments.d.ts +6 -0
  102. package/dist/mjs/localizations/comments.mjs +8 -0
  103. package/dist/mjs/localizations/es.mjs +68 -48
  104. package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -1
  105. package/dist/mjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  106. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +4 -3
  107. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +8 -19
  108. package/dist/mjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
  109. package/dist/mjs/toolbar/AbstractToolbar.d.ts +11 -3
  110. package/dist/mjs/toolbar/AbstractToolbar.mjs +20 -6
  111. package/dist/mjs/toolbar/EdgeToolbar.mjs +5 -6
  112. package/dist/mjs/toolbar/IconProvider.d.ts +1 -0
  113. package/dist/mjs/toolbar/IconProvider.mjs +43 -0
  114. package/dist/mjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  115. package/dist/mjs/toolbar/widgets/ActionButtonWidget.mjs +21 -2
  116. package/dist/mjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  117. package/dist/mjs/toolbar/widgets/BaseToolWidget.mjs +3 -0
  118. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  119. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +30 -2
  120. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +1 -1
  121. package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  122. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +6 -0
  123. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -1
  124. package/dist/mjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  125. package/dist/mjs/toolbar/widgets/OverflowWidget.mjs +3 -0
  126. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  127. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +3 -0
  128. package/dist/mjs/tools/BaseTool.d.ts +3 -0
  129. package/dist/mjs/tools/BaseTool.mjs +13 -2
  130. package/dist/mjs/tools/FindTool.d.ts +1 -0
  131. package/dist/mjs/tools/FindTool.mjs +4 -1
  132. package/dist/mjs/tools/PanZoom.d.ts +1 -0
  133. package/dist/mjs/tools/PanZoom.mjs +4 -0
  134. package/dist/mjs/tools/Pen.d.ts +0 -1
  135. package/dist/mjs/tools/Pen.mjs +1 -4
  136. package/dist/mjs/tools/PipetteTool.d.ts +1 -0
  137. package/dist/mjs/tools/PipetteTool.mjs +3 -0
  138. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  139. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.mjs +3 -0
  140. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -0
  141. package/dist/mjs/tools/SelectionTool/Selection.mjs +45 -9
  142. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  143. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +25 -7
  144. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +5 -0
  145. package/dist/mjs/tools/SoundUITool.d.ts +1 -0
  146. package/dist/mjs/tools/SoundUITool.mjs +4 -1
  147. package/dist/mjs/tools/TextTool.mjs +2 -2
  148. package/dist/mjs/tools/ToolController.d.ts +2 -0
  149. package/dist/mjs/tools/ToolController.mjs +13 -2
  150. package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  151. package/dist/mjs/tools/ToolSwitcherShortcut.mjs +3 -0
  152. package/dist/mjs/types.d.ts +9 -4
  153. package/dist/mjs/types.mjs +4 -3
  154. package/dist/mjs/util/ReactiveValue.d.ts +1 -1
  155. package/dist/mjs/util/ReactiveValue.mjs +2 -2
  156. package/dist/mjs/version.mjs +1 -1
  157. package/package.json +5 -5
  158. package/src/Editor.scss +6 -0
  159. package/src/toolbar/EdgeToolbar.scss +19 -2
  160. package/src/tools/SelectionTool/SelectionTool.scss +74 -0
  161. package/src/tools/tools.scss +1 -1
  162. package/src/tools/SelectionTool/SelectionTool.css +0 -35
  163. /package/dist/cjs/{EditorImage.test.d.ts → Editor.toSVGAsync.test.d.ts} +0 -0
  164. /package/dist/{mjs → cjs/image}/EditorImage.test.d.ts +0 -0
@@ -4,6 +4,7 @@ import { ToolbarLocalization } from '../localization';
4
4
  import ActionButtonWidget from './ActionButtonWidget';
5
5
  declare class SaveActionWidget extends ActionButtonWidget {
6
6
  constructor(editor: Editor, localization: ToolbarLocalization, saveCallback: () => void);
7
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
7
8
  protected onKeyPress(event: KeyPressEvent): boolean;
8
9
  mustBeInToplevelMenu(): boolean;
9
10
  }
@@ -6,6 +6,9 @@ class SaveActionWidget extends ActionButtonWidget {
6
6
  super(editor, 'save-button', editor.icons.makeSaveIcon, localization.save, saveCallback);
7
7
  this.setTags([ToolbarWidgetTag.Save]);
8
8
  }
9
+ shouldAutoDisableInReadOnlyEditor() {
10
+ return false;
11
+ }
9
12
  onKeyPress(event) {
10
13
  if (this.editor.shortcuts.matchesShortcut(saveKeyboardShortcut, event)) {
11
14
  this.clickAction();
@@ -8,6 +8,8 @@ export default abstract class BaseTool implements InputEventListener {
8
8
  private notifier;
9
9
  readonly description: string;
10
10
  protected constructor(notifier: EditorNotifier, description: string);
11
+ /** Override this to allow this tool to be enabled in a read-only editor */
12
+ canReceiveInputInReadOnlyEditor(): boolean;
11
13
  setInputMapper(mapper: InputMapper | null): void;
12
14
  getInputMapper(): InputMapper | null;
13
15
  private dispatchEventToCallback;
@@ -54,4 +56,5 @@ export default abstract class BaseTool implements InputEventListener {
54
56
  enabledValue(): ReactiveValue<boolean>;
55
57
  setToolGroup(group: ToolEnabledGroup): void;
56
58
  getToolGroup(): ToolEnabledGroup | null;
59
+ onDestroy(): void;
57
60
  }
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _BaseTool_enabled, _BaseTool_group, _BaseTool_inputMapper;
12
+ var _BaseTool_enabled, _BaseTool_group, _BaseTool_inputMapper, _BaseTool_readOnlyEditorChangeListener;
13
13
  import { EditorEventType } from '../types.mjs';
14
14
  import { InputEvtType } from '../inputEvents.mjs';
15
15
  import { ReactiveValue } from '../util/ReactiveValue.mjs';
@@ -20,6 +20,7 @@ class BaseTool {
20
20
  _BaseTool_enabled.set(this, void 0);
21
21
  _BaseTool_group.set(this, null);
22
22
  _BaseTool_inputMapper.set(this, null);
23
+ _BaseTool_readOnlyEditorChangeListener.set(this, null);
23
24
  __classPrivateFieldSet(this, _BaseTool_enabled, ReactiveValue.fromInitialValue(true), "f");
24
25
  __classPrivateFieldGet(this, _BaseTool_enabled, "f").onUpdate(enabled => {
25
26
  // Ensure that at most one tool in the group is enabled.
@@ -38,6 +39,10 @@ class BaseTool {
38
39
  }
39
40
  });
40
41
  }
42
+ /** Override this to allow this tool to be enabled in a read-only editor */
43
+ canReceiveInputInReadOnlyEditor() {
44
+ return false;
45
+ }
41
46
  setInputMapper(mapper) {
42
47
  __classPrivateFieldSet(this, _BaseTool_inputMapper, mapper, "f");
43
48
  if (mapper) {
@@ -155,6 +160,12 @@ class BaseTool {
155
160
  }
156
161
  return null;
157
162
  }
163
+ // Called when the tool is removed/when the editor is destroyed.
164
+ // Subclasses that override this method **must call super.onDestroy()**.
165
+ onDestroy() {
166
+ __classPrivateFieldGet(this, _BaseTool_readOnlyEditorChangeListener, "f")?.remove();
167
+ __classPrivateFieldSet(this, _BaseTool_readOnlyEditorChangeListener, null, "f");
168
+ }
158
169
  }
159
- _BaseTool_enabled = new WeakMap(), _BaseTool_group = new WeakMap(), _BaseTool_inputMapper = new WeakMap();
170
+ _BaseTool_enabled = new WeakMap(), _BaseTool_group = new WeakMap(), _BaseTool_inputMapper = new WeakMap(), _BaseTool_readOnlyEditorChangeListener = new WeakMap();
160
171
  export default BaseTool;
@@ -7,6 +7,7 @@ export default class FindTool extends BaseTool {
7
7
  private searchInput;
8
8
  private currentMatchIdx;
9
9
  constructor(editor: Editor);
10
+ canReceiveInputInReadOnlyEditor(): boolean;
10
11
  private getMatches;
11
12
  private focusCurrentMatch;
12
13
  private toNextMatch;
@@ -16,6 +16,9 @@ export default class FindTool extends BaseTool {
16
16
  this.overlay.style.display = 'none';
17
17
  this.overlay.classList.add(`${cssPrefix}-overlay`);
18
18
  }
19
+ canReceiveInputInReadOnlyEditor() {
20
+ return true;
21
+ }
19
22
  getMatches(searchFor) {
20
23
  searchFor = searchFor.toLocaleLowerCase();
21
24
  const allTextComponents = this.editor.image.getAllElements()
@@ -108,7 +111,7 @@ export default class FindTool extends BaseTool {
108
111
  }
109
112
  setEnabled(enabled) {
110
113
  super.setEnabled(enabled);
111
- if (enabled) {
114
+ if (this.isEnabled()) {
112
115
  this.setVisible(false);
113
116
  }
114
117
  }
@@ -32,6 +32,7 @@ export default class PanZoom extends BaseTool {
32
32
  private inertialScroller;
33
33
  private velocity;
34
34
  constructor(editor: Editor, mode: PanZoomMode, description: string);
35
+ canReceiveInputInReadOnlyEditor(): boolean;
35
36
  computePinchData(p1: Pointer, p2: Pointer): PinchData;
36
37
  private allPointersAreOfType;
37
38
  onPointerDown({ allPointers: pointers, current: currentPointer }: PointerEvt): boolean;
@@ -74,6 +74,10 @@ export default class PanZoom extends BaseTool {
74
74
  this.inertialScroller = null;
75
75
  this.velocity = null;
76
76
  }
77
+ // The pan/zoom tool can be used in a read-only editor.
78
+ canReceiveInputInReadOnlyEditor() {
79
+ return true;
80
+ }
77
81
  // Returns information about the pointers in a gesture
78
82
  computePinchData(p1, p2) {
79
83
  // Swap the pointers to ensure consistent ordering.
@@ -40,6 +40,5 @@ export default class Pen extends BaseTool {
40
40
  getColor(): Color4;
41
41
  getStrokeFactory(): ComponentBuilderFactory;
42
42
  getStyleValue(): MutableReactiveValue<PenStyle>;
43
- setEnabled(enabled: boolean): void;
44
43
  onKeyPress(event: KeyPressEvent): boolean;
45
44
  }
@@ -1,5 +1,5 @@
1
1
  import { Color4 } from '@js-draw/math';
2
- import EditorImage from '../EditorImage.mjs';
2
+ import EditorImage from '../image/EditorImage.mjs';
3
3
  import { PointerDevice } from '../Pointer.mjs';
4
4
  import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder.mjs';
5
5
  import { EditorEventType } from '../types.mjs';
@@ -200,9 +200,6 @@ export default class Pen extends BaseTool {
200
200
  getColor() { return this.style.color; }
201
201
  getStrokeFactory() { return this.style.factory; }
202
202
  getStyleValue() { return this.styleValue; }
203
- setEnabled(enabled) {
204
- super.setEnabled(enabled);
205
- }
206
203
  onKeyPress(event) {
207
204
  const shortcuts = this.editor.shortcuts;
208
205
  // Ctrl+Z: End the stroke so that it can be undone/redone.
@@ -16,6 +16,7 @@ export default class PipetteTool extends BaseTool {
16
16
  private colorPreviewListener;
17
17
  private colorSelectListener;
18
18
  constructor(editor: Editor, description: string);
19
+ canReceiveInputInReadOnlyEditor(): boolean;
19
20
  private updateSelectingStatus;
20
21
  setColorListener(colorPreviewListener: ColorListener, colorSelectListener: ColorListener): void;
21
22
  clearColorListener(): void;
@@ -18,6 +18,9 @@ export default class PipetteTool extends BaseTool {
18
18
  this.updateSelectingStatus();
19
19
  });
20
20
  }
21
+ canReceiveInputInReadOnlyEditor() {
22
+ return true;
23
+ }
21
24
  // Ensures that the root editor element correctly reflects whether color selection
22
25
  // is in progress.
23
26
  updateSelectingStatus() {
@@ -4,5 +4,6 @@ import BaseTool from '../BaseTool';
4
4
  export default class SelectAllShortcutHandler extends BaseTool {
5
5
  private editor;
6
6
  constructor(editor: Editor);
7
+ canReceiveInputInReadOnlyEditor(): boolean;
7
8
  onKeyPress(event: KeyPressEvent): boolean;
8
9
  }
@@ -7,6 +7,9 @@ export default class SelectAllShortcutHandler extends BaseTool {
7
7
  super(editor.notifier, editor.localization.selectAllTool);
8
8
  this.editor = editor;
9
9
  }
10
+ canReceiveInputInReadOnlyEditor() {
11
+ return true;
12
+ }
10
13
  // @internal
11
14
  onKeyPress(event) {
12
15
  if (this.editor.shortcuts.matchesShortcut(selectAllKeyboardShortcut, event)) {
@@ -11,6 +11,7 @@ export default class Selection {
11
11
  private editor;
12
12
  private handles;
13
13
  private originalRegion;
14
+ private selectionTightBoundingBox;
14
15
  private transformers;
15
16
  private transform;
16
17
  private selectedElems;
@@ -39,6 +40,7 @@ export default class Selection {
39
40
  private previewTransformCmds;
40
41
  resolveToObjects(): boolean;
41
42
  recomputeRegion(): boolean;
43
+ padRegion(): void;
42
44
  getMinCanvasSize(): number;
43
45
  getSelectedItemCount(): number;
44
46
  updateUI(): void;
@@ -5,20 +5,23 @@
5
5
  var _a;
6
6
  import SerializableCommand from '../../commands/SerializableCommand.mjs';
7
7
  import { Mat33, Rect2, Vec2 } from '@js-draw/math';
8
- import SelectionHandle, { HandleShape, handleSize } from './SelectionHandle.mjs';
8
+ import SelectionHandle, { HandleAction, handleSize } from './SelectionHandle.mjs';
9
9
  import { cssPrefix } from './SelectionTool.mjs';
10
10
  import Viewport from '../../Viewport.mjs';
11
11
  import Erase from '../../commands/Erase.mjs';
12
12
  import Duplicate from '../../commands/Duplicate.mjs';
13
13
  import { DragTransformer, ResizeTransformer, RotateTransformer } from './TransformMode.mjs';
14
14
  import { ResizeMode } from './types.mjs';
15
- import EditorImage from '../../EditorImage.mjs';
15
+ import EditorImage from '../../image/EditorImage.mjs';
16
16
  const updateChunkSize = 100;
17
17
  const maxPreviewElemCount = 500;
18
18
  // @internal
19
19
  class Selection {
20
20
  constructor(startPoint, editor) {
21
21
  this.editor = editor;
22
+ // The last-computed bounding box of selected content
23
+ // @see getTightBoundingBox
24
+ this.selectionTightBoundingBox = null;
22
25
  this.transform = Mat33.identity;
23
26
  this.selectedElems = [];
24
27
  this.hasParent = true;
@@ -37,10 +40,23 @@ class Selection {
37
40
  this.backgroundElem = document.createElement('div');
38
41
  this.backgroundElem.classList.add(`${cssPrefix}selection-background`);
39
42
  this.container.appendChild(this.backgroundElem);
40
- const resizeHorizontalHandle = new SelectionHandle(HandleShape.Square, Vec2.of(1, 0.5), this, this.editor.viewport, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.HorizontalOnly), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
41
- const resizeVerticalHandle = new SelectionHandle(HandleShape.Square, Vec2.of(0.5, 1), this, this.editor.viewport, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.VerticalOnly), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
42
- const resizeBothHandle = new SelectionHandle(HandleShape.Square, Vec2.of(1, 1), this, this.editor.viewport, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.Both), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
43
- const rotationHandle = new SelectionHandle(HandleShape.Circle, Vec2.of(0.5, 0), this, this.editor.viewport, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
43
+ const resizeHorizontalHandle = new SelectionHandle({
44
+ action: HandleAction.ResizeX,
45
+ side: Vec2.of(1, 0.5),
46
+ }, this, this.editor.viewport, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.HorizontalOnly), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
47
+ const resizeVerticalHandle = new SelectionHandle({
48
+ action: HandleAction.ResizeY,
49
+ side: Vec2.of(0.5, 1),
50
+ }, this, this.editor.viewport, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.VerticalOnly), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
51
+ const resizeBothHandle = new SelectionHandle({
52
+ action: HandleAction.ResizeXY,
53
+ side: Vec2.of(1, 1),
54
+ }, this, this.editor.viewport, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.Both), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
55
+ const rotationHandle = new SelectionHandle({
56
+ action: HandleAction.Rotate,
57
+ side: Vec2.of(0.5, 0),
58
+ icon: this.editor.icons.makeRotateIcon(),
59
+ }, this, this.editor.viewport, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
44
60
  this.handles = [
45
61
  resizeBothHandle,
46
62
  resizeHorizontalHandle,
@@ -167,18 +183,26 @@ class Selection {
167
183
  // Returns false if the selection is empty.
168
184
  recomputeRegion() {
169
185
  const newRegion = this.computeTightBoundingBox();
186
+ this.selectionTightBoundingBox = newRegion;
170
187
  if (!newRegion) {
171
188
  this.cancelSelection();
172
189
  return false;
173
190
  }
174
191
  this.originalRegion = newRegion;
192
+ this.padRegion();
193
+ return true;
194
+ }
195
+ // Applies padding to the current region if it is too small.
196
+ // @internal
197
+ padRegion() {
198
+ const sourceRegion = this.selectionTightBoundingBox ?? this.originalRegion;
175
199
  const minSize = this.getMinCanvasSize();
176
- if (this.originalRegion.w < minSize || this.originalRegion.h < minSize) {
200
+ if (sourceRegion.w < minSize || sourceRegion.h < minSize) {
177
201
  // Add padding
178
202
  const padding = minSize / 2;
179
- this.originalRegion = Rect2.bboxOf(this.originalRegion.corners, padding);
203
+ this.originalRegion = Rect2.bboxOf(sourceRegion.corners, padding);
204
+ this.updateUI();
180
205
  }
181
- return true;
182
206
  }
183
207
  getMinCanvasSize() {
184
208
  const canvasHandleSize = handleSize / this.editor.viewport.getScaleFactor();
@@ -202,6 +226,14 @@ class Selection {
202
226
  const rotationDeg = this.screenRegionRotation * 180 / Math.PI;
203
227
  this.backgroundElem.style.transform = `rotate(${rotationDeg}deg)`;
204
228
  this.backgroundElem.style.transformOrigin = 'center';
229
+ // If closer to perpendicular, apply different CSS
230
+ const perpendicularClassName = `${cssPrefix}rotated-near-perpendicular`;
231
+ if (Math.abs(Math.sin(this.screenRegionRotation)) > 0.5) {
232
+ this.container.classList.add(perpendicularClassName);
233
+ }
234
+ else {
235
+ this.container.classList.remove(perpendicularClassName);
236
+ }
205
237
  for (const handle of this.handles) {
206
238
  handle.updatePosition();
207
239
  }
@@ -373,6 +405,7 @@ class Selection {
373
405
  }
374
406
  setToPoint(point) {
375
407
  this.originalRegion = this.originalRegion.grownToPoint(point);
408
+ this.selectionTightBoundingBox = null;
376
409
  this.updateUI();
377
410
  }
378
411
  cancelSelection() {
@@ -380,12 +413,15 @@ class Selection {
380
413
  this.container.remove();
381
414
  }
382
415
  this.originalRegion = Rect2.empty;
416
+ this.selectionTightBoundingBox = null;
383
417
  this.hasParent = false;
384
418
  }
385
419
  setSelectedObjects(objects, bbox) {
386
420
  this.addRemoveSelectionFromImage(true);
387
421
  this.originalRegion = bbox;
422
+ this.selectionTightBoundingBox = bbox;
388
423
  this.selectedElems = objects.filter(object => object.isSelectable());
424
+ this.padRegion();
389
425
  this.updateUI();
390
426
  }
391
427
  getSelectedObjects() {
@@ -2,17 +2,23 @@ import { Point2, Vec2 } from '@js-draw/math';
2
2
  import Selection from './Selection';
3
3
  import Pointer from '../../Pointer';
4
4
  import Viewport from '../../Viewport';
5
- export declare enum HandleShape {
6
- Circle = 0,
7
- Square = 1
5
+ export declare enum HandleAction {
6
+ ResizeXY = "resize-xy",
7
+ Rotate = "rotate",
8
+ ResizeX = "resize-x",
9
+ ResizeY = "resize-y"
10
+ }
11
+ export interface HandlePresentation {
12
+ side: Vec2;
13
+ icon?: Element;
14
+ action: HandleAction;
8
15
  }
9
16
  export declare const handleSize = 30;
10
17
  export type DragStartCallback = (startPoint: Point2) => void;
11
18
  export type DragUpdateCallback = (canvasPoint: Point2) => void;
12
19
  export type DragEndCallback = () => void;
13
20
  export default class SelectionHandle {
14
- readonly shape: HandleShape;
15
- private readonly parentSide;
21
+ readonly presentation: HandlePresentation;
16
22
  private readonly parent;
17
23
  private readonly viewport;
18
24
  private readonly onDragStart;
@@ -20,7 +26,9 @@ export default class SelectionHandle {
20
26
  private readonly onDragEnd;
21
27
  private element;
22
28
  private snapToGrid;
23
- constructor(shape: HandleShape, parentSide: Vec2, parent: Selection, viewport: Viewport, onDragStart: DragStartCallback, onDragUpdate: DragUpdateCallback, onDragEnd: DragEndCallback);
29
+ private shape;
30
+ private parentSide;
31
+ constructor(presentation: HandlePresentation, parent: Selection, viewport: Viewport, onDragStart: DragStartCallback, onDragUpdate: DragUpdateCallback, onDragEnd: DragEndCallback);
24
32
  /**
25
33
  * Adds this to `container`, where `conatiner` should be the background/selection
26
34
  * element visible on the screen.
@@ -1,16 +1,22 @@
1
1
  import { assertUnreachable } from '../../util/assertions.mjs';
2
2
  import { Rect2, Vec2 } from '@js-draw/math';
3
3
  import { cssPrefix } from './SelectionTool.mjs';
4
- export var HandleShape;
4
+ var HandleShape;
5
5
  (function (HandleShape) {
6
6
  HandleShape[HandleShape["Circle"] = 0] = "Circle";
7
7
  HandleShape[HandleShape["Square"] = 1] = "Square";
8
8
  })(HandleShape || (HandleShape = {}));
9
+ export var HandleAction;
10
+ (function (HandleAction) {
11
+ HandleAction["ResizeXY"] = "resize-xy";
12
+ HandleAction["Rotate"] = "rotate";
13
+ HandleAction["ResizeX"] = "resize-x";
14
+ HandleAction["ResizeY"] = "resize-y";
15
+ })(HandleAction || (HandleAction = {}));
9
16
  export const handleSize = 30;
10
17
  export default class SelectionHandle {
11
- constructor(shape, parentSide, parent, viewport, onDragStart, onDragUpdate, onDragEnd) {
12
- this.shape = shape;
13
- this.parentSide = parentSide;
18
+ constructor(presentation, parent, viewport, onDragStart, onDragUpdate, onDragEnd) {
19
+ this.presentation = presentation;
14
20
  this.parent = parent;
15
21
  this.viewport = viewport;
16
22
  this.onDragStart = onDragStart;
@@ -18,8 +24,20 @@ export default class SelectionHandle {
18
24
  this.onDragEnd = onDragEnd;
19
25
  this.dragLastPos = null;
20
26
  this.element = document.createElement('div');
21
- this.element.classList.add(`${cssPrefix}handle`);
22
- switch (shape) {
27
+ this.element.classList.add(`${cssPrefix}handle`, `${cssPrefix}${presentation.action}`);
28
+ this.parentSide = presentation.side;
29
+ const icon = presentation.icon;
30
+ if (icon) {
31
+ this.element.appendChild(icon);
32
+ icon.classList.add('icon');
33
+ }
34
+ if (presentation.action === HandleAction.Rotate) {
35
+ this.shape = HandleShape.Circle;
36
+ }
37
+ else {
38
+ this.shape = HandleShape.Square;
39
+ }
40
+ switch (this.shape) {
23
41
  case HandleShape.Circle:
24
42
  this.element.classList.add(`${cssPrefix}circle`);
25
43
  break;
@@ -27,7 +45,7 @@ export default class SelectionHandle {
27
45
  this.element.classList.add(`${cssPrefix}square`);
28
46
  break;
29
47
  default:
30
- assertUnreachable(shape);
48
+ assertUnreachable(this.shape);
31
49
  }
32
50
  this.updatePosition();
33
51
  }
@@ -27,6 +27,11 @@ class SelectionTool extends BaseTool {
27
27
  // The selection box could be using the wet ink display if its transformation
28
28
  // hasn't been finalized yet. Clear before updating the UI.
29
29
  this.editor.clearWetInk();
30
+ // If not currently selecting, ensure that the selection box
31
+ // is large enough.
32
+ if (!this.expandingSelectionBox) {
33
+ this.selectionBox?.padRegion();
34
+ }
30
35
  this.selectionBox?.updateUI();
31
36
  });
32
37
  this.editor.handleKeyEventsFrom(this.handleOverlay);
@@ -14,6 +14,7 @@ export default class SoundUITool extends BaseTool {
14
14
  private toggleButton;
15
15
  private toggleButtonContainer;
16
16
  constructor(editor: Editor, description: string);
17
+ canReceiveInputInReadOnlyEditor(): boolean;
17
18
  private updateToggleButtonText;
18
19
  setEnabled(enabled: boolean): void;
19
20
  onKeyPress(event: KeyPressEvent): boolean;
@@ -100,6 +100,9 @@ export default class SoundUITool extends BaseTool {
100
100
  this.updateToggleButtonText();
101
101
  editor.createHTMLOverlay(this.toggleButtonContainer);
102
102
  }
103
+ canReceiveInputInReadOnlyEditor() {
104
+ return true;
105
+ }
103
106
  updateToggleButtonText() {
104
107
  const containerEnabledClass = 'sound-ui-tool-enabled';
105
108
  if (this.isEnabled()) {
@@ -113,7 +116,7 @@ export default class SoundUITool extends BaseTool {
113
116
  }
114
117
  setEnabled(enabled) {
115
118
  super.setEnabled(enabled);
116
- if (!enabled) {
119
+ if (!this.isEnabled()) {
117
120
  this.soundFeedback?.close();
118
121
  this.soundFeedback = null;
119
122
  }
@@ -1,5 +1,5 @@
1
1
  import TextComponent from '../components/TextComponent.mjs';
2
- import EditorImage from '../EditorImage.mjs';
2
+ import EditorImage from '../image/EditorImage.mjs';
3
3
  import { Rect2, Mat33, Vec2, Color4 } from '@js-draw/math';
4
4
  import { PointerDevice } from '../Pointer.mjs';
5
5
  import { EditorEventType } from '../types.mjs';
@@ -188,7 +188,7 @@ export default class TextTool extends BaseTool {
188
188
  }
189
189
  setEnabled(enabled) {
190
190
  super.setEnabled(enabled);
191
- if (!enabled) {
191
+ if (!this.isEnabled()) {
192
192
  this.flushInput();
193
193
  }
194
194
  this.textEditOverlay.style.display = enabled ? 'block' : 'none';
@@ -9,6 +9,7 @@ export default class ToolController implements InputEventListener {
9
9
  private activeTool;
10
10
  private primaryToolGroup;
11
11
  private inputPipeline;
12
+ private isEditorReadOnly;
12
13
  /** @internal */
13
14
  constructor(editor: Editor, localization: ToolLocalization);
14
15
  setTools(tools: BaseTool[], primaryToolGroup?: ToolEnabledGroup): void;
@@ -29,4 +30,5 @@ export default class ToolController implements InputEventListener {
29
30
  */
30
31
  addInputMapper(mapper: InputMapper): void;
31
32
  getMatchingTools<Type extends BaseTool>(type: new (...args: any[]) => Type): Type[];
33
+ onEditorDestroyed(): void;
32
34
  }
@@ -23,6 +23,7 @@ export default class ToolController {
23
23
  /** @internal */
24
24
  constructor(editor, localization) {
25
25
  this.activeTool = null;
26
+ this.isEditorReadOnly = editor.isReadOnlyReactiveValue();
26
27
  this.inputPipeline = new InputPipeline();
27
28
  this.inputPipeline.setEmitListener(event => this.onEventInternal(event));
28
29
  const primaryToolGroup = new ToolEnabledGroup();
@@ -107,6 +108,10 @@ export default class ToolController {
107
108
  }
108
109
  // @internal use `dispatchEvent` rather than calling `onEvent` directly.
109
110
  onEventInternal(event) {
111
+ const isEditorReadOnly = this.isEditorReadOnly.get();
112
+ const canToolReceiveInput = (tool) => {
113
+ return tool.isEnabled() && (!isEditorReadOnly || tool.canReceiveInputInReadOnlyEditor());
114
+ };
110
115
  let handled = false;
111
116
  if (event.kind === InputEvtType.PointerDownEvt) {
112
117
  let canOnlySendToActiveTool = false;
@@ -117,7 +122,7 @@ export default class ToolController {
117
122
  if (canOnlySendToActiveTool && tool !== this.activeTool) {
118
123
  continue;
119
124
  }
120
- if (tool.isEnabled() && tool.onEvent(event)) {
125
+ if (canToolReceiveInput(tool) && tool.onEvent(event)) {
121
126
  if (this.activeTool !== tool) {
122
127
  this.activeTool?.onEvent({ kind: InputEvtType.GestureCancelEvt });
123
128
  }
@@ -151,7 +156,7 @@ export default class ToolController {
151
156
  }
152
157
  else {
153
158
  for (const tool of this.tools) {
154
- if (!tool.isEnabled()) {
159
+ if (!canToolReceiveInput(tool)) {
155
160
  continue;
156
161
  }
157
162
  handled = tool.onEvent(event);
@@ -185,4 +190,10 @@ export default class ToolController {
185
190
  getMatchingTools(type) {
186
191
  return this.tools.filter(tool => tool instanceof type);
187
192
  }
193
+ // @internal
194
+ onEditorDestroyed() {
195
+ for (const tool of this.tools) {
196
+ tool.onDestroy();
197
+ }
198
+ }
188
199
  }
@@ -12,5 +12,6 @@ import BaseTool from './BaseTool';
12
12
  export default class ToolSwitcherShortcut extends BaseTool {
13
13
  private editor;
14
14
  constructor(editor: Editor);
15
+ canReceiveInputInReadOnlyEditor(): boolean;
15
16
  onKeyPress({ key }: KeyPressEvent): boolean;
16
17
  }
@@ -12,6 +12,9 @@ export default class ToolSwitcherShortcut extends BaseTool {
12
12
  super(editor.notifier, editor.localization.changeTool);
13
13
  this.editor = editor;
14
14
  }
15
+ canReceiveInputInReadOnlyEditor() {
16
+ return true;
17
+ }
15
18
  // @internal
16
19
  onKeyPress({ key }) {
17
20
  const toolController = this.editor.toolController;
@@ -16,12 +16,13 @@ export declare enum EditorEventType {
16
16
  ViewportChanged = 7,
17
17
  DisplayResized = 8,
18
18
  SelectionUpdated = 9,
19
+ ReadOnlyModeToggled = 10,
19
20
  /** @internal */
20
- ColorPickerToggled = 10,
21
+ ColorPickerToggled = 11,
21
22
  /** @internal */
22
- ColorPickerColorSelected = 11,
23
+ ColorPickerColorSelected = 12,
23
24
  /** @deprecated @internal */
24
- ToolbarDropdownShown = 12
25
+ ToolbarDropdownShown = 13
25
26
  }
26
27
  export declare enum UndoEventType {
27
28
  CommandDone = 0,
@@ -66,6 +67,10 @@ export interface SelectionUpdated {
66
67
  readonly selectedComponents: AbstractComponent[];
67
68
  readonly tool: BaseTool;
68
69
  }
70
+ export interface ReadOnlyToggled {
71
+ readonly kind: EditorEventType.ReadOnlyModeToggled;
72
+ readonly editorIsReadOnly: boolean;
73
+ }
69
74
  export interface ColorPickerToggled {
70
75
  readonly kind: EditorEventType.ColorPickerToggled;
71
76
  readonly open: boolean;
@@ -79,7 +84,7 @@ export interface ToolbarDropdownShownEvent {
79
84
  readonly fromToplevelDropdown: boolean;
80
85
  readonly layoutManager: WidgetContentLayoutManager;
81
86
  }
82
- export type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | SelectionUpdated | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
87
+ export type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | SelectionUpdated | ReadOnlyToggled | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
83
88
  export type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null | void;
84
89
  export type ComponentAddedListener = (component: AbstractComponent) => Promise<void> | void;
85
90
  export type OnDetermineExportRectListener = (exportRect: Rect2, options?: {
@@ -11,12 +11,13 @@ export var EditorEventType;
11
11
  EditorEventType[EditorEventType["ViewportChanged"] = 7] = "ViewportChanged";
12
12
  EditorEventType[EditorEventType["DisplayResized"] = 8] = "DisplayResized";
13
13
  EditorEventType[EditorEventType["SelectionUpdated"] = 9] = "SelectionUpdated";
14
+ EditorEventType[EditorEventType["ReadOnlyModeToggled"] = 10] = "ReadOnlyModeToggled";
14
15
  /** @internal */
15
- EditorEventType[EditorEventType["ColorPickerToggled"] = 10] = "ColorPickerToggled";
16
+ EditorEventType[EditorEventType["ColorPickerToggled"] = 11] = "ColorPickerToggled";
16
17
  /** @internal */
17
- EditorEventType[EditorEventType["ColorPickerColorSelected"] = 11] = "ColorPickerColorSelected";
18
+ EditorEventType[EditorEventType["ColorPickerColorSelected"] = 12] = "ColorPickerColorSelected";
18
19
  /** @deprecated @internal */
19
- EditorEventType[EditorEventType["ToolbarDropdownShown"] = 12] = "ToolbarDropdownShown";
20
+ EditorEventType[EditorEventType["ToolbarDropdownShown"] = 13] = "ToolbarDropdownShown";
20
21
  })(EditorEventType || (EditorEventType = {}));
21
22
  // Types of `EditorUndoStackUpdated` events.
22
23
  export var UndoEventType;
@@ -55,7 +55,7 @@ export declare abstract class ReactiveValue<T> {
55
55
  }
56
56
  export declare abstract class MutableReactiveValue<T> extends ReactiveValue<T> {
57
57
  /**
58
- * Changes the value of this and fires all update listeners.
58
+ * Changes the value of this and, if different, fires all update listeners.
59
59
  *
60
60
  * @see {@link onUpdate}
61
61
  */