js-draw 1.4.0 → 1.5.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 (47) hide show
  1. package/README.md +1 -1
  2. package/dist/Editor.css +11 -10
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/components/BackgroundComponent.js +12 -6
  6. package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -2
  7. package/dist/cjs/components/SVGGlobalAttributesObject.js +10 -14
  8. package/dist/cjs/image/export/adjustExportedSVGSize.d.ts +6 -0
  9. package/dist/cjs/image/export/{setExportedSVGSize.js → adjustExportedSVGSize.js} +4 -7
  10. package/dist/cjs/image/export/editorImageToSVG.d.ts +1 -1
  11. package/dist/cjs/image/export/editorImageToSVG.js +22 -8
  12. package/dist/cjs/rendering/renderers/AbstractRenderer.d.ts +1 -0
  13. package/dist/cjs/rendering/renderers/AbstractRenderer.js +8 -0
  14. package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +28 -1
  15. package/dist/cjs/rendering/renderers/SVGRenderer.js +58 -7
  16. package/dist/cjs/toolbar/AbstractToolbar.d.ts +9 -3
  17. package/dist/cjs/toolbar/AbstractToolbar.js +11 -4
  18. package/dist/cjs/toolbar/AbstractToolbar.test.d.ts +1 -0
  19. package/dist/cjs/toolbar/EdgeToolbar.js +2 -2
  20. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +2 -1
  21. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +6 -2
  22. package/dist/cjs/tools/SelectionTool/Selection.js +4 -2
  23. package/dist/cjs/version.js +1 -1
  24. package/dist/mjs/components/BackgroundComponent.mjs +12 -6
  25. package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -2
  26. package/dist/mjs/components/SVGGlobalAttributesObject.mjs +10 -14
  27. package/dist/mjs/image/export/adjustExportedSVGSize.d.ts +6 -0
  28. package/dist/mjs/image/export/{setExportedSVGSize.mjs → adjustExportedSVGSize.mjs} +4 -7
  29. package/dist/mjs/image/export/editorImageToSVG.d.ts +1 -1
  30. package/dist/mjs/image/export/editorImageToSVG.mjs +23 -9
  31. package/dist/mjs/rendering/renderers/AbstractRenderer.d.ts +1 -0
  32. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +8 -0
  33. package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +28 -1
  34. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +58 -7
  35. package/dist/mjs/toolbar/AbstractToolbar.d.ts +9 -3
  36. package/dist/mjs/toolbar/AbstractToolbar.mjs +11 -4
  37. package/dist/mjs/toolbar/AbstractToolbar.test.d.ts +1 -0
  38. package/dist/mjs/toolbar/EdgeToolbar.mjs +2 -2
  39. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +2 -1
  40. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +6 -2
  41. package/dist/mjs/tools/SelectionTool/Selection.mjs +4 -2
  42. package/dist/mjs/version.mjs +1 -1
  43. package/package.json +2 -2
  44. package/src/toolbar/EdgeToolbar.scss +7 -4
  45. package/src/tools/SelectionTool/SelectionTool.scss +2 -2
  46. package/dist/cjs/image/export/setExportedSVGSize.d.ts +0 -6
  47. package/dist/mjs/image/export/setExportedSVGSize.d.ts +0 -6
@@ -0,0 +1 @@
1
+ export {};
@@ -235,11 +235,11 @@ class EdgeToolbar extends AbstractToolbar_1.default {
235
235
  widget.removeCSSClassFromContainer('label-right');
236
236
  if (tags.includes(BaseWidget_1.ToolbarWidgetTag.Save)) {
237
237
  widget.addCSSClassToContainer('label-inline');
238
- widget.addCSSClassToContainer('label-right');
238
+ widget.addCSSClassToContainer('label-left');
239
239
  }
240
240
  if (tags.includes(BaseWidget_1.ToolbarWidgetTag.Exit)) {
241
241
  widget.addCSSClassToContainer('label-inline');
242
- widget.addCSSClassToContainer('label-left');
242
+ widget.addCSSClassToContainer('label-right');
243
243
  }
244
244
  }
245
245
  addWidgetInternal(widget) {
@@ -2,8 +2,9 @@ import { KeyPressEvent } from '../../inputEvents';
2
2
  import Editor from '../../Editor';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import ActionButtonWidget from './ActionButtonWidget';
5
+ import { ActionButtonIcon } from '../types';
5
6
  declare class SaveActionWidget extends ActionButtonWidget {
6
- constructor(editor: Editor, localization: ToolbarLocalization, saveCallback: () => void);
7
+ constructor(editor: Editor, localization: ToolbarLocalization, saveCallback: () => void, labelOverride?: Partial<ActionButtonIcon>);
7
8
  protected shouldAutoDisableInReadOnlyEditor(): boolean;
8
9
  protected onKeyPress(event: KeyPressEvent): boolean;
9
10
  mustBeInToplevelMenu(): boolean;
@@ -7,8 +7,12 @@ const ActionButtonWidget_1 = __importDefault(require("./ActionButtonWidget"));
7
7
  const BaseWidget_1 = require("./BaseWidget");
8
8
  const keybindings_1 = require("./keybindings");
9
9
  class SaveActionWidget extends ActionButtonWidget_1.default {
10
- constructor(editor, localization, saveCallback) {
11
- super(editor, 'save-button', editor.icons.makeSaveIcon, localization.save, saveCallback);
10
+ constructor(editor, localization, saveCallback, labelOverride = {}) {
11
+ super(editor, 'save-button',
12
+ // Creates an icon
13
+ () => {
14
+ return labelOverride.icon ?? editor.icons.makeSaveIcon();
15
+ }, labelOverride.label ?? localization.save, saveCallback);
12
16
  this.setTags([BaseWidget_1.ToolbarWidgetTag.Save]);
13
17
  }
14
18
  shouldAutoDisableInReadOnlyEditor() {
@@ -157,8 +157,10 @@ class Selection {
157
157
  // Reset for the next drag
158
158
  this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
159
159
  this.transform = math_1.Mat33.identity;
160
- // Make the commands undo-able
161
- await this.editor.dispatch(new Selection.ApplyTransformationCommand(this, selectedElems, fullTransform));
160
+ // Make the commands undo-able, but only if the transform is non-empty.
161
+ if (!fullTransform.eq(math_1.Mat33.identity)) {
162
+ await this.editor.dispatch(new Selection.ApplyTransformationCommand(this, selectedElems, fullTransform));
163
+ }
162
164
  // Clear renderings of any in-progress transformations
163
165
  const wetInkRenderer = this.editor.display.getWetInkRenderer();
164
166
  wetInkRenderer.clear();
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = {
4
- number: '1.4.0',
4
+ number: '1.5.0',
5
5
  };
@@ -190,6 +190,14 @@ export default class BackgroundComponent extends AbstractComponent {
190
190
  if (this.backgroundType === BackgroundType.None) {
191
191
  return;
192
192
  }
193
+ // If visibleRect is null, components should render everything.
194
+ // In that case, a full render is being done.
195
+ const mustRender = !visibleRect;
196
+ // If this.fillsScreen, the visibleRect needs to be known.
197
+ // Use the screen rect.
198
+ if (this.fillsScreen) {
199
+ visibleRect ??= canvas.getVisibleRect();
200
+ }
193
201
  const clip = this.backgroundType === BackgroundType.Grid;
194
202
  const contentBBox = this.getFullBoundingBox(visibleRect);
195
203
  canvas.startObject(contentBBox, clip);
@@ -197,13 +205,11 @@ export default class BackgroundComponent extends AbstractComponent {
197
205
  // If the rectangle for this region contains the visible rect,
198
206
  // we can fill the entire visible rectangle (which may be more efficient than
199
207
  // filling the entire region for this.)
200
- if (visibleRect) {
201
- const intersection = visibleRect.intersection(contentBBox);
202
- if (intersection) {
203
- canvas.fillRect(intersection, this.mainColor);
204
- }
208
+ const intersection = visibleRect?.intersection(contentBBox);
209
+ if (intersection) {
210
+ canvas.fillRect(intersection, this.mainColor);
205
211
  }
206
- else {
212
+ else if (mustRender) {
207
213
  canvas.fillRect(contentBBox, this.mainColor);
208
214
  }
209
215
  }
@@ -4,8 +4,8 @@ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent';
4
4
  import { ImageComponentLocalization } from './localization';
5
5
  type GlobalAttrsList = Array<[string, string | null]>;
6
6
  export default class SVGGlobalAttributesObject extends AbstractComponent {
7
- private readonly attrs;
8
7
  protected contentBBox: Rect2;
8
+ private readonly attrs;
9
9
  constructor(attrs: GlobalAttrsList);
10
10
  render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
11
11
  intersects(_lineSegment: LineSegment2): boolean;
@@ -15,6 +15,6 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
15
15
  protected createClone(): SVGGlobalAttributesObject;
16
16
  description(localization: ImageComponentLocalization): string;
17
17
  protected serializeToJSON(): string | null;
18
- static deserializeFromString(data: string): AbstractComponent;
18
+ static deserializeFromString(_data: string): AbstractComponent;
19
19
  }
20
20
  export {};
@@ -10,10 +10,16 @@ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent.mj
10
10
  const componentKind = 'svg-global-attributes';
11
11
  // Stores global SVG attributes (e.g. namespace identifiers.)
12
12
  export default class SVGGlobalAttributesObject extends AbstractComponent {
13
+ // Does not modify `attrs`
13
14
  constructor(attrs) {
14
15
  super(componentKind);
15
- this.attrs = attrs;
16
16
  this.contentBBox = Rect2.empty;
17
+ // Already stored/managed in `editor.image`.
18
+ const attrsManagedByRenderer = ['viewBox', 'width', 'height'];
19
+ // Only store attributes that aren't managed by other parts of the app.
20
+ this.attrs = attrs.filter(([attr, _value]) => {
21
+ return !attrsManagedByRenderer.includes(attr);
22
+ });
17
23
  }
18
24
  render(canvas, _visibleRect) {
19
25
  if (!(canvas instanceof SVGRenderer)) {
@@ -47,19 +53,9 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
47
53
  serializeToJSON() {
48
54
  return JSON.stringify(this.attrs);
49
55
  }
50
- static deserializeFromString(data) {
51
- const json = JSON.parse(data);
52
- const attrs = [];
53
- const numericAndSpaceContentExp = /^[ \t\n0-9.-eE]+$/;
54
- // Don't deserialize all attributes, just those that should be safe.
55
- for (const [key, val] of json) {
56
- if (key === 'viewBox' || key === 'width' || key === 'height') {
57
- if (val && numericAndSpaceContentExp.exec(val)) {
58
- attrs.push([key, val]);
59
- }
60
- }
61
- }
62
- return new SVGGlobalAttributesObject(attrs);
56
+ static deserializeFromString(_data) {
57
+ // To be safe, don't deserialize any attributes
58
+ return new SVGGlobalAttributesObject([]);
63
59
  }
64
60
  }
65
61
  AbstractComponent.registerComponent(componentKind, SVGGlobalAttributesObject.deserializeFromString);
@@ -0,0 +1,6 @@
1
+ import { Rect2 } from '@js-draw/math';
2
+ export type SVGSizingOptions = {
3
+ minDimension?: number;
4
+ };
5
+ declare const adjustExportedSVGSize: (svg: SVGElement, exportRect: Rect2, options: SVGSizingOptions) => void;
6
+ export default adjustExportedSVGSize;
@@ -1,12 +1,9 @@
1
1
  import { toRoundedString } from '@js-draw/math';
2
2
  // @internal
3
- const setExportedSVGSize = (svg, viewport, options) => {
4
- // Just show the main region
5
- const rect = viewport.visibleRect;
6
- svg.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
3
+ const adjustExportedSVGSize = (svg, exportRect, options) => {
7
4
  // Adjust the width/height as necessary
8
- let width = rect.w;
9
- let height = rect.h;
5
+ let width = exportRect.w;
6
+ let height = exportRect.h;
10
7
  if (options?.minDimension && width < options.minDimension) {
11
8
  const newWidth = options.minDimension;
12
9
  height *= newWidth / (width || 1);
@@ -20,4 +17,4 @@ const setExportedSVGSize = (svg, viewport, options) => {
20
17
  svg.setAttribute('width', toRoundedString(width));
21
18
  svg.setAttribute('height', toRoundedString(height));
22
19
  };
23
- export default setExportedSVGSize;
20
+ export default adjustExportedSVGSize;
@@ -1,5 +1,5 @@
1
1
  import EditorImage, { PreRenderComponentCallback } from '../EditorImage';
2
- import { SVGSizingOptions } from './setExportedSVGSize';
2
+ import { SVGSizingOptions } from './adjustExportedSVGSize';
3
3
  export interface SVGExportOptions extends SVGSizingOptions {
4
4
  sanitize?: boolean;
5
5
  minDimension?: number;
@@ -1,25 +1,39 @@
1
- import { Mat33 } from '@js-draw/math';
1
+ import { Rect2 } from '@js-draw/math';
2
2
  import SVGRenderer from '../../rendering/renderers/SVGRenderer.mjs';
3
3
  import { svgLoaderAutoresizeClassName } from '../../SVGLoader.mjs';
4
- import setExportedSVGSize from './setExportedSVGSize.mjs';
4
+ import adjustExportedSVGSize from './adjustExportedSVGSize.mjs';
5
5
  const toSVGInternal = (image, renderFunction, options) => {
6
6
  const importExportViewport = image.getImportExportViewport().getTemporaryClone();
7
- const { element: result, renderer } = SVGRenderer.fromViewport(importExportViewport, options.sanitize ?? false);
8
- const origTransform = importExportViewport.canvasToScreenTransform;
9
- // Render with (0,0) at (0,0) — we'll handle translation with
10
- // the viewBox property.
11
- importExportViewport.resetTransform(Mat33.identity);
7
+ // If the rectangle has zero width or height, its size can't be increased
8
+ // -- set its size to the minimum.
9
+ if (options?.minDimension) {
10
+ const originalRect = importExportViewport.visibleRect;
11
+ let rect = originalRect;
12
+ if (rect.w <= 0) {
13
+ rect = new Rect2(rect.x, rect.y, options.minDimension, rect.h);
14
+ }
15
+ if (rect.h <= 0) {
16
+ rect = new Rect2(rect.x, rect.y, rect.w, options.minDimension);
17
+ }
18
+ if (!rect.eq(originalRect)) {
19
+ importExportViewport.updateScreenSize(rect.size);
20
+ }
21
+ }
22
+ const { element: result, renderer } = SVGRenderer.fromViewport(importExportViewport, {
23
+ sanitize: options.sanitize ?? false,
24
+ useViewBoxForPositioning: true,
25
+ });
12
26
  // Use a callback rather than async/await to allow this function to create
13
27
  // both sync and async render functions
14
28
  renderFunction(renderer, () => {
15
- importExportViewport.resetTransform(origTransform);
16
29
  if (image.getAutoresizeEnabled()) {
17
30
  result.classList.add(svgLoaderAutoresizeClassName);
18
31
  }
19
32
  else {
20
33
  result.classList.remove(svgLoaderAutoresizeClassName);
21
34
  }
22
- setExportedSVGSize(result, importExportViewport, options);
35
+ const exportRect = importExportViewport.visibleRect;
36
+ adjustExportedSVGSize(result, exportRect, options);
23
37
  return result;
24
38
  });
25
39
  return result;
@@ -73,4 +73,5 @@ export default abstract class AbstractRenderer {
73
73
  getCanvasToScreenTransform(): Mat33;
74
74
  canvasToScreen(vec: Vec2): Vec2;
75
75
  getSizeOfCanvasPixelOnScreen(): number;
76
+ getVisibleRect(): Rect2;
76
77
  }
@@ -158,4 +158,12 @@ export default class AbstractRenderer {
158
158
  getSizeOfCanvasPixelOnScreen() {
159
159
  return this.getCanvasToScreenTransform().transformVec3(Vec2.unitX).length();
160
160
  }
161
+ // Returns the region in canvas space that is visible within the viewport this
162
+ // canvas is rendering to.
163
+ //
164
+ // Note that in some cases this might not be the same as the `visibleRect` given
165
+ // to components in their `render` method.
166
+ getVisibleRect() {
167
+ return this.viewport.visibleRect;
168
+ }
161
169
  }
@@ -6,6 +6,15 @@ import TextRenderingStyle from '../TextRenderingStyle';
6
6
  import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
7
7
  import RenderablePathSpec from '../RenderablePathSpec';
8
8
  export declare const renderedStylesheetId = "js-draw-style-sheet";
9
+ type FromViewportOptions = {
10
+ sanitize?: boolean;
11
+ /**
12
+ * Rather than having the top left of the `viewBox` set to (0, 0),
13
+ * if `useViewBoxForPositioning` is `true`, the `viewBox`'s top left
14
+ * is based on the top left of the rendering viewport's `visibleRect`.
15
+ */
16
+ useViewBoxForPositioning?: boolean;
17
+ };
9
18
  /**
10
19
  * Renders onto an `SVGElement`.
11
20
  *
@@ -50,8 +59,26 @@ export default class SVGRenderer extends AbstractRenderer {
50
59
  drawPoints(...points: Point2[]): void;
51
60
  drawSVGElem(elem: SVGElement): void;
52
61
  isTooSmallToRender(_rect: Rect2): boolean;
53
- static fromViewport(viewport: Viewport, sanitize?: boolean): {
62
+ private visibleRectOverride;
63
+ /**
64
+ * Overrides the visible region returned by `getVisibleRect`.
65
+ *
66
+ * This is useful when the `viewport`'s transform has been modified,
67
+ * for example, to compensate for storing part of the image's
68
+ * transformation in an SVG property.
69
+ */
70
+ private overrideVisibleRect;
71
+ getVisibleRect(): Rect2;
72
+ /**
73
+ * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
74
+ * and other metadata attributes set for the given `Viewport`.
75
+ *
76
+ * If `options` is a `boolean`, it is interpreted as whether to sanitize (not add unknown
77
+ * SVG entities to) the output.
78
+ */
79
+ static fromViewport(viewport: Viewport, options?: FromViewportOptions | boolean): {
54
80
  element: SVGSVGElement;
55
81
  renderer: SVGRenderer;
56
82
  };
57
83
  }
84
+ export {};
@@ -36,6 +36,7 @@ export default class SVGRenderer extends AbstractRenderer {
36
36
  this.textContainer = null;
37
37
  this.textContainerTransform = null;
38
38
  this.textParentStyle = defaultTextStyle;
39
+ this.visibleRectOverride = null;
39
40
  this.clear();
40
41
  this.addStyleSheet();
41
42
  }
@@ -338,20 +339,70 @@ export default class SVGRenderer extends AbstractRenderer {
338
339
  isTooSmallToRender(_rect) {
339
340
  return false;
340
341
  }
341
- // Creates a new SVG element and SVGRenerer with attributes set for the given Viewport.
342
- static fromViewport(viewport, sanitize = true) {
342
+ /**
343
+ * Overrides the visible region returned by `getVisibleRect`.
344
+ *
345
+ * This is useful when the `viewport`'s transform has been modified,
346
+ * for example, to compensate for storing part of the image's
347
+ * transformation in an SVG property.
348
+ */
349
+ overrideVisibleRect(newRect) {
350
+ this.visibleRectOverride = newRect;
351
+ }
352
+ getVisibleRect() {
353
+ return this.visibleRectOverride ?? super.getVisibleRect();
354
+ }
355
+ /**
356
+ * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
357
+ * and other metadata attributes set for the given `Viewport`.
358
+ *
359
+ * If `options` is a `boolean`, it is interpreted as whether to sanitize (not add unknown
360
+ * SVG entities to) the output.
361
+ */
362
+ static fromViewport(viewport, options = true) {
363
+ let sanitize;
364
+ let useViewBoxForPositioning;
365
+ if (typeof options === 'boolean') {
366
+ sanitize = options;
367
+ useViewBoxForPositioning = false;
368
+ }
369
+ else {
370
+ sanitize = options.sanitize ?? true;
371
+ useViewBoxForPositioning = options.useViewBoxForPositioning ?? false;
372
+ }
343
373
  const svgNameSpace = 'http://www.w3.org/2000/svg';
344
374
  const result = document.createElementNS(svgNameSpace, 'svg');
345
- const rect = viewport.getScreenRectSize();
375
+ const screenRectSize = viewport.getScreenRectSize();
376
+ const visibleRect = viewport.visibleRect;
377
+ let viewBoxComponents;
378
+ if (useViewBoxForPositioning) {
379
+ const exportRect = viewport.visibleRect;
380
+ viewBoxComponents = [
381
+ exportRect.x, exportRect.y, exportRect.w, exportRect.h,
382
+ ];
383
+ // Replace the viewport with a copy that has a modified transform.
384
+ // (Avoids modifying the original viewport).
385
+ viewport = viewport.getTemporaryClone();
386
+ // TODO: This currently discards any rotation information.
387
+ // Render with (0,0) at (0,0) -- the translation is handled by the viewBox.
388
+ viewport.resetTransform(Mat33.identity);
389
+ }
390
+ else {
391
+ viewBoxComponents = [0, 0, screenRectSize.x, screenRectSize.y];
392
+ }
346
393
  // rect.x -> size of rect in x direction, rect.y -> size of rect in y direction.
347
- result.setAttribute('viewBox', [0, 0, rect.x, rect.y].map(part => toRoundedString(part)).join(' '));
348
- result.setAttribute('width', toRoundedString(rect.x));
349
- result.setAttribute('height', toRoundedString(rect.y));
394
+ result.setAttribute('viewBox', viewBoxComponents.map(part => toRoundedString(part)).join(' '));
395
+ result.setAttribute('width', toRoundedString(screenRectSize.x));
396
+ result.setAttribute('height', toRoundedString(screenRectSize.y));
350
397
  // Ensure the image can be identified as an SVG if downloaded.
351
398
  // See https://jwatt.org/svg/authoring/
352
399
  result.setAttribute('version', '1.1');
353
400
  result.setAttribute('baseProfile', 'full');
354
401
  result.setAttribute('xmlns', svgNameSpace);
355
- return { element: result, renderer: new SVGRenderer(result, viewport, sanitize) };
402
+ const renderer = new SVGRenderer(result, viewport, sanitize);
403
+ if (!visibleRect.eq(viewport.visibleRect)) {
404
+ renderer.overrideVisibleRect(visibleRect);
405
+ }
406
+ return { element: result, renderer };
356
407
  }
357
408
  }
@@ -124,24 +124,30 @@ export default abstract class AbstractToolbar {
124
124
  * toolbar.addDefaults();
125
125
  * toolbar.addSaveButton(() => alert('save clicked!'));
126
126
  * ```
127
+ *
128
+ * `labelOverride` can optionally be used to change the `label` or `icon` of the button.
127
129
  */
128
- addSaveButton(saveCallback: () => void): BaseWidget;
130
+ addSaveButton(saveCallback: () => void, labelOverride?: Partial<ActionButtonIcon>): BaseWidget;
129
131
  /**
130
132
  * Adds an "Exit" button that, when clicked, calls `exitCallback`.
131
133
  *
132
- * **Note**: This is equivalent to
134
+ * **Note**: This is roughly equivalent to
133
135
  * ```ts
134
136
  * toolbar.addTaggedActionButton([ ToolbarWidgetTag.Exit ], {
135
137
  * label: this.editor.localization.exit,
136
138
  * icon: this.editor.icons.makeCloseIcon(),
139
+ *
140
+ * // labelOverride can be used to override label or icon.
141
+ * ...labelOverride,
137
142
  * }, () => {
138
143
  * exitCallback();
139
144
  * });
140
145
  * ```
146
+ * with some additional configuration.
141
147
  *
142
148
  * @final
143
149
  */
144
- addExitButton(exitCallback: () => void): BaseWidget;
150
+ addExitButton(exitCallback: () => void, labelOverride?: Partial<ActionButtonIcon>): BaseWidget;
145
151
  /**
146
152
  * Adds undo and redo buttons that trigger the editor's built-in undo and redo
147
153
  * functionality.
@@ -287,9 +287,11 @@ class AbstractToolbar {
287
287
  * toolbar.addDefaults();
288
288
  * toolbar.addSaveButton(() => alert('save clicked!'));
289
289
  * ```
290
+ *
291
+ * `labelOverride` can optionally be used to change the `label` or `icon` of the button.
290
292
  */
291
- addSaveButton(saveCallback) {
292
- const widget = new SaveActionWidget(this.editor, this.localizationTable, saveCallback);
293
+ addSaveButton(saveCallback, labelOverride = {}) {
294
+ const widget = new SaveActionWidget(this.editor, this.localizationTable, saveCallback, labelOverride);
293
295
  widget.setTags([ToolbarWidgetTag.Save]);
294
296
  this.addWidget(widget);
295
297
  return widget;
@@ -297,22 +299,27 @@ class AbstractToolbar {
297
299
  /**
298
300
  * Adds an "Exit" button that, when clicked, calls `exitCallback`.
299
301
  *
300
- * **Note**: This is equivalent to
302
+ * **Note**: This is roughly equivalent to
301
303
  * ```ts
302
304
  * toolbar.addTaggedActionButton([ ToolbarWidgetTag.Exit ], {
303
305
  * label: this.editor.localization.exit,
304
306
  * icon: this.editor.icons.makeCloseIcon(),
307
+ *
308
+ * // labelOverride can be used to override label or icon.
309
+ * ...labelOverride,
305
310
  * }, () => {
306
311
  * exitCallback();
307
312
  * });
308
313
  * ```
314
+ * with some additional configuration.
309
315
  *
310
316
  * @final
311
317
  */
312
- addExitButton(exitCallback) {
318
+ addExitButton(exitCallback, labelOverride = {}) {
313
319
  return this.addTaggedActionButton([ToolbarWidgetTag.Exit], {
314
320
  label: this.editor.localization.exit,
315
321
  icon: this.editor.icons.makeCloseIcon(),
322
+ ...labelOverride,
316
323
  }, () => {
317
324
  exitCallback();
318
325
  }, {
@@ -0,0 +1 @@
1
+ export {};
@@ -228,11 +228,11 @@ export default class EdgeToolbar extends AbstractToolbar {
228
228
  widget.removeCSSClassFromContainer('label-right');
229
229
  if (tags.includes(ToolbarWidgetTag.Save)) {
230
230
  widget.addCSSClassToContainer('label-inline');
231
- widget.addCSSClassToContainer('label-right');
231
+ widget.addCSSClassToContainer('label-left');
232
232
  }
233
233
  if (tags.includes(ToolbarWidgetTag.Exit)) {
234
234
  widget.addCSSClassToContainer('label-inline');
235
- widget.addCSSClassToContainer('label-left');
235
+ widget.addCSSClassToContainer('label-right');
236
236
  }
237
237
  }
238
238
  addWidgetInternal(widget) {
@@ -2,8 +2,9 @@ import { KeyPressEvent } from '../../inputEvents';
2
2
  import Editor from '../../Editor';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import ActionButtonWidget from './ActionButtonWidget';
5
+ import { ActionButtonIcon } from '../types';
5
6
  declare class SaveActionWidget extends ActionButtonWidget {
6
- constructor(editor: Editor, localization: ToolbarLocalization, saveCallback: () => void);
7
+ constructor(editor: Editor, localization: ToolbarLocalization, saveCallback: () => void, labelOverride?: Partial<ActionButtonIcon>);
7
8
  protected shouldAutoDisableInReadOnlyEditor(): boolean;
8
9
  protected onKeyPress(event: KeyPressEvent): boolean;
9
10
  mustBeInToplevelMenu(): boolean;
@@ -2,8 +2,12 @@ import ActionButtonWidget from './ActionButtonWidget.mjs';
2
2
  import { ToolbarWidgetTag } from './BaseWidget.mjs';
3
3
  import { saveKeyboardShortcut } from './keybindings.mjs';
4
4
  class SaveActionWidget extends ActionButtonWidget {
5
- constructor(editor, localization, saveCallback) {
6
- super(editor, 'save-button', editor.icons.makeSaveIcon, localization.save, saveCallback);
5
+ constructor(editor, localization, saveCallback, labelOverride = {}) {
6
+ super(editor, 'save-button',
7
+ // Creates an icon
8
+ () => {
9
+ return labelOverride.icon ?? editor.icons.makeSaveIcon();
10
+ }, labelOverride.label ?? localization.save, saveCallback);
7
11
  this.setTags([ToolbarWidgetTag.Save]);
8
12
  }
9
13
  shouldAutoDisableInReadOnlyEditor() {
@@ -129,8 +129,10 @@ class Selection {
129
129
  // Reset for the next drag
130
130
  this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
131
131
  this.transform = Mat33.identity;
132
- // Make the commands undo-able
133
- await this.editor.dispatch(new Selection.ApplyTransformationCommand(this, selectedElems, fullTransform));
132
+ // Make the commands undo-able, but only if the transform is non-empty.
133
+ if (!fullTransform.eq(Mat33.identity)) {
134
+ await this.editor.dispatch(new Selection.ApplyTransformationCommand(this, selectedElems, fullTransform));
135
+ }
134
136
  // Clear renderings of any in-progress transformations
135
137
  const wetInkRenderer = this.editor.display.getWetInkRenderer();
136
138
  wetInkRenderer.clear();
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.4.0',
2
+ number: '1.5.0',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "b520078c16a4d23d9bed4531eafda87bfce3f6b1"
89
+ "gitHead": "c2252181b1ab0eb7047ad66590bca2eabd61681d"
90
90
  }
@@ -60,7 +60,7 @@
60
60
  @media (hover: hover) {
61
61
  // Only show an animation when opening the label due to a hover --
62
62
  // show the label immediately otherwise.
63
- &:hover > #{$label-selector} {
63
+ &:hover:not(:focus-visible) > #{$label-selector} {
64
64
  opacity: $label-visible-opacity;
65
65
  animation: 1s $hover-active-animation;
66
66
  }
@@ -69,7 +69,7 @@
69
69
  // When the user is pressing/long-pressing the button
70
70
  &:active > #{$label-selector} {
71
71
  opacity: $label-visible-opacity;
72
- animation: 0.5s $hover-active-animation;
72
+ animation: 1s $hover-active-animation;
73
73
  }
74
74
 
75
75
  $keyboard-hide-animation: 1.5s ease rehide-label;
@@ -265,7 +265,7 @@
265
265
  --button-flex-direction: row-reverse;
266
266
 
267
267
  > .toolbar-button > .toolbar-icon {
268
- margin-left: 10px;
268
+ margin-left: 7px;
269
269
  margin-right: 0;
270
270
  }
271
271
 
@@ -284,8 +284,11 @@
284
284
 
285
285
  > .toolbar-icon {
286
286
  height: 100%;
287
- margin-right: 10px;
287
+ margin-right: 7px;
288
288
  margin-left: 0;
289
+
290
+ // Make smaller than the other icons
291
+ width: 22px;
289
292
  }
290
293
  }
291
294
  }
@@ -38,7 +38,7 @@
38
38
  }
39
39
 
40
40
  &.selection-tool-resize-xy {
41
- cursor: nw-resize;
41
+ cursor: nwse-resize;
42
42
  }
43
43
  }
44
44
 
@@ -52,7 +52,7 @@
52
52
  }
53
53
 
54
54
  &.selection-tool-resize-xy {
55
- cursor: sw-resize;
55
+ cursor: nesw-resize;
56
56
  }
57
57
  }
58
58
 
@@ -1,6 +0,0 @@
1
- import Viewport from '../../Viewport';
2
- export type SVGSizingOptions = {
3
- minDimension?: number;
4
- };
5
- declare const setExportedSVGSize: (svg: SVGElement, viewport: Viewport, options: SVGSizingOptions) => void;
6
- export default setExportedSVGSize;
@@ -1,6 +0,0 @@
1
- import Viewport from '../../Viewport';
2
- export type SVGSizingOptions = {
3
- minDimension?: number;
4
- };
5
- declare const setExportedSVGSize: (svg: SVGElement, viewport: Viewport, options: SVGSizingOptions) => void;
6
- export default setExportedSVGSize;