js-draw 1.0.1 → 1.0.2

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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/cjs/version.js +1 -1
  4. package/dist/mjs/version.mjs +1 -1
  5. package/docs/img/readme-images/js-draw.jpg +0 -0
  6. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  7. package/package.json +5 -4
  8. package/dist-test/test_imports/package-lock.json +0 -13
  9. package/dist-test/test_imports/package.json +0 -12
  10. package/dist-test/test_imports/test-imports.js +0 -11
  11. package/dist-test/test_imports/test-require.cjs +0 -14
  12. package/src/Editor.loadFrom.test.ts +0 -24
  13. package/src/Editor.test.ts +0 -107
  14. package/src/Editor.toSVG.test.ts +0 -294
  15. package/src/Editor.ts +0 -1443
  16. package/src/EditorImage.test.ts +0 -117
  17. package/src/EditorImage.ts +0 -609
  18. package/src/EventDispatcher.test.ts +0 -123
  19. package/src/EventDispatcher.ts +0 -72
  20. package/src/Pointer.ts +0 -183
  21. package/src/SVGLoader.test.ts +0 -114
  22. package/src/SVGLoader.ts +0 -672
  23. package/src/UndoRedoHistory.test.ts +0 -34
  24. package/src/UndoRedoHistory.ts +0 -102
  25. package/src/Viewport.ts +0 -322
  26. package/src/bundle/bundled.ts +0 -7
  27. package/src/commands/Command.ts +0 -45
  28. package/src/commands/Duplicate.ts +0 -75
  29. package/src/commands/Erase.ts +0 -95
  30. package/src/commands/SerializableCommand.ts +0 -49
  31. package/src/commands/UnresolvedCommand.ts +0 -37
  32. package/src/commands/invertCommand.ts +0 -58
  33. package/src/commands/lib.ts +0 -16
  34. package/src/commands/localization.ts +0 -47
  35. package/src/commands/uniteCommands.test.ts +0 -23
  36. package/src/commands/uniteCommands.ts +0 -140
  37. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  38. package/src/components/AbstractComponent.ts +0 -383
  39. package/src/components/BackgroundComponent.test.ts +0 -44
  40. package/src/components/BackgroundComponent.ts +0 -348
  41. package/src/components/ImageComponent.ts +0 -176
  42. package/src/components/RestylableComponent.ts +0 -161
  43. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  44. package/src/components/Stroke.test.ts +0 -137
  45. package/src/components/Stroke.ts +0 -294
  46. package/src/components/TextComponent.test.ts +0 -202
  47. package/src/components/TextComponent.ts +0 -429
  48. package/src/components/UnknownSVGObject.test.ts +0 -10
  49. package/src/components/UnknownSVGObject.ts +0 -60
  50. package/src/components/builders/ArrowBuilder.ts +0 -106
  51. package/src/components/builders/CircleBuilder.ts +0 -100
  52. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  53. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  54. package/src/components/builders/LineBuilder.ts +0 -77
  55. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  56. package/src/components/builders/RectangleBuilder.ts +0 -73
  57. package/src/components/builders/types.ts +0 -15
  58. package/src/components/lib.ts +0 -31
  59. package/src/components/localization.ts +0 -24
  60. package/src/components/util/StrokeSmoother.ts +0 -302
  61. package/src/components/util/describeComponentList.ts +0 -18
  62. package/src/dialogs/makeAboutDialog.ts +0 -82
  63. package/src/inputEvents.ts +0 -143
  64. package/src/lib.ts +0 -91
  65. package/src/localization.ts +0 -34
  66. package/src/localizations/de.ts +0 -146
  67. package/src/localizations/en.ts +0 -8
  68. package/src/localizations/es.ts +0 -74
  69. package/src/localizations/getLocalizationTable.test.ts +0 -27
  70. package/src/localizations/getLocalizationTable.ts +0 -74
  71. package/src/rendering/Display.ts +0 -247
  72. package/src/rendering/RenderablePathSpec.ts +0 -88
  73. package/src/rendering/RenderingStyle.test.ts +0 -68
  74. package/src/rendering/RenderingStyle.ts +0 -55
  75. package/src/rendering/TextRenderingStyle.ts +0 -55
  76. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  77. package/src/rendering/caching/CacheRecord.ts +0 -76
  78. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  79. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  80. package/src/rendering/caching/RenderingCache.ts +0 -66
  81. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  82. package/src/rendering/caching/testUtils.ts +0 -35
  83. package/src/rendering/caching/types.ts +0 -34
  84. package/src/rendering/lib.ts +0 -8
  85. package/src/rendering/localization.ts +0 -20
  86. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  87. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  88. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  89. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  90. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  91. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  92. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  93. package/src/shortcuts/KeyBinding.test.ts +0 -61
  94. package/src/shortcuts/KeyBinding.ts +0 -257
  95. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  96. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  97. package/src/shortcuts/lib.ts +0 -3
  98. package/src/testing/createEditor.ts +0 -11
  99. package/src/testing/getUniquePointerId.ts +0 -18
  100. package/src/testing/lib.ts +0 -3
  101. package/src/testing/sendPenEvent.ts +0 -36
  102. package/src/testing/sendTouchEvent.ts +0 -71
  103. package/src/toolbar/AbstractToolbar.ts +0 -542
  104. package/src/toolbar/DropdownToolbar.ts +0 -220
  105. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  106. package/src/toolbar/EdgeToolbar.ts +0 -543
  107. package/src/toolbar/IconProvider.ts +0 -861
  108. package/src/toolbar/constants.ts +0 -1
  109. package/src/toolbar/lib.ts +0 -6
  110. package/src/toolbar/localization.ts +0 -136
  111. package/src/toolbar/types.ts +0 -13
  112. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  113. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  114. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  115. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  116. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  117. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  118. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  119. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  120. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  121. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  122. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  123. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  124. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  125. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  126. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  127. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  128. package/src/toolbar/widgets/keybindings.ts +0 -19
  129. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  130. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  131. package/src/toolbar/widgets/layout/types.ts +0 -74
  132. package/src/toolbar/widgets/lib.ts +0 -13
  133. package/src/tools/BaseTool.ts +0 -169
  134. package/src/tools/Eraser.test.ts +0 -103
  135. package/src/tools/Eraser.ts +0 -173
  136. package/src/tools/FindTool.test.ts +0 -67
  137. package/src/tools/FindTool.ts +0 -153
  138. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  139. package/src/tools/InputFilter/InputMapper.ts +0 -41
  140. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  141. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  142. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  143. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  144. package/src/tools/PanZoom.test.ts +0 -339
  145. package/src/tools/PanZoom.ts +0 -525
  146. package/src/tools/PasteHandler.ts +0 -94
  147. package/src/tools/Pen.test.ts +0 -260
  148. package/src/tools/Pen.ts +0 -284
  149. package/src/tools/PipetteTool.ts +0 -84
  150. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  151. package/src/tools/SelectionTool/Selection.ts +0 -647
  152. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  153. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  154. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  155. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  156. package/src/tools/SelectionTool/types.ts +0 -11
  157. package/src/tools/SoundUITool.ts +0 -221
  158. package/src/tools/TextTool.ts +0 -339
  159. package/src/tools/ToolController.ts +0 -224
  160. package/src/tools/ToolEnabledGroup.ts +0 -14
  161. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  162. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  163. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  164. package/src/tools/UndoRedoShortcut.ts +0 -24
  165. package/src/tools/keybindings.ts +0 -85
  166. package/src/tools/lib.ts +0 -22
  167. package/src/tools/localization.ts +0 -76
  168. package/src/types.ts +0 -151
  169. package/src/util/ReactiveValue.test.ts +0 -168
  170. package/src/util/ReactiveValue.ts +0 -241
  171. package/src/util/assertions.ts +0 -55
  172. package/src/util/fileToBase64.ts +0 -18
  173. package/src/util/guessKeyCodeFromKey.ts +0 -36
  174. package/src/util/listPrefixMatch.ts +0 -19
  175. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  176. package/src/util/untilNextAnimationFrame.ts +0 -9
  177. package/src/util/waitForAll.ts +0 -18
  178. package/src/util/waitForTimeout.ts +0 -9
  179. package/src/version.test.ts +0 -12
  180. package/src/version.ts +0 -3
  181. package/tools/allLocales.js +0 -4
  182. package/tools/copyREADME.ts +0 -62
@@ -1,543 +0,0 @@
1
- import Editor from '../Editor';
2
- import { ToolbarLocalization } from './localization';
3
- import BaseWidget, { ToolbarWidgetTag } from './widgets/BaseWidget';
4
- import { toolbarCSSPrefix } from './constants';
5
- import EdgeToolbarLayoutManager from './widgets/layout/EdgeToolbarLayoutManager';
6
- import { MutableReactiveValue, ReactiveValue } from '../util/ReactiveValue';
7
- import AbstractToolbar, { SpacerOptions } from './AbstractToolbar';
8
- import stopPropagationOfScrollingWheelEvents from '../util/stopPropagationOfScrollingWheelEvents';
9
-
10
-
11
- export const makeEdgeToolbar = (editor: Editor): AbstractToolbar => {
12
- return new EdgeToolbar(editor, editor.getRootElement(), editor.localization);
13
- };
14
-
15
-
16
- export default class EdgeToolbar extends AbstractToolbar {
17
- private toolbarContainer: HTMLElement;
18
-
19
- // Row that contains action buttons
20
- private toolbarActionRow: HTMLElement;
21
-
22
- // Row that contains tools
23
- private toolbarToolRow: HTMLElement;
24
-
25
- private toolRowResizeObserver: ResizeObserver;
26
-
27
- private menuContainer: HTMLElement;
28
- private sidebarContainer: HTMLElement;
29
- private sidebarContent: HTMLElement;
30
- private closeButton: HTMLElement;
31
-
32
- private layoutManager: EdgeToolbarLayoutManager;
33
-
34
- private sidebarVisible: MutableReactiveValue<boolean>;
35
- private sidebarY: MutableReactiveValue<number>;
36
- private sidebarTitle: MutableReactiveValue<string>;
37
-
38
- /** @internal */
39
- public constructor(
40
- editor: Editor, parent: HTMLElement,
41
- localizationTable: ToolbarLocalization,
42
- ) {
43
- super(editor, localizationTable);
44
-
45
- this.toolbarContainer = document.createElement('div');
46
- this.toolbarContainer.classList.add(`${toolbarCSSPrefix}root`);
47
- this.toolbarContainer.classList.add(`${toolbarCSSPrefix}element`);
48
- this.toolbarContainer.classList.add(`${toolbarCSSPrefix}edge-toolbar`);
49
- this.toolbarContainer.setAttribute('role', 'toolbar');
50
-
51
- this.toolbarActionRow = document.createElement('div');
52
- this.toolbarActionRow.classList.add('toolbar-element', 'toolbar-action-row');
53
- this.toolbarToolRow = document.createElement('div');
54
- this.toolbarToolRow.classList.add('toolbar-element', 'toolbar-tool-row');
55
-
56
- stopPropagationOfScrollingWheelEvents(this.toolbarToolRow);
57
-
58
- if ('ResizeObserver' in window) {
59
- this.toolRowResizeObserver = new ResizeObserver((_entries) => {
60
- this.onToolbarRowResize();
61
- });
62
- this.toolRowResizeObserver.observe(this.toolbarToolRow);
63
- } else {
64
- console.warn('ResizeObserver not supported. Toolbar will not resize.');
65
- }
66
-
67
- this.toolbarContainer.replaceChildren(this.toolbarActionRow, this.toolbarToolRow);
68
- parent.appendChild(this.toolbarContainer);
69
-
70
- this.sidebarVisible = ReactiveValue.fromInitialValue(false);
71
- this.sidebarY = ReactiveValue.fromInitialValue(0);
72
-
73
- // Create the container elements
74
- this.menuContainer = document.createElement('div');
75
- this.menuContainer.classList.add(`${toolbarCSSPrefix}edgemenu-container`);
76
-
77
- this.sidebarContainer = document.createElement('div');
78
- this.sidebarContainer.classList.add(
79
- `${toolbarCSSPrefix}edgemenu`,
80
- `${toolbarCSSPrefix}element`,
81
- );
82
- this.sidebarContainer.classList.add(`${toolbarCSSPrefix}tool-properties`);
83
-
84
- this.sidebarContent = document.createElement('div');
85
-
86
- // Setup resizing/dragging
87
- this.sidebarY.onUpdateAndNow(y => {
88
- const belowEdgeClassName = 'dropdown-below-edge';
89
- if (y > 0) {
90
- this.sidebarContainer.style.translate = `0 ${y}px`;
91
- this.sidebarContainer.style.paddingBottom = '';
92
- this.menuContainer.classList.add(belowEdgeClassName);
93
- } else {
94
- this.sidebarContainer.style.translate = '';
95
- this.sidebarContainer.style.paddingBottom = `${-y}px`;
96
- this.menuContainer.classList.remove(belowEdgeClassName);
97
- }
98
- });
99
-
100
- this.closeButton = document.createElement('button');
101
- this.closeButton.classList.add('drag-elem');
102
-
103
- // The close button has default focus -- forward its events to the main editor so that keyboard
104
- // shortcuts still work.
105
- this.editor.handleKeyEventsFrom(this.closeButton, event => {
106
- // Don't send
107
- return event.code !== 'Space' && event.code !== 'Enter' && event.code !== 'Tab';
108
- });
109
-
110
- // Close the sidebar when pressing escape
111
- this.sidebarContainer.addEventListener('keyup', event => {
112
- if (!event.defaultPrevented && event.code === 'Escape') {
113
- this.sidebarVisible.set(false);
114
- event.preventDefault();
115
- }
116
- });
117
-
118
- this.initDragListeners();
119
-
120
- // Initialize the layout manager
121
- const setSidebarContent = (...content: HTMLElement[]) => {
122
- this.sidebarContent.replaceChildren(...content);
123
- this.setupColorPickers();
124
- };
125
-
126
- this.sidebarTitle = MutableReactiveValue.fromInitialValue('');
127
-
128
- this.layoutManager = new EdgeToolbarLayoutManager(
129
- setSidebarContent,
130
- this.sidebarTitle,
131
- this.sidebarVisible,
132
- editor.announceForAccessibility.bind(editor),
133
- localizationTable,
134
- );
135
-
136
- this.sidebarTitle.onUpdateAndNow(title => {
137
- this.closeButton.setAttribute('aria-label', localizationTable.closeSidebar(title));
138
- });
139
-
140
- // Make things visible/keep hidden.
141
- this.listenForVisibilityChanges();
142
-
143
- this.sidebarContainer.replaceChildren(this.closeButton, this.sidebarContent);
144
- this.menuContainer.replaceChildren(this.sidebarContainer);
145
- parent.appendChild(this.menuContainer);
146
- }
147
-
148
- private listenForVisibilityChanges() {
149
- let animationTimeout: ReturnType<typeof setTimeout>|null = null;
150
- const animationDuration = 170;
151
-
152
- if (!this.sidebarVisible.get()) {
153
- this.menuContainer.style.display = 'none';
154
-
155
- // Set the initial opacity to 0 to allow the `transition` property
156
- // to animate it to 1.
157
- this.menuContainer.style.opacity = '0';
158
- }
159
-
160
- this.sidebarVisible.onUpdate(visible => {
161
- const animationProperties = `${animationDuration}ms ease`;
162
-
163
- if (visible) {
164
- this.sidebarY.set(this.snappedSidebarY());
165
-
166
- if (animationTimeout) {
167
- clearTimeout(animationTimeout);
168
- animationTimeout = null;
169
- }
170
-
171
- this.menuContainer.style.display = '';
172
- this.sidebarContainer.style.animation = `${animationProperties} ${toolbarCSSPrefix}-edgemenu-transition-in`;
173
- this.menuContainer.style.animation = `${animationProperties} ${toolbarCSSPrefix}-edgemenu-container-transition-in`;
174
- this.menuContainer.style.opacity = '1';
175
-
176
-
177
- // Focus the close button when first shown.
178
- this.closeButton.focus();
179
- } else {
180
- this.closeColorPickers();
181
-
182
- if (animationTimeout === null) {
183
- this.sidebarContainer.style.animation = ` ${animationProperties} ${toolbarCSSPrefix}-edgemenu-transition-out`;
184
- this.menuContainer.style.animation = `${animationProperties} ${toolbarCSSPrefix}-edgemenu-container-transition-out`;
185
-
186
- // Manually set the container's opacity to prevent flickering when closing
187
- // the toolbar.
188
- this.menuContainer.style.opacity = '0';
189
-
190
- // Hide overflow -- don't show the part of the edge toolbar that's outside of
191
- // the editor.
192
- //this.menuContainer.style.overflowY = 'hidden';
193
-
194
- this.editor.announceForAccessibility(
195
- this.localizationTable.dropdownHidden(this.sidebarTitle.get())
196
- );
197
-
198
- animationTimeout = setTimeout(() => {
199
- this.menuContainer.style.display = 'none';
200
- this.menuContainer.style.overflowY = '';
201
- animationTimeout = null;
202
- }, animationDuration);
203
- }
204
- }
205
- });
206
- }
207
-
208
- private onToolbarRowResize() {
209
- const setExtraPadding = () => {
210
- const visibleWidth = this.toolbarToolRow.clientWidth;
211
-
212
- // Determine whether extra spacing needs to be added so that one button is cut
213
- // in half. Ideally, when there is scroll, one button will be cut in half to show
214
- // that scrolling is possible.
215
- let currentWidth = 0;
216
- let extraPadding = 0;
217
- let numVisibleButtons = 0;
218
- for (const child of this.toolbarToolRow.children) {
219
- // Use the first child -- padding is applied around that child. Assumes
220
- // that the button's width is its height plus some padding.
221
- const buttonBaseSize = child.clientHeight;
222
- currentWidth += buttonBaseSize;
223
- numVisibleButtons ++;
224
-
225
- if (currentWidth > visibleWidth) {
226
- // We want extraPadding + (currentWidth - buttonWidth / 2) = visibleWidth.
227
- // Thus, extraPadding = visibleWidth - currentWidth + buttonWidth / 2;
228
- extraPadding = visibleWidth - currentWidth + buttonBaseSize / 2;
229
-
230
- // Ensure that the padding is positive
231
- if (extraPadding < 0) {
232
- extraPadding += buttonBaseSize;
233
- }
234
- break;
235
- }
236
- }
237
-
238
- const perButtonPadding = Math.round(extraPadding/numVisibleButtons * 10) / 10;
239
- this.toolbarToolRow.style.setProperty('--extra-left-right-padding', `${perButtonPadding}px`);
240
- };
241
-
242
- const actionRowBBox = this.toolbarActionRow.getBoundingClientRect();
243
- const toolbarRowBBox = this.toolbarToolRow.getBoundingClientRect();
244
- const inSameRow = actionRowBBox.y === toolbarRowBBox.y;
245
- const onDifferentRows = actionRowBBox.y + actionRowBBox.height <= toolbarRowBBox.y;
246
-
247
- if (onDifferentRows) {
248
- this.toolbarContainer.classList.remove('one-row');
249
- } else {
250
- this.toolbarContainer.classList.add('one-row');
251
- }
252
-
253
- if (this.toolbarToolRow.clientWidth < this.toolbarToolRow.scrollWidth) {
254
- this.toolbarToolRow.classList.add('has-scroll');
255
-
256
- // If both button areas are in the same row, don't change the padding --
257
- // it could lead to an endless loop of reseize events.
258
- if (!inSameRow) {
259
- setExtraPadding();
260
- }
261
- } else {
262
- this.toolbarToolRow.classList.remove('has-scroll', 'extra-padding');
263
- }
264
- }
265
-
266
- public override addSpacer(_options?: Partial<SpacerOptions> | undefined): void {
267
- //throw new Error('Method not implemented.');
268
- // Unused for this toolbar.
269
- }
270
-
271
- public override addUndoRedoButtons(): void {
272
- super.addUndoRedoButtons(false);
273
- }
274
-
275
- public override addDefaults(): void {
276
- this.addDefaultActionButtons();
277
- this.addDefaultToolWidgets();
278
- }
279
-
280
- private updateWidgetCSSClasses(widget: BaseWidget) {
281
- const tags = widget.getTags();
282
- widget.removeCSSClassFromContainer('label-inline');
283
- widget.removeCSSClassFromContainer('label-left');
284
- widget.removeCSSClassFromContainer('label-right');
285
-
286
- if (tags.includes(ToolbarWidgetTag.Save)) {
287
- widget.addCSSClassToContainer('label-inline');
288
- widget.addCSSClassToContainer('label-right');
289
- }
290
-
291
- if (tags.includes(ToolbarWidgetTag.Exit)) {
292
- widget.addCSSClassToContainer('label-inline');
293
- widget.addCSSClassToContainer('label-left');
294
- }
295
- }
296
-
297
- protected override addWidgetInternal(widget: BaseWidget) {
298
- this.updateWidgetCSSClasses(widget);
299
-
300
- widget.setLayoutManager(this.layoutManager);
301
- if (widget.mustBeInToplevelMenu()) {
302
- widget.addTo(this.toolbarActionRow);
303
- } else {
304
- widget.addTo(this.toolbarToolRow);
305
- }
306
- }
307
-
308
- protected override removeWidgetInternal(widget: BaseWidget): void {
309
- widget.remove();
310
- }
311
-
312
- protected override onRemove() {
313
- this.toolbarContainer.remove();
314
- this.menuContainer.remove();
315
- this.toolRowResizeObserver.disconnect();
316
- }
317
-
318
- private initDragListeners() {
319
- let lastX = 0;
320
- let lastY = 0;
321
- let startX = 0;
322
- let startY = 0;
323
- let pointerDown = false;
324
- let capturedPointerId: number|null = null;
325
-
326
- const dragElements = [ this.closeButton, this.sidebarContainer, this.sidebarContent ];
327
-
328
- this.manageListener(this.editor.handlePointerEventsExceptClicksFrom(this.menuContainer, (eventName, event) => {
329
- if (event.target === this.menuContainer) {
330
- if (eventName === 'pointerdown') {
331
- this.sidebarVisible.set(false);
332
- }
333
-
334
- if (eventName === 'pointerup') {
335
- this.editor.focus();
336
- }
337
-
338
- return true;
339
- }
340
-
341
- if (!this.sidebarVisible.get()) {
342
- return true;
343
- }
344
-
345
- // Don't send pointer events that don't directly target mainContainer
346
- // to the editor
347
- return false;
348
- }, (_eventName, event) => {
349
- return event.target === this.menuContainer;
350
- }));
351
-
352
- const isDraggableElement = (element: HTMLElement|null) => {
353
- if (!element) {
354
- return false;
355
- }
356
-
357
- if (dragElements.includes(element)) {
358
- return true;
359
- }
360
-
361
- // Some inputs handle dragging themselves. Don't also interpret such gestures
362
- // as dragging the dropdown.
363
- const undraggableElementTypes = [ 'INPUT', 'SELECT', 'IMG' ];
364
-
365
- let hasSuitableAncestors = false;
366
- let ancestor = element.parentElement;
367
- while (ancestor) {
368
- if (undraggableElementTypes.includes(ancestor.tagName)) {
369
- break;
370
- }
371
- if (ancestor === this.sidebarContainer) {
372
- hasSuitableAncestors = true;
373
- break;
374
- }
375
- ancestor = ancestor.parentElement;
376
- }
377
-
378
- return !undraggableElementTypes.includes(element.tagName) && hasSuitableAncestors;
379
- };
380
-
381
- const clickThreshold = 5;
382
-
383
- // Returns whether the current (or if no current, **the last**) gesture is roughly a click.
384
- // Because this can be called **after** a gesture has just ended, it should not require
385
- // the gesture to be in progress.
386
- const isRoughlyClick = () => {
387
- return Math.hypot(lastX - startX, lastY - startY) < clickThreshold;
388
- };
389
-
390
- let startedDragging = false;
391
-
392
- this.sidebarContainer.addEventListener('pointerdown', event => {
393
- if (event.defaultPrevented || !isDraggableElement(event.target as HTMLElement)) {
394
- return;
395
- }
396
-
397
- if (event.isPrimary) {
398
- startedDragging = false;
399
-
400
- lastX = event.clientX;
401
- lastY = event.clientY;
402
-
403
- startX = event.clientX;
404
- startY = event.clientY;
405
-
406
- capturedPointerId = null;
407
- pointerDown = true;
408
- }
409
- }, { passive: true });
410
-
411
- let gestureEndTimestamp = 0;
412
- const onGestureEnd = (_event: Event) => {
413
- // If the pointerup/pointercancel event was for a pointer not being tracked,
414
- if (!pointerDown) {
415
- return;
416
- }
417
-
418
- gestureEndTimestamp = performance.now();
419
-
420
- if (capturedPointerId !== null) {
421
- this.sidebarContainer.releasePointerCapture(capturedPointerId);
422
- capturedPointerId = null;
423
- }
424
-
425
- this.finalizeDrag();
426
- pointerDown = false;
427
- startedDragging = false;
428
- };
429
-
430
- this.sidebarContainer.onpointermove = event => {
431
- if (!event.isPrimary || !pointerDown) {
432
- return undefined;
433
- }
434
-
435
- // Mouse event and no buttons pressed? Cancel the event.
436
- // This can happen if the event was canceled by a focus change (e.g. by opening a
437
- // right-click menu).
438
- if (event.pointerType === 'mouse' && event.buttons === 0) {
439
- onGestureEnd(event);
440
- return undefined;
441
- }
442
-
443
- // Only capture after motion -- capturing early prevents click events in Chrome.
444
- if (capturedPointerId === null && !isRoughlyClick()) {
445
- this.sidebarContainer.setPointerCapture(event.pointerId);
446
- capturedPointerId = event.pointerId;
447
- }
448
-
449
- const x = event.clientX;
450
- const y = event.clientY;
451
- const dx = x - lastX;
452
- const dy = y - lastY;
453
-
454
- if (Math.abs(y - startY) > clickThreshold || startedDragging) {
455
- this.handleDrag(dx, dy);
456
-
457
- lastX = x;
458
- lastY = y;
459
-
460
- startedDragging = true;
461
- }
462
- };
463
-
464
- this.sidebarContainer.onpointerleave = event => {{
465
- // Capture the pointer if it exits the container while dragging.
466
- if (capturedPointerId === null && pointerDown && event.isPrimary) {
467
- this.sidebarContainer.setPointerCapture(event.pointerId);
468
- capturedPointerId = event.pointerId;
469
- }
470
- }};
471
-
472
- this.closeButton.onclick = () => {
473
- const wasJustDragging = performance.now() - gestureEndTimestamp < 100;
474
- const roughlyClick = isRoughlyClick();
475
-
476
- // Ignore the click event if it was caused by dragging the button.
477
- if (wasJustDragging && roughlyClick || !wasJustDragging) {
478
- this.sidebarVisible.set(false);
479
- }
480
- };
481
-
482
- this.sidebarContainer.onpointerup = onGestureEnd;
483
- this.sidebarContainer.onpointercancel = onGestureEnd;
484
- }
485
-
486
- /**
487
- * Updates the position of this menu **during** a drag. After a drag ends,
488
- * {@link finalizeDrag} should be called.
489
- */
490
- private handleDrag(_deltaX: number, deltaY: number) {
491
- this.sidebarContainer.style.transition = 'none';
492
- this.sidebarY.set(this.sidebarY.get() + deltaY);
493
- }
494
-
495
- /** Returns `this.sidebarY` rounded to a valid value. */
496
- private snappedSidebarY(sidebarY?: number) {
497
- const y = sidebarY ?? this.sidebarY.get();
498
-
499
- const snapYs = [ -100, 0 ];
500
-
501
- // Allow some amount of scrolling if the sidebar is too tall to fit entirely
502
- // in the window.
503
- if (this.sidebarContainer.clientHeight > window.innerHeight) {
504
- snapYs.push(100);
505
- }
506
-
507
- let closestSnap = snapYs[0];
508
- for (const snapY of snapYs) {
509
- if (Math.abs(snapY - y) < Math.abs(closestSnap - y)) {
510
- closestSnap = snapY;
511
- }
512
- }
513
-
514
- return closestSnap;
515
- }
516
-
517
- /**
518
- * Moves the menu to a valid location or closes it, depending on
519
- * the position set by the drag.
520
- */
521
- private finalizeDrag() {
522
- this.sidebarContainer.style.transition = '';
523
- if (this.sidebarY.get() > this.sidebarContainer.clientHeight / 2) {
524
- this.sidebarVisible.set(false);
525
- } else {
526
- // Snap to the closest valid Y.
527
- this.sidebarY.set(this.snappedSidebarY());
528
- }
529
- }
530
-
531
- protected override serializeInternal() {
532
- return {
533
- menuSizeY: this.snappedSidebarY(),
534
- };
535
- }
536
-
537
- protected override deserializeInternal(json: any) {
538
- if (typeof json === 'object' && typeof json['menuSizeY'] === 'number') {
539
- // Load the y-position of the sidebar -- call snappedSidebarY to ensure validity.
540
- this.sidebarY.set(this.snappedSidebarY(json['menuSizeY']));
541
- }
542
- }
543
- }