js-draw 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/cjs/version.js +1 -1
  4. package/dist/mjs/version.mjs +1 -1
  5. package/docs/img/readme-images/js-draw.jpg +0 -0
  6. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  7. package/package.json +5 -4
  8. package/dist-test/test_imports/package-lock.json +0 -13
  9. package/dist-test/test_imports/package.json +0 -12
  10. package/dist-test/test_imports/test-imports.js +0 -11
  11. package/dist-test/test_imports/test-require.cjs +0 -14
  12. package/src/Editor.loadFrom.test.ts +0 -24
  13. package/src/Editor.test.ts +0 -107
  14. package/src/Editor.toSVG.test.ts +0 -294
  15. package/src/Editor.ts +0 -1443
  16. package/src/EditorImage.test.ts +0 -117
  17. package/src/EditorImage.ts +0 -609
  18. package/src/EventDispatcher.test.ts +0 -123
  19. package/src/EventDispatcher.ts +0 -72
  20. package/src/Pointer.ts +0 -183
  21. package/src/SVGLoader.test.ts +0 -114
  22. package/src/SVGLoader.ts +0 -672
  23. package/src/UndoRedoHistory.test.ts +0 -34
  24. package/src/UndoRedoHistory.ts +0 -102
  25. package/src/Viewport.ts +0 -322
  26. package/src/bundle/bundled.ts +0 -7
  27. package/src/commands/Command.ts +0 -45
  28. package/src/commands/Duplicate.ts +0 -75
  29. package/src/commands/Erase.ts +0 -95
  30. package/src/commands/SerializableCommand.ts +0 -49
  31. package/src/commands/UnresolvedCommand.ts +0 -37
  32. package/src/commands/invertCommand.ts +0 -58
  33. package/src/commands/lib.ts +0 -16
  34. package/src/commands/localization.ts +0 -47
  35. package/src/commands/uniteCommands.test.ts +0 -23
  36. package/src/commands/uniteCommands.ts +0 -140
  37. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  38. package/src/components/AbstractComponent.ts +0 -383
  39. package/src/components/BackgroundComponent.test.ts +0 -44
  40. package/src/components/BackgroundComponent.ts +0 -348
  41. package/src/components/ImageComponent.ts +0 -176
  42. package/src/components/RestylableComponent.ts +0 -161
  43. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  44. package/src/components/Stroke.test.ts +0 -137
  45. package/src/components/Stroke.ts +0 -294
  46. package/src/components/TextComponent.test.ts +0 -202
  47. package/src/components/TextComponent.ts +0 -429
  48. package/src/components/UnknownSVGObject.test.ts +0 -10
  49. package/src/components/UnknownSVGObject.ts +0 -60
  50. package/src/components/builders/ArrowBuilder.ts +0 -106
  51. package/src/components/builders/CircleBuilder.ts +0 -100
  52. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  53. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  54. package/src/components/builders/LineBuilder.ts +0 -77
  55. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  56. package/src/components/builders/RectangleBuilder.ts +0 -73
  57. package/src/components/builders/types.ts +0 -15
  58. package/src/components/lib.ts +0 -31
  59. package/src/components/localization.ts +0 -24
  60. package/src/components/util/StrokeSmoother.ts +0 -302
  61. package/src/components/util/describeComponentList.ts +0 -18
  62. package/src/dialogs/makeAboutDialog.ts +0 -82
  63. package/src/inputEvents.ts +0 -143
  64. package/src/lib.ts +0 -91
  65. package/src/localization.ts +0 -34
  66. package/src/localizations/de.ts +0 -146
  67. package/src/localizations/en.ts +0 -8
  68. package/src/localizations/es.ts +0 -74
  69. package/src/localizations/getLocalizationTable.test.ts +0 -27
  70. package/src/localizations/getLocalizationTable.ts +0 -74
  71. package/src/rendering/Display.ts +0 -247
  72. package/src/rendering/RenderablePathSpec.ts +0 -88
  73. package/src/rendering/RenderingStyle.test.ts +0 -68
  74. package/src/rendering/RenderingStyle.ts +0 -55
  75. package/src/rendering/TextRenderingStyle.ts +0 -55
  76. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  77. package/src/rendering/caching/CacheRecord.ts +0 -76
  78. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  79. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  80. package/src/rendering/caching/RenderingCache.ts +0 -66
  81. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  82. package/src/rendering/caching/testUtils.ts +0 -35
  83. package/src/rendering/caching/types.ts +0 -34
  84. package/src/rendering/lib.ts +0 -8
  85. package/src/rendering/localization.ts +0 -20
  86. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  87. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  88. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  89. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  90. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  91. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  92. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  93. package/src/shortcuts/KeyBinding.test.ts +0 -61
  94. package/src/shortcuts/KeyBinding.ts +0 -257
  95. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  96. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  97. package/src/shortcuts/lib.ts +0 -3
  98. package/src/testing/createEditor.ts +0 -11
  99. package/src/testing/getUniquePointerId.ts +0 -18
  100. package/src/testing/lib.ts +0 -3
  101. package/src/testing/sendPenEvent.ts +0 -36
  102. package/src/testing/sendTouchEvent.ts +0 -71
  103. package/src/toolbar/AbstractToolbar.ts +0 -542
  104. package/src/toolbar/DropdownToolbar.ts +0 -220
  105. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  106. package/src/toolbar/EdgeToolbar.ts +0 -543
  107. package/src/toolbar/IconProvider.ts +0 -861
  108. package/src/toolbar/constants.ts +0 -1
  109. package/src/toolbar/lib.ts +0 -6
  110. package/src/toolbar/localization.ts +0 -136
  111. package/src/toolbar/types.ts +0 -13
  112. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  113. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  114. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  115. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  116. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  117. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  118. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  119. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  120. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  121. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  122. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  123. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  124. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  125. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  126. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  127. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  128. package/src/toolbar/widgets/keybindings.ts +0 -19
  129. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  130. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  131. package/src/toolbar/widgets/layout/types.ts +0 -74
  132. package/src/toolbar/widgets/lib.ts +0 -13
  133. package/src/tools/BaseTool.ts +0 -169
  134. package/src/tools/Eraser.test.ts +0 -103
  135. package/src/tools/Eraser.ts +0 -173
  136. package/src/tools/FindTool.test.ts +0 -67
  137. package/src/tools/FindTool.ts +0 -153
  138. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  139. package/src/tools/InputFilter/InputMapper.ts +0 -41
  140. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  141. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  142. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  143. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  144. package/src/tools/PanZoom.test.ts +0 -339
  145. package/src/tools/PanZoom.ts +0 -525
  146. package/src/tools/PasteHandler.ts +0 -94
  147. package/src/tools/Pen.test.ts +0 -260
  148. package/src/tools/Pen.ts +0 -284
  149. package/src/tools/PipetteTool.ts +0 -84
  150. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  151. package/src/tools/SelectionTool/Selection.ts +0 -647
  152. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  153. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  154. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  155. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  156. package/src/tools/SelectionTool/types.ts +0 -11
  157. package/src/tools/SoundUITool.ts +0 -221
  158. package/src/tools/TextTool.ts +0 -339
  159. package/src/tools/ToolController.ts +0 -224
  160. package/src/tools/ToolEnabledGroup.ts +0 -14
  161. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  162. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  163. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  164. package/src/tools/UndoRedoShortcut.ts +0 -24
  165. package/src/tools/keybindings.ts +0 -85
  166. package/src/tools/lib.ts +0 -22
  167. package/src/tools/localization.ts +0 -76
  168. package/src/types.ts +0 -151
  169. package/src/util/ReactiveValue.test.ts +0 -168
  170. package/src/util/ReactiveValue.ts +0 -241
  171. package/src/util/assertions.ts +0 -55
  172. package/src/util/fileToBase64.ts +0 -18
  173. package/src/util/guessKeyCodeFromKey.ts +0 -36
  174. package/src/util/listPrefixMatch.ts +0 -19
  175. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  176. package/src/util/untilNextAnimationFrame.ts +0 -9
  177. package/src/util/waitForAll.ts +0 -18
  178. package/src/util/waitForTimeout.ts +0 -9
  179. package/src/version.test.ts +0 -12
  180. package/src/version.ts +0 -3
  181. package/tools/allLocales.js +0 -4
  182. package/tools/copyREADME.ts +0 -62
@@ -1,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';