js-draw 1.3.0 → 1.4.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 (164) hide show
  1. package/README.md +1 -1
  2. package/dist/Editor.css +55 -13
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +36 -3
  6. package/dist/cjs/Editor.js +63 -26
  7. package/dist/cjs/SVGLoader.js +37 -22
  8. package/dist/cjs/commands/Erase.js +1 -1
  9. package/dist/cjs/commands/UnresolvedCommand.d.ts +1 -1
  10. package/dist/cjs/components/AbstractComponent.d.ts +1 -1
  11. package/dist/cjs/components/AbstractComponent.js +1 -1
  12. package/dist/cjs/components/BackgroundComponent.d.ts +1 -1
  13. package/dist/cjs/components/BackgroundComponent.js +3 -2
  14. package/dist/cjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  15. package/dist/cjs/{EditorImage.js → image/EditorImage.js} +51 -7
  16. package/dist/cjs/image/export/editorImageToSVG.d.ts +8 -0
  17. package/dist/cjs/image/export/editorImageToSVG.js +49 -0
  18. package/dist/cjs/image/export/setExportedSVGSize.d.ts +6 -0
  19. package/dist/cjs/image/export/setExportedSVGSize.js +25 -0
  20. package/dist/cjs/image/lib.d.ts +1 -0
  21. package/dist/cjs/image/lib.js +8 -0
  22. package/dist/cjs/lib.d.ts +1 -1
  23. package/dist/cjs/lib.js +2 -3
  24. package/dist/cjs/localizations/comments.d.ts +6 -0
  25. package/dist/cjs/localizations/comments.js +10 -0
  26. package/dist/cjs/localizations/es.js +68 -48
  27. package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -1
  28. package/dist/cjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  29. package/dist/cjs/rendering/caching/RenderingCacheNode.js +4 -3
  30. package/dist/cjs/rendering/renderers/SVGRenderer.js +8 -19
  31. package/dist/cjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
  32. package/dist/cjs/toolbar/AbstractToolbar.d.ts +11 -3
  33. package/dist/cjs/toolbar/AbstractToolbar.js +20 -6
  34. package/dist/cjs/toolbar/EdgeToolbar.js +5 -6
  35. package/dist/cjs/toolbar/IconProvider.d.ts +1 -0
  36. package/dist/cjs/toolbar/IconProvider.js +43 -0
  37. package/dist/cjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  38. package/dist/cjs/toolbar/widgets/ActionButtonWidget.js +19 -1
  39. package/dist/cjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  40. package/dist/cjs/toolbar/widgets/BaseToolWidget.js +3 -0
  41. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  42. package/dist/cjs/toolbar/widgets/BaseWidget.js +30 -2
  43. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
  44. package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  45. package/dist/cjs/toolbar/widgets/HandToolWidget.js +6 -0
  46. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -1
  47. package/dist/cjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  48. package/dist/cjs/toolbar/widgets/OverflowWidget.js +3 -0
  49. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  50. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +3 -0
  51. package/dist/cjs/tools/BaseTool.d.ts +3 -0
  52. package/dist/cjs/tools/BaseTool.js +13 -2
  53. package/dist/cjs/tools/FindTool.d.ts +1 -0
  54. package/dist/cjs/tools/FindTool.js +4 -1
  55. package/dist/cjs/tools/PanZoom.d.ts +1 -0
  56. package/dist/cjs/tools/PanZoom.js +4 -0
  57. package/dist/cjs/tools/Pen.d.ts +0 -1
  58. package/dist/cjs/tools/Pen.js +1 -4
  59. package/dist/cjs/tools/PipetteTool.d.ts +1 -0
  60. package/dist/cjs/tools/PipetteTool.js +3 -0
  61. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  62. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.js +3 -0
  63. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -0
  64. package/dist/cjs/tools/SelectionTool/Selection.js +44 -8
  65. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  66. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +26 -8
  67. package/dist/cjs/tools/SelectionTool/SelectionTool.js +5 -0
  68. package/dist/cjs/tools/SoundUITool.d.ts +1 -0
  69. package/dist/cjs/tools/SoundUITool.js +4 -1
  70. package/dist/cjs/tools/TextTool.js +2 -2
  71. package/dist/cjs/tools/ToolController.d.ts +2 -0
  72. package/dist/cjs/tools/ToolController.js +13 -2
  73. package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  74. package/dist/cjs/tools/ToolSwitcherShortcut.js +3 -0
  75. package/dist/cjs/types.d.ts +9 -4
  76. package/dist/cjs/types.js +4 -3
  77. package/dist/cjs/util/ReactiveValue.d.ts +1 -1
  78. package/dist/cjs/util/ReactiveValue.js +2 -2
  79. package/dist/cjs/version.js +1 -1
  80. package/dist/mjs/Editor.d.ts +36 -3
  81. package/dist/mjs/Editor.mjs +64 -27
  82. package/dist/mjs/Editor.toSVGAsync.test.d.ts +1 -0
  83. package/dist/mjs/SVGLoader.mjs +37 -22
  84. package/dist/mjs/commands/Erase.mjs +1 -1
  85. package/dist/mjs/commands/UnresolvedCommand.d.ts +1 -1
  86. package/dist/mjs/components/AbstractComponent.d.ts +1 -1
  87. package/dist/mjs/components/AbstractComponent.mjs +1 -1
  88. package/dist/mjs/components/BackgroundComponent.d.ts +1 -1
  89. package/dist/mjs/components/BackgroundComponent.mjs +3 -2
  90. package/dist/mjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  91. package/dist/mjs/{EditorImage.mjs → image/EditorImage.mjs} +51 -7
  92. package/dist/mjs/image/EditorImage.test.d.ts +1 -0
  93. package/dist/mjs/image/export/editorImageToSVG.d.ts +8 -0
  94. package/dist/mjs/image/export/editorImageToSVG.mjs +41 -0
  95. package/dist/mjs/image/export/setExportedSVGSize.d.ts +6 -0
  96. package/dist/mjs/image/export/setExportedSVGSize.mjs +23 -0
  97. package/dist/mjs/image/lib.d.ts +1 -0
  98. package/dist/mjs/image/lib.mjs +1 -0
  99. package/dist/mjs/lib.d.ts +1 -1
  100. package/dist/mjs/lib.mjs +1 -1
  101. package/dist/mjs/localizations/comments.d.ts +6 -0
  102. package/dist/mjs/localizations/comments.mjs +8 -0
  103. package/dist/mjs/localizations/es.mjs +68 -48
  104. package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -1
  105. package/dist/mjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  106. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +4 -3
  107. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +8 -19
  108. package/dist/mjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
  109. package/dist/mjs/toolbar/AbstractToolbar.d.ts +11 -3
  110. package/dist/mjs/toolbar/AbstractToolbar.mjs +20 -6
  111. package/dist/mjs/toolbar/EdgeToolbar.mjs +5 -6
  112. package/dist/mjs/toolbar/IconProvider.d.ts +1 -0
  113. package/dist/mjs/toolbar/IconProvider.mjs +43 -0
  114. package/dist/mjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  115. package/dist/mjs/toolbar/widgets/ActionButtonWidget.mjs +21 -2
  116. package/dist/mjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  117. package/dist/mjs/toolbar/widgets/BaseToolWidget.mjs +3 -0
  118. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  119. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +30 -2
  120. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +1 -1
  121. package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  122. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +6 -0
  123. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -1
  124. package/dist/mjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  125. package/dist/mjs/toolbar/widgets/OverflowWidget.mjs +3 -0
  126. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  127. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +3 -0
  128. package/dist/mjs/tools/BaseTool.d.ts +3 -0
  129. package/dist/mjs/tools/BaseTool.mjs +13 -2
  130. package/dist/mjs/tools/FindTool.d.ts +1 -0
  131. package/dist/mjs/tools/FindTool.mjs +4 -1
  132. package/dist/mjs/tools/PanZoom.d.ts +1 -0
  133. package/dist/mjs/tools/PanZoom.mjs +4 -0
  134. package/dist/mjs/tools/Pen.d.ts +0 -1
  135. package/dist/mjs/tools/Pen.mjs +1 -4
  136. package/dist/mjs/tools/PipetteTool.d.ts +1 -0
  137. package/dist/mjs/tools/PipetteTool.mjs +3 -0
  138. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  139. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.mjs +3 -0
  140. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -0
  141. package/dist/mjs/tools/SelectionTool/Selection.mjs +45 -9
  142. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  143. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +25 -7
  144. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +5 -0
  145. package/dist/mjs/tools/SoundUITool.d.ts +1 -0
  146. package/dist/mjs/tools/SoundUITool.mjs +4 -1
  147. package/dist/mjs/tools/TextTool.mjs +2 -2
  148. package/dist/mjs/tools/ToolController.d.ts +2 -0
  149. package/dist/mjs/tools/ToolController.mjs +13 -2
  150. package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  151. package/dist/mjs/tools/ToolSwitcherShortcut.mjs +3 -0
  152. package/dist/mjs/types.d.ts +9 -4
  153. package/dist/mjs/types.mjs +4 -3
  154. package/dist/mjs/util/ReactiveValue.d.ts +1 -1
  155. package/dist/mjs/util/ReactiveValue.mjs +2 -2
  156. package/dist/mjs/version.mjs +1 -1
  157. package/package.json +5 -5
  158. package/src/Editor.scss +6 -0
  159. package/src/toolbar/EdgeToolbar.scss +19 -2
  160. package/src/tools/SelectionTool/SelectionTool.scss +74 -0
  161. package/src/tools/tools.scss +1 -1
  162. package/src/tools/SelectionTool/SelectionTool.css +0 -35
  163. /package/dist/cjs/{EditorImage.test.d.ts → Editor.toSVGAsync.test.d.ts} +0 -0
  164. /package/dist/{mjs → cjs/image}/EditorImage.test.d.ts +0 -0
@@ -1,68 +1,88 @@
1
1
  import { defaultEditorLocalization } from '../localization.mjs';
2
- // A partial Spanish localization.
2
+ // A partial Spanish localization, created with /scripts/markdownTranslationFormToTs.ts
3
3
  const localization = {
4
4
  ...defaultEditorLocalization,
5
- // Strings for the main editor interface
6
- // (see src/localization.ts)
7
- loading: (percentage) => `Cargando: ${percentage}%...`,
8
- imageEditor: 'Editor de dibujos',
9
- undoAnnouncement: (commandDescription) => `${commandDescription} fue deshecho`,
10
- redoAnnouncement: (commandDescription) => `${commandDescription} fue rehecho`,
11
- undo: 'Deshace',
12
- redo: 'Rehace',
13
- // Strings for the toolbar
14
- // (see src/toolbar/localization.ts)
15
5
  pen: 'Lapiz',
16
6
  eraser: 'Borrador',
17
7
  select: 'Selecciona',
18
- thicknessLabel: 'Tamaño',
19
- colorLabel: 'Color',
20
- doneLoading: 'El cargado terminó',
21
- fontLabel: 'Fuente: ',
22
- anyDevicePanning: 'Mover la pantalla con todo dispotivo',
23
- touchPanning: 'Mover la pantalla con un dedo',
24
- touchPanTool: 'Instrumento de mover la pantalla con un dedo',
25
- outlinedRectanglePen: 'Rectángulo con nada más que un borde',
26
- filledRectanglePen: 'Rectángulo sin borde',
27
- linePen: 'Línea',
28
- arrowPen: 'Flecha',
29
- roundedTipPen: 'Lapiz Redondeado',
30
- selectPenTip: 'Forma de dibuja',
31
8
  handTool: 'Mover',
32
- zoom: 'Zoom',
9
+ image: 'Imagen',
10
+ chooseFile: 'Seleccionar archivo',
11
+ cancel: 'Cancelar',
33
12
  resetView: 'Reiniciar vista',
13
+ thicknessLabel: 'Tamaño',
14
+ fontLabel: 'Fuente:',
15
+ textSize: 'Tamaño',
34
16
  resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado',
35
17
  deleteSelection: 'Borra la selección',
36
18
  duplicateSelection: 'Duplica la selección',
19
+ exit: 'Salir',
20
+ save: 'Guardar',
21
+ undo: 'Deshace',
22
+ redo: 'Rehace',
23
+ selectPenTip: 'Punta',
24
+ selectShape: 'Forma',
37
25
  pickColorFromScreen: 'Selecciona un color de la pantalla',
38
26
  clickToPickColorAnnouncement: 'Haga un clic en la pantalla para seleccionar un color',
39
- dropdownShown(toolName) {
40
- return `Menú por ${toolName} es visible`;
41
- },
42
- dropdownHidden: function (toolName) {
43
- return `Menú por ${toolName} fue ocultado`;
44
- },
45
- colorChangedAnnouncement: function (color) {
46
- return `Color fue cambiado a ${color}`;
47
- },
48
- keyboardPanZoom: 'Mover la pantalla con el teclado',
49
- penTool: function (penId) {
50
- return `Lapiz ${penId}`;
51
- },
27
+ documentProperties: 'Fondo',
28
+ backgroundColor: 'Color de fondo',
29
+ imageWidthOption: 'Ancho',
30
+ imageHeightOption: 'Alto',
31
+ toggleOverflow: 'Más',
32
+ touchPanning: 'Mover la pantalla con un dedo',
33
+ roundedTipPen: 'Lapiz Redondeado',
34
+ arrowPen: 'Flecha',
35
+ linePen: 'Línea',
36
+ outlinedRectanglePen: 'Rectángulo delineado',
37
+ filledRectanglePen: 'Rectángulo sin borde',
38
+ lockRotation: 'Bloquea rotación',
39
+ paste: 'Pegar',
40
+ closeSidebar: (toolName) => `Close sidebar for ${toolName}`,
41
+ dropdownShown: (toolName) => `Menú por ${toolName} es visible`,
42
+ dropdownHidden: (toolName) => { return `Menú por ${toolName} fue ocultado`; },
43
+ zoomLevel: (zoomPercent) => `Zoom: ${zoomPercent}%`,
44
+ colorChangedAnnouncement: (color) => { return `Color fue cambiado a ${color}`; },
45
+ imageSize: (size, units) => `Tamaño del imagen: ${size} ${units}`,
46
+ imageLoadError: (message) => `Error cargando imagen: ${message}`,
47
+ penTool: (penId) => { return `Lapiz ${penId}`; },
52
48
  selectionTool: 'Selecciona',
53
49
  eraserTool: 'Borrador',
50
+ touchPanTool: 'Instrumento de mover la pantalla con un dedo',
51
+ pipetteTool: 'Seleccione un color de la pantalla',
52
+ keyboardPanZoom: 'Mover la pantalla con el teclado',
54
53
  textTool: 'Texto',
55
54
  enterTextToInsert: 'Entra texto',
56
- textSize: 'Tamaño',
55
+ findLabel: 'Buscar',
56
+ toNextMatch: 'Próxima',
57
+ closeDialog: 'Cerrar',
58
+ focusedFoundText: (matchIdx, totalMatches) => `Viewing match ${matchIdx} of ${totalMatches}`,
59
+ anyDevicePanning: 'Mover la pantalla con todo dispotivo',
60
+ copied: (count, description) => `Copied ${count} ${description}`,
61
+ pasted: (count, description) => `Pasted ${count} ${description}`,
62
+ toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
63
+ toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
64
+ transformedElements: (elemCount) => `Transformed ${elemCount} element${elemCount === 1 ? '' : 's'}`,
65
+ resizeOutputCommand: (newSize) => `Resized image to ${newSize.w}x${newSize.h}`,
66
+ addElementAction: (componentDescription) => `Added ${componentDescription}`,
67
+ eraseAction: (componentDescription, numElems) => `Erased ${numElems} ${componentDescription}`,
68
+ duplicateAction: (componentDescription, numElems) => `Duplicated ${numElems} ${componentDescription}`,
69
+ unionOf: (actionDescription, actionCount) => `Union: ${actionCount} ${actionDescription}`,
70
+ inverseOf: (actionDescription) => `Inverse of ${actionDescription}`,
71
+ rotatedBy: (degrees) => `Rotated by ${Math.abs(degrees)} degrees ${degrees < 0 ? 'clockwise' : 'counter-clockwise'}`,
72
+ selectedElements: (count) => `Selected ${count} element${count === 1 ? '' : 's'}`,
73
+ filledBackgroundWithColor: (color) => `Filled background (${color})`,
74
+ text: (text) => `Text object: ${text}`,
75
+ imageNode: (label) => `Image: ${label}`,
76
+ restyledElement: (elementDescription) => `Restyled ${elementDescription}`,
77
+ pathNodeCount: (count) => `There are ${count} visible path objects.`,
78
+ textNodeCount: (count) => `There are ${count} visible text nodes.`,
79
+ imageNodeCount: (nodeCount) => `There are ${nodeCount} visible image nodes.`,
80
+ textNode: (content) => `Text: ${content}`,
57
81
  rerenderAsText: 'Redibuja la pantalla al texto',
58
- lockRotation: 'Bloquea rotación',
59
- image: 'Imagen',
60
- imageSize: (size, units) => `Tamaño del imagen: ${size} ${units}`,
61
- imageLoadError: (message) => `Error cargando imagen: ${message}`,
62
- toggleOverflow: 'Más',
63
- documentProperties: 'Fondo',
64
- imageWidthOption: 'Ancho',
65
- imageHeightOption: 'Alto',
66
- backgroundColor: 'Color de fondo: '
82
+ loading: (percentage) => `Cargando: ${percentage}%...`,
83
+ imageEditor: 'Editor de dibujos',
84
+ doneLoading: 'El cargado terminó',
85
+ undoAnnouncement: (commandDescription) => `${commandDescription} fue deshecho`,
86
+ redoAnnouncement: (commandDescription) => `${commandDescription} fue rehecho`,
67
87
  };
68
88
  export default localization;
@@ -1,4 +1,4 @@
1
- import { ImageNode } from '../../EditorImage';
1
+ import { ImageNode } from '../../image/EditorImage';
2
2
  import Viewport from '../../Viewport';
3
3
  import AbstractRenderer from '../renderers/AbstractRenderer';
4
4
  import { CacheProps } from './types';
@@ -1,4 +1,4 @@
1
- import { ImageNode } from '../../EditorImage';
1
+ import { ImageNode } from '../../image/EditorImage';
2
2
  import { Rect2 } from '@js-draw/math';
3
3
  import Viewport from '../../Viewport';
4
4
  import AbstractRenderer from '../renderers/AbstractRenderer';
@@ -1,5 +1,5 @@
1
1
  // A cache record with sub-nodes.
2
- import { sortLeavesByZIndex } from '../../EditorImage.mjs';
2
+ import { sortLeavesByZIndex } from '../../image/EditorImage.mjs';
3
3
  import { Rect2, Color4 } from '@js-draw/math';
4
4
  // 3x3 divisions for each node.
5
5
  const cacheDivisionSize = 3;
@@ -42,11 +42,12 @@ export default class RenderingCacheNode {
42
42
  // Generates children, if missing.
43
43
  generateChildren() {
44
44
  if (this.instantiatedChildren.length === 0) {
45
- const childRects = this.region.divideIntoGrid(cacheDivisionSize, cacheDivisionSize);
46
- if (this.region.size.x === 0 || this.region.size.y === 0) {
45
+ if (this.region.size.x / cacheDivisionSize === 0 || this.region.size.y / cacheDivisionSize === 0) {
47
46
  console.warn('Cache element has zero size! Not generating children.');
48
47
  return;
49
48
  }
49
+ const childRects = this.region.divideIntoGrid(cacheDivisionSize, cacheDivisionSize);
50
+ console.assert(childRects.length === cacheDivisionSize * cacheDivisionSize, 'Warning: divideIntoGrid created the wrong number of subrectangles!');
50
51
  for (const rect of childRects) {
51
52
  const child = new RenderingCacheNode(rect, this.cacheState);
52
53
  child.parent = this;
@@ -129,26 +129,18 @@ export default class SVGRenderer extends AbstractRenderer {
129
129
  }
130
130
  // Apply [elemTransform] to [elem]. Uses both a `matrix` and `.x`, `.y` properties if `setXY` is true.
131
131
  // Otherwise, just uses a `matrix`.
132
- transformFrom(elemTransform, elem, inCanvasSpace = false, setXY = true) {
133
- let transform = !inCanvasSpace ? this.getCanvasToScreenTransform().rightMul(elemTransform) : elemTransform;
134
- const translation = transform.transformVec2(Vec2.zero);
135
- if (setXY) {
136
- transform = transform.rightMul(Mat33.translation(translation.times(-1)));
137
- }
132
+ transformFrom(elemTransform, elem, inCanvasSpace = false) {
133
+ const transform = !inCanvasSpace ? this.getCanvasToScreenTransform().rightMul(elemTransform) : elemTransform;
138
134
  if (!transform.eq(Mat33.identity)) {
139
- elem.style.transform = `matrix(
140
- ${transform.a1}, ${transform.b1},
141
- ${transform.a2}, ${transform.b2},
142
- ${transform.a3}, ${transform.b3}
143
- )`;
135
+ const matrixString = transform.toCSSMatrix();
136
+ elem.style.transform = matrixString;
137
+ // Most browsers round the components of CSS transforms.
138
+ // Include a higher precision copy of the element's transform.
139
+ elem.setAttribute('data-highp-transform', matrixString);
144
140
  }
145
141
  else {
146
142
  elem.style.transform = '';
147
143
  }
148
- if (setXY) {
149
- elem.setAttribute('x', `${toRoundedString(translation.x)}`);
150
- elem.setAttribute('y', `${toRoundedString(translation.y)}`);
151
- }
152
144
  }
153
145
  drawText(text, transform, style) {
154
146
  const applyTextStyles = (elem, style) => {
@@ -184,10 +176,7 @@ export default class SVGRenderer extends AbstractRenderer {
184
176
  if (!this.textContainer) {
185
177
  const container = document.createElementNS(svgNameSpace, 'text');
186
178
  container.appendChild(document.createTextNode(text));
187
- // Don't set .x/.y properties (just use .style.transform).
188
- // Child nodes aren't translated by .x/.y properties, but are by .style.transform.
189
- const setXY = false;
190
- this.transformFrom(transform, container, true, setXY);
179
+ this.transformFrom(transform, container, true);
191
180
  applyTextStyles(container, style);
192
181
  this.elem.appendChild(container);
193
182
  this.objectElems?.push(container);
@@ -0,0 +1 @@
1
+ export {};
@@ -8,6 +8,10 @@ export interface SpacerOptions {
8
8
  minSize: string;
9
9
  maxSize: string;
10
10
  }
11
+ export type ToolbarActionButtonOptions = {
12
+ mustBeToplevel?: boolean;
13
+ autoDisableInReadOnlyEditors?: boolean;
14
+ };
11
15
  export default abstract class AbstractToolbar {
12
16
  #private;
13
17
  protected editor: Editor;
@@ -91,18 +95,22 @@ export default abstract class AbstractToolbar {
91
95
  * @see
92
96
  * {@link addActionButton}
93
97
  */
94
- protected makeActionButton(title: string | ActionButtonIcon, command: () => void, mustBeToplevel?: boolean): BaseWidget;
98
+ protected makeActionButton(title: string | ActionButtonIcon, command: () => void, options?: ToolbarActionButtonOptions | boolean): BaseWidget;
95
99
  /**
96
100
  * Adds an action button with `title` to this toolbar (or to the given `parent` element).
97
101
  *
102
+ * `options` can either be an object with properties `mustBeToplevel` and/or
103
+ * `autoDisableInReadOnlyEditors` or a boolean value. If a boolean, it is interpreted
104
+ * as being the value of `mustBeToplevel`.
105
+ *
98
106
  * @return The added button.
99
107
  */
100
- addActionButton(title: string | ActionButtonIcon, command: () => void, mustBeToplevel?: boolean): BaseWidget;
108
+ addActionButton(title: string | ActionButtonIcon, command: () => void, options?: ToolbarActionButtonOptions | boolean): BaseWidget;
101
109
  /**
102
110
  * Like {@link addActionButton}, except associates `tags` with the button that allow
103
111
  * different toolbar styles to give the button tag-dependent styles.
104
112
  */
105
- addTaggedActionButton(tags: (ToolbarWidgetTag | string)[], title: string | ActionButtonIcon, command: () => void, mustBeToplevel?: boolean): BaseWidget;
113
+ addTaggedActionButton(tags: (ToolbarWidgetTag | string)[], title: string | ActionButtonIcon, command: () => void, options?: ToolbarActionButtonOptions | boolean): BaseWidget;
106
114
  /**
107
115
  * Adds a save button that, when clicked, calls `saveCallback`.
108
116
  *
@@ -230,7 +230,15 @@ class AbstractToolbar {
230
230
  * @see
231
231
  * {@link addActionButton}
232
232
  */
233
- makeActionButton(title, command, mustBeToplevel = true) {
233
+ makeActionButton(title, command, options = true) {
234
+ // Parse options
235
+ if (typeof options === 'boolean') {
236
+ options = {
237
+ mustBeToplevel: options,
238
+ };
239
+ }
240
+ const mustBeToplevel = options.mustBeToplevel ?? true;
241
+ const autoDisableInReadOnlyEditors = options.autoDisableInReadOnlyEditors ?? true;
234
242
  const titleString = typeof title === 'string' ? title : title.label;
235
243
  const widgetId = 'action-button';
236
244
  const makeIcon = () => {
@@ -239,16 +247,20 @@ class AbstractToolbar {
239
247
  }
240
248
  return title.icon;
241
249
  };
242
- const widget = new ActionButtonWidget(this.editor, widgetId, makeIcon, titleString, command, this.editor.localization, mustBeToplevel);
250
+ const widget = new ActionButtonWidget(this.editor, widgetId, makeIcon, titleString, command, this.editor.localization, mustBeToplevel, autoDisableInReadOnlyEditors);
243
251
  return widget;
244
252
  }
245
253
  /**
246
254
  * Adds an action button with `title` to this toolbar (or to the given `parent` element).
247
255
  *
256
+ * `options` can either be an object with properties `mustBeToplevel` and/or
257
+ * `autoDisableInReadOnlyEditors` or a boolean value. If a boolean, it is interpreted
258
+ * as being the value of `mustBeToplevel`.
259
+ *
248
260
  * @return The added button.
249
261
  */
250
- addActionButton(title, command, mustBeToplevel = true) {
251
- const widget = this.makeActionButton(title, command, mustBeToplevel);
262
+ addActionButton(title, command, options = true) {
263
+ const widget = this.makeActionButton(title, command, options);
252
264
  this.addWidget(widget);
253
265
  return widget;
254
266
  }
@@ -256,8 +268,8 @@ class AbstractToolbar {
256
268
  * Like {@link addActionButton}, except associates `tags` with the button that allow
257
269
  * different toolbar styles to give the button tag-dependent styles.
258
270
  */
259
- addTaggedActionButton(tags, title, command, mustBeToplevel = true) {
260
- const widget = this.makeActionButton(title, command, mustBeToplevel);
271
+ addTaggedActionButton(tags, title, command, options = true) {
272
+ const widget = this.makeActionButton(title, command, options);
261
273
  widget.setTags(tags);
262
274
  this.addWidget(widget);
263
275
  return widget;
@@ -303,6 +315,8 @@ class AbstractToolbar {
303
315
  icon: this.editor.icons.makeCloseIcon(),
304
316
  }, () => {
305
317
  exitCallback();
318
+ }, {
319
+ autoDisableInReadOnlyEditors: false,
306
320
  });
307
321
  }
308
322
  /**
@@ -191,7 +191,6 @@ export default class EdgeToolbar extends AbstractToolbar {
191
191
  };
192
192
  const actionRowBBox = this.toolbarActionRow.getBoundingClientRect();
193
193
  const toolbarRowBBox = this.toolbarToolRow.getBoundingClientRect();
194
- const inSameRow = actionRowBBox.y === toolbarRowBBox.y;
195
194
  const onDifferentRows = actionRowBBox.y + actionRowBBox.height <= toolbarRowBBox.y;
196
195
  if (onDifferentRows) {
197
196
  this.toolbarContainer.classList.remove('one-row');
@@ -201,11 +200,11 @@ export default class EdgeToolbar extends AbstractToolbar {
201
200
  }
202
201
  if (this.toolbarToolRow.clientWidth < this.toolbarToolRow.scrollWidth) {
203
202
  this.toolbarToolRow.classList.add('has-scroll');
204
- // If both button areas are in the same row, don't change the padding --
205
- // it could lead to an endless loop of reseize events.
206
- if (!inSameRow) {
207
- setExtraPadding();
208
- }
203
+ // Note: This can potentially change the size of the tool row.
204
+ // Because this is run inside of a ResizeObserver callback, special
205
+ // care must be taken to ensure that this change doesn't re-trigger
206
+ // the resize observer.
207
+ setExtraPadding();
209
208
  }
210
209
  else {
211
210
  this.toolbarToolRow.classList.remove('has-scroll', 'extra-padding');
@@ -38,6 +38,7 @@ export default class IconProvider {
38
38
  makeDropdownIcon(): IconElemType;
39
39
  makeEraserIcon(eraserSize?: number): IconElemType;
40
40
  makeSelectionIcon(): IconElemType;
41
+ makeRotateIcon(): IconElemType;
41
42
  makeHandToolIcon(): IconElemType;
42
43
  makeTouchPanningIcon(): IconElemType;
43
44
  /** Unused by js-draw. @deprecated */
@@ -164,6 +164,49 @@ class IconProvider {
164
164
  icon.setAttribute('viewBox', '0 0 100 100');
165
165
  return icon;
166
166
  }
167
+ makeRotateIcon() {
168
+ const icon = document.createElementNS(svgNamespace, 'svg');
169
+ icon.innerHTML = `
170
+ <defs>
171
+ <marker
172
+ id="arrow-marker"
173
+ viewBox="0 0 10 10"
174
+ refX="3" refY="5"
175
+ markerWidth="3" markerHeight="3"
176
+ orient="auto-start-reverse"
177
+ >
178
+ <path
179
+ d="M0,0 L8,5 L0,10z"
180
+ fill="var(--icon-color)"
181
+ />
182
+ </marker>
183
+ </defs>
184
+
185
+ <path
186
+ marker-start="url(#arrow-marker)"
187
+ d="
188
+ M20,20
189
+ A30,30 0 1 1 80 80
190
+ "
191
+ fill="none"
192
+ stroke="var(--icon-color)"
193
+ stroke-width="12"
194
+ />
195
+ <path
196
+ d="
197
+ M80,80
198
+ A30,30 0 1 1 20 20
199
+ "
200
+ fill="none"
201
+ stroke="var(--icon-color)"
202
+ stroke-width="12"
203
+ stroke-dasharray="30 10 20 10 20 10 10"
204
+ style="stroke-linecap: butt;"
205
+ />
206
+ `;
207
+ icon.setAttribute('viewBox', '-5 -5 110 110');
208
+ return icon;
209
+ }
167
210
  makeHandToolIcon() {
168
211
  const fill = 'none';
169
212
  const strokeColor = 'var(--icon-color)';
@@ -2,11 +2,13 @@ import Editor from '../../Editor';
2
2
  import { ToolbarLocalization } from '../localization';
3
3
  import BaseWidget from './BaseWidget';
4
4
  export default class ActionButtonWidget extends BaseWidget {
5
+ #private;
5
6
  protected makeIcon: () => Element | null;
6
7
  protected title: string;
7
8
  protected clickAction: () => void;
8
9
  protected mustBeToplevel: boolean;
9
- constructor(editor: Editor, id: string, makeIcon: () => Element | null, title: string, clickAction: () => void, localizationTable?: ToolbarLocalization, mustBeToplevel?: boolean);
10
+ constructor(editor: Editor, id: string, makeIcon: () => Element | null, title: string, clickAction: () => void, localizationTable?: ToolbarLocalization, mustBeToplevel?: boolean, autoDisableInReadOnlyEditors?: boolean);
11
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
10
12
  protected handleClick(): void;
11
13
  protected getTitle(): string;
12
14
  protected createIcon(): Element | null;
@@ -1,11 +1,28 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _ActionButtonWidget_autoDisableInReadOnlyEditors;
1
13
  import BaseWidget from './BaseWidget.mjs';
2
- export default class ActionButtonWidget extends BaseWidget {
3
- constructor(editor, id, makeIcon, title, clickAction, localizationTable, mustBeToplevel = false) {
14
+ class ActionButtonWidget extends BaseWidget {
15
+ constructor(editor, id, makeIcon, title, clickAction, localizationTable, mustBeToplevel = false, autoDisableInReadOnlyEditors = true) {
4
16
  super(editor, id, localizationTable);
5
17
  this.makeIcon = makeIcon;
6
18
  this.title = title;
7
19
  this.clickAction = clickAction;
8
20
  this.mustBeToplevel = mustBeToplevel;
21
+ _ActionButtonWidget_autoDisableInReadOnlyEditors.set(this, void 0);
22
+ __classPrivateFieldSet(this, _ActionButtonWidget_autoDisableInReadOnlyEditors, autoDisableInReadOnlyEditors, "f");
23
+ }
24
+ shouldAutoDisableInReadOnlyEditor() {
25
+ return __classPrivateFieldGet(this, _ActionButtonWidget_autoDisableInReadOnlyEditors, "f");
9
26
  }
10
27
  handleClick() {
11
28
  this.clickAction();
@@ -23,3 +40,5 @@ export default class ActionButtonWidget extends BaseWidget {
23
40
  return this.mustBeToplevel;
24
41
  }
25
42
  }
43
+ _ActionButtonWidget_autoDisableInReadOnlyEditors = new WeakMap();
44
+ export default ActionButtonWidget;
@@ -6,6 +6,7 @@ import BaseWidget from './BaseWidget';
6
6
  export default abstract class BaseToolWidget extends BaseWidget {
7
7
  protected targetTool: BaseTool;
8
8
  constructor(editor: Editor, targetTool: BaseTool, id: string, localizationTable?: ToolbarLocalization);
9
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
9
10
  protected handleClick(): void;
10
11
  protected onKeyPress(event: KeyPressEvent): boolean;
11
12
  addTo(parent: HTMLElement): HTMLElement;
@@ -34,6 +34,9 @@ export default class BaseToolWidget extends BaseWidget {
34
34
  }
35
35
  });
36
36
  }
37
+ shouldAutoDisableInReadOnlyEditor() {
38
+ return !this.targetTool.canReceiveInputInReadOnlyEditor();
39
+ }
37
40
  handleClick() {
38
41
  if (this.hasDropdown) {
39
42
  if (!this.targetTool.isEnabled()) {
@@ -29,6 +29,11 @@ export default abstract class BaseWidget {
29
29
  private toplevel;
30
30
  protected readonly localizationTable: ToolbarLocalization;
31
31
  constructor(editor: Editor, id: string, localizationTable?: ToolbarLocalization);
32
+ /**
33
+ * Should return a constant true or false value. If true (the default),
34
+ * this widget must be automatically disabled when its editor is read-only.
35
+ */
36
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
32
37
  getId(): string;
33
38
  /**
34
39
  * Note: Tags should be set *before* a tool widget is added to a toolbar.
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _BaseWidget_hasDropdown, _BaseWidget_tags;
12
+ var _BaseWidget_hasDropdown, _BaseWidget_disabledDueToReadOnlyEditor, _BaseWidget_tags, _BaseWidget_readOnlyListener;
13
13
  import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler.mjs';
14
14
  import { keyPressEventFromHTMLEvent, keyUpEventFromHTMLEvent } from '../../inputEvents.mjs';
15
15
  import { toolbarCSSPrefix } from '../constants.mjs';
@@ -30,11 +30,16 @@ class BaseWidget {
30
30
  this.id = id;
31
31
  this.dropdown = null;
32
32
  _BaseWidget_hasDropdown.set(this, void 0);
33
+ // True iff this widget is disabled.
33
34
  this.disabled = false;
35
+ // True iff this widget is currently disabled because the editor is read only
36
+ _BaseWidget_disabledDueToReadOnlyEditor.set(this, false);
34
37
  _BaseWidget_tags.set(this, []);
35
38
  // Maps subWidget IDs to subWidgets.
36
39
  this.subWidgets = {};
37
40
  this.toplevel = true;
41
+ // Listens for changes in whether the editor is read-only
42
+ _BaseWidget_readOnlyListener.set(this, null);
38
43
  this.localizationTable = localizationTable ?? editor.localization;
39
44
  // Default layout manager
40
45
  const defaultLayoutManager = new DropdownLayoutManager((text) => this.editor.announceForAccessibility(text), this.localizationTable);
@@ -62,6 +67,13 @@ class BaseWidget {
62
67
  toolbarShortcutHandlers[0].registerListener(event => this.onKeyPress(event));
63
68
  }
64
69
  }
70
+ /**
71
+ * Should return a constant true or false value. If true (the default),
72
+ * this widget must be automatically disabled when its editor is read-only.
73
+ */
74
+ shouldAutoDisableInReadOnlyEditor() {
75
+ return true;
76
+ }
65
77
  getId() {
66
78
  return this.id;
67
79
  }
@@ -256,6 +268,19 @@ class BaseWidget {
256
268
  if (this.container.parentElement) {
257
269
  this.container.remove();
258
270
  }
271
+ __classPrivateFieldSet(this, _BaseWidget_readOnlyListener, this.editor.isReadOnlyReactiveValue().onUpdateAndNow(readOnly => {
272
+ if (readOnly && this.shouldAutoDisableInReadOnlyEditor() && !this.disabled) {
273
+ this.setDisabled(true);
274
+ __classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, true, "f");
275
+ if (__classPrivateFieldGet(this, _BaseWidget_hasDropdown, "f")) {
276
+ this.dropdown?.requestHide();
277
+ }
278
+ }
279
+ else if (!readOnly && __classPrivateFieldGet(this, _BaseWidget_disabledDueToReadOnlyEditor, "f")) {
280
+ __classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, false, "f");
281
+ this.setDisabled(false);
282
+ }
283
+ }), "f");
259
284
  parent.appendChild(this.container);
260
285
  return this.container;
261
286
  }
@@ -273,6 +298,8 @@ class BaseWidget {
273
298
  }
274
299
  remove() {
275
300
  this.container.remove();
301
+ __classPrivateFieldGet(this, _BaseWidget_readOnlyListener, "f")?.remove();
302
+ __classPrivateFieldSet(this, _BaseWidget_readOnlyListener, null, "f");
276
303
  }
277
304
  updateIcon() {
278
305
  let newIcon = this.createIcon();
@@ -289,6 +316,7 @@ class BaseWidget {
289
316
  }
290
317
  setDisabled(disabled) {
291
318
  this.disabled = disabled;
319
+ __classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, false, "f");
292
320
  if (this.disabled) {
293
321
  this.button.classList.add('disabled');
294
322
  this.button.setAttribute('aria-disabled', 'true');
@@ -409,5 +437,5 @@ class BaseWidget {
409
437
  }
410
438
  }
411
439
  }
412
- _BaseWidget_hasDropdown = new WeakMap(), _BaseWidget_tags = new WeakMap();
440
+ _BaseWidget_hasDropdown = new WeakMap(), _BaseWidget_disabledDueToReadOnlyEditor = new WeakMap(), _BaseWidget_tags = new WeakMap(), _BaseWidget_readOnlyListener = new WeakMap();
413
441
  export default BaseWidget;
@@ -1,7 +1,7 @@
1
1
  import Erase from '../../commands/Erase.mjs';
2
2
  import uniteCommands from '../../commands/uniteCommands.mjs';
3
3
  import BackgroundComponent, { BackgroundType } from '../../components/BackgroundComponent.mjs';
4
- import { EditorImageEventType } from '../../EditorImage.mjs';
4
+ import { EditorImageEventType } from '../../image/EditorImage.mjs';
5
5
  import { Rect2 } from '@js-draw/math';
6
6
  import { EditorEventType } from '../../types.mjs';
7
7
  import { toolbarCSSPrefix } from '../constants.mjs';
@@ -7,6 +7,7 @@ export default class HandToolWidget extends BaseToolWidget {
7
7
  protected overridePanZoomTool: PanZoom;
8
8
  private allowTogglingBaseTool;
9
9
  constructor(editor: Editor, overridePanZoomTool: PanZoom, localizationTable: ToolbarLocalization);
10
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
10
11
  private static getPrimaryHandTool;
11
12
  protected getTitle(): string;
12
13
  protected createIcon(): Element;
@@ -75,6 +75,9 @@ class HandModeWidget extends BaseWidget {
75
75
  });
76
76
  this.setSelected(false);
77
77
  }
78
+ shouldAutoDisableInReadOnlyEditor() {
79
+ return false;
80
+ }
78
81
  setModeFlag(enabled) {
79
82
  this.tool.setModeEnabled(this.flag, enabled);
80
83
  }
@@ -113,6 +116,9 @@ export default class HandToolWidget extends BaseToolWidget {
113
116
  this.addSubWidget(touchPanningWidget);
114
117
  this.addSubWidget(rotationLockWidget);
115
118
  }
119
+ shouldAutoDisableInReadOnlyEditor() {
120
+ return false;
121
+ }
116
122
  static getPrimaryHandTool(toolController) {
117
123
  const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom);
118
124
  const primaryPanZoomTool = primaryPanZoomToolList[0];
@@ -1,6 +1,6 @@
1
1
  import ImageComponent from '../../components/ImageComponent.mjs';
2
2
  import Erase from '../../commands/Erase.mjs';
3
- import EditorImage from '../../EditorImage.mjs';
3
+ import EditorImage from '../../image/EditorImage.mjs';
4
4
  import uniteCommands from '../../commands/uniteCommands.mjs';
5
5
  import SelectionTool from '../../tools/SelectionTool/SelectionTool.mjs';
6
6
  import { Mat33 } from '@js-draw/math';
@@ -5,6 +5,7 @@ export default class OverflowWidget extends BaseWidget {
5
5
  private overflowChildren;
6
6
  private overflowContainer;
7
7
  constructor(editor: Editor, localizationTable?: ToolbarLocalization);
8
+ protected shouldAutoDisableInReadOnlyEditor(): boolean;
8
9
  protected getTitle(): string;
9
10
  protected createIcon(): Element | null;
10
11
  protected handleClick(): void;
@@ -8,6 +8,9 @@ export default class OverflowWidget extends BaseWidget {
8
8
  this.container.classList.add('dropdownShowable');
9
9
  this.overflowContainer ??= document.createElement('div');
10
10
  }
11
+ shouldAutoDisableInReadOnlyEditor() {
12
+ return false;
13
+ }
11
14
  getTitle() {
12
15
  return this.localizationTable.toggleOverflow;
13
16
  }