js-draw 1.19.1 → 1.20.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +52 -1
- package/dist/Editor.css +49 -7
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/SVGLoader/index.js +3 -1
- package/dist/cjs/image/EditorImage.d.ts +2 -1
- package/dist/cjs/image/EditorImage.js +101 -5
- package/dist/cjs/rendering/renderers/CanvasRenderer.js +4 -4
- package/dist/cjs/toolbar/localization.d.ts +1 -0
- package/dist/cjs/toolbar/localization.js +1 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.js +7 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/index.js +11 -3
- package/dist/cjs/toolbar/widgets/components/makeFileInput.js +12 -1
- package/dist/cjs/toolbar/widgets/components/makeSnappedList.js +69 -4
- package/dist/cjs/tools/Eraser.js +22 -5
- package/dist/cjs/tools/PanZoom.d.ts +54 -0
- package/dist/cjs/tools/PanZoom.js +54 -2
- package/dist/cjs/util/ReactiveValue.d.ts +4 -0
- package/dist/cjs/util/ReactiveValue.js +5 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/SVGLoader/index.mjs +3 -1
- package/dist/mjs/image/EditorImage.d.ts +2 -1
- package/dist/mjs/image/EditorImage.mjs +101 -5
- package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +4 -4
- package/dist/mjs/toolbar/localization.d.ts +1 -0
- package/dist/mjs/toolbar/localization.mjs +1 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.mjs +7 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/index.mjs +11 -3
- package/dist/mjs/toolbar/widgets/components/makeFileInput.mjs +12 -1
- package/dist/mjs/toolbar/widgets/components/makeSnappedList.mjs +69 -4
- package/dist/mjs/tools/Eraser.mjs +22 -5
- package/dist/mjs/tools/PanZoom.d.ts +54 -0
- package/dist/mjs/tools/PanZoom.mjs +54 -2
- package/dist/mjs/util/ReactiveValue.d.ts +4 -0
- package/dist/mjs/util/ReactiveValue.mjs +5 -0
- package/dist/mjs/version.mjs +1 -1
- package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
- package/package.json +2 -2
- package/src/toolbar/EdgeToolbar.scss +8 -3
- package/src/toolbar/widgets/components/makeFileInput.scss +3 -2
- package/src/toolbar/widgets/components/makeSnappedList.scss +58 -12
@@ -176,13 +176,21 @@ class InsertImageWidget extends BaseWidget {
|
|
176
176
|
currentImage?.reset();
|
177
177
|
};
|
178
178
|
this.statusView.replaceChildren(sizeText);
|
179
|
-
|
180
|
-
if (imageData.length > largeImageThreshold) {
|
179
|
+
if (currentImage?.isLarge()) {
|
181
180
|
this.statusView.appendChild(decreaseSizeButton);
|
182
181
|
}
|
183
182
|
else if (currentImage?.isChanged()) {
|
184
183
|
this.statusView.appendChild(resetSizeButton);
|
185
184
|
}
|
185
|
+
else {
|
186
|
+
const hasLargeOrChangedImages = this.images.get().some(image => image.data?.isChanged() || image.data?.isLarge());
|
187
|
+
if (hasLargeOrChangedImages) {
|
188
|
+
// Still show the button -- prevents the layout from readjusting while
|
189
|
+
// scrolling through the image list
|
190
|
+
decreaseSizeButton.disabled = true;
|
191
|
+
this.statusView.appendChild(decreaseSizeButton);
|
192
|
+
}
|
193
|
+
}
|
186
194
|
}
|
187
195
|
updateInputs() {
|
188
196
|
const resetInputs = () => {
|
@@ -220,7 +228,7 @@ class InsertImageWidget extends BaseWidget {
|
|
220
228
|
}
|
221
229
|
const image = new Image();
|
222
230
|
image.src = imageWrapper.getBase64Url();
|
223
|
-
image.setAttribute('alt',
|
231
|
+
image.setAttribute('alt', imageWrapper.getAltText());
|
224
232
|
let component;
|
225
233
|
try {
|
226
234
|
component = await ImageComponent.fromImage(image, transform);
|
@@ -42,7 +42,18 @@ const makeFileInput = (labelText, context, { accepts = '*', allowMultiSelect = f
|
|
42
42
|
icon.style.display = 'none';
|
43
43
|
}
|
44
44
|
else if (files.length > 0) {
|
45
|
-
|
45
|
+
const fileNames = files.map(file => file.name);
|
46
|
+
const maxNames = 5;
|
47
|
+
if (fileNames.length <= maxNames) {
|
48
|
+
descriptionText.textContent = fileNames.join('\n');
|
49
|
+
}
|
50
|
+
else {
|
51
|
+
const fileNamesToShow = fileNames.slice(0, maxNames - 1);
|
52
|
+
descriptionText.textContent = [
|
53
|
+
...fileNamesToShow,
|
54
|
+
context.localization.fileInput__andNMoreFiles(fileNames.length - fileNamesToShow.length),
|
55
|
+
].join('\n');
|
56
|
+
}
|
46
57
|
// Only show the icon when there are files
|
47
58
|
icon.style.display = 'none';
|
48
59
|
}
|
@@ -6,8 +6,72 @@ import { MutableReactiveValue, ReactiveValue } from '../../../util/ReactiveVal
|
|
6
6
|
const makeSnappedList = (itemsValue) => {
|
7
7
|
const container = document.createElement('div');
|
8
8
|
container.classList.add('toolbar-snapped-scroll-list');
|
9
|
+
const scroller = document.createElement('div');
|
10
|
+
scroller.classList.add('scroller');
|
9
11
|
const visibleIndex = MutableReactiveValue.fromInitialValue(0);
|
10
12
|
let observer = null;
|
13
|
+
const makePageMarkers = () => {
|
14
|
+
const markerContainer = document.createElement('div');
|
15
|
+
markerContainer.classList.add('page-markers');
|
16
|
+
// Keyboard focus should go to the main scrolling list.
|
17
|
+
// TODO: Does it make sense for the page marker list to be focusable?
|
18
|
+
markerContainer.setAttribute('tabindex', '-1');
|
19
|
+
const markers = [];
|
20
|
+
const pairedItems = ReactiveValue.union([visibleIndex, itemsValue]);
|
21
|
+
pairedItems.onUpdateAndNow(([currentVisibleIndex, items]) => {
|
22
|
+
let addedOrRemovedMarkers = false;
|
23
|
+
// Items may have been removed from the list of pages. Make the markers reflect that.
|
24
|
+
while (items.length < markers.length) {
|
25
|
+
markers.pop();
|
26
|
+
addedOrRemovedMarkers = true;
|
27
|
+
}
|
28
|
+
let activeMarker;
|
29
|
+
for (let i = 0; i < items.length; i++) {
|
30
|
+
let marker;
|
31
|
+
if (i >= markers.length) {
|
32
|
+
marker = document.createElement('div');
|
33
|
+
// Use a separate content element to increase the clickable size of
|
34
|
+
// the marker.
|
35
|
+
const content = document.createElement('div');
|
36
|
+
content.classList.add('content');
|
37
|
+
marker.replaceChildren(content);
|
38
|
+
markers.push(marker);
|
39
|
+
addedOrRemovedMarkers = true;
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
marker = markers[i];
|
43
|
+
}
|
44
|
+
marker.classList.add('marker');
|
45
|
+
if (i === currentVisibleIndex) {
|
46
|
+
marker.classList.add('-active');
|
47
|
+
activeMarker = marker;
|
48
|
+
}
|
49
|
+
else {
|
50
|
+
marker.classList.remove('-active');
|
51
|
+
}
|
52
|
+
const markerIndex = i;
|
53
|
+
marker.onclick = () => {
|
54
|
+
wrappedItems.get()[markerIndex]?.element?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
55
|
+
};
|
56
|
+
}
|
57
|
+
// Only call .replaceChildren when necessary -- doing so on every change would
|
58
|
+
// break transitions.
|
59
|
+
if (addedOrRemovedMarkers) {
|
60
|
+
markerContainer.replaceChildren(...markers);
|
61
|
+
}
|
62
|
+
// Handles the case where there are many markers and the current is offscreen
|
63
|
+
if (activeMarker && markerContainer.scrollHeight > container.clientHeight) {
|
64
|
+
activeMarker.scrollIntoView({ block: 'nearest' });
|
65
|
+
}
|
66
|
+
if (markers.length === 1) {
|
67
|
+
markerContainer.classList.add('-one-element');
|
68
|
+
}
|
69
|
+
else {
|
70
|
+
markerContainer.classList.remove('-one-element');
|
71
|
+
}
|
72
|
+
});
|
73
|
+
return markerContainer;
|
74
|
+
};
|
11
75
|
const createObserver = () => {
|
12
76
|
observer = new IntersectionObserver((entries) => {
|
13
77
|
for (const entry of entries) {
|
@@ -23,7 +87,7 @@ const makeSnappedList = (itemsValue) => {
|
|
23
87
|
}, {
|
24
88
|
// Element to use as the boudning box with which to intersect.
|
25
89
|
// See https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
26
|
-
root:
|
90
|
+
root: scroller,
|
27
91
|
// Fraction of an element that must be visible to trigger the callback:
|
28
92
|
threshold: 0.9,
|
29
93
|
});
|
@@ -55,7 +119,7 @@ const makeSnappedList = (itemsValue) => {
|
|
55
119
|
for (const item of lastItems) {
|
56
120
|
observer?.unobserve(item.element);
|
57
121
|
}
|
58
|
-
|
122
|
+
scroller.replaceChildren();
|
59
123
|
// An observer is only necessary if there are multiple items to scroll through.
|
60
124
|
if (items.length > 1) {
|
61
125
|
createObserver();
|
@@ -71,7 +135,7 @@ const makeSnappedList = (itemsValue) => {
|
|
71
135
|
container.classList.remove('-empty');
|
72
136
|
}
|
73
137
|
for (const item of items) {
|
74
|
-
|
138
|
+
scroller.appendChild(item.element);
|
75
139
|
}
|
76
140
|
visibleIndex.set(0);
|
77
141
|
if (observer) {
|
@@ -89,7 +153,8 @@ const makeSnappedList = (itemsValue) => {
|
|
89
153
|
});
|
90
154
|
// makeSnappedList is generally shown within the toolbar. This allows users to
|
91
155
|
// scroll it with a touchpad.
|
92
|
-
stopPropagationOfScrollingWheelEvents(
|
156
|
+
stopPropagationOfScrollingWheelEvents(scroller);
|
157
|
+
container.replaceChildren(makePageMarkers(), scroller);
|
93
158
|
return {
|
94
159
|
container,
|
95
160
|
visibleItem,
|
@@ -65,6 +65,7 @@ export default class Eraser extends BaseTool {
|
|
65
65
|
this.editor = editor;
|
66
66
|
this.lastPoint = null;
|
67
67
|
this.isFirstEraseEvt = true;
|
68
|
+
this.toAdd = new Set();
|
68
69
|
// Commands that each remove one element
|
69
70
|
this.eraseCommands = [];
|
70
71
|
this.addCommands = [];
|
@@ -174,15 +175,17 @@ export default class Eraser extends BaseTool {
|
|
174
175
|
newAddCommands.forEach(command => command.apply(this.editor));
|
175
176
|
const finalToErase = [];
|
176
177
|
for (const item of toErase) {
|
177
|
-
if (this.toAdd.
|
178
|
-
this.toAdd
|
178
|
+
if (this.toAdd.has(item)) {
|
179
|
+
this.toAdd.delete(item);
|
179
180
|
}
|
180
181
|
else {
|
181
182
|
finalToErase.push(item);
|
182
183
|
}
|
183
184
|
}
|
184
185
|
this.toRemove.push(...finalToErase);
|
185
|
-
|
186
|
+
for (const item of toAdd) {
|
187
|
+
this.toAdd.add(item);
|
188
|
+
}
|
186
189
|
this.eraseCommands.push(new Erase(finalToErase));
|
187
190
|
this.addCommands.push(...newAddCommands);
|
188
191
|
}
|
@@ -193,7 +196,7 @@ export default class Eraser extends BaseTool {
|
|
193
196
|
if (event.allPointers.length === 1 || event.current.device === PointerDevice.Eraser) {
|
194
197
|
this.lastPoint = event.current.canvasPos;
|
195
198
|
this.toRemove = [];
|
196
|
-
this.toAdd
|
199
|
+
this.toAdd.clear();
|
197
200
|
this.isFirstEraseEvt = true;
|
198
201
|
this.drawPreviewAt(event.current.canvasPos);
|
199
202
|
return true;
|
@@ -209,7 +212,21 @@ export default class Eraser extends BaseTool {
|
|
209
212
|
const commands = [];
|
210
213
|
if (this.addCommands.length > 0) {
|
211
214
|
this.addCommands.forEach(cmd => cmd.unapply(this.editor));
|
212
|
-
|
215
|
+
// Remove items from toAdd that are also present in toRemove -- adding, then
|
216
|
+
// removing these does nothing, and can break undo/redo.
|
217
|
+
for (const item of this.toAdd) {
|
218
|
+
if (this.toRemove.includes(item)) {
|
219
|
+
this.toAdd.delete(item);
|
220
|
+
this.toRemove = this.toRemove.filter(other => other !== item);
|
221
|
+
}
|
222
|
+
}
|
223
|
+
for (const item of this.toRemove) {
|
224
|
+
if (this.toAdd.has(item)) {
|
225
|
+
this.toAdd.delete(item);
|
226
|
+
this.toRemove = this.toRemove.filter(other => other !== item);
|
227
|
+
}
|
228
|
+
}
|
229
|
+
commands.push(...[...this.toAdd].map(a => EditorImage.addElement(a)));
|
213
230
|
this.addCommands = [];
|
214
231
|
}
|
215
232
|
if (this.eraseCommands.length > 0) {
|
@@ -10,13 +10,27 @@ interface PinchData {
|
|
10
10
|
dist: number;
|
11
11
|
}
|
12
12
|
export declare enum PanZoomMode {
|
13
|
+
/** Touch gestures with a single pointer. Ignores non-touch gestures. */
|
13
14
|
OneFingerTouchGestures = 1,
|
15
|
+
/** Touch gestures with exactly two pointers. Ignores non-touch gestures. */
|
14
16
|
TwoFingerTouchGestures = 2,
|
15
17
|
RightClickDrags = 4,
|
18
|
+
/** Single-pointer gestures of *any* type (including touch). */
|
16
19
|
SinglePointerGestures = 8,
|
20
|
+
/** Keyboard navigation (e.g. LeftArrow to move left). */
|
17
21
|
Keyboard = 16,
|
22
|
+
/** If provided, prevents **this** tool from rotating the viewport (other tools may still do so). */
|
18
23
|
RotationLocked = 32
|
19
24
|
}
|
25
|
+
/**
|
26
|
+
* This tool moves the viewport in response to touchpad, touchscreen, mouse, and keyboard events.
|
27
|
+
*
|
28
|
+
* Which events are handled, and which are skipped, are determined by the tool's `mode`. For example,
|
29
|
+
* a `PanZoom` tool with `mode = PanZoomMode.TwoFingerTouchGestures|PanZoomMode.RightClickDrags` would
|
30
|
+
* respond to right-click drag events and two-finger touch gestures.
|
31
|
+
*
|
32
|
+
* @see {@link setModeEnabled}
|
33
|
+
*/
|
20
34
|
export default class PanZoom extends BaseTool {
|
21
35
|
private editor;
|
22
36
|
private mode;
|
@@ -58,8 +72,48 @@ export default class PanZoom extends BaseTool {
|
|
58
72
|
onWheel({ delta, screenPos }: WheelEvt): boolean;
|
59
73
|
onKeyPress(event: KeyPressEvent): boolean;
|
60
74
|
private isRotationLocked;
|
75
|
+
/**
|
76
|
+
* Changes the types of gestures used by this pan/zoom tool.
|
77
|
+
*
|
78
|
+
* @see {@link PanZoomMode} {@link setMode}
|
79
|
+
*
|
80
|
+
* @example
|
81
|
+
* ```ts,runnable
|
82
|
+
* import { Editor, PanZoomTool, PanZoomMode } from 'js-draw';
|
83
|
+
*
|
84
|
+
* const editor = new Editor(document.body);
|
85
|
+
*
|
86
|
+
* // By default, there are multiple PanZoom tools that handle different events.
|
87
|
+
* // This gets all PanZoomTools.
|
88
|
+
* const panZoomToolList = editor.toolController.getMatchingTools(PanZoomTool);
|
89
|
+
*
|
90
|
+
* // The first PanZoomTool is the highest priority -- by default,
|
91
|
+
* // this tool is responsible for handling multi-finger touch gestures.
|
92
|
+
* //
|
93
|
+
* // Lower-priority PanZoomTools handle one-finger touch gestures and
|
94
|
+
* // key-presses.
|
95
|
+
* const panZoomTool = panZoomToolList[0];
|
96
|
+
*
|
97
|
+
* // Lock rotation for multi-finger touch gestures.
|
98
|
+
* panZoomTool.setModeEnabled(PanZoomMode.RotationLocked, true);
|
99
|
+
* ```
|
100
|
+
*/
|
61
101
|
setModeEnabled(mode: PanZoomMode, enabled: boolean): void;
|
102
|
+
/**
|
103
|
+
* Sets all modes for this tool using a bitmask.
|
104
|
+
*
|
105
|
+
* @see {@link setModeEnabled}
|
106
|
+
*
|
107
|
+
* @example
|
108
|
+
* ```ts
|
109
|
+
* tool.setMode(PanZoomMode.RotationLocked|PanZoomMode.TwoFingerTouchGestures);
|
110
|
+
* ```
|
111
|
+
*/
|
62
112
|
setMode(mode: PanZoomMode): void;
|
113
|
+
/**
|
114
|
+
* Returns a bitmask indicating the currently-enabled modes.
|
115
|
+
* @see {@link setModeEnabled}
|
116
|
+
*/
|
63
117
|
getMode(): PanZoomMode;
|
64
118
|
}
|
65
119
|
export {};
|
@@ -7,11 +7,16 @@ import BaseTool from './BaseTool.mjs';
|
|
7
7
|
import { moveDownKeyboardShortcutId, moveLeftKeyboardShortcutId, moveRightKeyboardShortcutId, moveUpKeyboardShortcutId, rotateClockwiseKeyboardShortcutId, rotateCounterClockwiseKeyboardShortcutId, zoomInKeyboardShortcutId, zoomOutKeyboardShortcutId } from './keybindings.mjs';
|
8
8
|
export var PanZoomMode;
|
9
9
|
(function (PanZoomMode) {
|
10
|
+
/** Touch gestures with a single pointer. Ignores non-touch gestures. */
|
10
11
|
PanZoomMode[PanZoomMode["OneFingerTouchGestures"] = 1] = "OneFingerTouchGestures";
|
12
|
+
/** Touch gestures with exactly two pointers. Ignores non-touch gestures. */
|
11
13
|
PanZoomMode[PanZoomMode["TwoFingerTouchGestures"] = 2] = "TwoFingerTouchGestures";
|
12
14
|
PanZoomMode[PanZoomMode["RightClickDrags"] = 4] = "RightClickDrags";
|
15
|
+
/** Single-pointer gestures of *any* type (including touch). */
|
13
16
|
PanZoomMode[PanZoomMode["SinglePointerGestures"] = 8] = "SinglePointerGestures";
|
17
|
+
/** Keyboard navigation (e.g. LeftArrow to move left). */
|
14
18
|
PanZoomMode[PanZoomMode["Keyboard"] = 16] = "Keyboard";
|
19
|
+
/** If provided, prevents **this** tool from rotating the viewport (other tools may still do so). */
|
15
20
|
PanZoomMode[PanZoomMode["RotationLocked"] = 32] = "RotationLocked";
|
16
21
|
})(PanZoomMode || (PanZoomMode = {}));
|
17
22
|
class InertialScroller {
|
@@ -59,6 +64,15 @@ class InertialScroller {
|
|
59
64
|
}
|
60
65
|
}
|
61
66
|
}
|
67
|
+
/**
|
68
|
+
* This tool moves the viewport in response to touchpad, touchscreen, mouse, and keyboard events.
|
69
|
+
*
|
70
|
+
* Which events are handled, and which are skipped, are determined by the tool's `mode`. For example,
|
71
|
+
* a `PanZoom` tool with `mode = PanZoomMode.TwoFingerTouchGestures|PanZoomMode.RightClickDrags` would
|
72
|
+
* respond to right-click drag events and two-finger touch gestures.
|
73
|
+
*
|
74
|
+
* @see {@link setModeEnabled}
|
75
|
+
*/
|
62
76
|
export default class PanZoom extends BaseTool {
|
63
77
|
constructor(editor, mode, description) {
|
64
78
|
super(editor.notifier, description);
|
@@ -422,8 +436,32 @@ export default class PanZoom extends BaseTool {
|
|
422
436
|
isRotationLocked() {
|
423
437
|
return !!(this.mode & PanZoomMode.RotationLocked);
|
424
438
|
}
|
425
|
-
|
426
|
-
|
439
|
+
/**
|
440
|
+
* Changes the types of gestures used by this pan/zoom tool.
|
441
|
+
*
|
442
|
+
* @see {@link PanZoomMode} {@link setMode}
|
443
|
+
*
|
444
|
+
* @example
|
445
|
+
* ```ts,runnable
|
446
|
+
* import { Editor, PanZoomTool, PanZoomMode } from 'js-draw';
|
447
|
+
*
|
448
|
+
* const editor = new Editor(document.body);
|
449
|
+
*
|
450
|
+
* // By default, there are multiple PanZoom tools that handle different events.
|
451
|
+
* // This gets all PanZoomTools.
|
452
|
+
* const panZoomToolList = editor.toolController.getMatchingTools(PanZoomTool);
|
453
|
+
*
|
454
|
+
* // The first PanZoomTool is the highest priority -- by default,
|
455
|
+
* // this tool is responsible for handling multi-finger touch gestures.
|
456
|
+
* //
|
457
|
+
* // Lower-priority PanZoomTools handle one-finger touch gestures and
|
458
|
+
* // key-presses.
|
459
|
+
* const panZoomTool = panZoomToolList[0];
|
460
|
+
*
|
461
|
+
* // Lock rotation for multi-finger touch gestures.
|
462
|
+
* panZoomTool.setModeEnabled(PanZoomMode.RotationLocked, true);
|
463
|
+
* ```
|
464
|
+
*/
|
427
465
|
setModeEnabled(mode, enabled) {
|
428
466
|
let newMode = this.mode;
|
429
467
|
if (enabled) {
|
@@ -434,6 +472,16 @@ export default class PanZoom extends BaseTool {
|
|
434
472
|
}
|
435
473
|
this.setMode(newMode);
|
436
474
|
}
|
475
|
+
/**
|
476
|
+
* Sets all modes for this tool using a bitmask.
|
477
|
+
*
|
478
|
+
* @see {@link setModeEnabled}
|
479
|
+
*
|
480
|
+
* @example
|
481
|
+
* ```ts
|
482
|
+
* tool.setMode(PanZoomMode.RotationLocked|PanZoomMode.TwoFingerTouchGestures);
|
483
|
+
* ```
|
484
|
+
*/
|
437
485
|
setMode(mode) {
|
438
486
|
if (mode !== this.mode) {
|
439
487
|
this.mode = mode;
|
@@ -443,6 +491,10 @@ export default class PanZoom extends BaseTool {
|
|
443
491
|
});
|
444
492
|
}
|
445
493
|
}
|
494
|
+
/**
|
495
|
+
* Returns a bitmask indicating the currently-enabled modes.
|
496
|
+
* @see {@link setModeEnabled}
|
497
|
+
*/
|
446
498
|
getMode() {
|
447
499
|
return this.mode;
|
448
500
|
}
|
@@ -2,6 +2,9 @@ type ListenerResult = {
|
|
2
2
|
remove(): void;
|
3
3
|
};
|
4
4
|
type UpdateCallback<T> = (value: T) => void;
|
5
|
+
type ReactiveValuesOf<T extends unknown[]> = {
|
6
|
+
[key in keyof T]: ReactiveValue<T[key]>;
|
7
|
+
};
|
5
8
|
/**
|
6
9
|
* A `ReactiveValue` is a value that
|
7
10
|
* - updates periodically,
|
@@ -56,6 +59,7 @@ export declare abstract class ReactiveValue<T> {
|
|
56
59
|
* Returns a reactive value derived from a single `source`.
|
57
60
|
*/
|
58
61
|
static map<A, B>(source: ReactiveValue<A>, map: (a: A) => B, inverseMap: (b: B) => A): MutableReactiveValue<B>;
|
62
|
+
static union<Values extends [...unknown[]]>(values: ReactiveValuesOf<Values>): ReactiveValue<Values>;
|
59
63
|
}
|
60
64
|
export declare abstract class MutableReactiveValue<T> extends ReactiveValue<T> {
|
61
65
|
/**
|
@@ -103,6 +103,11 @@ export class ReactiveValue {
|
|
103
103
|
}
|
104
104
|
return result;
|
105
105
|
}
|
106
|
+
static union(values) {
|
107
|
+
return ReactiveValue.fromCallback(() => {
|
108
|
+
return values.map(value => value.get());
|
109
|
+
}, values);
|
110
|
+
}
|
106
111
|
}
|
107
112
|
export class MutableReactiveValue extends ReactiveValue {
|
108
113
|
static fromProperty(sourceValue, propertyName) {
|
package/dist/mjs/version.mjs
CHANGED
Binary file
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.20.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": "
|
89
|
+
"gitHead": "a981ae8ef5f2ac8ef27c8ce2872a2dbf0c25050d"
|
90
90
|
}
|
@@ -307,14 +307,19 @@
|
|
307
307
|
border: none;
|
308
308
|
padding: 10px;
|
309
309
|
|
310
|
-
color: var(--foreground-color-1);
|
311
|
-
|
312
310
|
transition: 0.2s ease box-shadow;
|
313
311
|
|
314
|
-
&:hover {
|
312
|
+
&:not(:disabled):hover {
|
315
313
|
box-shadow: 0 1px 2px var(--shadow-color);
|
316
314
|
}
|
317
315
|
|
316
|
+
&:disabled {
|
317
|
+
opacity: 0.5;
|
318
|
+
font-weight: unset;
|
319
|
+
cursor: unset;
|
320
|
+
color: var(--foreground-color-1);
|
321
|
+
}
|
322
|
+
|
318
323
|
font-weight: bold;
|
319
324
|
color: var(--primary-action-foreground-color);
|
320
325
|
}
|
@@ -2,27 +2,73 @@
|
|
2
2
|
// Repeat for specificity.
|
3
3
|
// TODO(v2): Refactor everything to use RCSS.
|
4
4
|
:root .toolbar-snapped-scroll-list.toolbar-snapped-scroll-list.toolbar-snapped-scroll-list {
|
5
|
-
overflow-y: auto;
|
6
|
-
scroll-snap-type: y mandatory;
|
7
5
|
height: min(200px, 50vh);
|
6
|
+
position: relative;
|
8
7
|
display: flex;
|
9
|
-
|
8
|
+
align-items: center;
|
9
|
+
|
10
|
+
> .scroller {
|
11
|
+
display: flex;
|
12
|
+
flex-direction: column;
|
13
|
+
overflow-y: auto;
|
14
|
+
scroll-snap-type: y mandatory;
|
10
15
|
|
11
|
-
> .item {
|
12
16
|
height: 100%;
|
13
17
|
width: 100%;
|
14
|
-
flex-
|
15
|
-
|
16
|
-
display: flex;
|
17
|
-
justify-content: center;
|
18
|
-
align-items: center;
|
18
|
+
flex-grow: 1;
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
> .item {
|
21
|
+
height: 100%;
|
22
|
+
width: 100%;
|
23
|
+
flex-shrink: 0;
|
24
|
+
|
25
|
+
display: flex;
|
26
|
+
justify-content: center;
|
27
|
+
align-items: center;
|
28
|
+
|
29
|
+
scroll-snap-align: start;
|
30
|
+
scroll-snap-stop: always;
|
31
|
+
box-sizing: border-box;
|
32
|
+
}
|
23
33
|
}
|
24
34
|
|
25
35
|
&.-empty {
|
26
36
|
display: none;
|
27
37
|
}
|
38
|
+
|
39
|
+
> .page-markers {
|
40
|
+
overflow: hidden;
|
41
|
+
|
42
|
+
display: flex;
|
43
|
+
flex-direction: column;
|
44
|
+
align-items: center;
|
45
|
+
|
46
|
+
max-height: 100%;
|
47
|
+
min-height: 0;
|
48
|
+
|
49
|
+
&.-one-element {
|
50
|
+
visibility: hidden;
|
51
|
+
}
|
52
|
+
|
53
|
+
> .marker {
|
54
|
+
> .content {
|
55
|
+
background-color: var(--foreground-color-1);
|
56
|
+
border-radius: 2px;
|
57
|
+
padding: 2px;
|
58
|
+
}
|
59
|
+
|
60
|
+
padding: 2px;
|
61
|
+
opacity: 0.1;
|
62
|
+
cursor: pointer;
|
63
|
+
|
64
|
+
left: 0;
|
65
|
+
transition: left 0.2s ease;
|
66
|
+
|
67
|
+
&.-active {
|
68
|
+
position: relative;
|
69
|
+
left: 2px;
|
70
|
+
opacity: 0.2;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
28
74
|
}
|