js-draw 1.14.0 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. package/dist/Editor.css +288 -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/rendering/renderers/CanvasRenderer.js +1 -1
  8. package/dist/cjs/testing/sendHtmlSwipe.d.ts +4 -0
  9. package/dist/cjs/testing/sendHtmlSwipe.js +14 -0
  10. package/dist/cjs/toolbar/EdgeToolbar.d.ts +1 -0
  11. package/dist/cjs/toolbar/EdgeToolbar.js +30 -110
  12. package/dist/cjs/toolbar/IconProvider.d.ts +1 -0
  13. package/dist/cjs/toolbar/IconProvider.js +27 -0
  14. package/dist/cjs/toolbar/localization.d.ts +28 -1
  15. package/dist/cjs/toolbar/localization.js +30 -1
  16. package/dist/cjs/toolbar/utils/HelpDisplay.d.ts +37 -0
  17. package/dist/cjs/toolbar/utils/HelpDisplay.js +442 -0
  18. package/dist/cjs/toolbar/utils/HelpDisplay.test.d.ts +1 -0
  19. package/dist/cjs/toolbar/utils/localization.d.ts +9 -0
  20. package/dist/cjs/toolbar/utils/localization.js +11 -0
  21. package/dist/cjs/toolbar/utils/makeDraggable.d.ts +16 -0
  22. package/dist/cjs/toolbar/utils/makeDraggable.js +130 -0
  23. package/dist/cjs/toolbar/widgets/ActionButtonWidget.d.ts +7 -0
  24. package/dist/cjs/toolbar/widgets/ActionButtonWidget.js +14 -2
  25. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +8 -1
  26. package/dist/cjs/toolbar/widgets/BaseWidget.js +25 -3
  27. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.d.ts +3 -1
  28. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +19 -4
  29. package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +3 -1
  30. package/dist/cjs/toolbar/widgets/HandToolWidget.js +19 -7
  31. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -0
  32. package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +4 -2
  33. package/dist/cjs/toolbar/widgets/PenToolWidget.js +27 -8
  34. package/dist/cjs/toolbar/widgets/SelectionToolWidget.d.ts +3 -1
  35. package/dist/cjs/toolbar/widgets/SelectionToolWidget.js +19 -5
  36. package/dist/cjs/toolbar/widgets/components/makeColorInput.d.ts +2 -0
  37. package/dist/cjs/toolbar/widgets/components/makeColorInput.js +17 -7
  38. package/dist/cjs/toolbar/widgets/components/makeGridSelector.d.ts +6 -0
  39. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +3 -0
  40. package/dist/cjs/tools/FindTool.js +18 -5
  41. package/dist/cjs/tools/PanZoom.d.ts +8 -2
  42. package/dist/cjs/tools/PanZoom.js +29 -10
  43. package/dist/cjs/tools/SelectionTool/Selection.js +16 -2
  44. package/dist/cjs/util/addLongPressOrHoverCssClasses.d.ts +3 -1
  45. package/dist/cjs/util/addLongPressOrHoverCssClasses.js +2 -1
  46. package/dist/cjs/util/cloneElementWithStyles.d.ts +6 -0
  47. package/dist/cjs/util/cloneElementWithStyles.js +32 -0
  48. package/dist/cjs/version.js +1 -1
  49. package/dist/mjs/Editor.mjs +5 -0
  50. package/dist/mjs/components/util/StrokeSmoother.mjs +11 -4
  51. package/dist/mjs/rendering/caching/CacheRecordManager.mjs +1 -1
  52. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +1 -1
  53. package/dist/mjs/testing/sendHtmlSwipe.d.ts +4 -0
  54. package/dist/mjs/testing/sendHtmlSwipe.mjs +12 -0
  55. package/dist/mjs/toolbar/EdgeToolbar.d.ts +1 -0
  56. package/dist/mjs/toolbar/EdgeToolbar.mjs +30 -110
  57. package/dist/mjs/toolbar/IconProvider.d.ts +1 -0
  58. package/dist/mjs/toolbar/IconProvider.mjs +27 -0
  59. package/dist/mjs/toolbar/localization.d.ts +28 -1
  60. package/dist/mjs/toolbar/localization.mjs +30 -1
  61. package/dist/mjs/toolbar/utils/HelpDisplay.d.ts +37 -0
  62. package/dist/mjs/toolbar/utils/HelpDisplay.mjs +437 -0
  63. package/dist/mjs/toolbar/utils/HelpDisplay.test.d.ts +1 -0
  64. package/dist/mjs/toolbar/utils/localization.d.ts +9 -0
  65. package/dist/mjs/toolbar/utils/localization.mjs +8 -0
  66. package/dist/mjs/toolbar/utils/makeDraggable.d.ts +16 -0
  67. package/dist/mjs/toolbar/utils/makeDraggable.mjs +128 -0
  68. package/dist/mjs/toolbar/widgets/ActionButtonWidget.d.ts +7 -0
  69. package/dist/mjs/toolbar/widgets/ActionButtonWidget.mjs +14 -2
  70. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +8 -1
  71. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +25 -3
  72. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.d.ts +3 -1
  73. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +19 -4
  74. package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +3 -1
  75. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +19 -7
  76. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -0
  77. package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +4 -2
  78. package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +27 -8
  79. package/dist/mjs/toolbar/widgets/SelectionToolWidget.d.ts +3 -1
  80. package/dist/mjs/toolbar/widgets/SelectionToolWidget.mjs +19 -5
  81. package/dist/mjs/toolbar/widgets/components/makeColorInput.d.ts +2 -0
  82. package/dist/mjs/toolbar/widgets/components/makeColorInput.mjs +17 -7
  83. package/dist/mjs/toolbar/widgets/components/makeGridSelector.d.ts +6 -0
  84. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +3 -0
  85. package/dist/mjs/tools/FindTool.mjs +18 -5
  86. package/dist/mjs/tools/PanZoom.d.ts +8 -2
  87. package/dist/mjs/tools/PanZoom.mjs +29 -10
  88. package/dist/mjs/tools/SelectionTool/Selection.mjs +16 -2
  89. package/dist/mjs/util/addLongPressOrHoverCssClasses.d.ts +3 -1
  90. package/dist/mjs/util/addLongPressOrHoverCssClasses.mjs +2 -1
  91. package/dist/mjs/util/cloneElementWithStyles.d.ts +6 -0
  92. package/dist/mjs/util/cloneElementWithStyles.mjs +30 -0
  93. package/dist/mjs/version.mjs +1 -1
  94. package/package.json +3 -3
  95. package/src/toolbar/EdgeToolbar.scss +23 -2
  96. package/src/toolbar/toolbar.scss +2 -0
  97. package/src/toolbar/utils/HelpDisplay.scss +315 -0
  98. package/src/toolbar/widgets/components/makeColorInput.scss +7 -0
  99. package/src/tools/SelectionTool/SelectionTool.scss +4 -0
@@ -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
  }
@@ -13,7 +13,7 @@ const makeFormatMenu = (editor, selectionTool, localizationTable) => {
13
13
  container.classList.add('selection-format-menu', `${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}indentedList`);
14
14
  const colorRow = document.createElement('div');
15
15
  const colorLabel = document.createElement('label');
16
- const { input: colorInput, container: colorInputContainer, setValue: setColorInputValue } = makeColorInput(editor, color => {
16
+ const colorInputControl = makeColorInput(editor, color => {
17
17
  const selection = selectionTool.getSelection();
18
18
  if (selection) {
19
19
  const updateStyleCommands = [];
@@ -26,6 +26,7 @@ const makeFormatMenu = (editor, selectionTool, localizationTable) => {
26
26
  editor.dispatch(unitedCommand);
27
27
  }
28
28
  });
29
+ const { input: colorInput, container: colorInputContainer } = colorInputControl;
29
30
  colorLabel.innerText = localizationTable.colorLabel;
30
31
  const update = () => {
31
32
  const selection = selectionTool.getSelection();
@@ -41,12 +42,12 @@ const makeFormatMenu = (editor, selectionTool, localizationTable) => {
41
42
  }
42
43
  }
43
44
  }
44
- setColorInputValue(Color4.average(colors));
45
+ colorInputControl.setValue(Color4.average(colors));
45
46
  }
46
47
  else {
47
48
  colorInput.disabled = true;
48
49
  container.classList.add('disabled');
49
- setColorInputValue(Color4.transparent);
50
+ colorInputControl.setValue(Color4.transparent);
50
51
  }
51
52
  };
52
53
  colorRow.replaceChildren(colorLabel, colorInputContainer);
@@ -56,6 +57,10 @@ const makeFormatMenu = (editor, selectionTool, localizationTable) => {
56
57
  parent.appendChild(container);
57
58
  },
58
59
  update,
60
+ registerHelpText: (helpDisplay) => {
61
+ helpDisplay.registerTextHelpForElement(colorRow, localizationTable.selectionDropdown__changeColorHelpText);
62
+ colorInputControl.registerWithHelpTextDisplay(helpDisplay);
63
+ },
59
64
  };
60
65
  };
61
66
  export default class SelectionToolWidget extends BaseToolWidget {
@@ -66,16 +71,19 @@ export default class SelectionToolWidget extends BaseToolWidget {
66
71
  const resizeButton = new ActionButtonWidget(editor, 'resize-btn', () => editor.icons.makeResizeImageToSelectionIcon(), this.localizationTable.resizeImageToSelection, () => {
67
72
  this.resizeImageToSelection();
68
73
  }, localization);
74
+ resizeButton.setHelpText(this.localizationTable.selectionDropdown__resizeToHelpText);
69
75
  const deleteButton = new ActionButtonWidget(editor, 'delete-btn', () => editor.icons.makeDeleteSelectionIcon(), this.localizationTable.deleteSelection, () => {
70
76
  const selection = this.tool.getSelection();
71
77
  this.editor.dispatch(selection.deleteSelectedObjects());
72
78
  this.tool.clearSelection();
73
79
  }, localization);
80
+ deleteButton.setHelpText(this.localizationTable.selectionDropdown__deleteHelpText);
74
81
  const duplicateButton = new ActionButtonWidget(editor, 'duplicate-btn', () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, async () => {
75
82
  const selection = this.tool.getSelection();
76
83
  this.editor.dispatch(await selection.duplicateSelectedObjects());
77
84
  this.setDropdownVisible(false);
78
85
  }, localization);
86
+ duplicateButton.setHelpText(this.localizationTable.selectionDropdown__duplicateHelpText);
79
87
  this.addSubWidget(resizeButton);
80
88
  this.addSubWidget(deleteButton);
81
89
  this.addSubWidget(duplicateButton);
@@ -124,8 +132,11 @@ export default class SelectionToolWidget extends BaseToolWidget {
124
132
  createIcon() {
125
133
  return this.editor.icons.makeSelectionIcon();
126
134
  }
127
- fillDropdown(dropdown) {
128
- super.fillDropdown(dropdown);
135
+ getHelpText() {
136
+ return this.localizationTable.selectionDropdown__baseHelpText;
137
+ }
138
+ fillDropdown(dropdown, helpDisplay) {
139
+ super.fillDropdown(dropdown, helpDisplay);
129
140
  const controlsContainer = document.createElement('div');
130
141
  controlsContainer.classList.add(`${toolbarCSSPrefix}nonbutton-controls-main-list`);
131
142
  dropdown.appendChild(controlsContainer);
@@ -133,6 +144,9 @@ export default class SelectionToolWidget extends BaseToolWidget {
133
144
  const formatMenu = makeFormatMenu(this.editor, this.tool, this.localizationTable);
134
145
  formatMenu.addTo(controlsContainer);
135
146
  this.updateFormatMenu = () => formatMenu.update();
147
+ if (helpDisplay) {
148
+ formatMenu.registerHelpText(helpDisplay);
149
+ }
136
150
  formatMenu.update();
137
151
  return true;
138
152
  }
@@ -1,10 +1,12 @@
1
1
  import { Color4 } from '@js-draw/math';
2
2
  import Editor from '../../../Editor';
3
+ import type HelpDisplay from '../../utils/HelpDisplay';
3
4
  type OnColorChangeListener = (color: Color4) => void;
4
5
  export declare const makeColorInput: (editor: Editor, onColorChange: OnColorChangeListener) => {
5
6
  input: HTMLInputElement;
6
7
  container: HTMLSpanElement;
7
8
  setValue: (color: Color4 | string) => void;
8
9
  closePicker: () => void;
10
+ registerWithHelpTextDisplay: (helpDisplay: HelpDisplay) => void;
9
11
  };
10
12
  export default makeColorInput;
@@ -3,13 +3,16 @@ import PipetteTool from '../../../tools/PipetteTool.mjs';
3
3
  import { EditorEventType } from '../../../types.mjs';
4
4
  // Returns [ color input, input container, callback to change the color value ].
5
5
  export const makeColorInput = (editor, onColorChange) => {
6
- const colorInputContainer = document.createElement('span');
6
+ const container = document.createElement('span');
7
+ const inputWrapper = document.createElement('span');
7
8
  const colorInput = document.createElement('input');
8
9
  colorInput.type = 'button';
9
10
  colorInput.classList.add('coloris_input');
10
- colorInputContainer.classList.add('color-input-container');
11
- colorInputContainer.appendChild(colorInput);
12
- const pipetteController = addPipetteTool(editor, colorInputContainer, (color) => {
11
+ container.classList.add('color-input-container');
12
+ inputWrapper.classList.add('color-input-wrapper');
13
+ inputWrapper.appendChild(colorInput);
14
+ container.appendChild(inputWrapper);
15
+ const pipetteController = addPipetteTool(editor, container, (color) => {
13
16
  colorInput.value = color.toHexString();
14
17
  onInputEnd();
15
18
  // Update the color preview, if it exists (may be managed by Coloris).
@@ -44,7 +47,7 @@ export const makeColorInput = (editor, onColorChange) => {
44
47
  open: true,
45
48
  });
46
49
  pipetteController.cancel();
47
- colorInputContainer.classList.add('picker-open');
50
+ container.classList.add('picker-open');
48
51
  // Focus the Coloris color picker, if it exists.
49
52
  // Don't focus the text input within the color picker, however,
50
53
  // as this displays a keyboard on mobile devices.
@@ -60,7 +63,7 @@ export const makeColorInput = (editor, onColorChange) => {
60
63
  onInputEnd();
61
64
  // Restore focus to the input that opened the color picker
62
65
  colorInput.focus();
63
- colorInputContainer.classList.remove('picker-open');
66
+ container.classList.remove('picker-open');
64
67
  };
65
68
  colorInput.addEventListener('close', () => {
66
69
  onClose();
@@ -76,13 +79,17 @@ export const makeColorInput = (editor, onColorChange) => {
76
79
  };
77
80
  return {
78
81
  input: colorInput,
79
- container: colorInputContainer,
82
+ container,
80
83
  setValue: setColorInputValue,
81
84
  closePicker: () => {
82
85
  if (isOpen) {
83
86
  onInputEnd();
84
87
  }
85
88
  },
89
+ registerWithHelpTextDisplay: (helpDisplay) => {
90
+ helpDisplay.registerTextHelpForElement(inputWrapper, editor.localization.colorPickerToggleHelpText);
91
+ pipetteController.registerWithHelpTextDisplay(helpDisplay);
92
+ },
86
93
  };
87
94
  };
88
95
  const addPipetteTool = (editor, container, onColorChange) => {
@@ -136,6 +143,9 @@ const addPipetteTool = (editor, container, onColorChange) => {
136
143
  cancel: () => {
137
144
  endColorSelectMode();
138
145
  },
146
+ registerWithHelpTextDisplay: (helpDisplay) => {
147
+ helpDisplay.registerTextHelpForElement(pipetteButton, editor.localization.colorPickerPipetteHelpText);
148
+ },
139
149
  };
140
150
  };
141
151
  export default makeColorInput;
@@ -7,8 +7,14 @@ interface GridSelectChoice<ChoiceIdType> {
7
7
  }
8
8
  interface GridSelector<ChoiceIdType> {
9
9
  value: MutableReactiveValue<ChoiceIdType>;
10
+ /**
11
+ * Connects this grid selector with `other` such that only one item in
12
+ * either this or `other` can be selected at a time.
13
+ */
10
14
  linkWith: (other: GridSelector<ChoiceIdType>) => void;
15
+ /** Re-builds the icons shown in the grid selector. */
11
16
  updateIcons: () => void;
17
+ getRootElement: () => HTMLElement;
12
18
  addTo: (parent: HTMLElement) => void;
13
19
  /** Used internally @internal */
14
20
  _radiogroupName: string;
@@ -131,6 +131,9 @@ labelText, defaultId, choices) => {
131
131
  updateIcons: () => {
132
132
  buttons.forEach(button => button.updateIcon());
133
133
  },
134
+ getRootElement() {
135
+ return outerContainer;
136
+ },
134
137
  addTo: (parent) => {
135
138
  parent.appendChild(outerContainer);
136
139
  },
@@ -2,6 +2,7 @@
2
2
  //
3
3
  // @packageDocumentation
4
4
  import TextComponent from '../components/TextComponent.mjs';
5
+ import ImageComponent from '../components/ImageComponent.mjs';
5
6
  import BaseTool from './BaseTool.mjs';
6
7
  import { toggleFindVisibleShortcutId } from './keybindings.mjs';
7
8
  const cssPrefix = 'find-tool';
@@ -20,11 +21,23 @@ export default class FindTool extends BaseTool {
20
21
  return true;
21
22
  }
22
23
  getMatches(searchFor) {
23
- searchFor = searchFor.toLocaleLowerCase();
24
- const allTextComponents = this.editor.image.getAllElements()
25
- .filter(elem => elem instanceof TextComponent);
26
- const matches = allTextComponents.filter(text => text.getText().toLocaleLowerCase().indexOf(searchFor) !== -1);
27
- return matches.map(match => match.getBBox());
24
+ const lowerSearchFor = searchFor.toLocaleLowerCase();
25
+ const matchingComponents = this.editor.image.getAllElements().filter(component => {
26
+ let text = '';
27
+ if (component instanceof TextComponent) {
28
+ text = component.getText();
29
+ }
30
+ else if (component instanceof ImageComponent) {
31
+ text = component.getAltText() ?? '';
32
+ }
33
+ else {
34
+ return false;
35
+ }
36
+ const hasLowercaseMatch = text.toLocaleLowerCase().indexOf(lowerSearchFor) !== -1;
37
+ const hasSameCaseMatch = text.indexOf(searchFor) !== -1;
38
+ return hasLowercaseMatch || hasSameCaseMatch;
39
+ });
40
+ return matchingComponents.map(match => match.getBBox());
28
41
  }
29
42
  focusCurrentMatch() {
30
43
  const matches = this.getMatches(this.searchInput.value);
@@ -24,13 +24,14 @@ export default class PanZoom extends BaseTool {
24
24
  private readonly initialRotationSnapAngle;
25
25
  private readonly afterRotationStartSnapAngle;
26
26
  private readonly pinchZoomStartThreshold;
27
- private startDist;
28
- private lastDist;
27
+ private startTouchDist;
28
+ private lastTouchDist;
29
29
  private lastScreenCenter;
30
30
  private lastTimestamp;
31
31
  private lastPointerDownTimestamp;
32
32
  private initialTouchAngle;
33
33
  private initialViewportRotation;
34
+ private initialViewportScale;
34
35
  private isScaling;
35
36
  private isRotating;
36
37
  private inertialScroller;
@@ -43,6 +44,11 @@ export default class PanZoom extends BaseTool {
43
44
  private updateVelocity;
44
45
  private getCenterDelta;
45
46
  private toSnappedRotationDelta;
47
+ /**
48
+ * Given a scale update, `scaleFactor`, returns a new scale factor snapped
49
+ * to a power of two (if within some tolerance of that scale).
50
+ */
51
+ private toSnappedScaleFactor;
46
52
  private handleTwoFingerMove;
47
53
  private handleOneFingerMove;
48
54
  onPointerMove({ allPointers }: PointerEvt): void;