js-draw 1.19.1 → 1.20.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.
- 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 | 
             
            }
         |