js-draw 1.12.0 → 1.13.1

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/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 +48 -37
  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/components/makeGridSelector.js +10 -0
  18. package/dist/cjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
  19. package/dist/cjs/util/addLongPressOrHoverCssClasses.js +34 -0
  20. package/dist/cjs/util/listenForLongPressOrHover.d.ts +13 -0
  21. package/dist/cjs/util/listenForLongPressOrHover.js +80 -0
  22. package/dist/cjs/util/listenForLongPressOrHover.test.d.ts +1 -0
  23. package/dist/cjs/version.js +1 -1
  24. package/dist/mjs/Editor.d.ts +4 -2
  25. package/dist/mjs/Editor.mjs +48 -37
  26. package/dist/mjs/components/AbstractComponent.d.ts +7 -0
  27. package/dist/mjs/components/AbstractComponent.mjs +7 -5
  28. package/dist/mjs/components/util/StrokeSmoother.d.ts +0 -1
  29. package/dist/mjs/components/util/StrokeSmoother.mjs +6 -17
  30. package/dist/mjs/localizations/es.mjs +11 -22
  31. package/dist/mjs/rendering/Display.d.ts +2 -0
  32. package/dist/mjs/rendering/Display.mjs +4 -0
  33. package/dist/mjs/toolbar/DropdownToolbar.d.ts +3 -1
  34. package/dist/mjs/toolbar/DropdownToolbar.mjs +2 -0
  35. package/dist/mjs/toolbar/EdgeToolbar.mjs +6 -5
  36. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +2 -0
  37. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +10 -0
  38. package/dist/mjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
  39. package/dist/mjs/util/addLongPressOrHoverCssClasses.mjs +29 -0
  40. package/dist/mjs/util/listenForLongPressOrHover.d.ts +13 -0
  41. package/dist/mjs/util/listenForLongPressOrHover.mjs +78 -0
  42. package/dist/mjs/util/listenForLongPressOrHover.test.d.ts +1 -0
  43. package/dist/mjs/version.mjs +1 -1
  44. package/package.json +2 -2
  45. package/src/toolbar/EdgeToolbar.scss +19 -97
  46. package/src/toolbar/utils/labelVisibleOnHover.scss +114 -0
  47. package/src/toolbar/widgets/components/makeGridSelector.scss +1 -0
@@ -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,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.1',
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.1",
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": "814f5d57897fd16fd45860522d9ecbc6b8b04a38"
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
 
@@ -0,0 +1,114 @@
1
+
2
+ $label-visible-opacity: 0.8;
3
+ $show-delay: 0.3s;
4
+
5
+ @keyframes rehide-label {
6
+ 0% { opacity: $label-visible-opacity; }
7
+ 80% { opacity: $label-visible-opacity; }
8
+ 100% { opacity: 0.1; }
9
+ }
10
+
11
+ @keyframes show-label-delayed {
12
+ 0% { opacity: 0; }
13
+ // Keep the label hidden before showing
14
+ 80% { opacity: 0; }
15
+ 100% { opacity: $label-visible-opacity; }
16
+ }
17
+
18
+ @keyframes show-label-now {
19
+ 0% { opacity: 0; }
20
+ 5% { opacity: 0; }
21
+ 100% { opacity: $label-visible-opacity; }
22
+ }
23
+
24
+ @keyframes keep-label-hidden {
25
+ 0% { opacity: 0; }
26
+ 100% { opacity: 0; }
27
+ }
28
+
29
+ // Shows a lable on hover
30
+ // Uses the --button-label-hover-offset-y and --button-label-hover-offset-x
31
+ // variables to determine the label's position.
32
+ //
33
+ // If the label is in a scrolling container, that container should have position: relative;
34
+ // as this allows absolutely positioned children to scroll with the container (rather than
35
+ // remaining stationary).
36
+ @mixin label-visible-on-hover($label-selector) {
37
+ // Using CSS classes set by addLongPressOrHoverCssClasses avoids sticky
38
+ // hover on touch devices.
39
+ &:not(.no-long-press-or-hover):not(.has-long-press-or-hover) {
40
+ // Only show an animation when opening the label due to a hover --
41
+ // show the label immediately otherwise.
42
+ &:hover:not(:focus-visible) > #{$label-selector},
43
+ // When the user is pressing/long-pressing the button
44
+ &:active > #{$label-selector}
45
+ {
46
+ opacity: $label-visible-opacity;
47
+ animation: 1s ease show-label-delayed;
48
+ }
49
+ }
50
+
51
+ &.has-long-press-or-hover > #{$label-selector} {
52
+ opacity: $label-visible-opacity;
53
+ }
54
+
55
+ $keyboard-hide-animation: 1.5s ease rehide-label;
56
+
57
+ // .focus-visible: Allow setting focus-visible from JS (in cases where this
58
+ // element isn't the focus target and the browser doesn't support :has).
59
+ &:focus-visible, &.focus-visible {
60
+ & > #{$label-selector} {
61
+ animation: $keyboard-hide-animation;
62
+ opacity: 0;
63
+ }
64
+ }
65
+
66
+ // Make the :has selector separate its own statement -- some browsers don't
67
+ // support :has, which would make the entire statement block have no effect.
68
+ &:has(:focus-visible) > #{$label-selector} {
69
+ animation: $keyboard-hide-animation;
70
+ opacity: 0;
71
+ }
72
+
73
+ & > #{$label-selector} {
74
+ opacity: 0;
75
+ position: absolute;
76
+ margin-top: var(--button-label-hover-offset-y);
77
+ margin-left: var(--button-label-hover-offset-x);
78
+ z-index: 1;
79
+
80
+ // The label is often mostly invisible/just below a toolbar item.
81
+ // If there are multiple toolbar rows, ensure that a label doesn't prevent
82
+ // clicking on items in the second row:
83
+ pointer-events: none;
84
+
85
+ background-color: var(--background-color-1);
86
+ color: var(--foreground-color-1);
87
+ border-radius: 25px;
88
+ padding: 10px;
89
+
90
+ transition: $show-delay ease opacity, 0.2s ease margin-top;
91
+
92
+ @media (prefers-reduced-motion: reduce) {
93
+ transition: none;
94
+ }
95
+ }
96
+ }
97
+
98
+ // Allows manually showing the label
99
+ @mixin show-label-now($label-selector) {
100
+ & > #{$label-selector} {
101
+ // Set opacity manually (rather than in an animation) to take advantage of
102
+ // `transition`. Without this, when animations are cancelled and replaced,
103
+ // the label may flicker.
104
+ opacity: $label-visible-opacity;
105
+
106
+ $hide-animation-length: 1.5s;
107
+ $keep-hidden-delay: calc($hide-animation-length + $show-delay);
108
+
109
+ $hide-animation: 1.5s ease rehide-label $show-delay;
110
+ $keep-hidden-animation: 1s ease keep-label-hidden $keep-hidden-delay infinite;
111
+
112
+ animation: $hide-animation, $keep-hidden-animation;
113
+ }
114
+ }
@@ -16,6 +16,7 @@
16
16
  display: flex;
17
17
  flex-direction: column-reverse;
18
18
  box-sizing: border-box;
19
+ cursor: pointer;
19
20
 
20
21
  flex-shrink: 1;
21
22
  margin: 2px;