js-draw 1.12.0 → 1.13.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 (59) hide show
  1. package/dist/Editor.css +66 -118
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +4 -2
  5. package/dist/cjs/Editor.js +50 -39
  6. package/dist/cjs/components/AbstractComponent.d.ts +7 -0
  7. package/dist/cjs/components/AbstractComponent.js +7 -5
  8. package/dist/cjs/components/util/StrokeSmoother.d.ts +0 -1
  9. package/dist/cjs/components/util/StrokeSmoother.js +6 -17
  10. package/dist/cjs/localizations/es.js +11 -22
  11. package/dist/cjs/rendering/Display.d.ts +2 -0
  12. package/dist/cjs/rendering/Display.js +4 -0
  13. package/dist/cjs/toolbar/DropdownToolbar.d.ts +3 -1
  14. package/dist/cjs/toolbar/DropdownToolbar.js +2 -0
  15. package/dist/cjs/toolbar/EdgeToolbar.js +6 -5
  16. package/dist/cjs/toolbar/widgets/BaseWidget.js +2 -0
  17. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +11 -4
  18. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +10 -0
  19. package/dist/cjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
  20. package/dist/cjs/util/addLongPressOrHoverCssClasses.js +34 -0
  21. package/dist/cjs/util/fileToBase64Url.d.ts +9 -0
  22. package/dist/cjs/util/fileToBase64Url.js +39 -0
  23. package/dist/cjs/util/fileToBase64Url.test.d.ts +1 -0
  24. package/dist/cjs/util/listenForLongPressOrHover.d.ts +13 -0
  25. package/dist/cjs/util/listenForLongPressOrHover.js +80 -0
  26. package/dist/cjs/util/listenForLongPressOrHover.test.d.ts +1 -0
  27. package/dist/cjs/version.js +1 -1
  28. package/dist/mjs/Editor.d.ts +4 -2
  29. package/dist/mjs/Editor.mjs +50 -39
  30. package/dist/mjs/components/AbstractComponent.d.ts +7 -0
  31. package/dist/mjs/components/AbstractComponent.mjs +7 -5
  32. package/dist/mjs/components/util/StrokeSmoother.d.ts +0 -1
  33. package/dist/mjs/components/util/StrokeSmoother.mjs +6 -17
  34. package/dist/mjs/localizations/es.mjs +11 -22
  35. package/dist/mjs/rendering/Display.d.ts +2 -0
  36. package/dist/mjs/rendering/Display.mjs +4 -0
  37. package/dist/mjs/toolbar/DropdownToolbar.d.ts +3 -1
  38. package/dist/mjs/toolbar/DropdownToolbar.mjs +2 -0
  39. package/dist/mjs/toolbar/EdgeToolbar.mjs +6 -5
  40. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +2 -0
  41. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +11 -4
  42. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +10 -0
  43. package/dist/mjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
  44. package/dist/mjs/util/addLongPressOrHoverCssClasses.mjs +29 -0
  45. package/dist/mjs/util/fileToBase64Url.d.ts +9 -0
  46. package/dist/mjs/util/fileToBase64Url.mjs +37 -0
  47. package/dist/mjs/util/fileToBase64Url.test.d.ts +1 -0
  48. package/dist/mjs/util/listenForLongPressOrHover.d.ts +13 -0
  49. package/dist/mjs/util/listenForLongPressOrHover.mjs +78 -0
  50. package/dist/mjs/util/listenForLongPressOrHover.test.d.ts +1 -0
  51. package/dist/mjs/version.mjs +1 -1
  52. package/package.json +2 -2
  53. package/src/toolbar/EdgeToolbar.scss +19 -97
  54. package/src/toolbar/utils/labelVisibleOnHover.scss +114 -0
  55. package/src/toolbar/widgets/components/makeGridSelector.scss +1 -0
  56. package/dist/cjs/util/fileToBase64.d.ts +0 -3
  57. package/dist/cjs/util/fileToBase64.js +0 -15
  58. package/dist/mjs/util/fileToBase64.d.ts +0 -3
  59. package/dist/mjs/util/fileToBase64.mjs +0 -13
@@ -1,5 +1,5 @@
1
1
  import { defaultEditorLocalization } from '../localization.mjs';
2
- // A partial Spanish localization, created with /scripts/markdownTranslationFormToTs.ts
2
+ // A partial Spanish localization
3
3
  const localization = {
4
4
  ...defaultEditorLocalization,
5
5
  pen: 'Lapiz',
@@ -7,6 +7,8 @@ const localization = {
7
7
  select: 'Selecciona',
8
8
  handTool: 'Mover',
9
9
  image: 'Imagen',
10
+ inputAltText: 'Texto alternativo',
11
+ resetImage: 'Reiniciar',
10
12
  chooseFile: 'Seleccionar archivo',
11
13
  cancel: 'Cancelar',
12
14
  resetView: 'Reiniciar vista',
@@ -28,7 +30,9 @@ const localization = {
28
30
  backgroundColor: 'Color de fondo',
29
31
  imageWidthOption: 'Ancho',
30
32
  imageHeightOption: 'Alto',
33
+ enableAutoresizeOption: 'Redimensionar automático',
31
34
  toggleOverflow: 'Más',
35
+ about: 'Acerca de',
32
36
  touchPanning: 'Mover la pantalla con un dedo',
33
37
  roundedTipPen: 'Lapiz Redondeado',
34
38
  arrowPen: 'Flecha',
@@ -44,10 +48,11 @@ const localization = {
44
48
  colorChangedAnnouncement: (color) => { return `Color fue cambiado a ${color}`; },
45
49
  imageSize: (size, units) => `Tamaño del imagen: ${size} ${units}`,
46
50
  imageLoadError: (message) => `Error cargando imagen: ${message}`,
47
- penTool: (penId) => { return `Lapiz ${penId}`; },
51
+ penTool: (penId) => `Lapiz ${penId}`,
48
52
  selectionTool: 'Selecciona',
49
53
  eraserTool: 'Borrador',
50
54
  touchPanTool: 'Instrumento de mover la pantalla con un dedo',
55
+ undoRedoTool: 'Deshace/rehace',
51
56
  pipetteTool: 'Seleccione un color de la pantalla',
52
57
  keyboardPanZoom: 'Mover la pantalla con el teclado',
53
58
  textTool: 'Texto',
@@ -55,29 +60,13 @@ const localization = {
55
60
  findLabel: 'Buscar',
56
61
  toNextMatch: 'Próxima',
57
62
  closeDialog: 'Cerrar',
58
- focusedFoundText: (matchIdx, totalMatches) => `Viewing match ${matchIdx} of ${totalMatches}`,
59
63
  anyDevicePanning: 'Mover la pantalla con todo dispotivo',
60
64
  copied: (count, description) => `Copied ${count} ${description}`,
61
65
  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}`,
66
+ toolEnabledAnnouncement: (toolName) => `${toolName} fue activado`,
67
+ toolDisabledAnnouncement: (toolName) => `${toolName} fue desactivado`,
68
+ resizeOutputCommand: (newSize) => `Tamaño de imagen fue cambiado a ${newSize.w}x${newSize.h}`,
69
+ eraseAction: (componentDescription, numElems) => `Borrado: ${numElems} ${componentDescription}`,
81
70
  rerenderAsText: 'Redibuja la pantalla al texto',
82
71
  loading: (percentage) => `Cargando: ${percentage}%...`,
83
72
  imageEditor: 'Editor de dibujos',
@@ -55,6 +55,8 @@ export default class Display {
55
55
  * @internal
56
56
  */
57
57
  setDevicePixelRatio(dpr: number): Promise<void> | undefined;
58
+ /** @internal */
59
+ getDevicePixelRatio(): number;
58
60
  /**
59
61
  * Rerenders the text-based display.
60
62
  * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
@@ -199,6 +199,10 @@ export default class Display {
199
199
  }
200
200
  return undefined;
201
201
  }
202
+ /** @internal */
203
+ getDevicePixelRatio() {
204
+ return this.devicePixelRatio;
205
+ }
202
206
  /**
203
207
  * Rerenders the text-based display.
204
208
  * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
@@ -21,12 +21,14 @@ import AbstractToolbar, { SpacerOptions } from './AbstractToolbar';
21
21
  * });
22
22
  * ```
23
23
  *
24
+ * Returns a subclass of {@link AbstractToolbar}.
25
+ *
24
26
  * @see
25
27
  * - {@link makeEdgeToolbar}
26
28
  * - {@link AbstractToolbar.addSaveButton}
27
29
  * - {@link AbstractToolbar.addExitButton}
28
30
  */
29
- export declare const makeDropdownToolbar: (editor: Editor) => AbstractToolbar;
31
+ export declare const makeDropdownToolbar: (editor: Editor) => DropdownToolbar;
30
32
  export default class DropdownToolbar extends AbstractToolbar {
31
33
  protected container: HTMLElement;
32
34
  private resizeObserver;
@@ -21,6 +21,8 @@ import { toolbarCSSPrefix } from './constants.mjs';
21
21
  * });
22
22
  * ```
23
23
  *
24
+ * Returns a subclass of {@link AbstractToolbar}.
25
+ *
24
26
  * @see
25
27
  * - {@link makeEdgeToolbar}
26
28
  * - {@link AbstractToolbar.addSaveButton}
@@ -136,8 +136,10 @@ export default class EdgeToolbar extends AbstractToolbar {
136
136
  this.sidebarContainer.style.animation = `${animationProperties} ${toolbarCSSPrefix}-edgemenu-transition-in`;
137
137
  this.menuContainer.style.animation = `${animationProperties} ${toolbarCSSPrefix}-edgemenu-container-transition-in`;
138
138
  this.menuContainer.style.opacity = '1';
139
- // Focus the close button when first shown.
140
- this.closeButton.focus();
139
+ // Focus the close button when first shown, but prevent scroll because the button
140
+ // is likely at the bottom of the screen (and we want the full sidebar to remain
141
+ // visible).
142
+ this.closeButton.focus({ preventScroll: true, });
141
143
  }
142
144
  else {
143
145
  this.closeColorPickers();
@@ -265,9 +267,8 @@ export default class EdgeToolbar extends AbstractToolbar {
265
267
  if (event.target === this.menuContainer) {
266
268
  if (eventName === 'pointerdown') {
267
269
  this.sidebarVisible.set(false);
268
- }
269
- if (eventName === 'pointerup') {
270
- this.editor.focus();
270
+ // A delay seems necessary for the editor
271
+ setTimeout(() => this.editor.focus(), 0);
271
272
  }
272
273
  return true;
273
274
  }
@@ -14,6 +14,7 @@ import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler.mjs';
14
14
  import { keyPressEventFromHTMLEvent, keyUpEventFromHTMLEvent } from '../../inputEvents.mjs';
15
15
  import { toolbarCSSPrefix } from '../constants.mjs';
16
16
  import DropdownLayoutManager from './layout/DropdownLayoutManager.mjs';
17
+ import addLongPressOrHoverCssClasses from '../../util/addLongPressOrHoverCssClasses.mjs';
17
18
  /**
18
19
  * A set of labels that allow toolbar themes to treat buttons differently.
19
20
  */
@@ -60,6 +61,7 @@ class BaseWidget {
60
61
  this.button.oncontextmenu = event => {
61
62
  event.preventDefault();
62
63
  };
64
+ addLongPressOrHoverCssClasses(this.button);
63
65
  }
64
66
  /**
65
67
  * Should return a constant true or false value. If true (the default),
@@ -4,7 +4,7 @@ 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';
7
- import fileToBase64 from '../../util/fileToBase64.mjs';
7
+ import fileToBase64Url from '../../util/fileToBase64Url.mjs';
8
8
  import BaseWidget from './BaseWidget.mjs';
9
9
  import { EditorEventType } from '../../types.mjs';
10
10
  import { toolbarCSSPrefix } from '../constants.mjs';
@@ -106,11 +106,13 @@ class InsertImageWidget extends BaseWidget {
106
106
  this.imagePreview.style.display = 'block';
107
107
  const image = files[0];
108
108
  let data = null;
109
+ let errorMessage = null;
109
110
  try {
110
- data = await fileToBase64(image);
111
+ data = await fileToBase64Url(image);
111
112
  }
112
- catch (e) {
113
- this.statusView.innerText = this.localizationTable.imageLoadError(e);
113
+ catch (error) {
114
+ console.error('Image load error', error);
115
+ errorMessage = this.localizationTable.imageLoadError(error);
114
116
  }
115
117
  if (data) {
116
118
  this.image = ImageWrapper.fromSrcAndPreview(data, this.imagePreview, () => this.onImageDataUpdate());
@@ -119,6 +121,11 @@ class InsertImageWidget extends BaseWidget {
119
121
  this.image = null;
120
122
  }
121
123
  this.onImageDataUpdate();
124
+ // Show the error after image update callbacks to ensure it is
125
+ // actually shown.
126
+ if (errorMessage) {
127
+ this.statusView.innerText = errorMessage;
128
+ }
122
129
  });
123
130
  altTextRow.replaceChildren(imageAltTextLabel, this.imageAltTextInput);
124
131
  actionButtonRow.replaceChildren(this.submitButton);
@@ -1,5 +1,6 @@
1
1
  import { MutableReactiveValue } from '../../../util/ReactiveValue.mjs';
2
2
  import stopPropagationOfScrollingWheelEvents from '../../../util/stopPropagationOfScrollingWheelEvents.mjs';
3
+ import addLongPressOrHoverCssClasses from '../../../util/addLongPressOrHoverCssClasses.mjs';
3
4
  import { toolbarCSSPrefix } from '../../constants.mjs';
4
5
  let idCounter = 0;
5
6
  /**
@@ -31,6 +32,9 @@ labelText, defaultId, choices) => {
31
32
  const button = document.createElement('input');
32
33
  button.type = 'radio';
33
34
  button.id = `${toolbarCSSPrefix}-grid-select-button-${idCounter++}`;
35
+ // Some toolbars only show the label on hover. Having long press or hover
36
+ // CSS classes are helpful here.
37
+ addLongPressOrHoverCssClasses(buttonContainer);
34
38
  // Clicking any part of labelContainer triggers the radio button.
35
39
  const labelContainer = document.createElement('label');
36
40
  const rebuildLabel = () => {
@@ -76,6 +80,12 @@ labelText, defaultId, choices) => {
76
80
  button.onblur = () => {
77
81
  buttonContainer.classList.remove('focus-visible');
78
82
  };
83
+ // Prevent the right-click menu from being shown on long-press
84
+ // (important for some toolbars that use long-press gestures to
85
+ // show grid selector labels).
86
+ buttonContainer.oncontextmenu = event => {
87
+ event.preventDefault();
88
+ };
79
89
  buttonContainer.replaceChildren(button, labelContainer);
80
90
  menuContainer.appendChild(buttonContainer);
81
91
  // Set whether the current button is checked
@@ -0,0 +1,10 @@
1
+ /**
2
+ * When a pointer is inside `element`, after a delay, adds the `has-long-press-or-hover`
3
+ * CSS class to `element`.
4
+ *
5
+ * When no pointers are inside `element`, adds the CSS class `no-long-press-or-hover`.
6
+ */
7
+ declare const addLongPressOrHoverCssClasses: (element: HTMLElement) => {
8
+ removeEventListeners: () => void;
9
+ };
10
+ export default addLongPressOrHoverCssClasses;
@@ -0,0 +1,29 @@
1
+ import listenForLongPressOrHover from './listenForLongPressOrHover.mjs';
2
+ /**
3
+ * When a pointer is inside `element`, after a delay, adds the `has-long-press-or-hover`
4
+ * CSS class to `element`.
5
+ *
6
+ * When no pointers are inside `element`, adds the CSS class `no-long-press-or-hover`.
7
+ */
8
+ const addLongPressOrHoverCssClasses = (element) => {
9
+ const hasLongPressClass = 'has-long-press-or-hover';
10
+ const noLongPressClass = 'no-long-press-or-hover';
11
+ element.classList.add('no-long-press-or-hover');
12
+ const { removeListeners } = listenForLongPressOrHover(element, {
13
+ onStart() {
14
+ element.classList.remove(noLongPressClass);
15
+ element.classList.add(hasLongPressClass);
16
+ },
17
+ onEnd() {
18
+ element.classList.add(noLongPressClass);
19
+ element.classList.remove(hasLongPressClass);
20
+ },
21
+ });
22
+ return {
23
+ removeEventListeners: () => {
24
+ element.classList.remove(noLongPressClass);
25
+ removeListeners();
26
+ },
27
+ };
28
+ };
29
+ export default addLongPressOrHoverCssClasses;
@@ -0,0 +1,9 @@
1
+ export interface FileToBase64UrlOptions {
2
+ onprogress?: (evt: ProgressEvent<FileReader>) => void;
3
+ onWarning?: (message: string, error: any) => void;
4
+ }
5
+ /**
6
+ * Converts `file` to a base64 data URL.
7
+ */
8
+ declare const fileToBase64Url: (file: Blob, options?: FileToBase64UrlOptions) => Promise<string | null>;
9
+ export default fileToBase64Url;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Converts `file` to a base64 data URL.
3
+ */
4
+ const fileToBase64Url = async (file, options = {}) => {
5
+ try {
6
+ const reader = new FileReader();
7
+ return await new Promise((resolve, reject) => {
8
+ reader.onload = () => resolve(reader.result);
9
+ reader.onerror = reject;
10
+ reader.onabort = reject;
11
+ reader.onprogress = (evt) => {
12
+ options.onprogress?.(evt);
13
+ };
14
+ reader.readAsDataURL(file);
15
+ });
16
+ }
17
+ catch (error) {
18
+ // Files can fail to load with a FileReader in some cases. For example,
19
+ // in iOS Lockdown mode, where FileReader is unavailable.
20
+ (options.onWarning ?? console.warn)('Unable to convert file to base64 with a FileReader: ', error);
21
+ const arrayBuffer = await file.arrayBuffer();
22
+ const array = new Uint8Array(arrayBuffer);
23
+ // step: must be divisible by 3 (3 bytes = 4 base64 numerals)
24
+ // If too large, this will fail (String.fromCharCode accepts a limited
25
+ // number of arguments).
26
+ const step = 30;
27
+ const result = [];
28
+ for (let i = 0; i < array.length; i += step) {
29
+ // btoa accepts only characters with byte value 0-255 (which can be created
30
+ // with String.fromCharCode)
31
+ const stringByteArray = String.fromCharCode(...array.slice(i, i + step));
32
+ result.push(btoa(stringByteArray));
33
+ }
34
+ return `data:${file.type ?? 'image/*'};base64,${result.join('')}`;
35
+ }
36
+ };
37
+ export default fileToBase64Url;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ type Options = {
2
+ onStart: () => void;
3
+ onEnd: () => void;
4
+ longPressTimeout?: number;
5
+ };
6
+ /**
7
+ * Calls `options.onStart` at the start of a long press or hover.
8
+ * Calls `options.onEnd` when no pointers are within the container.
9
+ */
10
+ declare const listenForLongPressOrHover: (target: HTMLElement, options: Options) => {
11
+ removeListeners: () => void;
12
+ };
13
+ export default listenForLongPressOrHover;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Calls `options.onStart` at the start of a long press or hover.
3
+ * Calls `options.onEnd` when no pointers are within the container.
4
+ */
5
+ const listenForLongPressOrHover = (target, options) => {
6
+ const pointersInside = new Map();
7
+ let timeoutId = null;
8
+ let isLongPressInProgress = false;
9
+ const updateTimeout = () => {
10
+ if (pointersInside.size === 0) {
11
+ if (isLongPressInProgress) {
12
+ isLongPressInProgress = false;
13
+ options.onEnd();
14
+ }
15
+ else if (timeoutId !== null) {
16
+ clearTimeout(timeoutId);
17
+ timeoutId = null;
18
+ }
19
+ }
20
+ else {
21
+ const nowTime = Date.now();
22
+ let timeSinceFirstPointer = 0; // ms
23
+ for (const record of pointersInside.values()) {
24
+ const timeSince = nowTime - record.timeEnter;
25
+ timeSinceFirstPointer = Math.max(timeSince, timeSinceFirstPointer);
26
+ }
27
+ const longPressTimeout = options.longPressTimeout ?? 700; // ms
28
+ if (timeoutId !== null) {
29
+ clearTimeout(timeoutId);
30
+ timeoutId = null;
31
+ }
32
+ const timeLeft = longPressTimeout - timeSinceFirstPointer;
33
+ if (timeLeft <= 0) {
34
+ options.onStart();
35
+ isLongPressInProgress = true;
36
+ }
37
+ else {
38
+ timeoutId = setTimeout(() => {
39
+ timeoutId = null;
40
+ updateTimeout();
41
+ }, timeLeft);
42
+ }
43
+ }
44
+ };
45
+ // Detects long press
46
+ const pointerEventListener = (event) => {
47
+ const eventRecord = {
48
+ timeEnter: Date.now(),
49
+ };
50
+ if (event.type === 'pointerenter') {
51
+ pointersInside.set(event.pointerId, eventRecord);
52
+ }
53
+ else if (event.type === 'pointerleave' || event.type === 'pointercancel') {
54
+ // In some cases (for example, a click with a stylus on Android/Chrome), moving the pen
55
+ // over the target, clicking, then moving the pen out of the target produces input
56
+ // similar to this:
57
+ // - pointerenter (pointerId: 4)
58
+ // - pointerleave (pointerId: 4)
59
+ // - pointerenter (pointerId: 6)
60
+ // - pointerenter (pointerId: 1)
61
+ // - pointerleave (pointerId: 6)
62
+ // Observe that no pointerleave event was fired for the pointer with ID 1.
63
+ pointersInside.clear();
64
+ }
65
+ updateTimeout();
66
+ };
67
+ target.addEventListener('pointerenter', pointerEventListener);
68
+ target.addEventListener('pointerleave', pointerEventListener);
69
+ target.addEventListener('pointercancel', pointerEventListener);
70
+ return {
71
+ removeListeners: () => {
72
+ target.removeEventListener('pointerenter', pointerEventListener);
73
+ target.removeEventListener('pointerleave', pointerEventListener);
74
+ target.removeEventListener('pointercancel', pointerEventListener);
75
+ },
76
+ };
77
+ };
78
+ export default listenForLongPressOrHover;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.12.0',
2
+ number: '1.13.2',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.12.0",
3
+ "version": "1.13.2",
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": "c179c5b3ebca482dfb156527ec6631c8f5159033"
89
+ "gitHead": "9a48e4d746783977e2b2808be9d97f3521fb830f"
90
90
  }
@@ -1,3 +1,5 @@
1
+ @use "./utils/labelVisibleOnHover.scss";
2
+
1
3
  @keyframes toolbar--edgemenu-transition-in {
2
4
  from { transform: translate(0, 100%); }
3
5
  to { transform: translate(0, 0); }
@@ -21,94 +23,6 @@
21
23
  to { overflow-y: hidden; }
22
24
  }
23
25
 
24
- // Shows a lable on hover
25
- // Uses the --button-label-hover-offset-y and --button-label-hover-offset-x
26
- // variables to determine the label's position.
27
- //
28
- // If the label is in a scrolling container, that container should have position: relative;
29
- // as this allows absolutely positioned children to scroll with the container (rather than
30
- // remaining stationary).
31
- @mixin label-visible-on-hover($label-selector) {
32
- $label-visible-opacity: 0.8;
33
-
34
- // Make the label hide only when hovering.
35
-
36
- @keyframes rehide-label {
37
- 0% { opacity: $label-visible-opacity; }
38
- 80% { opacity: $label-visible-opacity; }
39
- 100% { opacity: 0.1; }
40
- }
41
-
42
- @keyframes show-label {
43
- 0% { opacity: 0; }
44
- // Keep the label hidden before showing
45
- 80% { opacity: 0; }
46
- 100% { opacity: $label-visible-opacity; }
47
- }
48
-
49
- @keyframes keep-label-hidden {
50
- 0% { opacity: 0; }
51
- 100% { opacity: 0; }
52
- }
53
-
54
-
55
- $hover-active-animation: ease show-label;
56
-
57
- // Avoids sticky hover on touch devices. See
58
- // https://css-tricks.com/solving-sticky-hover-states-with-media-hover-hover/
59
- // When the primary device supports hover:
60
- @media (hover: hover) {
61
- // Only show an animation when opening the label due to a hover --
62
- // show the label immediately otherwise.
63
- &:hover:not(:focus-visible) > #{$label-selector} {
64
- opacity: $label-visible-opacity;
65
- animation: 1s $hover-active-animation;
66
- }
67
- }
68
-
69
- // When the user is pressing/long-pressing the button
70
- &:active > #{$label-selector} {
71
- opacity: $label-visible-opacity;
72
- animation: 1s $hover-active-animation;
73
- }
74
-
75
- $keyboard-hide-animation: 1.5s ease rehide-label;
76
-
77
- &:focus-visible > #{$label-selector} {
78
- animation: $keyboard-hide-animation;
79
- opacity: 0;
80
- }
81
-
82
- // Make the :has selector separate its own statement -- some browsers don't
83
- // support :has, which would make the entire statement block have no effect.
84
- &:has(:focus-visible) > #{$label-selector} {
85
- animation: $keyboard-hide-animation;
86
- opacity: 0;
87
- }
88
-
89
- & > #{$label-selector} {
90
- opacity: 0;
91
- position: absolute;
92
- margin-top: var(--button-label-hover-offset-y);
93
- margin-left: var(--button-label-hover-offset-x);
94
-
95
- // The label is often mostly invisible/just below a toolbar item.
96
- // If there are multiple toolbar rows, ensure that a label doesn't prevent
97
- // clicking on items in the second row:
98
- pointer-events: none;
99
-
100
- background-color: var(--background-color-1);
101
- color: var(--foreground-color-1);
102
- border-radius: 25px;
103
- padding: 10px;
104
-
105
- transition: 0.2s ease opacity, 0.2s ease margin-top;
106
-
107
- @media (prefers-reduced-motion: reduce) {
108
- transition: none;
109
- }
110
- }
111
- }
112
26
 
113
27
  // The toolbar portion (the bar along the top of the screen)
114
28
  .toolbar-edge-toolbar {
@@ -134,7 +48,7 @@
134
48
  }
135
49
  }
136
50
 
137
- // Hide labels when above a certain width
51
+ // Hide inline labels when above a certain width
138
52
  @media screen and (max-width: 700px) {
139
53
  &.one-row > * > .toolbar-toolContainer.label-inline {
140
54
  font-size: 0.9em;
@@ -152,7 +66,8 @@
152
66
  animation: 0.2s linear hide-initially;
153
67
  }
154
68
 
155
- @include label-visible-on-hover(label);
69
+ // DO show the labels on hover.
70
+ @include labelVisibleOnHover.label-visible-on-hover(label);
156
71
 
157
72
  // Clear additional margins added because of the left/right side labels.
158
73
  // (Repeat selector to increase specificity).
@@ -296,14 +211,21 @@
296
211
  --button-label-hover-offset-y: var(--label-hover-offset-size);
297
212
  --button-label-hover-offset-x: 0;
298
213
 
299
- .toolbar-toolContainer:not(.no-icon):not(.label-inline) .toolbar-button {
300
- width: calc(var(--toolbar-button-size) + var(--extra-left-right-padding));
214
+ .toolbar-toolContainer:not(.no-icon):not(.label-inline) {
215
+
216
+ .toolbar-button {
217
+ width: calc(var(--toolbar-button-size) + var(--extra-left-right-padding));
218
+
219
+ // Note: EdgeToolbar.ts currently assumes that the height of a button is
220
+ // equivalent to --toolbar-button-size.
221
+ height: var(--toolbar-button-size);
301
222
 
302
- // Note: EdgeToolbar.ts currently assumes that the height of a button is
303
- // equivalent to --toolbar-button-size.
304
- height: var(--toolbar-button-size);
223
+ @include labelVisibleOnHover.label-visible-on-hover(label);
224
+ }
305
225
 
306
- @include label-visible-on-hover(label);
226
+ &.dropdownVisible > .toolbar-button {
227
+ @include labelVisibleOnHover.show-label-now(label);
228
+ }
307
229
  }
308
230
 
309
231
  & > div > .toolbar-toolContainer:not(.selected):not(.dropdownShowable) > .toolbar-button > .toolbar-showHideDropdownIcon {
@@ -388,7 +310,7 @@
388
310
  // Special styles for the enum selector
389
311
  .toolbar-grid-selector .choice-button {
390
312
  --button-label-hover-offset-y: var(--button-size);
391
- @include label-visible-on-hover('label > .button-label-text');
313
+ @include labelVisibleOnHover.label-visible-on-hover('label > .button-label-text');
392
314
  }
393
315
  }
394
316