js-draw 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Editor.css +1 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/toolbar/AbstractToolbar.d.ts +9 -13
  6. package/dist/cjs/toolbar/AbstractToolbar.js +14 -19
  7. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  8. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +26 -0
  9. package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
  10. package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/mjs/toolbar/AbstractToolbar.d.ts +9 -13
  13. package/dist/mjs/toolbar/AbstractToolbar.mjs +14 -19
  14. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  15. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +21 -0
  16. package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
  17. package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
  18. package/dist/mjs/version.mjs +1 -1
  19. package/docs/img/readme-images/js-draw.jpg +0 -0
  20. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  21. package/package.json +5 -4
  22. package/src/toolbar/EdgeToolbar.scss +1 -0
  23. package/dist-test/test_imports/package-lock.json +0 -13
  24. package/dist-test/test_imports/package.json +0 -12
  25. package/dist-test/test_imports/test-imports.js +0 -11
  26. package/dist-test/test_imports/test-require.cjs +0 -14
  27. package/src/Editor.loadFrom.test.ts +0 -24
  28. package/src/Editor.test.ts +0 -107
  29. package/src/Editor.toSVG.test.ts +0 -294
  30. package/src/Editor.ts +0 -1443
  31. package/src/EditorImage.test.ts +0 -117
  32. package/src/EditorImage.ts +0 -609
  33. package/src/EventDispatcher.test.ts +0 -123
  34. package/src/EventDispatcher.ts +0 -72
  35. package/src/Pointer.ts +0 -183
  36. package/src/SVGLoader.test.ts +0 -114
  37. package/src/SVGLoader.ts +0 -672
  38. package/src/UndoRedoHistory.test.ts +0 -34
  39. package/src/UndoRedoHistory.ts +0 -102
  40. package/src/Viewport.ts +0 -322
  41. package/src/bundle/bundled.ts +0 -7
  42. package/src/commands/Command.ts +0 -45
  43. package/src/commands/Duplicate.ts +0 -75
  44. package/src/commands/Erase.ts +0 -95
  45. package/src/commands/SerializableCommand.ts +0 -49
  46. package/src/commands/UnresolvedCommand.ts +0 -37
  47. package/src/commands/invertCommand.ts +0 -58
  48. package/src/commands/lib.ts +0 -16
  49. package/src/commands/localization.ts +0 -47
  50. package/src/commands/uniteCommands.test.ts +0 -23
  51. package/src/commands/uniteCommands.ts +0 -140
  52. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  53. package/src/components/AbstractComponent.ts +0 -383
  54. package/src/components/BackgroundComponent.test.ts +0 -44
  55. package/src/components/BackgroundComponent.ts +0 -348
  56. package/src/components/ImageComponent.ts +0 -176
  57. package/src/components/RestylableComponent.ts +0 -161
  58. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  59. package/src/components/Stroke.test.ts +0 -137
  60. package/src/components/Stroke.ts +0 -294
  61. package/src/components/TextComponent.test.ts +0 -202
  62. package/src/components/TextComponent.ts +0 -429
  63. package/src/components/UnknownSVGObject.test.ts +0 -10
  64. package/src/components/UnknownSVGObject.ts +0 -60
  65. package/src/components/builders/ArrowBuilder.ts +0 -106
  66. package/src/components/builders/CircleBuilder.ts +0 -100
  67. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  68. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  69. package/src/components/builders/LineBuilder.ts +0 -77
  70. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  71. package/src/components/builders/RectangleBuilder.ts +0 -73
  72. package/src/components/builders/types.ts +0 -15
  73. package/src/components/lib.ts +0 -31
  74. package/src/components/localization.ts +0 -24
  75. package/src/components/util/StrokeSmoother.ts +0 -302
  76. package/src/components/util/describeComponentList.ts +0 -18
  77. package/src/dialogs/makeAboutDialog.ts +0 -82
  78. package/src/inputEvents.ts +0 -143
  79. package/src/lib.ts +0 -91
  80. package/src/localization.ts +0 -34
  81. package/src/localizations/de.ts +0 -146
  82. package/src/localizations/en.ts +0 -8
  83. package/src/localizations/es.ts +0 -74
  84. package/src/localizations/getLocalizationTable.test.ts +0 -27
  85. package/src/localizations/getLocalizationTable.ts +0 -74
  86. package/src/rendering/Display.ts +0 -247
  87. package/src/rendering/RenderablePathSpec.ts +0 -88
  88. package/src/rendering/RenderingStyle.test.ts +0 -68
  89. package/src/rendering/RenderingStyle.ts +0 -55
  90. package/src/rendering/TextRenderingStyle.ts +0 -55
  91. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  92. package/src/rendering/caching/CacheRecord.ts +0 -76
  93. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  94. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  95. package/src/rendering/caching/RenderingCache.ts +0 -66
  96. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  97. package/src/rendering/caching/testUtils.ts +0 -35
  98. package/src/rendering/caching/types.ts +0 -34
  99. package/src/rendering/lib.ts +0 -8
  100. package/src/rendering/localization.ts +0 -20
  101. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  102. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  103. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  104. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  105. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  106. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  107. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  108. package/src/shortcuts/KeyBinding.test.ts +0 -61
  109. package/src/shortcuts/KeyBinding.ts +0 -257
  110. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  111. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  112. package/src/shortcuts/lib.ts +0 -3
  113. package/src/testing/createEditor.ts +0 -11
  114. package/src/testing/getUniquePointerId.ts +0 -18
  115. package/src/testing/lib.ts +0 -3
  116. package/src/testing/sendPenEvent.ts +0 -36
  117. package/src/testing/sendTouchEvent.ts +0 -71
  118. package/src/toolbar/AbstractToolbar.ts +0 -542
  119. package/src/toolbar/DropdownToolbar.ts +0 -220
  120. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  121. package/src/toolbar/EdgeToolbar.ts +0 -543
  122. package/src/toolbar/IconProvider.ts +0 -861
  123. package/src/toolbar/constants.ts +0 -1
  124. package/src/toolbar/lib.ts +0 -6
  125. package/src/toolbar/localization.ts +0 -136
  126. package/src/toolbar/types.ts +0 -13
  127. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  128. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  129. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  130. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  131. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  132. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  133. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  134. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  135. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  136. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  137. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  138. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  139. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  140. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  141. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  142. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  143. package/src/toolbar/widgets/keybindings.ts +0 -19
  144. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  145. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  146. package/src/toolbar/widgets/layout/types.ts +0 -74
  147. package/src/toolbar/widgets/lib.ts +0 -13
  148. package/src/tools/BaseTool.ts +0 -169
  149. package/src/tools/Eraser.test.ts +0 -103
  150. package/src/tools/Eraser.ts +0 -173
  151. package/src/tools/FindTool.test.ts +0 -67
  152. package/src/tools/FindTool.ts +0 -153
  153. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  154. package/src/tools/InputFilter/InputMapper.ts +0 -41
  155. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  156. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  157. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  158. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  159. package/src/tools/PanZoom.test.ts +0 -339
  160. package/src/tools/PanZoom.ts +0 -525
  161. package/src/tools/PasteHandler.ts +0 -94
  162. package/src/tools/Pen.test.ts +0 -260
  163. package/src/tools/Pen.ts +0 -284
  164. package/src/tools/PipetteTool.ts +0 -84
  165. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  166. package/src/tools/SelectionTool/Selection.ts +0 -647
  167. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  168. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  169. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  170. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  171. package/src/tools/SelectionTool/types.ts +0 -11
  172. package/src/tools/SoundUITool.ts +0 -221
  173. package/src/tools/TextTool.ts +0 -339
  174. package/src/tools/ToolController.ts +0 -224
  175. package/src/tools/ToolEnabledGroup.ts +0 -14
  176. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  177. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  178. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  179. package/src/tools/UndoRedoShortcut.ts +0 -24
  180. package/src/tools/keybindings.ts +0 -85
  181. package/src/tools/lib.ts +0 -22
  182. package/src/tools/localization.ts +0 -76
  183. package/src/types.ts +0 -151
  184. package/src/util/ReactiveValue.test.ts +0 -168
  185. package/src/util/ReactiveValue.ts +0 -241
  186. package/src/util/assertions.ts +0 -55
  187. package/src/util/fileToBase64.ts +0 -18
  188. package/src/util/guessKeyCodeFromKey.ts +0 -36
  189. package/src/util/listPrefixMatch.ts +0 -19
  190. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  191. package/src/util/untilNextAnimationFrame.ts +0 -9
  192. package/src/util/waitForAll.ts +0 -18
  193. package/src/util/waitForTimeout.ts +0 -9
  194. package/src/version.test.ts +0 -12
  195. package/src/version.ts +0 -3
  196. package/tools/allLocales.js +0 -4
  197. package/tools/copyREADME.ts +0 -62
@@ -1,179 +0,0 @@
1
- import { MutableReactiveValue } from '../../../util/ReactiveValue';
2
- import stopPropagationOfScrollingWheelEvents from '../../../util/stopPropagationOfScrollingWheelEvents';
3
- import { IconElemType } from '../../IconProvider';
4
- import { toolbarCSSPrefix } from '../../constants';
5
-
6
- interface GridSelectChoice<ChoiceIdType> {
7
- // `id` should be unique in all choices
8
- id: ChoiceIdType;
9
- makeIcon: ()=>IconElemType;
10
- title: string;
11
- }
12
-
13
- interface GridSelector<ChoiceIdType> {
14
- value: MutableReactiveValue<ChoiceIdType>,
15
- linkWith: (other: GridSelector<ChoiceIdType>)=>void;
16
- updateIcons: ()=>void;
17
- addTo: (parent: HTMLElement)=>void;
18
-
19
- /** Used internally @internal */
20
- _radiogroupName: string;
21
- }
22
-
23
- let idCounter = 0;
24
-
25
- /**
26
- * Creates a widget that allows users to select one of serveral items from a list.
27
- *
28
- * `ChoiceIdType` should be `string`, a `number`, or an `enum` (or similar).
29
- *
30
- * If this input is set to an ID that is not in `choices`, no item is selected.
31
- */
32
- const makeGridSelector = <ChoiceIdType> (
33
- // Text before the grid selector used as a label
34
- labelText: string,
35
- defaultId: ChoiceIdType,
36
- choices: GridSelectChoice<ChoiceIdType>[],
37
- ): GridSelector<ChoiceIdType> => {
38
- const outerContainer = document.createElement('div');
39
- outerContainer.classList.add(`${toolbarCSSPrefix}grid-selector`);
40
-
41
- const selectedValue = MutableReactiveValue.fromInitialValue(defaultId);
42
-
43
- const menuContainer = document.createElement('div');
44
- menuContainer.setAttribute('role', 'menu');
45
- menuContainer.id = `${toolbarCSSPrefix}-grid-select-id-${idCounter++}`;
46
-
47
- stopPropagationOfScrollingWheelEvents(menuContainer);
48
-
49
- const label = document.createElement('label');
50
- label.innerText = labelText;
51
- label.htmlFor = menuContainer.id;
52
- outerContainer.appendChild(label);
53
-
54
- // All buttons in a radiogroup need the same name attribute.
55
- let radiogroupName = `${toolbarCSSPrefix}-grid-selector-${idCounter++}`;
56
-
57
- type ChoiceType = GridSelectChoice<ChoiceIdType>;
58
-
59
- const createChoiceButton = (record: ChoiceType) => {
60
- const buttonContainer = document.createElement('div');
61
- buttonContainer.classList.add('choice-button');
62
-
63
- const button = document.createElement('input');
64
- button.type = 'radio';
65
- button.id = `${toolbarCSSPrefix}-grid-select-button-${idCounter++}`;
66
-
67
- // Clicking any part of labelContainer triggers the radio button.
68
- const labelContainer = document.createElement('label');
69
-
70
- const rebuildLabel = () => {
71
- labelContainer.setAttribute('title', record.title);
72
-
73
- const labelText = document.createElement('span');
74
- labelText.classList.add('button-label-text');
75
-
76
- const icon = record.makeIcon();
77
- icon.classList.add('icon');
78
-
79
- // The title of the record
80
- labelText.innerText = record.title;
81
- labelContainer.htmlFor = button.id;
82
-
83
- labelContainer.replaceChildren(icon, labelText);
84
- };
85
- rebuildLabel();
86
-
87
- // Mark the button as belonging to the current group (causes
88
- // other buttons in the same group to automatically uncheck
89
- // when this button is checked).
90
- const updateButtonRadiogroupName = () => {
91
- button.name = radiogroupName;
92
- };
93
-
94
- updateButtonRadiogroupName();
95
-
96
- const updateButtonCSS = () => {
97
- if (button.checked) {
98
- buttonContainer.classList.add('checked');
99
- } else {
100
- buttonContainer.classList.remove('checked');
101
- }
102
- };
103
-
104
- button.oninput = () => {
105
- // Setting the selected value fires an event that causes the value
106
- // of this button to be set.
107
- if (button.checked) {
108
- selectedValue.set(record.id);
109
- }
110
-
111
- updateButtonCSS();
112
- };
113
-
114
- buttonContainer.replaceChildren(button, labelContainer);
115
- menuContainer.appendChild(buttonContainer);
116
-
117
- // Set whether the current button is checked
118
- const setChecked = (checked: boolean) => {
119
- button.checked = checked;
120
- updateButtonCSS();
121
- };
122
- setChecked(false);
123
-
124
- // Updates the factory's icon based on the current style of the tool.
125
- const updateIcon = () => {
126
- rebuildLabel();
127
- };
128
-
129
- return {
130
- choiceRecord: record,
131
- setChecked,
132
- updateIcon,
133
- updateButtonRadiogroupName
134
- };
135
- };
136
-
137
- const buttons: Array<ReturnType<typeof createChoiceButton>> = [];
138
- for (const choice of choices) {
139
- buttons.push(createChoiceButton(choice));
140
- }
141
- // invariant: buttons.length = choices.length
142
- // However, it is still possible that selectedValue does not correspond
143
- // to a choice in `choices`. This is acceptable.
144
-
145
- outerContainer.appendChild(menuContainer);
146
-
147
- selectedValue.onUpdateAndNow(choiceId => {
148
- for (let i = 0; i < buttons.length; i++) {
149
- buttons[i].setChecked(buttons[i].choiceRecord.id === choiceId);
150
- }
151
- });
152
-
153
- const result = {
154
- value: selectedValue,
155
-
156
- _radiogroupName: radiogroupName,
157
-
158
- linkWith: (other: GridSelector<ChoiceIdType>) => {
159
- result._radiogroupName = other._radiogroupName;
160
- radiogroupName = other._radiogroupName;
161
-
162
- for (const button of buttons) {
163
- button.updateButtonRadiogroupName();
164
- }
165
- },
166
-
167
- updateIcons: () => {
168
- buttons.forEach(button => button.updateIcon());
169
- },
170
-
171
- addTo: (parent: HTMLElement) => {
172
- parent.appendChild(outerContainer);
173
- },
174
- };
175
-
176
- return result;
177
- };
178
-
179
- export default makeGridSelector;
@@ -1,17 +0,0 @@
1
-
2
- /**
3
- * Creates a separator element that renders a line and, optionally, a header.
4
- */
5
- const makeSeparator = (header: string = '') => {
6
- const container = document.createElement('div');
7
- container.classList.add('tool-dropdown-separator');
8
- container.innerText = header;
9
-
10
- return {
11
- addTo: (parent: HTMLElement) => {
12
- parent.appendChild(container);
13
- },
14
- };
15
- };
16
-
17
- export default makeSeparator;
@@ -1,62 +0,0 @@
1
- import { toRoundedString } from '@js-draw/math';
2
- import { toolbarCSSPrefix } from '../../constants';
3
- import { ToolbarContext } from '../../types';
4
-
5
- let idCounter = 0;
6
-
7
- const makeThicknessSlider = (
8
- context: ToolbarContext,
9
- onChange: (value: number)=>void
10
- ) => {
11
- const container = document.createElement('div');
12
-
13
- const thicknessLabel = document.createElement('label');
14
- const thicknessInput = document.createElement('input');
15
-
16
- container.classList.add(`${toolbarCSSPrefix}thicknessSliderContainer`);
17
-
18
- // Give inputs IDs so we can label them with a <label for=...>Label text</label>
19
- thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${idCounter++}`;
20
-
21
- thicknessLabel.innerText = context.localization.thicknessLabel;
22
- thicknessLabel.setAttribute('for', thicknessInput.id);
23
-
24
- // Use a logarithmic scale for thicknessInput (finer control over thinner strokewidths.)
25
- const inverseThicknessInputFn = (t: number) => Math.log10(t);
26
- const thicknessInputFn = (t: number) => 10**t;
27
-
28
- thicknessInput.type = 'range';
29
- thicknessInput.oninput = () => {
30
- onChange(thicknessInputFn(parseFloat(thicknessInput.value)));
31
- };
32
- container.appendChild(thicknessLabel);
33
- container.appendChild(thicknessInput);
34
-
35
- const setBounds = (min: number, max: number) => {
36
- const round = (value: number, roundUp: boolean) => {
37
- const roundFn = roundUp ? Math.ceil : Math.floor;
38
- return roundFn(value * 100) / 100;
39
- };
40
- const sliderMin = round(inverseThicknessInputFn(min), false);
41
- const sliderMax = round(inverseThicknessInputFn(max), true);
42
-
43
- thicknessInput.min = `${sliderMin}`;
44
- thicknessInput.max = `${sliderMax}`;
45
- thicknessInput.step = `${toRoundedString((sliderMax - sliderMin) / 20)}`;
46
- };
47
-
48
- setBounds(2, 262);
49
-
50
- return {
51
- container,
52
- addTo: (parent: HTMLElement) => {
53
- parent.appendChild(container);
54
- },
55
- setBounds,
56
- setValue: (thickness: number) => {
57
- thicknessInput.value = inverseThicknessInputFn(thickness).toString();
58
- },
59
- };
60
- };
61
-
62
- export default makeThicknessSlider;
@@ -1,19 +0,0 @@
1
- import KeyboardShortcutManager from '../../shortcuts/KeyboardShortcutManager';
2
-
3
- // Selection
4
- export const resizeImageToSelectionKeyboardShortcut =
5
- 'jsdraw.toolbar.SelectionTool.resizeImageToSelection';
6
- KeyboardShortcutManager.registerDefaultKeyboardShortcut(
7
- resizeImageToSelectionKeyboardShortcut, [ 'ctrlOrMeta+r' ], 'Resize image to selection'
8
- );
9
-
10
- // Pen tool
11
- export const selectStrokeTypeKeyboardShortcutIds: string[] =
12
- [1, 2, 3, 4, 5, 6, 7].map(id => `jsdraw.toolbar.PenTool.select-pen-${id}`);
13
-
14
- for (let i = 0; i < selectStrokeTypeKeyboardShortcutIds.length; i++) {
15
- const id = selectStrokeTypeKeyboardShortcutIds[i];
16
- KeyboardShortcutManager.registerDefaultKeyboardShortcut(
17
- id, [ `CtrlOrMeta+Digit${(i + 1)}` ], 'Select pen style ' + (i + 1),
18
- );
19
- }
@@ -1,262 +0,0 @@
1
- import { EditorEventType, EditorNotifier } from '../../../types';
2
- import EventDispatcher, { DispatcherEventListener } from '../../../EventDispatcher';
3
- import { ToolbarLocalization } from '../../localization';
4
- import { ToolMenu, WidgetContentLayoutManager, ToolMenuParent } from './types';
5
- import { toolbarCSSPrefix } from '../../constants';
6
- import { MutableReactiveValue, ReactiveValue } from '../../../util/ReactiveValue';
7
-
8
- enum DropdownEventType {
9
- DropdownShown,
10
- DropdownHidden,
11
- }
12
-
13
- interface DropdownShownEvent {
14
- // If undefined, the event is forwarded from a different layout manager
15
- dropdown?: Dropdown;
16
-
17
- fromToplevelDropdown: boolean;
18
- }
19
-
20
- type NotifierType = EventDispatcher<DropdownEventType, DropdownShownEvent>;
21
-
22
- class Dropdown implements ToolMenu {
23
- private dropdownContainer: HTMLElement;
24
- public readonly visible: MutableReactiveValue<boolean>;
25
-
26
- private dropdownToggleListener: DispatcherEventListener|null = null;
27
-
28
- public constructor(
29
- public parent: ToolMenuParent,
30
- private notifier: NotifierType,
31
- private onDestroy: ()=>void,
32
- ) {
33
- this.visible = ReactiveValue.fromInitialValue(false);
34
-
35
- this.dropdownContainer = document.createElement('div');
36
- this.dropdownContainer.classList.add(`${toolbarCSSPrefix}dropdown`);
37
- this.dropdownContainer.classList.add('hidden');
38
-
39
- parent.target.insertAdjacentElement('afterend', this.dropdownContainer);
40
-
41
- // When another dropdown is shown,
42
- this.dropdownToggleListener = this.notifier.on(DropdownEventType.DropdownShown, (evt) => {
43
- if (
44
- evt.dropdown !== this &&
45
-
46
- // Don't hide if a submenu was shown (it might be a submenu of
47
- // the current menu).
48
- evt.fromToplevelDropdown
49
- ) {
50
- this.setVisible(false);
51
- }
52
- });
53
- }
54
-
55
- public onActivated(): void {
56
- // Do nothing.
57
- }
58
-
59
- protected repositionDropdown() {
60
- const dropdownBBox = this.dropdownContainer.getBoundingClientRect();
61
- const screenWidth = document.scrollingElement?.clientWidth ?? document.body.clientHeight;
62
- const screenHeight = document.scrollingElement?.clientHeight ?? document.body.clientHeight;
63
-
64
- let translateX = undefined;
65
- let translateY = undefined;
66
-
67
- if (dropdownBBox.left > screenWidth / 2) {
68
- const targetElem = this.parent.target;
69
- translateX = `calc(${targetElem.clientWidth + 'px'} - 100%)`;
70
- }
71
-
72
- // Shift the dropdown if it's off the screen, but only if doing so moves it on to the screen
73
- // (prevents dropdowns from going almost completely offscreen on small screens).
74
- if (dropdownBBox.bottom > screenHeight && (dropdownBBox.top - dropdownBBox.height > 0)) {
75
- const targetElem = this.parent.target;
76
- translateY = `calc(-${targetElem.clientHeight}px - 100%)`;
77
- }
78
-
79
- // Use .translate so as not to conflict with CSS animating the
80
- // transform property.
81
- if (translateX || translateY) {
82
- this.dropdownContainer.style.translate = `${translateX ?? '0'} ${translateY ?? '0'}`;
83
- } else {
84
- this.dropdownContainer.style.translate = '';
85
- }
86
- }
87
-
88
- private hideDropdownTimeout: any|null = null;
89
- private setVisible(visible: boolean) {
90
- const currentlyVisible = this.visible.get();
91
- if (currentlyVisible === visible) {
92
- return;
93
- }
94
-
95
- // If waiting to hide the dropdown, cancel it.
96
- if (this.hideDropdownTimeout) {
97
- clearTimeout(this.hideDropdownTimeout);
98
- this.hideDropdownTimeout = null;
99
- this.dropdownContainer.classList.remove('hiding');
100
- this.repositionDropdown();
101
- }
102
-
103
-
104
- const animationDuration = 150; // ms
105
-
106
- this.visible.set(visible);
107
- if (visible) {
108
- this.dropdownContainer.classList.remove('hidden');
109
-
110
- this.notifier.dispatch(DropdownEventType.DropdownShown, {
111
- dropdown: this,
112
- fromToplevelDropdown: this.parent.isToplevel(),
113
- });
114
-
115
- this.repositionDropdown();
116
- } else {
117
- this.notifier.dispatch(DropdownEventType.DropdownHidden, {
118
- dropdown: this,
119
- fromToplevelDropdown: this.parent.isToplevel(),
120
- });
121
-
122
- this.dropdownContainer.classList.add('hiding');
123
-
124
- // Hide the dropdown *slightly* before the animation finishes. This
125
- // prevents flickering in some browsers.
126
- const hideDelay = animationDuration * 0.95;
127
-
128
- this.hideDropdownTimeout = setTimeout(() => {
129
- this.dropdownContainer.classList.add('hidden');
130
- this.dropdownContainer.classList.remove('hiding');
131
- this.repositionDropdown();
132
- }, hideDelay);
133
- }
134
-
135
- // Animate
136
- const animationName = `var(--dropdown-${
137
- visible ? 'show' : 'hide'
138
- }-animation)`;
139
- this.dropdownContainer.style.animation = `${animationDuration}ms ease ${animationName}`;
140
- }
141
-
142
- public requestShow(): void {
143
- this.setVisible(true);
144
- }
145
-
146
- public requestHide(): void {
147
- this.setVisible(false);
148
- }
149
-
150
- public appendChild(item: HTMLElement): void {
151
- this.dropdownContainer.appendChild(item);
152
- }
153
-
154
- public clearChildren(): void {
155
- this.dropdownContainer.replaceChildren();
156
- }
157
-
158
- public destroy(): void {
159
- this.setVisible(false);
160
- this.dropdownContainer.remove();
161
- this.dropdownToggleListener?.remove();
162
-
163
- // Allow children to be added to other parents
164
- this.clearChildren();
165
- this.onDestroy();
166
- }
167
- }
168
-
169
- export default class DropdownLayoutManager implements WidgetContentLayoutManager {
170
- private notifier: NotifierType;
171
- private dropdowns: Set<Dropdown> = new Set();
172
-
173
- public constructor(
174
- announceForAccessibility: (text: string)=>void,
175
- private localization: ToolbarLocalization,
176
- ) {
177
- this.notifier = new EventDispatcher();
178
- this.notifier.on(DropdownEventType.DropdownShown, ({ dropdown, fromToplevelDropdown }) => {
179
- if (!dropdown) return;
180
-
181
- announceForAccessibility(
182
- this.localization.dropdownShown(dropdown.parent.getTitle())
183
- );
184
-
185
- // Share the event with other connected notifiers
186
- this.connectedNotifiers.forEach(notifier => {
187
- notifier.dispatch(EditorEventType.ToolbarDropdownShown, {
188
- kind: EditorEventType.ToolbarDropdownShown,
189
- fromToplevelDropdown,
190
- layoutManager: this,
191
- });
192
- });
193
- });
194
-
195
- this.notifier.on(DropdownEventType.DropdownHidden, ({ dropdown }) => {
196
- if (!dropdown) return;
197
-
198
- announceForAccessibility(
199
- this.localization.dropdownHidden(dropdown.parent.getTitle())
200
- );
201
- });
202
- }
203
-
204
- private listeners: DispatcherEventListener[] = [];
205
- private connectedNotifiers: EditorNotifier[] = [];
206
- public connectToEditorNotifier(notifier: EditorNotifier) {
207
- this.connectedNotifiers.push(notifier);
208
- this.refreshListeners();
209
- }
210
-
211
- /** Creates a dropdown within `parent`. */
212
- public createToolMenu(parent: ToolMenuParent): ToolMenu {
213
- const dropdown = new Dropdown(
214
- parent,
215
- this.notifier,
216
- () => {
217
- this.dropdowns.delete(dropdown);
218
-
219
- this.refreshListeners();
220
- }
221
- );
222
- this.dropdowns.add(dropdown);
223
- this.refreshListeners();
224
-
225
- return dropdown;
226
- }
227
-
228
- /**
229
- * Adds/removes listeners based on whether we have any managed dropdowns.
230
- *
231
- * We attempt to clean up all resources when `dropdowns.size == 0`, at which
232
- * point, an instance of this could be safely garbage collected.
233
- */
234
- private refreshListeners() {
235
- const clearListeners = () => {
236
- // Remove all listeners & resources that won't be garbage collected.
237
- this.listeners.forEach(l => l.remove());
238
- this.listeners = [];
239
- };
240
-
241
- if (this.dropdowns.size === 0) {
242
- clearListeners();
243
- } else if (this.listeners.length !== this.connectedNotifiers.length) {
244
- clearListeners();
245
-
246
- this.listeners = this.connectedNotifiers.map(notifier => {
247
- return notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
248
- if (evt.kind !== EditorEventType.ToolbarDropdownShown
249
-
250
- // Don't forward to ourselves events that we originally triggered.
251
- || evt.layoutManager === this) {
252
- return;
253
- }
254
-
255
- this.notifier.dispatch(DropdownEventType.DropdownShown, {
256
- fromToplevelDropdown: evt.fromToplevelDropdown,
257
- });
258
- });
259
- });
260
- }
261
- }
262
- }
@@ -1,71 +0,0 @@
1
- import { MutableReactiveValue, ReactiveValue } from '../../../util/ReactiveValue';
2
- import { ToolbarLocalization } from '../../localization';
3
- import { ToolMenu, WidgetContentLayoutManager, ToolMenuParent } from './types';
4
-
5
- export default class EdgeToolbarLayoutManager implements WidgetContentLayoutManager {
6
- private visibleWidgetContent: MutableReactiveValue<ToolMenu|null> = ReactiveValue.fromInitialValue(null);
7
-
8
- // @internal
9
- public constructor(
10
- private setSidebarContent: (...content: HTMLElement[])=>void,
11
- private sidebarTitle: MutableReactiveValue<string>,
12
- private sidebarVisibility: MutableReactiveValue<boolean>,
13
- private announceForAccessibility: (text: string)=>void,
14
- private localization: ToolbarLocalization,
15
- ) {
16
-
17
- }
18
-
19
- /** Creates a dropdown within `parent`. */
20
- public createToolMenu(parent: ToolMenuParent): ToolMenu {
21
- const contentElem = document.createElement('div');
22
- let result: ToolMenu|null = null;
23
-
24
- const visible = ReactiveValue.fromCallback(() => {
25
- return this.visibleWidgetContent.get() === result && this.sidebarVisibility.get();
26
- }, [ this.visibleWidgetContent, this.sidebarVisibility ]);
27
-
28
- result = {
29
- visible,
30
- requestShow: () => {
31
- this.setSidebarContent(contentElem);
32
- this.sidebarTitle.set(parent.getTitle());
33
-
34
- // Set visibleWidgetContent first -- this causes the previously visible (if any)
35
- // item to not be sent a shown event.
36
- this.visibleWidgetContent.set(result);
37
- this.sidebarVisibility.set(true);
38
-
39
- this.announceForAccessibility(this.localization.dropdownShown(parent.getTitle()));
40
- },
41
- onActivated: () => {
42
- // TODO: Only request show when in sidebar mode
43
- //result?.requestShow();
44
- },
45
- requestHide: () => {
46
- if (visible.get()) {
47
- this.sidebarVisibility.set(false);
48
- }
49
- },
50
- appendChild: (item: HTMLElement) => {
51
- contentElem.appendChild(item);
52
- },
53
- clearChildren: () => {
54
- contentElem.replaceChildren();
55
- },
56
- destroy: () => {
57
- result?.requestHide();
58
-
59
- if (contentElem.parentElement) {
60
- contentElem.remove();
61
- }
62
-
63
- if (this.visibleWidgetContent.get() === result) {
64
- this.visibleWidgetContent.set(null);
65
- }
66
- },
67
- };
68
-
69
- return result;
70
- }
71
- }
@@ -1,74 +0,0 @@
1
- import ReactiveValue from 'js-draw/src/util/ReactiveValue';
2
-
3
- /**
4
- * A class that manages whether/what content is shown for a widget.
5
- *
6
- * This might be a dropdown menu or a sidebar.
7
- *
8
- * TODO: Shouldn't be an interface, unless always internal.
9
- * @internal
10
- */
11
- export interface ToolMenu {
12
- /**
13
- * Request that the layout manager show the dropdown. In general,
14
- * this makes the content of the dropdown visible.
15
- */
16
- requestShow(): void;
17
-
18
- /**
19
- * Request that the layout manager hide the dropdown. Even after calling this,
20
- * the dropdown may still be visible.
21
- */
22
- requestHide(): void;
23
-
24
- /** Whether the dropdown is visible (not hidden). */
25
- readonly visible: ReactiveValue<boolean>;
26
-
27
- /** Note that the tool associated with this dropdown has been activated. */
28
- onActivated(): void;
29
-
30
- /** Adds the given `child` to the content of the dropdown. */
31
- appendChild(child: HTMLElement): void;
32
-
33
- /** Removes all children from this dropdown. */
34
- clearChildren(): void;
35
-
36
- /**
37
- * Destroy the dropdown and remove it from the document. This should be called when
38
- * the creator of the dropdown is destroyed.
39
- */
40
- destroy(): void;
41
- }
42
-
43
- /**
44
- * Provides information about the element a tool menu is attached to.
45
- */
46
- export interface ToolMenuParent {
47
- /** The dropdown may be added **after** this element. */
48
- target: HTMLElement;
49
-
50
- /**
51
- * @returns the title of the element the dropdown is associated with.
52
- *
53
- * This is used for accessibility announcements (and possibly to display
54
- * a heading).
55
- */
56
- getTitle(): string;
57
-
58
- /**
59
- * Returns true iff the parent is a toplevel element (not contained within
60
- * a ContentLayoutManager of the same type as the current).
61
- */
62
- isToplevel(): boolean;
63
- }
64
-
65
- export interface WidgetContentLayoutManager {
66
- /**
67
- * Creates a tool menu (e.g. a dropdown). The dropdown *may* be added to `parent` or addded
68
- * elsewhere (this depends on the layout manager).
69
- *
70
- * Regardless, `parent` should be a place where an absolutely-positioned dropdown
71
- * element could be added.
72
- */
73
- createToolMenu(parent: ToolMenuParent): ToolMenu;
74
- }
@@ -1,13 +0,0 @@
1
-
2
- export { default as ActionButtonWidget } from './ActionButtonWidget';
3
- export { default as BaseToolWidget } from './BaseToolWidget';
4
- export { default as BaseWidget, ToolbarWidgetTag } from './BaseWidget';
5
-
6
- export { default as PenToolWidget } from './PenToolWidget';
7
- export { default as TextToolWidget } from './TextToolWidget';
8
- export { default as HandToolWidget } from './HandToolWidget';
9
- export { default as SelectionToolWidget } from './SelectionToolWidget';
10
- export { default as EraserToolWidget } from './EraserToolWidget';
11
-
12
- export { default as InsertImageWidget } from './InsertImageWidget';
13
- export { default as DocumentPropertiesWidget } from './DocumentPropertiesWidget';