@infinite-canvas-tutorial/webcomponents
Version:
WebComponents UI implementation
698 lines (696 loc) • 29.9 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextMenu = void 0;
exports.executeCopy = executeCopy;
exports.executeCut = executeCut;
exports.executePaste = executePaste;
const localize_1 = require("@lit/localize");
const lit_1 = require("lit");
const decorators_js_1 = require("lit/decorators.js");
const when_js_1 = require("lit/directives/when.js");
const context_1 = require("@lit/context");
const ecs_1 = require("@infinite-canvas-tutorial/ecs");
const base_1 = require("@spectrum-web-components/base");
const overlay_1 = require("@spectrum-web-components/overlay");
const uuid_1 = require("uuid");
const context_2 = require("../context");
const url_1 = require("../utils/url");
const utils_1 = require("../utils");
const common_1 = require("../utils/common");
const ZINDEX_OFFSET = 0.0001;
function executeCopy(api, appState, event) {
api.copyToClipboard(appState.layersSelected.map((id) => api.getNodeById(id)), event);
}
function executeCut(api, appState, event) {
executeCopy(api, appState, event);
// delete nodes
api.deleteNodesById(appState.layersSelected);
api.record();
}
function getMaxZIndex(api) {
return api.getNodes().reduce((max, node) => { var _a; return Math.max(max, (_a = node.zIndex) !== null && _a !== void 0 ? _a : 0); }, 0);
}
function createSVG(api, appState, svg, position) {
var _a, _b;
// TODO: Extract semantic groups inside comments
const doc = ecs_1.DOMAdapter.get()
.getDOMParser()
.parseFromString(svg, 'image/svg+xml');
const $svg = doc.documentElement;
const width = $svg.width.baseVal.value;
const height = $svg.height.baseVal.value;
// This method also works, but it may lose the namespace of the SVG element.
// const $container = document.createElement('div');
// $container.innerHTML = string;
// const $svg = $container.children[0] as SVGSVGElement;
const root = {
id: (0, uuid_1.v4)(),
type: 'rect',
zIndex: getMaxZIndex(api) + 1,
x: (_a = position === null || position === void 0 ? void 0 : position.x) !== null && _a !== void 0 ? _a : 0,
y: (_b = position === null || position === void 0 ? void 0 : position.y) !== null && _b !== void 0 ? _b : 0,
width,
height,
};
const nodes = (0, ecs_1.svgElementsToSerializedNodes)(Array.from($svg.children));
nodes.forEach((node) => {
node.parentId = root.id;
node.locked = true;
});
(0, common_1.updateAndSelectNodes)(api, appState, [root, ...nodes]);
}
function createText(api, appState, text, position) {
var _a, _b;
(0, common_1.updateAndSelectNodes)(api, appState, [
{
id: (0, uuid_1.v4)(),
type: 'text',
anchorX: (_a = position === null || position === void 0 ? void 0 : position.x) !== null && _a !== void 0 ? _a : 0,
anchorY: (_b = position === null || position === void 0 ? void 0 : position.y) !== null && _b !== void 0 ? _b : 0,
content: text,
fontSize: 16,
fontFamily: 'system-ui',
fill: 'black',
zIndex: getMaxZIndex(api) + 1,
},
]);
}
function createHTML(api, appState, html, position) {
var _a, _b;
const { width, height } = (0, utils_1.measureHTML)(html);
(0, common_1.updateAndSelectNodes)(api, appState, [
{
id: (0, uuid_1.v4)(),
type: 'html',
x: (_a = position === null || position === void 0 ? void 0 : position.x) !== null && _a !== void 0 ? _a : 0,
y: (_b = position === null || position === void 0 ? void 0 : position.y) !== null && _b !== void 0 ? _b : 0,
width,
height,
html,
zIndex: getMaxZIndex(api) + 1,
},
]);
}
function executePaste(api, appState, event, position) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
// FIXME: Paste text inside a textfield
if (!document.hasFocus()) {
return;
}
if (!event) {
let types;
try {
types = yield (0, ecs_1.readSystemClipboard)();
}
catch (error) { }
event = (0, ecs_1.createPasteEvent)({ types });
}
let canvasPosition = null;
if (position) {
canvasPosition = api.viewport2Canvas(api.client2Viewport(position));
}
// must be called in the same frame (thus before any awaits) as the paste
// event else some browsers (FF...) will clear the clipboardData
// (something something security)
const file = (_a = event === null || event === void 0 ? void 0 : event.clipboardData) === null || _a === void 0 ? void 0 : _a.files[0];
const data = yield (0, ecs_1.parseClipboard)(event, false);
if (!file) {
if (data.html) {
createHTML(api, appState, data.html, canvasPosition);
// return this.addElementsFromMixedContentPaste(data.mixedContent, {
// isPlainPaste,
// sceneX,
// sceneY,
// });
}
else if (data.text) {
const string = data.text.trim();
if ((0, ecs_1.isUrl)(data.text)) {
// TODO: youtube, figma, google maps, etc.
// Plain url, extract metadata
const meta = yield (0, url_1.extractExternalUrlMetadata)(data.text);
// console.log(meta);
// TODO: create bookmark asset
}
else if (string.startsWith('<svg') && string.endsWith('</svg>')) {
createSVG(api, appState, string, canvasPosition);
}
else {
// const nonEmptyLines = data.text
// .replace(/\r?\n|\r/g, '\n')
// .split(/\n+/)
// .map((s) => s.trim())
// .filter(Boolean);
createText(api, appState, data.text, canvasPosition);
}
}
else if (data.elements) {
const nodes = data.elements.map((node) => {
node.id = (0, uuid_1.v4)();
if (node.zIndex) {
node.zIndex += ZINDEX_OFFSET;
}
if (canvasPosition) {
node.x = canvasPosition.x;
node.y = canvasPosition.y;
}
else {
node.x = node.x + 10;
node.y = node.y + 10;
}
return node;
});
(0, common_1.updateAndSelectNodes)(api, appState, nodes);
}
}
else if ((0, ecs_1.isSupportedImageFileType)(file === null || file === void 0 ? void 0 : file.type)) {
yield api.createImageFromFile(file, { position: canvasPosition });
}
});
}
/**
* @see https://opensource.adobe.com/spectrum-web-components/components/imperative-api/#using-a-virtual-trigger
*/
let ContextMenu = class ContextMenu extends lit_1.LitElement {
constructor() {
super(...arguments);
this.isClipboardEmpty = true;
this.binded = false;
this.lastContextMenuPosition = null;
this.lastPointerMovePosition = null;
this.handleExecuteAction = (event) => {
const value = event.target.value;
if (value === 'copy') {
executeCopy(this.api, this.appState);
}
else if (value === 'paste') {
executePaste(this.api, this.appState, undefined, this.lastContextMenuPosition);
}
else if (value === 'cut') {
executeCut(this.api, this.appState);
}
else if (value === 'bring-to-front') {
this.executeBringToFront();
}
else if (value === 'bring-forward') {
this.executeBringForward();
}
else if (value === 'send-backward') {
this.executeSendBackward();
}
else if (value === 'send-to-back') {
this.executeSendToBack();
}
else if (value === 'toggle-visibility') {
this.executeToggleVisibility();
}
else if (value === 'toggle-lock') {
this.executeToggleLock();
}
else if (value === 'crop') {
this.executeCrop();
}
};
this.handleExport = (event) => {
const format = event.target.value;
const nodes = this.api
.getAppState()
.layersSelected.map((id) => this.api.getNodeById(id));
// Should export children recursively
const allNodes = nodes.flatMap((node) => {
return [node, ...this.api.getChildrenRecursively(node)];
});
this.api.export(format, true, allNodes);
};
this.handleContextMenu = (event) => __awaiter(this, void 0, void 0, function* () {
event.preventDefault();
event.stopPropagation();
this.lastContextMenuPosition = { x: event.clientX, y: event.clientY };
const trigger = event.target;
const virtualTrigger = new overlay_1.VirtualTrigger(this.lastContextMenuPosition.x, this.lastContextMenuPosition.y);
const fragment = document.createDocumentFragment();
(0, base_1.render)(this.contextMenuTemplate(), fragment);
const popover = fragment.querySelector('sp-popover');
const overlay = yield (0, overlay_1.openOverlay)(popover, {
trigger: virtualTrigger,
placement: 'right-start',
offset: 0,
notImmediatelyClosable: true,
type: 'auto',
});
trigger.insertAdjacentElement('afterend', overlay);
this.renderRoot.appendChild(overlay);
});
this.handleCopy = (event) => {
const { layersSelected } = this.appState;
if (document.activeElement !== this.api.element ||
layersSelected.length === 0) {
return;
}
executeCopy(this.api, this.appState, event);
event.preventDefault();
event.stopPropagation();
};
this.handleCut = (event) => {
const { layersSelected } = this.appState;
if (document.activeElement !== this.api.element ||
layersSelected.length === 0) {
return;
}
executeCut(this.api, this.appState, event);
event.preventDefault();
event.stopPropagation();
};
this.handlePaste = (event) => {
if (document.activeElement !== this.api.element) {
return;
}
executePaste(this.api, this.appState, event, this.lastPointerMovePosition);
event.preventDefault();
event.stopPropagation();
};
this.handleKeyDown = (event) => {
if (document.activeElement !== this.api.element) {
return;
}
// bring to front ⌥⌘]
if (event.key === ']' && event.metaKey && event.ctrlKey) {
this.executeBringToFront();
event.preventDefault();
event.stopPropagation();
}
else if (event.key === '[' && event.metaKey && event.ctrlKey) {
this.executeSendToBack();
event.preventDefault();
event.stopPropagation();
}
else if (event.key === ']' && event.metaKey) {
this.executeBringForward();
event.preventDefault();
event.stopPropagation();
}
else if (event.key === '[' && event.metaKey) {
this.executeSendBackward();
event.preventDefault();
event.stopPropagation();
}
const { layersSelected } = this.api.getAppState();
if (layersSelected.length === 0) {
return;
}
if (event.key === 'ArrowUp') {
event.preventDefault();
layersSelected.forEach((id) => {
const node = this.api.getNodeById(id);
if (node) {
this.api.updateNodeOBB(node, { y: node.y - 10 });
}
});
this.api.record();
}
else if (event.key === 'ArrowDown') {
event.preventDefault();
layersSelected.forEach((id) => {
const node = this.api.getNodeById(id);
if (node) {
this.api.updateNodeOBB(node, { y: node.y + 10 });
}
});
this.api.record();
}
else if (event.key === 'ArrowLeft') {
event.preventDefault();
layersSelected.forEach((id) => {
const node = this.api.getNodeById(id);
if (node) {
this.api.updateNodeOBB(node, { x: node.x - 10 });
}
});
this.api.record();
}
else if (event.key === 'ArrowRight') {
event.preventDefault();
layersSelected.forEach((id) => {
const node = this.api.getNodeById(id);
if (node) {
this.api.updateNodeOBB(node, { x: node.x + 10 });
}
});
this.api.record();
}
else if (event.key === 'Backspace') {
event.preventDefault();
this.api.deleteNodesById(layersSelected);
this.api.record();
}
else if (event.key === 'Escape') {
event.preventDefault();
this.api.selectNodes([]);
this.api.record();
}
};
this.handlePointerMove = (event) => {
this.lastPointerMovePosition = { x: event.clientX, y: event.clientY };
};
/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop#prevent_the_browsers_default_drag_behavior
*/
this.handleDragOver = (event) => {
event.preventDefault();
};
/**
* @see https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/components/App.tsx#L10242
*/
this.handleDrop = (event) => __awaiter(this, void 0, void 0, function* () {
event.preventDefault();
const canvasPosition = this.api.viewport2Canvas(this.api.client2Viewport({
x: event.clientX,
y: event.clientY,
}));
const url = event.dataTransfer.getData('text/uri-list');
if (url) {
try {
yield this.api.createImageFromFile(url, { position: canvasPosition });
return;
}
catch (error) {
console.error(error);
}
}
const text = event.dataTransfer.getData('text/plain');
if (text) {
createText(this.api, this.appState, text, canvasPosition);
return;
}
for (const file of Array.from(event.dataTransfer.files)) {
if ((0, ecs_1.isSupportedImageFileType)(file.type)) {
if (file.type === ecs_1.MIME_TYPES.svg) {
const svg = yield file.text();
createSVG(this.api, this.appState, svg, canvasPosition);
}
else {
yield this.api.createImageFromFile(file, { position: canvasPosition });
}
}
}
});
}
contextMenuTemplate() {
const { layersSelected, contextMenuVisible } = this.appState;
if (document.hasFocus()) {
// check if clipboard is empty
(0, ecs_1.readSystemClipboard)().then((clipboard) => {
this.isClipboardEmpty = Object.keys(clipboard).length === 0;
});
}
// Locked element or unselected element should also be included in the context menu
// const { x, y } = this.lastContextMenuPosition;
// const canvasPosition = this.api.viewport2Canvas({ x, y });
// const [topmost] = this.api.elementsFromBBox(canvasPosition.x, canvasPosition.y, canvasPosition.x, canvasPosition.y, false);
const isSelectedEmpty = layersSelected.length === 0;
let bringForwardDisabled = false;
let sendBackwardDisabled = false;
let isLocked = false;
let isVisible = true;
if (layersSelected.length === 1) {
const node = this.api.getNodeById(layersSelected[0]);
const children = this.api
.getSiblings(node)
.filter((child) => !child.has(ecs_1.UI));
const maxZIndex = Math.max(...children.map((child) => child.read(ecs_1.ZIndex).value));
const minZIndex = Math.min(...children.map((child) => child.read(ecs_1.ZIndex).value));
if (node.zIndex === maxZIndex) {
bringForwardDisabled = true;
}
if (node.zIndex === minZIndex) {
sendBackwardDisabled = true;
}
isLocked = node.locked;
isVisible = node.visibility !== 'hidden';
}
return (0, base_1.html) `${(0, when_js_1.when)(contextMenuVisible, () => (0, base_1.html) `<sp-popover
style="width:200px;"
=${(event) => {
event.target.dispatchEvent(new Event('close', { bubbles: true }));
}}
>
<h4>${(0, localize_1.msg)((0, localize_1.str) `Actions`)}</h4>
<sp-menu =${this.handleExecuteAction}>
<sp-menu-item ?disabled=${isSelectedEmpty} value="copy">
<sp-icon-copy slot="icon"></sp-icon-copy>
${(0, localize_1.msg)((0, localize_1.str) `Copy`)}
<kbd slot="value">⌘C</kbd>
</sp-menu-item>
<sp-menu-item ?disabled=${this.isClipboardEmpty} value="paste">
<sp-icon-paste slot="icon"></sp-icon-paste>
${(0, localize_1.msg)((0, localize_1.str) `Paste`)}
<kbd slot="value">⌘V</kbd>
</sp-menu-item>
<sp-menu-item ?disabled=${isSelectedEmpty} value="cut">
<sp-icon-cut slot="icon"></sp-icon-cut>
${(0, localize_1.msg)((0, localize_1.str) `Cut`)}
<kbd slot="value">⌘X</kbd>
</sp-menu-item>
<sp-menu-divider></sp-menu-divider>
<sp-menu-item ?disabled=${isSelectedEmpty} value="toggle-visibility">
${(0, when_js_1.when)(isVisible, () => (0, base_1.html) `<sp-icon-visibility slot="icon"></sp-icon-visibility>`, () => (0, base_1.html) `<sp-icon-visibility-off slot="icon"></sp-icon-visibility-off>`)}
${isVisible ? (0, localize_1.msg)((0, localize_1.str) `Hide layer`) : (0, localize_1.msg)((0, localize_1.str) `Show layer`)}
<kbd slot="value">⌘H</kbd>
</sp-menu-item>
<sp-menu-item ?disabled=${isSelectedEmpty} value="toggle-lock">
${(0, when_js_1.when)(isLocked, () => (0, base_1.html) `<sp-icon-lock-closed slot="icon"></sp-icon-lock-closed>`, () => (0, base_1.html) `<sp-icon-lock-open slot="icon"></sp-icon-lock-open>`)}
${isLocked ? (0, localize_1.msg)((0, localize_1.str) `Unlock layer`) : (0, localize_1.msg)((0, localize_1.str) `Lock layer`)}
<kbd slot="value">⌘L</kbd>
</sp-menu-item>
<sp-menu-divider></sp-menu-divider>
<sp-menu-item
?disabled=${isSelectedEmpty || bringForwardDisabled}
value="bring-to-front"
>
<sp-icon-layers-bring-to-front
slot="icon"
></sp-icon-layers-bring-to-front>
${(0, localize_1.msg)((0, localize_1.str) `Bring to front`)}
<kbd slot="value">⌥⌘]</kbd>
</sp-menu-item>
<sp-menu-item
?disabled=${isSelectedEmpty || bringForwardDisabled}
value="bring-forward"
>
<sp-icon-layers-forward slot="icon"></sp-icon-layers-forward>
${(0, localize_1.msg)((0, localize_1.str) `Bring forward`)}
<kbd slot="value">⌘]</kbd>
</sp-menu-item>
<sp-menu-item
?disabled=${isSelectedEmpty || sendBackwardDisabled}
value="send-backward"
>
<sp-icon-layers-backward slot="icon"></sp-icon-layers-backward>
${(0, localize_1.msg)((0, localize_1.str) `Send backward`)}
<kbd slot="value">⌘[</kbd>
</sp-menu-item>
<sp-menu-item
?disabled=${isSelectedEmpty || sendBackwardDisabled}
value="send-to-back"
>
<sp-icon-layers-send-to-back
slot="icon"
></sp-icon-layers-send-to-back>
${(0, localize_1.msg)((0, localize_1.str) `Send to back`)}
<kbd slot="value">⌥⌘[</kbd>
</sp-menu-item>
<sp-menu-divider></sp-menu-divider>
<sp-menu-item ?disabled=${isSelectedEmpty} value="crop">
<sp-icon-crop slot="icon"></sp-icon-crop>
${(0, localize_1.msg)((0, localize_1.str) `Crop`)}
<kbd slot="value">⌘K</kbd>
</sp-menu-item>
<sp-menu-item>
${(0, localize_1.msg)((0, localize_1.str) `Export as...`)}
<sp-menu slot="submenu" =${this.handleExport}>
<sp-menu-item
value=${ecs_1.ExportFormat.SVG}
?disabled=${isSelectedEmpty}
>SVG</sp-menu-item
>
<sp-menu-item
value=${ecs_1.ExportFormat.PNG}
?disabled=${isSelectedEmpty}
>PNG</sp-menu-item
>
<sp-menu-item
value=${ecs_1.ExportFormat.JPEG}
?disabled=${isSelectedEmpty}
>JPEG</sp-menu-item
>
</sp-menu>
</sp-menu-item>
</sp-menu>
</sp-popover>`)}`;
}
executeBringToFront() {
const node = this.api.getNodeById(this.appState.layersSelected[0]);
if (node) {
this.api.bringToFront(node);
this.api.record();
}
}
executeBringForward() {
const node = this.api.getNodeById(this.appState.layersSelected[0]);
if (node) {
this.api.bringForward(node);
this.api.record();
}
}
executeSendBackward() {
const node = this.api.getNodeById(this.appState.layersSelected[0]);
if (node) {
this.api.sendBackward(node);
this.api.record();
}
}
executeSendToBack() {
const node = this.api.getNodeById(this.appState.layersSelected[0]);
if (node) {
this.api.sendToBack(node);
this.api.record();
}
}
executeToggleVisibility() {
const node = this.api.getNodeById(this.appState.layersSelected[0]);
if (node) {
this.api.updateNode(node, {
visibility: node.visibility === 'hidden' ? 'visible' : 'hidden',
});
this.api.record();
}
}
executeToggleLock() {
const node = this.api.getNodeById(this.appState.layersSelected[0]);
if (node) {
const isLocked = !!node.locked;
this.api.updateNode(node, {
locked: !isLocked,
});
this.api.record();
}
}
executeCrop() {
const { layersSelected } = this.api.getAppState();
if (layersSelected.length === 1) {
const node = this.api.getNodeById(layersSelected[0]);
if (node.clipMode) {
this.api.setAppState({
layersCropping: layersSelected,
penbarSelected: ecs_1.Pen.SELECT,
});
// already is a clipping node, do nothing
return;
}
}
const children = layersSelected.map((id) => this.api.getNodeById(id));
const bounds = this.api.getBounds(children);
const { minX, minY, maxX, maxY } = bounds;
// create a clip parent for all the selected nodes
const clipParent = {
id: (0, uuid_1.v4)(),
type: 'rect',
clipMode: 'clip',
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
zIndex: 0,
};
this.api.runAtNextTick(() => {
this.api.updateNodes([clipParent]);
children.forEach((child) => {
this.api.reparentNode(child, clipParent);
});
this.api.setAppState({
layersCropping: [clipParent.id],
penbarSelected: ecs_1.Pen.SELECT,
});
this.api.record();
});
}
disconnectedCallback() {
var _a, _b, _c, _d, _e, _f;
super.disconnectedCallback();
(_b = (_a = this.api) === null || _a === void 0 ? void 0 : _a.element) === null || _b === void 0 ? void 0 : _b.removeEventListener('contextmenu', this.handleContextMenu);
(_d = (_c = this.api) === null || _c === void 0 ? void 0 : _c.element) === null || _d === void 0 ? void 0 : _d.removeEventListener('pointermove', this.handlePointerMove);
(_f = (_e = this.api) === null || _e === void 0 ? void 0 : _e.element) === null || _f === void 0 ? void 0 : _f.removeEventListener('drop', this.handleDrop);
document.removeEventListener('copy', this.handleCopy);
document.removeEventListener('cut', this.handleCut);
document.removeEventListener('paste', this.handlePaste);
// this.api
// .getCanvasElement()
// .removeEventListener('keydown', this.handleKeyDown);
}
render() {
var _a;
// FIXME: wait for the element to be ready.
if (((_a = this.api) === null || _a === void 0 ? void 0 : _a.element) && !this.binded) {
this.api.element.addEventListener('contextmenu', this.handleContextMenu);
this.api.element.addEventListener('pointermove', this.handlePointerMove);
this.api.element.addEventListener('dragover', this.handleDragOver);
this.api.element.addEventListener('drop', this.handleDrop);
document.addEventListener('copy', this.handleCopy, { passive: false });
document.addEventListener('cut', this.handleCut, { passive: false });
document.addEventListener('paste', this.handlePaste, { passive: false });
this.api
.getCanvasElement()
.addEventListener('keydown', this.handleKeyDown);
}
return (0, base_1.html) ``;
}
};
exports.ContextMenu = ContextMenu;
ContextMenu.styles = (0, lit_1.css) `
sp-popover {
padding: 0;
}
kbd {
font-family: var(--spectrum-alias-body-text-font-family);
letter-spacing: 0.1em;
white-space: nowrap;
border: none;
padding: none;
padding: 0;
line-height: normal;
}
h4 {
margin: 0;
padding: 8px;
}
`;
__decorate([
(0, context_1.consume)({ context: context_2.appStateContext, subscribe: true })
], ContextMenu.prototype, "appState", void 0);
__decorate([
(0, context_1.consume)({ context: context_2.apiContext, subscribe: true })
], ContextMenu.prototype, "api", void 0);
__decorate([
(0, decorators_js_1.state)()
], ContextMenu.prototype, "isClipboardEmpty", void 0);
exports.ContextMenu = ContextMenu = __decorate([
(0, decorators_js_1.customElement)('ic-spectrum-context-menu'),
(0, localize_1.localized)()
], ContextMenu);
//# sourceMappingURL=context-menu.js.map