js-draw 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. package/dist/Editor.css +285 -1
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.js +5 -0
  5. package/dist/cjs/components/util/StrokeSmoother.js +11 -4
  6. package/dist/cjs/rendering/caching/CacheRecordManager.js +1 -1
  7. package/dist/cjs/testing/sendHtmlSwipe.d.ts +4 -0
  8. package/dist/cjs/testing/sendHtmlSwipe.js +14 -0
  9. package/dist/cjs/toolbar/EdgeToolbar.d.ts +1 -0
  10. package/dist/cjs/toolbar/EdgeToolbar.js +30 -110
  11. package/dist/cjs/toolbar/IconProvider.d.ts +1 -0
  12. package/dist/cjs/toolbar/IconProvider.js +27 -0
  13. package/dist/cjs/toolbar/localization.d.ts +28 -1
  14. package/dist/cjs/toolbar/localization.js +30 -1
  15. package/dist/cjs/toolbar/utils/HelpDisplay.d.ts +37 -0
  16. package/dist/cjs/toolbar/utils/HelpDisplay.js +442 -0
  17. package/dist/cjs/toolbar/utils/HelpDisplay.test.d.ts +1 -0
  18. package/dist/cjs/toolbar/utils/localization.d.ts +9 -0
  19. package/dist/cjs/toolbar/utils/localization.js +11 -0
  20. package/dist/cjs/toolbar/utils/makeDraggable.d.ts +16 -0
  21. package/dist/cjs/toolbar/utils/makeDraggable.js +130 -0
  22. package/dist/cjs/toolbar/widgets/ActionButtonWidget.d.ts +7 -0
  23. package/dist/cjs/toolbar/widgets/ActionButtonWidget.js +14 -2
  24. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +8 -1
  25. package/dist/cjs/toolbar/widgets/BaseWidget.js +25 -3
  26. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.d.ts +3 -1
  27. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +19 -4
  28. package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +3 -1
  29. package/dist/cjs/toolbar/widgets/HandToolWidget.js +19 -7
  30. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -0
  31. package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +4 -2
  32. package/dist/cjs/toolbar/widgets/PenToolWidget.js +27 -8
  33. package/dist/cjs/toolbar/widgets/SelectionToolWidget.d.ts +3 -1
  34. package/dist/cjs/toolbar/widgets/SelectionToolWidget.js +19 -5
  35. package/dist/cjs/toolbar/widgets/components/makeColorInput.d.ts +2 -0
  36. package/dist/cjs/toolbar/widgets/components/makeColorInput.js +17 -7
  37. package/dist/cjs/toolbar/widgets/components/makeGridSelector.d.ts +6 -0
  38. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +3 -0
  39. package/dist/cjs/tools/FindTool.js +18 -5
  40. package/dist/cjs/util/addLongPressOrHoverCssClasses.d.ts +3 -1
  41. package/dist/cjs/util/addLongPressOrHoverCssClasses.js +2 -1
  42. package/dist/cjs/util/cloneElementWithStyles.d.ts +6 -0
  43. package/dist/cjs/util/cloneElementWithStyles.js +32 -0
  44. package/dist/cjs/version.js +1 -1
  45. package/dist/mjs/Editor.mjs +5 -0
  46. package/dist/mjs/components/util/StrokeSmoother.mjs +11 -4
  47. package/dist/mjs/rendering/caching/CacheRecordManager.mjs +1 -1
  48. package/dist/mjs/testing/sendHtmlSwipe.d.ts +4 -0
  49. package/dist/mjs/testing/sendHtmlSwipe.mjs +12 -0
  50. package/dist/mjs/toolbar/EdgeToolbar.d.ts +1 -0
  51. package/dist/mjs/toolbar/EdgeToolbar.mjs +30 -110
  52. package/dist/mjs/toolbar/IconProvider.d.ts +1 -0
  53. package/dist/mjs/toolbar/IconProvider.mjs +27 -0
  54. package/dist/mjs/toolbar/localization.d.ts +28 -1
  55. package/dist/mjs/toolbar/localization.mjs +30 -1
  56. package/dist/mjs/toolbar/utils/HelpDisplay.d.ts +37 -0
  57. package/dist/mjs/toolbar/utils/HelpDisplay.mjs +437 -0
  58. package/dist/mjs/toolbar/utils/HelpDisplay.test.d.ts +1 -0
  59. package/dist/mjs/toolbar/utils/localization.d.ts +9 -0
  60. package/dist/mjs/toolbar/utils/localization.mjs +8 -0
  61. package/dist/mjs/toolbar/utils/makeDraggable.d.ts +16 -0
  62. package/dist/mjs/toolbar/utils/makeDraggable.mjs +128 -0
  63. package/dist/mjs/toolbar/widgets/ActionButtonWidget.d.ts +7 -0
  64. package/dist/mjs/toolbar/widgets/ActionButtonWidget.mjs +14 -2
  65. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +8 -1
  66. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +25 -3
  67. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.d.ts +3 -1
  68. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +19 -4
  69. package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +3 -1
  70. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +19 -7
  71. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -0
  72. package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +4 -2
  73. package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +27 -8
  74. package/dist/mjs/toolbar/widgets/SelectionToolWidget.d.ts +3 -1
  75. package/dist/mjs/toolbar/widgets/SelectionToolWidget.mjs +19 -5
  76. package/dist/mjs/toolbar/widgets/components/makeColorInput.d.ts +2 -0
  77. package/dist/mjs/toolbar/widgets/components/makeColorInput.mjs +17 -7
  78. package/dist/mjs/toolbar/widgets/components/makeGridSelector.d.ts +6 -0
  79. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +3 -0
  80. package/dist/mjs/tools/FindTool.mjs +18 -5
  81. package/dist/mjs/util/addLongPressOrHoverCssClasses.d.ts +3 -1
  82. package/dist/mjs/util/addLongPressOrHoverCssClasses.mjs +2 -1
  83. package/dist/mjs/util/cloneElementWithStyles.d.ts +6 -0
  84. package/dist/mjs/util/cloneElementWithStyles.mjs +30 -0
  85. package/dist/mjs/version.mjs +1 -1
  86. package/package.json +2 -2
  87. package/src/toolbar/EdgeToolbar.scss +23 -2
  88. package/src/toolbar/toolbar.scss +2 -0
  89. package/src/toolbar/utils/HelpDisplay.scss +315 -0
  90. package/src/toolbar/widgets/components/makeColorInput.scss +7 -0
@@ -0,0 +1,128 @@
1
+ import { Vec2 } from '@js-draw/math';
2
+ const makeDraggable = (dragElement, options) => {
3
+ const dragElements = [
4
+ ...options.draggableChildElements,
5
+ dragElement,
6
+ ];
7
+ let lastX = 0;
8
+ let lastY = 0;
9
+ let startX = 0;
10
+ let startY = 0;
11
+ let pointerDown = false;
12
+ let capturedPointerId = null;
13
+ const isDraggableElement = (element) => {
14
+ if (!element) {
15
+ return false;
16
+ }
17
+ if (dragElements.includes(element)) {
18
+ return true;
19
+ }
20
+ // Some inputs handle dragging themselves. Don't also interpret such gestures
21
+ // as dragging the dropdown.
22
+ const undraggableElementTypes = ['INPUT', 'SELECT', 'IMG'];
23
+ let hasSuitableAncestors = false;
24
+ let ancestor = element.parentElement;
25
+ while (ancestor) {
26
+ if (undraggableElementTypes.includes(ancestor.tagName)) {
27
+ break;
28
+ }
29
+ if (dragElements.includes(ancestor)) {
30
+ hasSuitableAncestors = true;
31
+ break;
32
+ }
33
+ ancestor = ancestor.parentElement;
34
+ }
35
+ return !undraggableElementTypes.includes(element.tagName) && hasSuitableAncestors;
36
+ };
37
+ const removeEventListenerCallbacks = [];
38
+ const addEventListener = (listenerType, listener, options) => {
39
+ dragElement.addEventListener(listenerType, listener, options);
40
+ removeEventListenerCallbacks.push(() => {
41
+ dragElement.removeEventListener(listenerType, listener);
42
+ });
43
+ };
44
+ const clickThreshold = 5;
45
+ // Returns whether the current (or if no current, **the last**) gesture is roughly a click.
46
+ // Because this can be called **after** a gesture has just ended, it should not require
47
+ // the gesture to be in progress.
48
+ const isRoughlyClick = () => {
49
+ return Math.hypot(lastX - startX, lastY - startY) < clickThreshold;
50
+ };
51
+ let startedDragging = false;
52
+ addEventListener('pointerdown', event => {
53
+ if (event.defaultPrevented || !isDraggableElement(event.target)) {
54
+ return;
55
+ }
56
+ if (event.isPrimary) {
57
+ startedDragging = false;
58
+ lastX = event.clientX;
59
+ lastY = event.clientY;
60
+ startX = event.clientX;
61
+ startY = event.clientY;
62
+ capturedPointerId = null;
63
+ pointerDown = true;
64
+ }
65
+ }, { passive: true });
66
+ const onGestureEnd = (_event) => {
67
+ // If the pointerup/pointercancel event was for a pointer not being tracked,
68
+ if (!pointerDown) {
69
+ return;
70
+ }
71
+ if (capturedPointerId !== null) {
72
+ dragElement.releasePointerCapture(capturedPointerId);
73
+ capturedPointerId = null;
74
+ }
75
+ options.onDragEnd({
76
+ roughlyClick: isRoughlyClick(),
77
+ endTimestamp: performance.now(),
78
+ displacement: Vec2.of(lastX - startX, lastY - startY),
79
+ });
80
+ pointerDown = false;
81
+ startedDragging = false;
82
+ };
83
+ addEventListener('pointermove', event => {
84
+ if (!event.isPrimary || !pointerDown) {
85
+ return undefined;
86
+ }
87
+ // Mouse event and no buttons pressed? Cancel the event.
88
+ // This can happen if the event was canceled by a focus change (e.g. by opening a
89
+ // right-click menu).
90
+ if (event.pointerType === 'mouse' && event.buttons === 0) {
91
+ onGestureEnd(event);
92
+ return undefined;
93
+ }
94
+ // Only capture after motion -- capturing early prevents click events in Chrome.
95
+ if (capturedPointerId === null && !isRoughlyClick()) {
96
+ dragElement.setPointerCapture(event.pointerId);
97
+ capturedPointerId = event.pointerId;
98
+ }
99
+ const x = event.clientX;
100
+ const y = event.clientY;
101
+ const dx = x - lastX;
102
+ const dy = y - lastY;
103
+ const isClick = Math.abs(x - startX) <= clickThreshold && Math.abs(y - startY) <= clickThreshold;
104
+ if (!isClick || startedDragging) {
105
+ options.onDrag(dx, dy, Vec2.of(x - startX, y - startY));
106
+ lastX = x;
107
+ lastY = y;
108
+ startedDragging = true;
109
+ }
110
+ });
111
+ addEventListener('pointerleave', event => {
112
+ // Capture the pointer if it exits the container while dragging.
113
+ if (capturedPointerId === null && pointerDown && event.isPrimary) {
114
+ dragElement.setPointerCapture(event.pointerId);
115
+ capturedPointerId = event.pointerId;
116
+ }
117
+ });
118
+ addEventListener('pointerup', onGestureEnd);
119
+ addEventListener('pointercancel', onGestureEnd);
120
+ return {
121
+ removeListeners: () => {
122
+ for (const removeListenerCallback of removeEventListenerCallbacks) {
123
+ removeListenerCallback();
124
+ }
125
+ },
126
+ };
127
+ };
128
+ export default makeDraggable;
@@ -8,6 +8,13 @@ export default class ActionButtonWidget extends BaseWidget {
8
8
  protected clickAction: () => void;
9
9
  protected mustBeToplevel: boolean;
10
10
  constructor(editor: Editor, id: string, makeIcon: () => Element | null, title: string, clickAction: () => void, localizationTable?: ToolbarLocalization, mustBeToplevel?: boolean, autoDisableInReadOnlyEditors?: boolean);
11
+ /**
12
+ * Sets the text shown in a help overlay for this button.
13
+ *
14
+ * See {@link getHelpText}.
15
+ */
16
+ setHelpText(helpText: string): void;
17
+ protected getHelpText(): string | undefined;
11
18
  protected shouldAutoDisableInReadOnlyEditor(): boolean;
12
19
  protected handleClick(): void;
13
20
  protected getTitle(): string;
@@ -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 _ActionButtonWidget_autoDisableInReadOnlyEditors;
12
+ var _ActionButtonWidget_autoDisableInReadOnlyEditors, _ActionButtonWidget_helpText;
13
13
  import BaseWidget from './BaseWidget.mjs';
14
14
  class ActionButtonWidget extends BaseWidget {
15
15
  constructor(editor, id, makeIcon, title, clickAction, localizationTable, mustBeToplevel = false, autoDisableInReadOnlyEditors = true) {
@@ -19,8 +19,20 @@ class ActionButtonWidget extends BaseWidget {
19
19
  this.clickAction = clickAction;
20
20
  this.mustBeToplevel = mustBeToplevel;
21
21
  _ActionButtonWidget_autoDisableInReadOnlyEditors.set(this, void 0);
22
+ _ActionButtonWidget_helpText.set(this, undefined);
22
23
  __classPrivateFieldSet(this, _ActionButtonWidget_autoDisableInReadOnlyEditors, autoDisableInReadOnlyEditors, "f");
23
24
  }
25
+ /**
26
+ * Sets the text shown in a help overlay for this button.
27
+ *
28
+ * See {@link getHelpText}.
29
+ */
30
+ setHelpText(helpText) {
31
+ __classPrivateFieldSet(this, _ActionButtonWidget_helpText, helpText, "f");
32
+ }
33
+ getHelpText() {
34
+ return __classPrivateFieldGet(this, _ActionButtonWidget_helpText, "f");
35
+ }
24
36
  shouldAutoDisableInReadOnlyEditor() {
25
37
  return __classPrivateFieldGet(this, _ActionButtonWidget_autoDisableInReadOnlyEditors, "f");
26
38
  }
@@ -40,5 +52,5 @@ class ActionButtonWidget extends BaseWidget {
40
52
  return this.mustBeToplevel;
41
53
  }
42
54
  }
43
- _ActionButtonWidget_autoDisableInReadOnlyEditors = new WeakMap();
55
+ _ActionButtonWidget_autoDisableInReadOnlyEditors = new WeakMap(), _ActionButtonWidget_helpText = new WeakMap();
44
56
  export default ActionButtonWidget;
@@ -2,6 +2,7 @@ import Editor from '../../Editor';
2
2
  import { KeyPressEvent } from '../../inputEvents';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import { WidgetContentLayoutManager } from './layout/types';
5
+ import HelpDisplay from '../utils/HelpDisplay';
5
6
  export type SavedToolbuttonState = Record<string, any>;
6
7
  /**
7
8
  * A set of labels that allow toolbar themes to treat buttons differently.
@@ -66,7 +67,13 @@ export default abstract class BaseWidget {
66
67
  getUniqueIdIn(container: Record<string, BaseWidget>): string;
67
68
  protected abstract getTitle(): string;
68
69
  protected abstract createIcon(): Element | null;
69
- protected fillDropdown(dropdown: HTMLElement): boolean;
70
+ protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
71
+ /**
72
+ * Should return a 1-2 sentence description of the widget.
73
+ *
74
+ * At present, this is only used if this widget has an associated dropdown.
75
+ */
76
+ protected getHelpText(): undefined | string;
70
77
  /** @deprecated Renamed to `setUpButtonEventListeners`. */
71
78
  protected setupActionBtnClickListener(button: HTMLElement): void;
72
79
  protected setUpButtonEventListeners(button: HTMLElement): void;
@@ -15,6 +15,7 @@ import { keyPressEventFromHTMLEvent, keyUpEventFromHTMLEvent } from '../../inp
15
15
  import { toolbarCSSPrefix } from '../constants.mjs';
16
16
  import DropdownLayoutManager from './layout/DropdownLayoutManager.mjs';
17
17
  import addLongPressOrHoverCssClasses from '../../util/addLongPressOrHoverCssClasses.mjs';
18
+ import HelpDisplay from '../utils/HelpDisplay.mjs';
18
19
  /**
19
20
  * A set of labels that allow toolbar themes to treat buttons differently.
20
21
  */
@@ -127,17 +128,30 @@ class BaseWidget {
127
128
  }
128
129
  // Add content to the widget's associated dropdown menu.
129
130
  // Returns true if such a menu should be created, false otherwise.
130
- fillDropdown(dropdown) {
131
+ fillDropdown(dropdown, helpDisplay) {
131
132
  if (Object.keys(this.subWidgets).length === 0) {
132
133
  return false;
133
134
  }
134
135
  for (const widgetId in this.subWidgets) {
135
136
  const widget = this.subWidgets[widgetId];
136
- widget.addTo(dropdown);
137
+ const widgetElement = widget.addTo(dropdown);
137
138
  widget.setIsToplevel(false);
139
+ // Add help information
140
+ const helpText = widget.getHelpText();
141
+ if (helpText) {
142
+ helpDisplay?.registerTextHelpForElement(widgetElement, helpText);
143
+ }
138
144
  }
139
145
  return true;
140
146
  }
147
+ /**
148
+ * Should return a 1-2 sentence description of the widget.
149
+ *
150
+ * At present, this is only used if this widget has an associated dropdown.
151
+ */
152
+ getHelpText() {
153
+ return undefined;
154
+ }
141
155
  /** @deprecated Renamed to `setUpButtonEventListeners`. */
142
156
  setupActionBtnClickListener(button) {
143
157
  return this.setUpButtonEventListeners(button);
@@ -229,10 +243,15 @@ class BaseWidget {
229
243
  this.container.replaceChildren();
230
244
  this.button.replaceChildren(this.icon, this.label);
231
245
  this.container.appendChild(this.button);
246
+ const helpDisplay = new HelpDisplay(content => this.editor.createHTMLOverlay(content), this.editor);
247
+ const helpText = this.getHelpText();
248
+ if (helpText) {
249
+ helpDisplay.registerTextHelpForElement(this.dropdownContent, [this.getTitle(), helpText].join('\n\n'));
250
+ }
232
251
  // Clear the dropdownContainer in case this element is being moved to another
233
252
  // parent.
234
253
  this.dropdownContent.replaceChildren();
235
- __classPrivateFieldSet(this, _BaseWidget_hasDropdown, this.fillDropdown(this.dropdownContent), "f");
254
+ __classPrivateFieldSet(this, _BaseWidget_hasDropdown, this.fillDropdown(this.dropdownContent, helpDisplay), "f");
236
255
  if (__classPrivateFieldGet(this, _BaseWidget_hasDropdown, "f")) {
237
256
  this.button.classList.add('has-dropdown');
238
257
  // We're re-creating the dropdown.
@@ -258,6 +277,9 @@ class BaseWidget {
258
277
  this.focus();
259
278
  }
260
279
  });
280
+ if (helpDisplay.hasHelpText()) {
281
+ this.dropdown.appendChild(helpDisplay.createToggleButton());
282
+ }
261
283
  this.dropdown.appendChild(this.dropdownContent);
262
284
  }
263
285
  this.setDropdownVisible(false);
@@ -1,6 +1,7 @@
1
1
  import Editor from '../../Editor';
2
2
  import { ToolbarLocalization } from '../localization';
3
3
  import BaseWidget from './BaseWidget';
4
+ import HelpDisplay from '../utils/HelpDisplay';
4
5
  export default class DocumentPropertiesWidget extends BaseWidget {
5
6
  private updateDropdownContent;
6
7
  constructor(editor: Editor, localizationTable?: ToolbarLocalization);
@@ -18,6 +19,7 @@ export default class DocumentPropertiesWidget extends BaseWidget {
18
19
  /** Returns the type of the topmost background component */
19
20
  private getBackgroundType;
20
21
  private updateImportExportRectSize;
22
+ protected getHelpText(): string;
21
23
  private static idCounter;
22
- protected fillDropdown(dropdown: HTMLElement): boolean;
24
+ protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
23
25
  }
@@ -90,7 +90,10 @@ class DocumentPropertiesWidget extends BaseWidget {
90
90
  this.editor.dispatch(this.editor.image.setImportExportRect(newRect));
91
91
  this.editor.queueRerender();
92
92
  }
93
- fillDropdown(dropdown) {
93
+ getHelpText() {
94
+ return this.localizationTable.pageDropdown__baseHelpText;
95
+ }
96
+ fillDropdown(dropdown, helpDisplay) {
94
97
  const container = document.createElement('div');
95
98
  container.classList.add(`${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}nonbutton-controls-main-list`, `${toolbarCSSPrefix}document-properties-widget`);
96
99
  // Background color input
@@ -98,7 +101,7 @@ class DocumentPropertiesWidget extends BaseWidget {
98
101
  const backgroundColorRow = document.createElement('div');
99
102
  const backgroundColorLabel = document.createElement('label');
100
103
  backgroundColorLabel.innerText = this.localizationTable.backgroundColor;
101
- const { input: colorInput, container: backgroundColorInputContainer, setValue: setBgColorInputValue } = makeColorInput(this.editor, color => {
104
+ const { input: colorInput, container: backgroundColorInputContainer, setValue: setBgColorInputValue, registerWithHelpTextDisplay: registerHelpForInputs, } = makeColorInput(this.editor, color => {
102
105
  if (!color.eq(this.getBackgroundColor())) {
103
106
  this.setBackgroundColor(color);
104
107
  }
@@ -106,9 +109,16 @@ class DocumentPropertiesWidget extends BaseWidget {
106
109
  colorInput.id = `${toolbarCSSPrefix}docPropertiesColorInput-${DocumentPropertiesWidget.idCounter++}`;
107
110
  backgroundColorLabel.htmlFor = colorInput.id;
108
111
  backgroundColorRow.replaceChildren(backgroundColorLabel, backgroundColorInputContainer);
109
- return { setBgColorInputValue, backgroundColorRow };
112
+ const registerWithHelp = (helpDisplay) => {
113
+ if (!helpDisplay) {
114
+ return;
115
+ }
116
+ helpDisplay?.registerTextHelpForElement(backgroundColorRow, this.localizationTable.pageDropdown__backgroundColorHelpText);
117
+ registerHelpForInputs(helpDisplay);
118
+ };
119
+ return { setBgColorInputValue, backgroundColorRow, registerWithHelp, };
110
120
  };
111
- const { backgroundColorRow, setBgColorInputValue } = makeBackgroundColorInput();
121
+ const { backgroundColorRow, setBgColorInputValue, registerWithHelp: registerBackgroundRowWithHelp, } = makeBackgroundColorInput();
112
122
  const makeCheckboxRow = (labelText, onChange) => {
113
123
  const rowContainer = document.createElement('div');
114
124
  const labelElement = document.createElement('label');
@@ -189,6 +199,11 @@ class DocumentPropertiesWidget extends BaseWidget {
189
199
  aboutButton.onclick = () => {
190
200
  this.editor.showAboutDialog();
191
201
  };
202
+ // Add help text
203
+ registerBackgroundRowWithHelp(helpDisplay);
204
+ helpDisplay?.registerTextHelpForElement(useGridRow, this.localizationTable.pageDropdown__gridCheckboxHelpText);
205
+ helpDisplay?.registerTextHelpForElement(auroresizeRow, this.localizationTable.pageDropdown__autoresizeCheckboxHelpText);
206
+ helpDisplay?.registerTextHelpForElement(aboutButton, this.localizationTable.pageDropdown__aboutButtonHelpText);
192
207
  this.updateDropdownContent = () => {
193
208
  setBgColorInputValue(this.getBackgroundColor());
194
209
  const autoresize = this.editor.image.getAutoresizeEnabled();
@@ -3,6 +3,7 @@ import PanZoom from '../../tools/PanZoom';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import BaseToolWidget from './BaseToolWidget';
5
5
  import { SavedToolbuttonState } from './BaseWidget';
6
+ import HelpDisplay from '../utils/HelpDisplay';
6
7
  export default class HandToolWidget extends BaseToolWidget {
7
8
  private allowTogglingBaseTool;
8
9
  protected overridePanZoomTool: PanZoom;
@@ -13,7 +14,8 @@ export default class HandToolWidget extends BaseToolWidget {
13
14
  protected getTitle(): string;
14
15
  protected createIcon(): Element;
15
16
  protected handleClick(): void;
16
- protected fillDropdown(dropdown: HTMLElement): boolean;
17
+ protected getHelpText(): string;
18
+ protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
17
19
  setSelected(selected: boolean): void;
18
20
  serializeState(): SavedToolbuttonState;
19
21
  deserializeFrom(state: SavedToolbuttonState): void;
@@ -6,7 +6,7 @@ import { toolbarCSSPrefix } from '../constants.mjs';
6
6
  import BaseToolWidget from './BaseToolWidget.mjs';
7
7
  import BaseWidget from './BaseWidget.mjs';
8
8
  import makeSeparator from './components/makeSeparator.mjs';
9
- const makeZoomControl = (localizationTable, editor) => {
9
+ const makeZoomControl = (localizationTable, editor, helpDisplay) => {
10
10
  const zoomLevelRow = document.createElement('div');
11
11
  const increaseButton = document.createElement('button');
12
12
  const decreaseButton = document.createElement('button');
@@ -55,15 +55,20 @@ const makeZoomControl = (localizationTable, editor) => {
55
55
  const addToHistory = false;
56
56
  editor.dispatch(Viewport.transformBy(editor.viewport.canvasToScreenTransform.inverse()), addToHistory);
57
57
  };
58
+ helpDisplay?.registerTextHelpForElement(increaseButton, localizationTable.handDropdown__zoomInHelpText);
59
+ helpDisplay?.registerTextHelpForElement(decreaseButton, localizationTable.handDropdown__zoomOutHelpText);
60
+ helpDisplay?.registerTextHelpForElement(resetViewButton, localizationTable.handDropdown__resetViewHelpText);
61
+ helpDisplay?.registerTextHelpForElement(zoomLevelDisplay, localizationTable.handDropdown__zoomDisplayHelpText);
58
62
  return zoomLevelRow;
59
63
  };
60
64
  class HandModeWidget extends BaseWidget {
61
- constructor(editor, tool, flag, makeIcon, title, localizationTable) {
65
+ constructor(editor, tool, flag, makeIcon, title, helpText, localizationTable) {
62
66
  super(editor, `pan-mode-${flag}`, localizationTable);
63
67
  this.tool = tool;
64
68
  this.flag = flag;
65
69
  this.makeIcon = makeIcon;
66
70
  this.title = title;
71
+ this.helpText = helpText;
67
72
  editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
68
73
  if (toolEvt.kind === EditorEventType.ToolUpdated && toolEvt.tool === tool) {
69
74
  const allEnabled = !!(tool.getMode() & PanZoomMode.SinglePointerGestures);
@@ -93,6 +98,9 @@ class HandModeWidget extends BaseWidget {
93
98
  fillDropdown(_dropdown) {
94
99
  return false;
95
100
  }
101
+ getHelpText() {
102
+ return this.helpText;
103
+ }
96
104
  }
97
105
  export default class HandToolWidget extends BaseToolWidget {
98
106
  constructor(editor,
@@ -117,8 +125,8 @@ export default class HandToolWidget extends BaseToolWidget {
117
125
  this.container.classList.add('dropdownShowable');
118
126
  }
119
127
  // Controls for the overriding hand tool.
120
- const touchPanningWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning, localizationTable);
121
- const rotationLockWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoomMode.RotationLocked, () => this.editor.icons.makeRotationLockIcon(), localizationTable.lockRotation, localizationTable);
128
+ const touchPanningWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning, localizationTable.handDropdown__touchPanningHelpText, localizationTable);
129
+ const rotationLockWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoomMode.RotationLocked, () => this.editor.icons.makeRotationLockIcon(), localizationTable.lockRotation, localizationTable.handDropdown__lockRotationHelpText, localizationTable);
122
130
  this.addSubWidget(touchPanningWidget);
123
131
  this.addSubWidget(rotationLockWidget);
124
132
  }
@@ -149,13 +157,17 @@ export default class HandToolWidget extends BaseToolWidget {
149
157
  this.setDropdownVisible(!this.isDropdownVisible());
150
158
  }
151
159
  }
152
- fillDropdown(dropdown) {
153
- super.fillDropdown(dropdown);
160
+ getHelpText() {
161
+ return this.localizationTable.handDropdown__baseHelpText;
162
+ }
163
+ fillDropdown(dropdown, helpDisplay) {
164
+ super.fillDropdown(dropdown, helpDisplay);
154
165
  // The container for all actions that come after the toolbar buttons.
155
166
  const nonbuttonActionContainer = document.createElement('div');
156
167
  nonbuttonActionContainer.classList.add(`${toolbarCSSPrefix}nonbutton-controls-main-list`);
157
168
  makeSeparator().addTo(nonbuttonActionContainer);
158
- nonbuttonActionContainer.appendChild(makeZoomControl(this.localizationTable, this.editor));
169
+ const zoomControl = makeZoomControl(this.localizationTable, this.editor, helpDisplay);
170
+ nonbuttonActionContainer.appendChild(zoomControl);
159
171
  dropdown.appendChild(nonbuttonActionContainer);
160
172
  return true;
161
173
  }
@@ -95,6 +95,7 @@ class InsertImageWidget extends BaseWidget {
95
95
  imageAltTextLabel.htmlFor = altTextInputId;
96
96
  imageAltTextLabel.innerText = this.localizationTable.inputAltText;
97
97
  this.imageAltTextInput.type = 'text';
98
+ this.imageAltTextInput.placeholder = this.localizationTable.describeTheImage;
98
99
  this.statusView.setAttribute('aria-live', 'polite');
99
100
  this.submitButton.innerText = this.localizationTable.submit;
100
101
  this.selectedFiles.onUpdateAndNow(async (files) => {
@@ -5,6 +5,7 @@ import { KeyPressEvent } from '../../inputEvents';
5
5
  import { ToolbarLocalization } from '../localization';
6
6
  import BaseToolWidget from './BaseToolWidget';
7
7
  import { SavedToolbuttonState } from './BaseWidget';
8
+ import HelpDisplay from '../utils/HelpDisplay';
8
9
  export interface PenTypeRecord {
9
10
  name: string;
10
11
  id: string;
@@ -24,11 +25,12 @@ export default class PenToolWidget extends BaseToolWidget {
24
25
  private createIconForRecord;
25
26
  protected createIcon(): Element;
26
27
  private createPenTypeSelector;
27
- protected createStrokeCorrectionOptions(): {
28
+ protected createStrokeCorrectionOptions(helpOverlay?: HelpDisplay): {
28
29
  update: () => void;
29
30
  addTo: (parent: HTMLElement) => void;
30
31
  };
31
- protected fillDropdown(dropdown: HTMLElement): boolean;
32
+ protected getHelpText(): string;
33
+ protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
32
34
  protected onKeyPress(event: KeyPressEvent): boolean;
33
35
  serializeState(): SavedToolbuttonState;
34
36
  deserializeFrom(state: SavedToolbuttonState): void;
@@ -116,7 +116,7 @@ class PenToolWidget extends BaseToolWidget {
116
116
  return this.createIconForRecord(this.getCurrentPenType());
117
117
  }
118
118
  // Creates a widget that allows selecting different pen types
119
- createPenTypeSelector() {
119
+ createPenTypeSelector(helpOverlay) {
120
120
  const allChoices = this.penTypes.map((penType, index) => {
121
121
  return {
122
122
  id: index,
@@ -132,6 +132,7 @@ class PenToolWidget extends BaseToolWidget {
132
132
  };
133
133
  penSelector.value.onUpdate(onSelectorUpdate);
134
134
  shapeSelector.value.onUpdate(onSelectorUpdate);
135
+ helpOverlay?.registerTextHelpForElements([penSelector.getRootElement(), shapeSelector.getRootElement()], this.localizationTable.penDropdown__penTypeHelpText);
135
136
  return {
136
137
  setValue: (penTypeIndex) => {
137
138
  penSelector.value.set(penTypeIndex);
@@ -147,7 +148,7 @@ class PenToolWidget extends BaseToolWidget {
147
148
  },
148
149
  };
149
150
  }
150
- createStrokeCorrectionOptions() {
151
+ createStrokeCorrectionOptions(helpOverlay) {
151
152
  const container = document.createElement('div');
152
153
  container.classList.add('action-button-row', `${toolbarCSSPrefix}-pen-tool-toggle-buttons`);
153
154
  const addToggleButton = (labelText, icon) => {
@@ -171,6 +172,9 @@ class PenToolWidget extends BaseToolWidget {
171
172
  setOnInputListener(listener) {
172
173
  onChangeListener = listener;
173
174
  },
175
+ addHelpText(text) {
176
+ helpOverlay?.registerTextHelpForElement(button, text);
177
+ },
174
178
  };
175
179
  button.onclick = () => {
176
180
  result.setChecked(!checked);
@@ -185,6 +189,9 @@ class PenToolWidget extends BaseToolWidget {
185
189
  autocorrectOption.setOnInputListener(enabled => {
186
190
  this.tool.setStrokeAutocorrectEnabled(enabled);
187
191
  });
192
+ // Help text
193
+ autocorrectOption.addHelpText(this.localizationTable.penDropdown__autocorrectHelpText);
194
+ stabilizationOption.addHelpText(this.localizationTable.penDropdown__stabilizationHelpText);
188
195
  return {
189
196
  update: () => {
190
197
  stabilizationOption.setChecked(!!this.tool.getInputMapper());
@@ -192,30 +199,42 @@ class PenToolWidget extends BaseToolWidget {
192
199
  },
193
200
  addTo: (parent) => {
194
201
  parent.appendChild(container);
195
- }
202
+ },
196
203
  };
197
204
  }
198
- fillDropdown(dropdown) {
205
+ getHelpText() {
206
+ return this.localizationTable.penDropdown__baseHelpText;
207
+ }
208
+ fillDropdown(dropdown, helpDisplay) {
199
209
  const container = document.createElement('div');
200
210
  container.classList.add(`${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}nonbutton-controls-main-list`);
201
211
  // Thickness: Value of the input is squared to allow for finer control/larger values.
202
212
  const { container: thicknessRow, setValue: setThickness } = makeThicknessSlider(this.editor, thickness => {
203
213
  this.tool.setThickness(thickness);
204
214
  });
205
- const penTypeSelect = this.createPenTypeSelector();
206
215
  const colorRow = document.createElement('div');
207
216
  const colorLabel = document.createElement('label');
208
- const { input: colorInput, container: colorInputContainer, setValue: setColorInputValue } = makeColorInput(this.editor, color => {
217
+ const colorInputControl = makeColorInput(this.editor, color => {
209
218
  this.tool.setColor(color);
210
219
  });
220
+ const { input: colorInput, container: colorInputContainer } = colorInputControl;
211
221
  colorInput.id = `${toolbarCSSPrefix}colorInput${PenToolWidget.idCounter++}`;
212
222
  colorLabel.innerText = this.localizationTable.colorLabel;
213
223
  colorLabel.setAttribute('for', colorInput.id);
214
224
  colorRow.appendChild(colorLabel);
215
225
  colorRow.appendChild(colorInputContainer);
216
- const toggleButtonRow = this.createStrokeCorrectionOptions();
226
+ // Autocorrect and stabilization options
227
+ const toggleButtonRow = this.createStrokeCorrectionOptions(helpDisplay);
228
+ const penTypeSelect = this.createPenTypeSelector(helpDisplay);
229
+ // Add help text for color and thickness last, as these are likely to be
230
+ // features users are least interested in.
231
+ helpDisplay?.registerTextHelpForElement(colorRow, this.localizationTable.penDropdown__colorHelpText);
232
+ if (helpDisplay) {
233
+ colorInputControl.registerWithHelpTextDisplay(helpDisplay);
234
+ }
235
+ helpDisplay?.registerTextHelpForElement(thicknessRow, this.localizationTable.penDropdown__thicknessHelpText);
217
236
  this.updateInputs = () => {
218
- setColorInputValue(this.tool.getColor());
237
+ colorInputControl.setValue(this.tool.getColor());
219
238
  setThickness(this.tool.getThickness());
220
239
  penTypeSelect.updateIcons();
221
240
  // Update the selected stroke factory.
@@ -3,6 +3,7 @@ import SelectionTool from '../../tools/SelectionTool/SelectionTool';
3
3
  import { KeyPressEvent } from '../../inputEvents';
4
4
  import { ToolbarLocalization } from '../localization';
5
5
  import BaseToolWidget from './BaseToolWidget';
6
+ import HelpDisplay from '../utils/HelpDisplay';
6
7
  export default class SelectionToolWidget extends BaseToolWidget {
7
8
  private tool;
8
9
  private updateFormatMenu;
@@ -11,5 +12,6 @@ export default class SelectionToolWidget extends BaseToolWidget {
11
12
  protected onKeyPress(event: KeyPressEvent): boolean;
12
13
  protected getTitle(): string;
13
14
  protected createIcon(): Element;
14
- protected fillDropdown(dropdown: HTMLElement): boolean;
15
+ protected getHelpText(): string;
16
+ protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
15
17
  }