diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx index f9013723e..330ca8082 100644 --- a/demo/lib/App.tsx +++ b/demo/lib/App.tsx @@ -1,4 +1,6 @@ import * as React from "react"; +import { Menu as ContextMenu, Item, Separator, Submenu, useContextMenu } from "react-contexify"; +import "react-contexify/dist/ReactContexify.css"; import { Button, Checkbox, @@ -13,15 +15,21 @@ import { Sidebar, } from "semantic-ui-react"; import seqparse from "seqparse"; +import tippy from "tippy.js"; +import "tippy.js/dist/tippy.css"; +import { TextSpan } from "typescript"; import Circular from "../../src/Circular/Circular"; import Linear from "../../src/Linear/Linear"; import SeqViz from "../../src/SeqViz"; import { chooseRandomColor } from "../../src/colors"; import { AnnotationProp, Primer, TranslationProp } from "../../src/elements"; +import { SEQVIZ_ELEMENTS_TYPES } from "../../src/seqvizElementsTypes"; import Header from "./Header"; import file from "./file"; +const CONTEXT_MENU_ID = "menu-id"; + const viewerTypeOptions = [ { key: "both", text: "Both", value: "both" }, { key: "circular", text: "Circular", value: "circular" }, @@ -35,6 +43,7 @@ interface AppState { enzymes: any[]; name: string; primers: Primer[]; + hoveredBase: number; search: { query: string }; searchResults: any; selection: any; @@ -53,6 +62,7 @@ export default class App extends React.Component { annotations: [], customChildren: true, enzymes: ["PstI", "EcoRI", "XbaI", "SpeI"], + hoveredBase: 0, name: "", primers: [ { @@ -107,12 +117,90 @@ export default class App extends React.Component { linearRef: React.RefObject = React.createRef(); circularRef: React.RefObject = React.createRef(); + showContextMenu: (params: any) => void = () => { + //do nothing + }; + componentDidMount = async () => { const seq = await seqparse(file); this.setState({ annotations: seq.annotations, name: seq.name, seq: seq.seq }); + + const { show } = useContextMenu({ + id: CONTEXT_MENU_ID, + }); + + this.showContextMenu = show; + + this.setState({ annotations: seq.annotations, name: seq.name, seq: seq.seq, hoveredBase: 0 }); + }; + + handleContextMenuAddForwardTranslation = ({ event, props, triggerEvent, data }) => { + // console.log(event, props, triggerEvent, data); + console.log(this.state.selection); + if ( + !this.state.selection.start || + !this.state.selection.end || + !this.state.selection.length || + this.state.selection.length === 0 + ) { + this.state.translations.push({ + direction: 1, + end: this.state.seq.length, + start: 0, + name: "", + }); + } else { + const { start, end, clockwise } = this.state.selection; + this.state.translations.push({ + direction: 1, + end: clockwise ? end : start, + start: clockwise ? start : end, + name: "", + }); + } + this.setState({ + translations: this.state.translations, + }); + }; + + handleContextMenuAddReverseTranslation = ({ event, props, triggerEvent, data }) => { + // console.log(event, props, triggerEvent, data); + // console.log(this.state.selection); + if ( + !this.state.selection.start || + !this.state.selection.end || + !this.state.selection.length || + this.state.selection.length === 0 + ) { + this.state.translations.push({ + direction: 1, + end: this.state.seq.length, + start: 0, + name: "", + }); + } else { + const { start, end, clockwise } = this.state.selection; + this.state.translations.push({ + direction: -1, + end: clockwise ? end : start, + start: clockwise ? start : end, + name: "", + }); + } + this.setState({ + translations: this.state.translations, + }); }; + displayContextMenu(e) { + // put whatever custom logic you need + // you can even decide to not display the Menu + this.showContextMenu({ + event: e, + }); + } + toggleSidebar = () => { const { showSidebar } = this.state; this.setState({ showSidebar: !showSidebar }); @@ -256,6 +344,55 @@ export default class App extends React.Component { enzymes={this.state.enzymes} highlights={[{ start: 0, end: 10 }]} name={this.state.name} + onClick={(element, circular, linear, container) => { + // console.log({ element, circular, linear, container }); + }} + onContextMenu={(element, circular, linear, event) => { + // console.log({ element, circular, linear, event }); + this.displayContextMenu(event); + }} + onDoubleClick={(element, circular, linear, container) => { + // console.log({ element, circular, linear, container }); + }} + onHover={(element, hover, view, container) => { + // console.log({ element, hover, view, container }); + if (element.type === SEQVIZ_ELEMENTS_TYPES.base) { + if (hover) { + this.setState({ hoveredBase: element.start + element.index + 1 }); + } else { + this.setState({ hoveredBase: 0 }); + } + } else if (element.type == SEQVIZ_ELEMENTS_TYPES.annotation) { + if (hover) { + if ((container as any)._tippy) { + (container as any)._tippy?.show(); + } else { + // you can pass some details with annotations to display in tippy + tippy(container, { + content: ` +
+
+ Name: ${element.name} +
+
+ position: [${element.start}-${element.end}] +
+
+ Length: ${element.end - element.start + 1} +
+
+ `, + allowHTML: true, + }).show(); + } + } else { + (container as any)._tippy?.hide(); + } + } + }} + onKeyPress={(e, selection) => { + // console.log(e, selection) + }} onSelection={selection => this.setState({ selection })} refs={{ circular: this.circularRef, linear: this.linearRef }} search={this.state.search} @@ -271,9 +408,16 @@ export default class App extends React.Component { )} +
+
Over base {this.state.hoveredBase > 0 ? this.state.hoveredBase : "-"}
+
+ + Add forward translations + Add reverse translations + ); } diff --git a/demo/package-lock.json b/demo/package-lock.json index ffde8f9f8..5648c3211 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -14,9 +14,11 @@ "next": "^13.5.6", "next-with-less": "^2.0.5", "react": "^18.2.0", + "react-contexify": "^6.0.0", "react-dom": "^18.2.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.1.3", + "tippy.js": "^6.3.7", "typescript": "^4.8.2" }, "devDependencies": { @@ -709,16 +711,6 @@ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", "dev": true }, - "node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "peer": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/electron-to-chromium": { "version": "1.4.241", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.241.tgz", @@ -736,19 +728,6 @@ "node": ">=10.13.0" } }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "optional": true, - "peer": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -855,32 +834,6 @@ "value-equal": "^1.0.1" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "optional": true, - "peer": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -1036,38 +989,11 @@ "loose-envify": "cli.js" } }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "optional": true, - "peer": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "optional": true, - "peer": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1087,13 +1013,6 @@ "node": ">= 0.6" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "optional": true, - "peer": true - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1111,24 +1030,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/needle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz", - "integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -1219,16 +1120,6 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -1271,13 +1162,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "optional": true, - "peer": true - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -1305,6 +1189,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-contexify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz", + "integrity": "sha512-jMhz6yZI81Jv3UDj7TXqCkhdkCFEEmvwGCPXsQuA2ZUC8EbCuVQ6Cy8FzKMXa0y454XTDClBN2YFvvmoFlrFkg==", + "dependencies": { + "clsx": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -1370,20 +1266,6 @@ } ] }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "optional": true, - "peer": true - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "optional": true, - "peer": true - }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -1441,16 +1323,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -1612,6 +1484,14 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -2204,16 +2084,6 @@ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", "dev": true }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "peer": true, - "requires": { - "ms": "^2.1.1" - } - }, "electron-to-chromium": { "version": "1.4.241", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.241.tgz", @@ -2228,16 +2098,6 @@ "tapable": "^2.2.0" } }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "optional": true, - "peer": true, - "requires": { - "prr": "~1.0.1" - } - }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -2325,23 +2185,6 @@ "value-equal": "^1.0.1" } }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, - "peer": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "optional": true, - "peer": true - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -2457,29 +2300,11 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "optional": true, - "peer": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "optional": true, - "peer": true - }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2493,30 +2318,11 @@ "mime-db": "1.52.0" } }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "optional": true, - "peer": true - }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, - "needle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz", - "integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==", - "optional": true, - "peer": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - } - }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -2574,13 +2380,6 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "optional": true, - "peer": true - }, "postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -2608,13 +2407,6 @@ } } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "optional": true, - "peer": true - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -2636,6 +2428,14 @@ "loose-envify": "^1.1.0" } }, + "react-contexify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz", + "integrity": "sha512-jMhz6yZI81Jv3UDj7TXqCkhdkCFEEmvwGCPXsQuA2ZUC8EbCuVQ6Cy8FzKMXa0y454XTDClBN2YFvvmoFlrFkg==", + "requires": { + "clsx": "^1.2.1" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -2679,20 +2479,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "optional": true, - "peer": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "optional": true, - "peer": true - }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -2739,13 +2525,6 @@ "shallowequal": "^1.1.0" } }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "optional": true, - "peer": true - }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -2845,6 +2624,14 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "requires": { + "@popperjs/core": "^2.9.0" + } + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", diff --git a/demo/package.json b/demo/package.json index 7b7f54ee2..3fc75f20b 100644 --- a/demo/package.json +++ b/demo/package.json @@ -18,9 +18,11 @@ "next": "^13.5.6", "next-with-less": "^2.0.5", "react": "^18.2.0", + "react-contexify": "^6.0.0", "react-dom": "^18.2.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.1.3", + "tippy.js": "^6.3.7", "typescript": "^4.8.2" }, "browserslist": { diff --git a/package-lock.json b/package-lock.json index d90d46adf..f54f7b462 100644 --- a/package-lock.json +++ b/package-lock.json @@ -847,32 +847,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -1867,38 +1841,6 @@ } } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -2944,14 +2886,6 @@ "node": ">= 8" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3776,14 +3710,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4013,17 +3939,6 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "28.1.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", @@ -10282,62 +10197,6 @@ "node": ">=10" } }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -10532,14 +10391,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/v8-to-istanbul": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", @@ -11204,17 +11055,6 @@ "node": ">=12" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -11854,31 +11694,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, "@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -12603,38 +12418,6 @@ "lodash": "^4.17.21" } }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true, - "optional": true, - "peer": true - }, "@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -13490,14 +13273,6 @@ "picomatch": "^2.0.4" } }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -14114,14 +13889,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -14298,14 +14065,6 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true - }, "diff-sequences": { "version": "28.1.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", @@ -18955,39 +18714,6 @@ } } }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -19125,14 +18851,6 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, "v8-to-istanbul": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", @@ -19605,14 +19323,6 @@ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/src/EventHandler.tsx b/src/EventHandler.tsx index aff47a38f..537328fcc 100644 --- a/src/EventHandler.tsx +++ b/src/EventHandler.tsx @@ -9,6 +9,7 @@ export interface EventsHandlerProps { children: React.ReactNode; copyEvent: (e: React.KeyboardEvent) => boolean; handleMouseEvent: (e: any) => void; + onKeyPress?: (event: React.KeyboardEvent, selection: Selection) => void; selectAllEvent: (e: React.KeyboardEvent) => boolean; selection: Selection; seq: string; @@ -30,12 +31,14 @@ export class EventHandler extends React.PureComponent { * action handler for a keyboard keypresses. */ handleKeyPress = (e: React.KeyboardEvent) => { + const { onKeyPress, selection } = this.props; const keyType = this.keypressMap(e); - if (!keyType) { + if (!keyType && !onKeyPress) { return; // not recognized key } e.preventDefault(); this.handleSeqInteraction(keyType); + onKeyPress?.(e, selection); }; /** diff --git a/src/Linear/Annotations.tsx b/src/Linear/Annotations.tsx index 6e3607eb6..dda228ca5 100644 --- a/src/Linear/Annotations.tsx +++ b/src/Linear/Annotations.tsx @@ -27,6 +27,15 @@ const AnnotationRows = (props: { fullSeq: string; inputRef: InputRefFunc; lastBase: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; width: number; yDiff: number; @@ -46,6 +55,10 @@ const AnnotationRows = (props: { seqBlockRef={props.seqBlockRef} width={props.width} y={props.yDiff + props.elementHeight * i} + onClick={props.onClick} + onContextMenu={props.onContextMenu} + onDoubleClick={props.onDoubleClick} + onHover={props.onHover} /> ))} @@ -66,6 +79,15 @@ const AnnotationRow = (props: { height: number; inputRef: InputRefFunc; lastBase: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; seqBlockRef: unknown; width: number; y: number; @@ -101,8 +123,29 @@ const SingleNamedElement = (props: { index: number; inputRef: InputRefFunc; lastBase: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; }) => { - const { element, elements, findXAndWidth, firstBase, index, inputRef, lastBase } = props; + const { + element, + elements, + findXAndWidth, + firstBase, + index, + inputRef, + lastBase, + onClick, + onContextMenu, + onDoubleClick, + onHover, + } = props; const { color, direction, end, name, start } = element; const forward = direction === 1; @@ -198,6 +241,12 @@ const SingleNamedElement = (props: { } } + const annotationElement = { + ...element, + type: "ANNOTATION", + viewer: "LINEAR", + }; + return ( {/* provides a hover tooltip on most browsers */} @@ -221,9 +270,24 @@ const SingleNamedElement = (props: { onBlur={() => { // do nothing }} + onClick={e => { + onClick?.(annotationElement, false, true, e.target as SVGGElement); + }} + onContextMenu={e => { + onContextMenu?.(annotationElement, false, true, e); + }} + onDoubleClick={e => { + onDoubleClick?.(annotationElement, false, true, e.target as SVGGElement); + }} onFocus={() => { // do nothing }} + onMouseEnter={e => { + onHover?.(annotationElement, true, "LINEAR", e.target as SVGGElement); + }} + onMouseLeave={e => { + onHover?.(annotationElement, false, "LINEAR", e.target as SVGGElement); + }} onMouseOut={() => hoverOtherAnnotationRows(element.id, 0.7)} onMouseOver={() => hoverOtherAnnotationRows(element.id, 1.0)} /> diff --git a/src/Linear/Linear.tsx b/src/Linear/Linear.tsx index 64c6193e1..ca919d5a1 100644 --- a/src/Linear/Linear.tsx +++ b/src/Linear/Linear.tsx @@ -20,6 +20,15 @@ export interface LinearProps { highlights: Highlight[]; inputRef: InputRefFunc; lineHeight: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent<Element, MouseEvent> + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (id: string) => void; primers: Primer[]; search: NameRange[]; @@ -69,6 +78,10 @@ export default class Linear extends React.Component<LinearProps> { elementHeight, highlights, lineHeight, + onClick, + onContextMenu, + onDoubleClick, + onHover, onUnmount, primers, search, @@ -183,6 +196,7 @@ export default class Linear extends React.Component<LinearProps> { const firstBase = i * bpsPerBlock; seqBlocks.push( <SeqBlock + {...this.props} key={ids[i]} annotationRows={annotationRows[i]} blockHeight={blockHeights[i]} @@ -212,6 +226,10 @@ export default class Linear extends React.Component<LinearProps> { y={yDiff} zoom={zoom} zoomed={zoomed} + onClick={onClick} + onContextMenu={onContextMenu} + onDoubleClick={onDoubleClick} + onHover={onHover} onUnmount={onUnmount} /> ); diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx index 8b179d169..ea8a36095 100644 --- a/src/Linear/SeqBlock.tsx +++ b/src/Linear/SeqBlock.tsx @@ -2,7 +2,9 @@ import * as React from "react"; import { InputRefFunc } from "../SelectionHandler"; import { Annotation, CutSite, Highlight, NameRange, Primer, Range, SeqType, Size, Translation } from "../elements"; +import { SEQVIZ_ELEMENTS_TYPES } from "../seqvizElementsTypes"; import { seqBlock, svgText } from "../style"; +import { VIEWER_TYPES } from "../viewerTypes"; import AnnotationRows from "./Annotations"; import { CutSites } from "./CutSites"; import Find from "./Find"; @@ -43,6 +45,15 @@ interface SeqBlockProps { inputRef: InputRefFunc; key: string; lineHeight: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent<Element, MouseEvent> + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: string) => void; primerFwdRows: Primer[][]; primerRevRows: Primer[][]; @@ -194,7 +205,8 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> { * wrapping it in a textSpan with that color as a fill */ seqTextSpan = (bp: string, i: number) => { - const { bpColors, charWidth, firstBase, id } = this.props; + const { bpColors, bpsPerBlock, charWidth, firstBase, id, onClick, onContextMenu, onDoubleClick, onHover } = + this.props; let color: string | undefined; if (bpColors) { @@ -206,10 +218,30 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> { undefined; } + const key = i + bp + id; + + const element = { + end: firstBase + bpsPerBlock, + index: i, + key: key, + start: firstBase, + type: SEQVIZ_ELEMENTS_TYPES.base, + viewer: VIEWER_TYPES.linear, + }; + return ( // the +0.2 here and above is to offset the characters they're not right on the left edge. When they are, // other elements look like they're shifted too far to the right. - <tspan key={i + bp + id} fill={color || undefined} x={charWidth * i + charWidth * 0.2}> + <tspan + key={key} + fill={color || undefined} + x={charWidth * i + charWidth * 0.2} + onClick={e => onClick?.(element, false, true, e.target as HTMLElement)} + onContextMenu={e => onContextMenu?.(element, false, true, e)} + onDoubleClick={e => onDoubleClick?.(element, false, true, e.target as HTMLElement)} + onMouseEnter={e => onHover?.(element, true, "LINEAR", e.target as HTMLElement)} + onMouseLeave={e => onHover?.(element, false, "LINEAR", e.target as HTMLElement)} + > {bp} </tspan> ); @@ -231,6 +263,10 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> { id, inputRef, lineHeight, + onClick, + onContextMenu, + onDoubleClick, + onHover, onUnmount, primerFwdRows: primerFwdRows, primerRevRows: primerRevRows, @@ -437,6 +473,10 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> { seqType={seqType} translationRows={translationRows} yDiff={translationYDiff} + onClick={onClick} + onContextMenu={onContextMenu} + onDoubleClick={onDoubleClick} + onHover={onHover} onUnmount={onUnmount} /> )} @@ -453,6 +493,10 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> { seqBlockRef={this} width={size.width} yDiff={annYDiff} + onClick={onClick} + onContextMenu={onContextMenu} + onDoubleClick={onDoubleClick} + onHover={onHover} /> )} diff --git a/src/Linear/Translations.tsx b/src/Linear/Translations.tsx index 334325f11..8ebc9c196 100644 --- a/src/Linear/Translations.tsx +++ b/src/Linear/Translations.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { InputRefFunc } from "../SelectionHandler"; +import { InputRefFunc, RefSelection } from "../SelectionHandler"; import { borderColorByIndex, colorByIndex } from "../colors"; import { NameRange, SeqType, Translation } from "../elements"; import { randomID } from "../sequence"; @@ -25,6 +25,15 @@ interface TranslationRowsProps { fullSeq: string; inputRef: InputRefFunc; lastBase: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent<Element, MouseEvent> + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translationRows: Translation[][]; @@ -42,6 +51,10 @@ export const TranslationRows = ({ fullSeq, inputRef, lastBase, + onClick, + onContextMenu, + onDoubleClick, + onHover, onUnmount, seqType, translationRows, @@ -71,6 +84,10 @@ export const TranslationRows = ({ seqType={seqType} translations={translations} y={currentElementY} + onClick={onClick} + onContextMenu={onContextMenu} + onDoubleClick={onDoubleClick} + onHover={onHover} onUnmount={onUnmount} /> ); @@ -93,6 +110,15 @@ const TranslationRow = (props: { height: number; inputRef: InputRefFunc; lastBase: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent<Element, MouseEvent> + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translations: Translation[]; @@ -129,6 +155,15 @@ interface SingleNamedElementAminoacidsProps { height: number; inputRef: InputRefFunc; lastBase: number; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent<Element, MouseEvent> + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; onUnmount: (a: unknown) => void; seqType: SeqType; translation: Translation; @@ -176,6 +211,10 @@ class SingleNamedElementAminoacids extends React.PureComponent<SingleNamedElemen height: h, inputRef, lastBase, + onClick, + onContextMenu, + onDoubleClick, + onHover, seqType, translation, y, @@ -255,18 +294,35 @@ class SingleNamedElementAminoacids extends React.PureComponent<SingleNamedElemen // arrow are facing const path = this.genPath(bpCount, direction === 1 ? 1 : -1); + const aaElement: RefSelection = { + end: AAEnd, + parent: { ...translation, type: "TRANSLATION" }, + start: AAStart, + type: "AMINOACID", + viewer: "LINEAR", + }; + return ( <g key={aaId} - ref={inputRef(aaId, { - end: AAEnd, - parent: { ...translation, type: "TRANSLATION" }, - start: AAStart, - type: "AMINOACID", - viewer: "LINEAR", - })} + ref={inputRef(aaId, aaElement)} id={aaId} transform={`translate(${x}, 0)`} + onClick={e => { + onClick?.(aaElement, false, true, e.target as SVGGElement); + }} + onContextMenu={e => { + onContextMenu?.(aaElement, false, true, e); + }} + onDoubleClick={e => { + onDoubleClick?.(aaElement, false, true, e.target as SVGGElement); + }} + onMouseEnter={e => { + onHover?.(aaElement, true, "LINEAR", e.target as SVGGElement); + }} + onMouseLeave={e => { + onHover?.(aaElement, false, "LINEAR", e.target as SVGGElement); + }} > <path d={path} diff --git a/src/SelectionHandler.tsx b/src/SelectionHandler.tsx index b2bb238a1..e2b94a287 100644 --- a/src/SelectionHandler.tsx +++ b/src/SelectionHandler.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import SelectionContext, { Selection, defaultSelection } from "./selectionContext"; -interface RefSelection extends Selection { +export interface RefSelection extends Selection { viewer: "LINEAR" | "CIRCULAR"; } @@ -206,6 +206,11 @@ export default class SelectionHandler extends React.PureComponent<SelectionHandl * Handle a sequence selection on a linear viewer */ handleLinearSeqEvent = (e: SeqVizMouseEvent, knownRange: { end: number; start: number }) => { + // don't reset selection on right click + if (e.button == 2) { + return; + } + const selection = this.context; const currBase = this.calculateBaseLinear(e, knownRange); diff --git a/src/SeqViewerContainer.tsx b/src/SeqViewerContainer.tsx index e17bd2651..749c5a82c 100644 --- a/src/SeqViewerContainer.tsx +++ b/src/SeqViewerContainer.tsx @@ -40,6 +40,16 @@ interface SeqViewerContainerProps { height: number; highlights: Highlight[]; name: string; + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent<Element, MouseEvent> + ) => void; + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + onKeyPress?: (event: React.KeyboardEvent<HTMLElement>, selection: Selection) => void; onSelection: (selection: Selection) => void; primers: Primer[]; refs?: SeqVizChildRefs; @@ -294,6 +304,7 @@ class SeqViewerContainer extends React.Component<SeqViewerContainerProps, SeqVie selection={mergedSelection} seq={seq} setSelection={this.setSelection} + onKeyPress={this.props.onKeyPress} > {this.props.children ? ( this.props.children({ diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index e26b50f6e..0e47c2ce8 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -94,6 +94,26 @@ export interface SeqVizProps { /** the name of the sequence to show in the middle of the circular viewer */ name?: string; + /** a callback that's executed on click on elements in the viewer. */ + onClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + + /** a callback that's executed on right click on elements in the viewer. */ + onContextMenu?: ( + element: any, + circular: boolean, + linear: boolean, + event: React.MouseEvent<Element, MouseEvent> + ) => void; + + /** a callback that's executed on double click on elements on the viewer. */ + onDoubleClick?: (element: any, circular: boolean, linear: boolean, container: Element) => void; + + /** a callback that's executed on hover on emements in the viewer. */ + onHover?: (element: any, hover: boolean, view: "LINEAR" | "CIRCULAR", container: Element) => void; + + /** a callback that's executed on press keyboard buttons on the viewer. */ + onKeyPress?: (event: React.KeyboardEvent<HTMLElement>, selection: Selection) => void; + /** a callback that's executed on each change to the search parameters or sequence */ onSearch?: (search: Range[]) => void; @@ -192,6 +212,11 @@ export default class SeqViz extends React.Component<SeqVizProps, SeqVizState> { enzymes: [], enzymesCustom: {}, name: "", + onClick: () => {}, + onContextMenu: () => {}, + onDoubleClick: () => {}, + onHover: () => {}, + onKeyPress: () => {}, onSearch: (_: Range[]) => null, onSelection: (_: Selection) => null, primers: [], @@ -444,6 +469,31 @@ export default class SeqViz extends React.Component<SeqVizProps, SeqVizState> { start: h.start % (seq.length + 1), }) ), + onClick: + this.props.onClick || + (() => { + // do nothing + }), + onContextMenu: + this.props.onContextMenu || + (() => { + // do nothing + }), + onDoubleClick: + this.props.onDoubleClick || + (() => { + // do nothing + }), + onHover: + this.props.onHover || + (() => { + // do nothing + }), + onKeyPress: + this.props.onKeyPress || + (() => { + // do nothing + }), onSelection: this.props.onSelection || (() => { diff --git a/src/seqvizElementsTypes.ts b/src/seqvizElementsTypes.ts new file mode 100644 index 000000000..36e95ff44 --- /dev/null +++ b/src/seqvizElementsTypes.ts @@ -0,0 +1,10 @@ +export const SEQVIZ_ELEMENTS_TYPES = { + aminoacid: "AMINOACID", + annotation: "ANNOTATION", + base: "BASE", + enzyme: "ENZYME", + find: "FIND", + highlight: "HIGHLIGHT", + seq: "SEQ", + translation: "TRANSLATION", +}; diff --git a/src/viewerTypes.ts b/src/viewerTypes.ts new file mode 100644 index 000000000..3d6540ae0 --- /dev/null +++ b/src/viewerTypes.ts @@ -0,0 +1,4 @@ +export const VIEWER_TYPES = { + circular: "CIRCULAR", + linear: "LINEAR", +};