js-draw 1.3.1 → 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 (158) 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/commands/Erase.js +1 -1
  8. package/dist/cjs/commands/UnresolvedCommand.d.ts +1 -1
  9. package/dist/cjs/components/AbstractComponent.d.ts +1 -1
  10. package/dist/cjs/components/AbstractComponent.js +1 -1
  11. package/dist/cjs/components/BackgroundComponent.d.ts +1 -1
  12. package/dist/cjs/components/BackgroundComponent.js +1 -1
  13. package/dist/cjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  14. package/dist/cjs/{EditorImage.js → image/EditorImage.js} +51 -7
  15. package/dist/cjs/image/export/editorImageToSVG.d.ts +8 -0
  16. package/dist/cjs/image/export/editorImageToSVG.js +49 -0
  17. package/dist/cjs/image/export/setExportedSVGSize.d.ts +6 -0
  18. package/dist/cjs/image/export/setExportedSVGSize.js +25 -0
  19. package/dist/cjs/image/lib.d.ts +1 -0
  20. package/dist/cjs/image/lib.js +8 -0
  21. package/dist/cjs/lib.d.ts +1 -1
  22. package/dist/cjs/lib.js +2 -3
  23. package/dist/cjs/localizations/comments.d.ts +6 -0
  24. package/dist/cjs/localizations/comments.js +10 -0
  25. package/dist/cjs/localizations/es.js +68 -48
  26. package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -1
  27. package/dist/cjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  28. package/dist/cjs/rendering/caching/RenderingCacheNode.js +4 -3
  29. package/dist/cjs/toolbar/AbstractToolbar.d.ts +11 -3
  30. package/dist/cjs/toolbar/AbstractToolbar.js +20 -6
  31. package/dist/cjs/toolbar/EdgeToolbar.js +5 -6
  32. package/dist/cjs/toolbar/IconProvider.d.ts +1 -0
  33. package/dist/cjs/toolbar/IconProvider.js +43 -0
  34. package/dist/cjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  35. package/dist/cjs/toolbar/widgets/ActionButtonWidget.js +19 -1
  36. package/dist/cjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  37. package/dist/cjs/toolbar/widgets/BaseToolWidget.js +3 -0
  38. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  39. package/dist/cjs/toolbar/widgets/BaseWidget.js +30 -2
  40. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
  41. package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  42. package/dist/cjs/toolbar/widgets/HandToolWidget.js +6 -0
  43. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -1
  44. package/dist/cjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  45. package/dist/cjs/toolbar/widgets/OverflowWidget.js +3 -0
  46. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  47. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +3 -0
  48. package/dist/cjs/tools/BaseTool.d.ts +3 -0
  49. package/dist/cjs/tools/BaseTool.js +13 -2
  50. package/dist/cjs/tools/FindTool.d.ts +1 -0
  51. package/dist/cjs/tools/FindTool.js +4 -1
  52. package/dist/cjs/tools/PanZoom.d.ts +1 -0
  53. package/dist/cjs/tools/PanZoom.js +4 -0
  54. package/dist/cjs/tools/Pen.d.ts +0 -1
  55. package/dist/cjs/tools/Pen.js +1 -4
  56. package/dist/cjs/tools/PipetteTool.d.ts +1 -0
  57. package/dist/cjs/tools/PipetteTool.js +3 -0
  58. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  59. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.js +3 -0
  60. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -0
  61. package/dist/cjs/tools/SelectionTool/Selection.js +44 -8
  62. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  63. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +26 -8
  64. package/dist/cjs/tools/SelectionTool/SelectionTool.js +5 -0
  65. package/dist/cjs/tools/SoundUITool.d.ts +1 -0
  66. package/dist/cjs/tools/SoundUITool.js +4 -1
  67. package/dist/cjs/tools/TextTool.js +2 -2
  68. package/dist/cjs/tools/ToolController.d.ts +2 -0
  69. package/dist/cjs/tools/ToolController.js +13 -2
  70. package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  71. package/dist/cjs/tools/ToolSwitcherShortcut.js +3 -0
  72. package/dist/cjs/types.d.ts +9 -4
  73. package/dist/cjs/types.js +4 -3
  74. package/dist/cjs/util/ReactiveValue.d.ts +1 -1
  75. package/dist/cjs/util/ReactiveValue.js +2 -2
  76. package/dist/cjs/version.js +1 -1
  77. package/dist/mjs/Editor.d.ts +36 -3
  78. package/dist/mjs/Editor.mjs +64 -27
  79. package/dist/mjs/Editor.toSVGAsync.test.d.ts +1 -0
  80. package/dist/mjs/commands/Erase.mjs +1 -1
  81. package/dist/mjs/commands/UnresolvedCommand.d.ts +1 -1
  82. package/dist/mjs/components/AbstractComponent.d.ts +1 -1
  83. package/dist/mjs/components/AbstractComponent.mjs +1 -1
  84. package/dist/mjs/components/BackgroundComponent.d.ts +1 -1
  85. package/dist/mjs/components/BackgroundComponent.mjs +1 -1
  86. package/dist/mjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  87. package/dist/mjs/{EditorImage.mjs → image/EditorImage.mjs} +51 -7
  88. package/dist/mjs/image/EditorImage.test.d.ts +1 -0
  89. package/dist/mjs/image/export/editorImageToSVG.d.ts +8 -0
  90. package/dist/mjs/image/export/editorImageToSVG.mjs +41 -0
  91. package/dist/mjs/image/export/setExportedSVGSize.d.ts +6 -0
  92. package/dist/mjs/image/export/setExportedSVGSize.mjs +23 -0
  93. package/dist/mjs/image/lib.d.ts +1 -0
  94. package/dist/mjs/image/lib.mjs +1 -0
  95. package/dist/mjs/lib.d.ts +1 -1
  96. package/dist/mjs/lib.mjs +1 -1
  97. package/dist/mjs/localizations/comments.d.ts +6 -0
  98. package/dist/mjs/localizations/comments.mjs +8 -0
  99. package/dist/mjs/localizations/es.mjs +68 -48
  100. package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -1
  101. package/dist/mjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  102. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +4 -3
  103. package/dist/mjs/toolbar/AbstractToolbar.d.ts +11 -3
  104. package/dist/mjs/toolbar/AbstractToolbar.mjs +20 -6
  105. package/dist/mjs/toolbar/EdgeToolbar.mjs +5 -6
  106. package/dist/mjs/toolbar/IconProvider.d.ts +1 -0
  107. package/dist/mjs/toolbar/IconProvider.mjs +43 -0
  108. package/dist/mjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  109. package/dist/mjs/toolbar/widgets/ActionButtonWidget.mjs +21 -2
  110. package/dist/mjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  111. package/dist/mjs/toolbar/widgets/BaseToolWidget.mjs +3 -0
  112. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  113. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +30 -2
  114. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +1 -1
  115. package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  116. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +6 -0
  117. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -1
  118. package/dist/mjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  119. package/dist/mjs/toolbar/widgets/OverflowWidget.mjs +3 -0
  120. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  121. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +3 -0
  122. package/dist/mjs/tools/BaseTool.d.ts +3 -0
  123. package/dist/mjs/tools/BaseTool.mjs +13 -2
  124. package/dist/mjs/tools/FindTool.d.ts +1 -0
  125. package/dist/mjs/tools/FindTool.mjs +4 -1
  126. package/dist/mjs/tools/PanZoom.d.ts +1 -0
  127. package/dist/mjs/tools/PanZoom.mjs +4 -0
  128. package/dist/mjs/tools/Pen.d.ts +0 -1
  129. package/dist/mjs/tools/Pen.mjs +1 -4
  130. package/dist/mjs/tools/PipetteTool.d.ts +1 -0
  131. package/dist/mjs/tools/PipetteTool.mjs +3 -0
  132. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  133. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.mjs +3 -0
  134. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -0
  135. package/dist/mjs/tools/SelectionTool/Selection.mjs +45 -9
  136. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  137. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +25 -7
  138. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +5 -0
  139. package/dist/mjs/tools/SoundUITool.d.ts +1 -0
  140. package/dist/mjs/tools/SoundUITool.mjs +4 -1
  141. package/dist/mjs/tools/TextTool.mjs +2 -2
  142. package/dist/mjs/tools/ToolController.d.ts +2 -0
  143. package/dist/mjs/tools/ToolController.mjs +13 -2
  144. package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  145. package/dist/mjs/tools/ToolSwitcherShortcut.mjs +3 -0
  146. package/dist/mjs/types.d.ts +9 -4
  147. package/dist/mjs/types.mjs +4 -3
  148. package/dist/mjs/util/ReactiveValue.d.ts +1 -1
  149. package/dist/mjs/util/ReactiveValue.mjs +2 -2
  150. package/dist/mjs/version.mjs +1 -1
  151. package/package.json +5 -5
  152. package/src/Editor.scss +6 -0
  153. package/src/toolbar/EdgeToolbar.scss +19 -2
  154. package/src/tools/SelectionTool/SelectionTool.scss +74 -0
  155. package/src/tools/tools.scss +1 -1
  156. package/src/tools/SelectionTool/SelectionTool.css +0 -35
  157. /package/dist/cjs/{EditorImage.test.d.ts → Editor.toSVGAsync.test.d.ts} +0 -0
  158. /package/dist/{mjs → cjs/image}/EditorImage.test.d.ts +0 -0
@@ -29,6 +29,11 @@ export default abstract class BaseWidget {
29
29
  private toplevel;
30
30
  protected readonly localizationTable: ToolbarLocalization;
31
31
  constructor(editor: Editor, id: string, localizationTable?: ToolbarLocalization);
32
+ /**
33
+ * Should return a constant true or false value. If true (the default),
34
+ * this widget must be automatically disabled when its editor is read-only.
35
+ */
36
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
32
37
  getId(): string;
33
38
  /**
34
39
  * Note: Tags should be set *before* a tool widget is added to a toolbar.
@@ -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 _BaseWidget_hasDropdown, _BaseWidget_tags;
12
+ var _BaseWidget_hasDropdown, _BaseWidget_disabledDueToReadOnlyEditor, _BaseWidget_tags, _BaseWidget_readOnlyListener;
13
13
  import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler.mjs';
14
14
  import { keyPressEventFromHTMLEvent, keyUpEventFromHTMLEvent } from '../../inputEvents.mjs';
15
15
  import { toolbarCSSPrefix } from '../constants.mjs';
@@ -30,11 +30,16 @@ class BaseWidget {
30
30
  this.id = id;
31
31
  this.dropdown = null;
32
32
  _BaseWidget_hasDropdown.set(this, void 0);
33
+ // True iff this widget is disabled.
33
34
  this.disabled = false;
35
+ // True iff this widget is currently disabled because the editor is read only
36
+ _BaseWidget_disabledDueToReadOnlyEditor.set(this, false);
34
37
  _BaseWidget_tags.set(this, []);
35
38
  // Maps subWidget IDs to subWidgets.
36
39
  this.subWidgets = {};
37
40
  this.toplevel = true;
41
+ // Listens for changes in whether the editor is read-only
42
+ _BaseWidget_readOnlyListener.set(this, null);
38
43
  this.localizationTable = localizationTable ?? editor.localization;
39
44
  // Default layout manager
40
45
  const defaultLayoutManager = new DropdownLayoutManager((text) => this.editor.announceForAccessibility(text), this.localizationTable);
@@ -62,6 +67,13 @@ class BaseWidget {
62
67
  toolbarShortcutHandlers[0].registerListener(event => this.onKeyPress(event));
63
68
  }
64
69
  }
70
+ /**
71
+ * Should return a constant true or false value. If true (the default),
72
+ * this widget must be automatically disabled when its editor is read-only.
73
+ */
74
+ shouldAutoDisableInReadOnlyEditor() {
75
+ return true;
76
+ }
65
77
  getId() {
66
78
  return this.id;
67
79
  }
@@ -256,6 +268,19 @@ class BaseWidget {
256
268
  if (this.container.parentElement) {
257
269
  this.container.remove();
258
270
  }
271
+ __classPrivateFieldSet(this, _BaseWidget_readOnlyListener, this.editor.isReadOnlyReactiveValue().onUpdateAndNow(readOnly => {
272
+ if (readOnly && this.shouldAutoDisableInReadOnlyEditor() && !this.disabled) {
273
+ this.setDisabled(true);
274
+ __classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, true, "f");
275
+ if (__classPrivateFieldGet(this, _BaseWidget_hasDropdown, "f")) {
276
+ this.dropdown?.requestHide();
277
+ }
278
+ }
279
+ else if (!readOnly && __classPrivateFieldGet(this, _BaseWidget_disabledDueToReadOnlyEditor, "f")) {
280
+ __classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, false, "f");
281
+ this.setDisabled(false);
282
+ }
283
+ }), "f");
259
284
  parent.appendChild(this.container);
260
285
  return this.container;
261
286
  }
@@ -273,6 +298,8 @@ class BaseWidget {
273
298
  }
274
299
  remove() {
275
300
  this.container.remove();
301
+ __classPrivateFieldGet(this, _BaseWidget_readOnlyListener, "f")?.remove();
302
+ __classPrivateFieldSet(this, _BaseWidget_readOnlyListener, null, "f");
276
303
  }
277
304
  updateIcon() {
278
305
  let newIcon = this.createIcon();
@@ -289,6 +316,7 @@ class BaseWidget {
289
316
  }
290
317
  setDisabled(disabled) {
291
318
  this.disabled = disabled;
319
+ __classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, false, "f");
292
320
  if (this.disabled) {
293
321
  this.button.classList.add('disabled');
294
322
  this.button.setAttribute('aria-disabled', 'true');
@@ -409,5 +437,5 @@ class BaseWidget {
409
437
  }
410
438
  }
411
439
  }
412
- _BaseWidget_hasDropdown = new WeakMap(), _BaseWidget_tags = new WeakMap();
440
+ _BaseWidget_hasDropdown = new WeakMap(), _BaseWidget_disabledDueToReadOnlyEditor = new WeakMap(), _BaseWidget_tags = new WeakMap(), _BaseWidget_readOnlyListener = new WeakMap();
413
441
  export default BaseWidget;
@@ -1,7 +1,7 @@
1
1
  import Erase from '../../commands/Erase.mjs';
2
2
  import uniteCommands from '../../commands/uniteCommands.mjs';
3
3
  import BackgroundComponent, { BackgroundType } from '../../components/BackgroundComponent.mjs';
4
- import { EditorImageEventType } from '../../EditorImage.mjs';
4
+ import { EditorImageEventType } from '../../image/EditorImage.mjs';
5
5
  import { Rect2 } from '@js-draw/math';
6
6
  import { EditorEventType } from '../../types.mjs';
7
7
  import { toolbarCSSPrefix } from '../constants.mjs';
@@ -7,6 +7,7 @@ export default class HandToolWidget extends BaseToolWidget {
7
7
  protected overridePanZoomTool: PanZoom;
8
8
  private allowTogglingBaseTool;
9
9
  constructor(editor: Editor, overridePanZoomTool: PanZoom, localizationTable: ToolbarLocalization);
10
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
10
11
  private static getPrimaryHandTool;
11
12
  protected getTitle(): string;
12
13
  protected createIcon(): Element;
@@ -75,6 +75,9 @@ class HandModeWidget extends BaseWidget {
75
75
  });
76
76
  this.setSelected(false);
77
77
  }
78
+ shouldAutoDisableInReadOnlyEditor() {
79
+ return false;
80
+ }
78
81
  setModeFlag(enabled) {
79
82
  this.tool.setModeEnabled(this.flag, enabled);
80
83
  }
@@ -113,6 +116,9 @@ export default class HandToolWidget extends BaseToolWidget {
113
116
  this.addSubWidget(touchPanningWidget);
114
117
  this.addSubWidget(rotationLockWidget);
115
118
  }
119
+ shouldAutoDisableInReadOnlyEditor() {
120
+ return false;
121
+ }
116
122
  static getPrimaryHandTool(toolController) {
117
123
  const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom);
118
124
  const primaryPanZoomTool = primaryPanZoomToolList[0];
@@ -1,6 +1,6 @@
1
1
  import ImageComponent from '../../components/ImageComponent.mjs';
2
2
  import Erase from '../../commands/Erase.mjs';
3
- import EditorImage from '../../EditorImage.mjs';
3
+ import EditorImage from '../../image/EditorImage.mjs';
4
4
  import uniteCommands from '../../commands/uniteCommands.mjs';
5
5
  import SelectionTool from '../../tools/SelectionTool/SelectionTool.mjs';
6
6
  import { Mat33 } from '@js-draw/math';
@@ -5,6 +5,7 @@ export default class OverflowWidget extends BaseWidget {
5
5
  private overflowChildren;
6
6
  private overflowContainer;
7
7
  constructor(editor: Editor, localizationTable?: ToolbarLocalization);
8
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
8
9
  protected getTitle(): string;
9
10
  protected createIcon(): Element | null;
10
11
  protected handleClick(): void;
@@ -8,6 +8,9 @@ export default class OverflowWidget extends BaseWidget {
8
8
  this.container.classList.add('dropdownShowable');
9
9
  this.overflowContainer ??= document.createElement('div');
10
10
  }
11
+ shouldAutoDisableInReadOnlyEditor() {
12
+ return false;
13
+ }
11
14
  getTitle() {
12
15
  return this.localizationTable.toggleOverflow;
13
16
  }
@@ -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
  }