UNPKG

js-draw

Version:

Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.

127 lines (126 loc) 5.46 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const waitForTimeout_1 = __importDefault(require("../../util/waitForTimeout")); const types_1 = require("../../types"); let idCounter = 0; const createMenuOverlay = async (editor, canvasAnchor, options) => { const overlay = document.createElement('div'); const { remove: removeOverlay } = editor.createHTMLOverlay(overlay); const menuModal = document.createElement('dialog'); menuModal.classList.add('editor-popup-menu'); const hideMenuTimeout = 240; menuModal.style.setProperty('--hide-menu-animation-timeout', `${hideMenuTimeout}ms`); const updateMenuLocation = () => { const overlayRect = editor.getOutputBBoxInDOM(); const anchor = editor.viewport.canvasToScreen(canvasAnchor).plus(overlayRect.topLeft); menuModal.style.setProperty('--anchor-x', `${anchor.x}px`); menuModal.style.setProperty('--anchor-y', `${anchor.y}px`); }; updateMenuLocation(); const viewportChangeListener = editor.notifier.on(types_1.EditorEventType.ViewportChanged, updateMenuLocation); overlay.appendChild(menuModal); let dismissing = false; const dismissMenu = async () => { if (dismissing) return; dismissing = true; viewportChangeListener.remove(); menuModal.classList.add('-hide'); await (0, waitForTimeout_1.default)(hideMenuTimeout); menuModal.close(); }; return new Promise((resolve) => { let resolved = false; let result = null; const resolveWithSelectedResult = () => { if (!resolved) { resolve(result); resolved = true; } }; menuModal.onclose = () => { removeOverlay(); resolveWithSelectedResult(); }; const onOptionSelected = (key) => { result = key; void dismissMenu(); // To properly handle clipboard events, this needs to be called synchronously // and not after a delay: resolveWithSelectedResult(); }; editor.handlePointerEventsExceptClicksFrom(menuModal, (eventName, event) => { if (event.target === menuModal && eventName === 'pointerdown') { void dismissMenu(); return true; } else if (dismissing) { // Send pointer events to the editor if the dialog is in the process of being // dismissed (e.g. pointermove events just after a pointerdown outside of the // editor). return true; } return false; }, (_eventName, event) => { return event.target === menuModal; }); const contentElement = document.createElement('div'); contentElement.classList.add('content'); contentElement.role = 'menu'; const optionElements = []; // Keyboard focus handling as described in // - https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menu_role and // - https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/ contentElement.addEventListener('keydown', (event) => { const focusedIndex = optionElements.findIndex((item) => item === document.activeElement); if (focusedIndex === -1) return; let newFocusedIndex = focusedIndex; if (event.key === 'ArrowDown') { newFocusedIndex++; } else if (event.key === 'ArrowUp') { newFocusedIndex--; } else if (event.key === 'End') { newFocusedIndex = optionElements.length - 1; } else if (event.key === 'Home') { newFocusedIndex = 0; } if (newFocusedIndex < 0) { newFocusedIndex += optionElements.length; } newFocusedIndex %= optionElements.length; if (newFocusedIndex !== focusedIndex) { event.preventDefault(); optionElements[newFocusedIndex].focus(); } }); for (const option of options) { const optionElement = document.createElement('button'); optionElement.id = `menu-overlay-option-${idCounter++}`; optionElement.role = 'menuitem'; optionElement.classList.add('option', 'editor-popup-menu-option'); optionElement.replaceChildren(option.icon(), document.createTextNode(option.text)); optionElement.onclick = (event) => { if (event.defaultPrevented) return; onOptionSelected(option.key); }; contentElement.appendChild(optionElement); if (optionElements.length === 0) { optionElement.autofocus = true; } optionElements.push(optionElement); } menuModal.appendChild(contentElement); menuModal.showModal(); // Ensures that the menu is visible even if triggered near the edge of the screen. contentElement.scrollIntoView({ block: 'nearest' }); }); }; exports.default = createMenuOverlay;