├── src ├── ex_index.ts ├── Canvas │ ├── Icon │ │ ├── Icon.module.scss │ │ └── Icon.tsx │ ├── Util │ │ ├── Utils.ts │ │ └── useEventManager.tsx │ ├── Models │ │ ├── index.ts │ │ ├── Point.ts │ │ ├── Connection.ts │ │ └── Node.ts │ ├── Grid │ │ ├── Grid.module.scss │ │ └── Grid.tsx │ ├── Canvas.module.scss │ ├── Types.ts │ ├── Hooks │ │ ├── index.ts │ │ ├── useCanvasSize.tsx │ │ ├── usePanning.tsx │ │ ├── useZoom.tsx │ │ └── useConnections.tsx │ ├── styles │ │ └── _variables.scss │ ├── Components │ │ ├── index.ts │ │ ├── CanvasEvents.tsx │ │ ├── CanvasInitialization.tsx │ │ ├── CanvasBackground.tsx │ │ ├── CanvasSVG.tsx │ │ └── CanvasContent.tsx │ ├── Connections │ │ ├── ConnectionPreview.tsx │ │ ├── Connections.module.scss │ │ ├── util.ts │ │ └── ConnectionComponent.tsx │ ├── Node │ │ ├── Port.module.scss │ │ ├── NodeTitle.module.scss │ │ ├── ShapeEnd.tsx │ │ ├── ShapeStart.tsx │ │ ├── NodeTitle.tsx │ │ ├── Node.module.scss │ │ ├── Ports.tsx │ │ ├── Shape.tsx │ │ └── Node.tsx │ ├── Canvas.tsx │ └── CanvasContext.tsx ├── Workflow │ ├── Workflow.module.scss │ ├── Types.ts │ ├── useWorkflow.tsx │ ├── Workflow.tsx │ └── WorkflowData.ts ├── types │ ├── index.ts │ ├── models.ts │ ├── base.ts │ ├── workflow.ts │ └── components.ts ├── test.ts ├── Components │ └── CanvasButton.tsx ├── typings.d.ts └── index.tsx ├── .yarnrc.yml ├── .travis.yml ├── example ├── .gitignore ├── src │ ├── react-app-env.d.ts │ ├── index.js │ ├── main.jsx │ ├── Components │ │ ├── Panel │ │ │ ├── Panel.module.css │ │ │ └── Panel.tsx │ │ ├── Button │ │ │ ├── ButtonGroup.tsx │ │ │ ├── Button.tsx │ │ │ ├── ButtonToggle.tsx │ │ │ └── Buttons.module.scss │ │ ├── Sidebar │ │ │ ├── Sidebar.module.css │ │ │ └── Sidebar.tsx │ │ └── useLocalStorage.tsx │ ├── typings.d.ts │ ├── index.css │ └── App.jsx ├── README.md ├── public │ ├── manifest.json │ └── index.html ├── tsconfig.node.json ├── index.html ├── vite.config.js ├── package.json ├── tsconfig.json └── yarn.lock ├── images └── image.png ├── tsconfig.test.json ├── tsup.config.ts ├── .gitignore ├── biome.json ├── tsconfig.json ├── rollup.config.js ├── vite.config.js ├── README.md └── package.json /src/ex_index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 9 4 | - 8 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .yarn/install-state.gz 3 | .env -------------------------------------------------------------------------------- /example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/Canvas/Icon/Icon.module.scss: -------------------------------------------------------------------------------- 1 | .Icon { 2 | fill: #000; 3 | cursor: pointer; 4 | } 5 | -------------------------------------------------------------------------------- /images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astanciu/svg-workflow-canvas/HEAD/images/image.png -------------------------------------------------------------------------------- /src/Canvas/Util/Utils.ts: -------------------------------------------------------------------------------- 1 | export const generateId = () => Math.random().toString(36).substring(2, 9); 2 | -------------------------------------------------------------------------------- /src/Canvas/Models/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Connection"; 2 | export * from "./Node"; 3 | export * from "./Point"; 4 | -------------------------------------------------------------------------------- /src/Workflow/Workflow.module.scss: -------------------------------------------------------------------------------- 1 | .CanvasContainer { 2 | height: 100%; 3 | width: 100%; 4 | overflow: hidden; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Canvas/Grid/Grid.module.scss: -------------------------------------------------------------------------------- 1 | .small { 2 | stroke-width: 1px; 3 | stroke: #0000000d; 4 | } 5 | 6 | .big { 7 | stroke-width: 3px; 8 | stroke: #0000000d; 9 | } -------------------------------------------------------------------------------- /src/Canvas/Canvas.module.scss: -------------------------------------------------------------------------------- 1 | .Canvas { 2 | /* height: 100%; 3 | width: 100%; */ 4 | touch-action: manipulation; 5 | box-sizing: border-box; 6 | background: #e7ebee; 7 | } 8 | -------------------------------------------------------------------------------- /src/Canvas/Types.ts: -------------------------------------------------------------------------------- 1 | // This file now re-exports types from the centralized types directory 2 | // Keeping this file ensures backward compatibility 3 | 4 | export * from "../types"; 5 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | // Export all types from a central location 2 | export * from "./base"; 3 | export * from "./models"; 4 | export * from "./components"; 5 | export * from "./workflow"; 6 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## SVG Workflow Canvas 2 | 3 | This is the example project in which to load and test the react component. 4 | 5 | Viewable at https://astanciu.github.io/svg-workflow-canvas 6 | -------------------------------------------------------------------------------- /src/Workflow/Types.ts: -------------------------------------------------------------------------------- 1 | // This file now re-exports types from the centralized types directory 2 | // Keeping this file ensures backward compatibility 3 | 4 | export * from "../types/workflow"; 5 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import ExampleComponent from "./ex_index"; 2 | 3 | describe("ExampleComponent", () => { 4 | it("is truthy", () => { 5 | expect(ExampleComponent).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | format: ["cjs", "esm"], 6 | dts: true, 7 | minify: true, 8 | }); 9 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import "./index.css"; 5 | import App from "./App"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "svg-workflow-canvas", 3 | "name": "svg-workflow-canvas", 4 | "start_url": "./index.html", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /src/Canvas/Hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useCanvasSize } from "./useCanvasSize"; 2 | export { default as usePanning } from "./usePanning"; 3 | export { default as useZoom } from "./useZoom"; 4 | export { default as useConnections } from "./useConnections"; 5 | -------------------------------------------------------------------------------- /src/Canvas/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | $background: #e0e0e0; 3 | $primary-color: #616A74; 4 | $green: #9fcc00; 5 | $magenta: #e15aa6; 6 | 7 | $node-bg: $primary-color; 8 | $lines: color.mix($node-bg, $background, 50%); 9 | 10 | 11 | $node-icon: #d1d7e4; 12 | -------------------------------------------------------------------------------- /example/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.js"] 10 | } 11 | -------------------------------------------------------------------------------- /example/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./index.css"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render( 7 | 8 | 9 | , 10 | ); 11 | -------------------------------------------------------------------------------- /example/src/Components/Panel/Panel.module.css: -------------------------------------------------------------------------------- 1 | .Panel { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | width: 250px; 6 | height: 100vh; 7 | box-sizing: border-box; 8 | padding: 10px; 9 | background-color: #fdfeff; 10 | overflow: hidden; 11 | border-left: 1px solid #bbc5ca; 12 | } 13 | -------------------------------------------------------------------------------- /example/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // declare module "*.module.scss"; 2 | 3 | declare module "*.css" { 4 | const content: { [className: string]: string }; 5 | export default content; 6 | } 7 | 8 | declare module "*.scss" { 9 | const content: { [className: string]: string }; 10 | export default content; 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # builds 5 | build 6 | dist 7 | .rpt2_cache 8 | 9 | # misc 10 | .DS_Store 11 | .env 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | .yarn/install-state.gz 22 | -------------------------------------------------------------------------------- /example/src/Components/Button/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FunctionComponent } from "react"; 2 | import styles from "./Buttons.module.scss"; 3 | 4 | type Props = {}; 5 | 6 | const fn: FunctionComponent = ({ children }) => { 7 | return
{children}
; 8 | }; 9 | 10 | export const ButtonGroup = React.memo(fn); 11 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SVG Workflow Canvas 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Canvas/Components/index.ts: -------------------------------------------------------------------------------- 1 | // Export all canvas components 2 | export { default as CanvasBackground } from "./CanvasBackground"; 3 | export { default as CanvasContent } from "./CanvasContent"; 4 | export { default as CanvasEvents } from "./CanvasEvents"; 5 | export { default as CanvasInitialization } from "./CanvasInitialization"; 6 | export { default as CanvasSVG } from "./CanvasSVG"; 7 | -------------------------------------------------------------------------------- /example/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import { resolve } from "node:path"; 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "svg-workflow-canvas": resolve(__dirname, "../src"), 10 | }, 11 | }, 12 | server: { 13 | port: 3000, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/Components/CanvasButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // import styles from './Icon.module.scss'; 4 | 5 | type Props = Record; 6 | 7 | const CanvasButton = React.memo((_props: Props) => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }); 14 | 15 | export default CanvasButton; 16 | -------------------------------------------------------------------------------- /example/src/Components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Icon } from "svg-workflow-canvas"; 3 | import styles from "./Buttons.module.scss"; 4 | 5 | type Props = { 6 | icon: string; 7 | tooltip: string; 8 | enabled: boolean; 9 | onClick: () => void; 10 | }; 11 | 12 | export const Button = React.memo(({ icon, tooltip, onClick }: Props) => { 13 | return ( 14 |
15 | 16 |
17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/Canvas/Connections/ConnectionPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type { Node, Point } from "../Models"; 3 | import styles from "./Connections.module.scss"; 4 | import { makeSVGPath } from "./util"; 5 | 6 | type Props = { 7 | startNode: Node; 8 | mouse: Point; 9 | }; 10 | 11 | const ConnectionPreview = ({ startNode, mouse }: Props) => { 12 | const { path } = makeSVGPath(startNode.outPortPosition, mouse); 13 | 14 | return ; 15 | }; 16 | 17 | export default ConnectionPreview; 18 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | svg-workflow-canvas 11 | 12 | 13 | 14 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default CSS definition for typescript, 3 | * will be overridden with file-specific definitions by rollup 4 | */ 5 | declare module "*.css" { 6 | const content: { [className: string]: string }; 7 | export default content; 8 | } 9 | 10 | declare module "*.scss" { 11 | const content: { [className: string]: string }; 12 | export default content; 13 | } 14 | 15 | // declare module '*.svg' { 16 | // const svgUrl: string; 17 | // const svgComponent: SvgrComponent; 18 | // export default svgUrl; 19 | // export { svgComponent as ReactComponent }; 20 | // } 21 | -------------------------------------------------------------------------------- /src/Canvas/Models/Point.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Point class for representing 2D coordinates 3 | * This class is used as both the implementation and the type 4 | */ 5 | export class Point { 6 | public x: number; 7 | public y: number; 8 | 9 | constructor(x = 0, y = 0) { 10 | this.x = x; 11 | this.y = y; 12 | } 13 | 14 | add(point: Point): Point { 15 | return new Point(this.x + point.x, this.y + point.y); 16 | } 17 | 18 | distanceTo(point: Point): number { 19 | const a = this.x - point.x; 20 | const b = this.y - point.y; 21 | 22 | return Math.sqrt(a * a + b * b); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Workflow } from "./Workflow/Workflow"; 2 | import Canvas from "./Canvas/Canvas"; 3 | import { Node } from "./Canvas/Models/Node"; 4 | import { Connection } from "./Canvas/Models/Connection"; 5 | import { Point } from "./Canvas/Models/Point"; 6 | import Icon from "./Canvas/Icon/Icon"; 7 | import CanvasButton from "./Components/CanvasButton"; 8 | import { useWorkflow } from "./Workflow/useWorkflow"; 9 | import { WorkflowData } from "./Workflow/WorkflowData"; 10 | 11 | export * from "./types"; 12 | 13 | export { Workflow, Canvas, Node, Connection, Point, Icon, CanvasButton, useWorkflow, WorkflowData }; 14 | 15 | export default Workflow; 16 | -------------------------------------------------------------------------------- /example/src/Components/Sidebar/Sidebar.module.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | position: absolute; 3 | left: 0px; 4 | /* border-right: 2px solid #34373c; */ 5 | /* background-color: #e8ebee; */ 6 | top: 50%; 7 | transform: translateY(-50%); 8 | background-color: #34373c; 9 | border-top-right-radius: 20px; 10 | border-bottom-right-radius: 20px; 11 | padding: 20px 0px; 12 | } 13 | 14 | .Item { 15 | height: 35px; 16 | width: 35px; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | /* border: 1px solid blue; */ 21 | } 22 | 23 | .Icon { 24 | position: relative; 25 | 26 | fill: #f7bc0e; 27 | cursor: pointer; 28 | } 29 | -------------------------------------------------------------------------------- /example/src/Components/Sidebar/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from "svg-workflow-canvas"; 2 | import styles from "./Sidebar.module.css"; 3 | 4 | export const Sidebar = ({ add, save }) => { 5 | return ( 6 |
7 | 8 | Add 9 | 10 | 11 | Save 12 | 13 |
14 | ); 15 | }; 16 | 17 | const Item = ({ icon, onClick, children }) => { 18 | return ( 19 |
20 | 21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "files": { 4 | "ignore": ["dist/*"] 5 | }, 6 | "formatter": { 7 | "enabled": true, 8 | "lineWidth": 120, 9 | "formatWithErrors": true, 10 | "indentStyle": "space" 11 | }, 12 | "javascript": { 13 | "formatter": { 14 | "quoteStyle": "double" 15 | }, 16 | "jsxRuntime": "reactClassic" 17 | }, 18 | "linter": { 19 | "enabled": true, 20 | "rules": { 21 | "recommended": true, 22 | "a11y": { 23 | "useKeyWithClickEvents": "off" 24 | } 25 | } 26 | }, 27 | "organizeImports": { 28 | "enabled": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | width: 100%; 4 | height: 100%; 5 | overflow: hidden; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | padding: 0; 11 | font-family: sans-serif; 12 | } 13 | 14 | label { 15 | margin-left: 10px; 16 | font-size: 14px; 17 | } 18 | 19 | input { 20 | display: block; 21 | border: 1px solid #a7afb7; 22 | margin: 10px; 23 | padding: 5px 10px; 24 | font-size: 14px; 25 | color: #5a616d; 26 | } 27 | 28 | .btn-remove { 29 | display: block; 30 | background-color: red; 31 | border: 1px solid red; 32 | margin: 30px 10px; 33 | padding: 5px 10px; 34 | color: white; 35 | font-size: 14px; 36 | font-weight: bold; 37 | } 38 | -------------------------------------------------------------------------------- /src/Canvas/Node/Port.module.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | @use '../styles/variables'; 3 | 4 | $fill: variables.$lines; //#8a939a; 5 | $outline: $fill; //#4b5056; 6 | 7 | .Port { 8 | fill: $fill; 9 | stroke: $outline; 10 | cursor: pointer; 11 | stroke-width: 3; 12 | } 13 | 14 | .PortHighlight { 15 | composes: Port; 16 | fill: #c4ff00; 17 | stroke-width: 3; 18 | } 19 | 20 | .PortUnselected { 21 | composes: Port; 22 | fill: color.mix($fill, variables.$background, 30%); //#dfe5e8; 23 | stroke: color.mix($outline, variables.$background, 30%); //#c8c8c8; 24 | } 25 | 26 | .PortHitBox { 27 | fill: #ffffff00; 28 | cursor: pointer; 29 | stroke-width: 0; 30 | } 31 | -------------------------------------------------------------------------------- /example/src/Components/Button/ButtonToggle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Icon } from "svg-workflow-canvas"; 3 | import styles from "./Buttons.module.scss"; 4 | 5 | type Props = { 6 | icon: string; 7 | tooltip: string; 8 | enabled: boolean; 9 | onClick: () => void; 10 | }; 11 | 12 | export const ButtonToggle = React.memo(({ icon, tooltip, enabled, onClick }: Props) => { 13 | const buttonClass = enabled ? styles.ToggleButtonOn : styles.ToggleButtonOff; 14 | const iconClass = enabled ? styles.IconOn : styles.IconOff; 15 | return ( 16 |
17 | 18 |
19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svg-workflow-canvas-example", 3 | "homepage": "https://astanciu.github.io/svg-workflow-canvas", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "private": true, 7 | "type": "module", 8 | "dependencies": { 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "svg-workflow-canvas": "link:..", 12 | "vite": "^6.2.2" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "^18.2.15", 16 | "@types/react-dom": "^18.2.7", 17 | "@vitejs/plugin-react": "^4.0.3", 18 | "sass": "^1.69.5", 19 | "typescript": "^5.0.2" 20 | }, 21 | "scripts": { 22 | "dev": "vite", 23 | "build": "vite build", 24 | "preview": "vite preview" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Canvas/Models/Connection.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from "./Node"; 2 | import { generateId } from "../Util/Utils"; 3 | 4 | /** 5 | * Connection class that represents a connection between two nodes 6 | * This class is used as both the implementation and the type 7 | */ 8 | export class Connection { 9 | public id = "0"; 10 | public from: Node; 11 | public to: Node; 12 | public selected = false; 13 | 14 | constructor(from: Node, to: Node, id: string = generateId()) { 15 | this.from = from; 16 | this.to = to; 17 | this.id = id; 18 | } 19 | 20 | clone(): Connection { 21 | const conn = new Connection(this.from, this.to, this.id); 22 | conn.selected = this.selected; 23 | return conn; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "jsx": "react", 10 | "declaration": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "strictNullChecks": true, 16 | "noUnusedParameters": true, 17 | "allowSyntheticDefaultImports": true, 18 | "esModuleInterop": true, 19 | "skipLibCheck": true 20 | }, 21 | "include": ["src"], 22 | "exclude": ["node_modules", "build", "dist", "example", "rollup.config.js"] 23 | } 24 | -------------------------------------------------------------------------------- /src/types/models.ts: -------------------------------------------------------------------------------- 1 | // Re-export the Point class type from Canvas/Models 2 | export type { Point } from "../Canvas/Models/Point"; 3 | 4 | // Re-export the Node class type from Canvas/Models 5 | // This creates a single source of truth while avoiding duplication 6 | export type { Node } from "../Canvas/Models/Node"; 7 | 8 | // Re-export the Connection class type from Canvas/Models 9 | // This creates a single source of truth while avoiding duplication 10 | export type { Connection } from "../Canvas/Models/Connection"; 11 | 12 | // Import the types we need for ConnectionInProgress 13 | import type { Node } from "../Canvas/Models/Node"; 14 | import type { Point } from "../Canvas/Models/Point"; 15 | 16 | /** 17 | * In-progress connection state 18 | */ 19 | export interface ConnectionInProgress { 20 | from: Node; 21 | to: Point; 22 | } 23 | -------------------------------------------------------------------------------- /example/src/Components/useLocalStorage.tsx: -------------------------------------------------------------------------------- 1 | // taken from https://usehooks.com/useLocalStorage/ 2 | 3 | import { useState } from "react"; 4 | 5 | export const useLocalStorage = (key, initialValue) => { 6 | const [storedValue, setStoredValue] = useState(() => { 7 | try { 8 | const item = window.localStorage.getItem(key); 9 | 10 | return item ? JSON.parse(item) : initialValue; 11 | } catch (error) { 12 | console.log(error); 13 | return initialValue; 14 | } 15 | }); 16 | 17 | const setValue = (value) => { 18 | try { 19 | const valueToStore = value instanceof Function ? value(storedValue) : value; 20 | setStoredValue(valueToStore); 21 | window.localStorage.setItem(key, JSON.stringify(valueToStore)); 22 | } catch (error) { 23 | console.log(error); 24 | } 25 | }; 26 | 27 | return [storedValue, setValue]; 28 | }; 29 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "allowJs": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noImplicitAny": false, 21 | "noUnusedLocals": false, 22 | "noUnusedParameters": false, 23 | "noFallthroughCasesInSwitch": true, 24 | "allowSyntheticDefaultImports": true, 25 | "esModuleInterop": true, 26 | "forceConsistentCasingInFileNames": true 27 | }, 28 | "include": ["src"], 29 | "references": [{ "path": "./tsconfig.node.json" }] 30 | } 31 | -------------------------------------------------------------------------------- /example/src/Components/Panel/Panel.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FunctionComponent } from "react"; 2 | import type { Node } from "svg-workflow-canvas"; 3 | import styles from "./Panel.module.css"; 4 | 5 | type Props = { 6 | updateNode: (node) => void; 7 | removeNode: (node) => void; 8 | selectedNode: Node; 9 | }; 10 | 11 | export const Panel: FunctionComponent = ({ updateNode, selectedNode: node, removeNode }) => { 12 | if (!node) { 13 | return null; 14 | } 15 | const setName = (e) => { 16 | node.name = e.target.value; 17 | updateNode(node); 18 | }; 19 | 20 | return ( 21 |
22 |
23 | 24 | 25 | 28 |
29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/Canvas/Icon/Icon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IconLibrary } from "../Assets/icon-library"; 3 | import styles from "./Icon.module.scss"; 4 | 5 | interface IconProps { 6 | icon: string; 7 | className?: string; 8 | size: number; 9 | } 10 | 11 | const Icon: React.FC = React.memo(({ icon, className, size }) => { 12 | const offsetX = -(size / 2); 13 | const offsetY = -(size / 2); 14 | const iconData = IconLibrary[icon] || IconLibrary.cog; 15 | 16 | return ( 17 | 28 | 29 | 30 | ); 31 | }); 32 | 33 | Icon.displayName = "Icon"; 34 | 35 | export default Icon; 36 | -------------------------------------------------------------------------------- /src/Canvas/Node/NodeTitle.module.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | @use '../styles/variables'; 3 | 4 | $text-color: #464a53; 5 | $label-color: color.mix(variables.$primary-color, variables.$background, 15%); 6 | 7 | .Title { 8 | font-family: 'Fira Code', monospace; 9 | font-size: 12px; 10 | fill: $text-color; 11 | font-weight: normal; 12 | // paint-order: stroke fill; 13 | // stroke: rgba(231, 235, 238, 1); 14 | // stroke-width: 2; 15 | } 16 | 17 | .TitleHack { 18 | stroke-width: 4; 19 | } 20 | 21 | .UnselectedTitle { 22 | font-family: 'Fira Code', monospace; 23 | font-size: 12px; 24 | fill: color.mix($text-color, $label-color, 30%); 25 | font-weight: normal; 26 | } 27 | .Description { 28 | font-family: 'Fira Code', monospace; 29 | font-size: 11px; 30 | fill: #34373c; 31 | font-weight: normal; 32 | inline-size: 50px; 33 | } 34 | 35 | .label { 36 | fill: $label-color; 37 | } 38 | 39 | .unselectedLabel { 40 | fill: color.change($label-color, $alpha: 0); 41 | } 42 | -------------------------------------------------------------------------------- /src/Canvas/Connections/Connections.module.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | @use '../styles/variables'; 3 | 4 | $stroke: variables.$lines; //#8a939a;; 5 | $strokeUnselected: color.mix($stroke, variables.$background, 25%); 6 | 7 | .previewConnection { 8 | stroke: #8a939a; 9 | stroke-dasharray: 10; 10 | stroke-width: 2px; 11 | fill: none; 12 | } 13 | 14 | .Connection { 15 | stroke: $stroke; 16 | fill: none; 17 | cursor: pointer; 18 | } 19 | .ConnectionSelected { 20 | composes: Connection; 21 | stroke: red; 22 | } 23 | .ConnectionUnselected { 24 | composes: Connection; 25 | stroke: $strokeUnselected; // #d7d7da; 26 | } 27 | 28 | .ConnectionHitBox { 29 | composes: Connection; 30 | stroke: #ffffff00; 31 | stroke-width: 24px; 32 | } 33 | 34 | .Close { 35 | fill: #ff0000; 36 | cursor: pointer; 37 | } 38 | 39 | .CloseOutline { 40 | fill: #fff; 41 | stroke-width: 1; 42 | stroke: #ff0000; 43 | } 44 | .CloseHitbox { 45 | fill: #ff00ff00; 46 | stroke-width: 24; 47 | stroke: #ff00ff00; 48 | } 49 | .CloseX { 50 | fill: #ffffff; 51 | cursor: pointer; 52 | } 53 | -------------------------------------------------------------------------------- /src/Canvas/Components/CanvasEvents.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import type { CanvasEventsProps } from "../../types"; 3 | 4 | /** 5 | * Manages canvas event handlers 6 | */ 7 | const CanvasEvents: React.FC = ({ 8 | eventManager, 9 | handleTap, 10 | handleMove, 11 | handleMoveEnd, 12 | handlePinch, 13 | cancelAnimation, 14 | }) => { 15 | // Set up event handlers 16 | useEffect(() => { 17 | if (!eventManager) return; 18 | 19 | eventManager.onTap(handleTap); 20 | eventManager.onMove(handleMove); 21 | eventManager.onMoveEnd(handleMoveEnd); 22 | eventManager.onPinch(handlePinch); 23 | 24 | // Cleanup 25 | return () => { 26 | eventManager.offTap(handleTap); 27 | eventManager.offMove(handleMove); 28 | eventManager.offMoveEnd(handleMoveEnd); 29 | eventManager.offPinch(handlePinch); 30 | 31 | cancelAnimation(); 32 | }; 33 | }, [eventManager, handleTap, handleMove, handleMoveEnd, handlePinch, cancelAnimation]); 34 | 35 | return null; 36 | }; 37 | 38 | export default CanvasEvents; 39 | -------------------------------------------------------------------------------- /src/types/base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base type definitions for the canvas 3 | */ 4 | 5 | // Re-export the Point class type from Canvas/Models 6 | export type { Point } from "../Canvas/Models/Point"; 7 | 8 | /** 9 | * Canvas view properties 10 | */ 11 | export interface ViewType { 12 | width: number; 13 | height: number; 14 | x: number; 15 | y: number; 16 | scale: number; 17 | offsetTop?: number; 18 | offsetLeft?: number; 19 | } 20 | 21 | /** 22 | * Scale constraints for the canvas 23 | */ 24 | export interface ScaleConstraints { 25 | min: number; 26 | max: number; 27 | } 28 | 29 | /** 30 | * Event detail for tap events 31 | */ 32 | export interface TapEventDetail { 33 | x: number; 34 | y: number; 35 | } 36 | 37 | /** 38 | * Event detail for move events 39 | */ 40 | export interface MoveEventDetail { 41 | x: number; 42 | y: number; 43 | delta: { 44 | x: number; 45 | y: number; 46 | }; 47 | } 48 | 49 | /** 50 | * Event detail for pinch events 51 | */ 52 | export interface PinchEventDetail { 53 | x: number; 54 | y: number; 55 | scale: number; 56 | delta: { 57 | x: number; 58 | y: number; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/Canvas/Components/CanvasInitialization.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import type { CanvasInitializationProps } from "../../types"; 3 | 4 | /** 5 | * Handles initial canvas setup and visibility 6 | */ 7 | const CanvasInitialization: React.FC = ({ updateView, setVisibility }) => { 8 | useEffect(() => { 9 | try { 10 | // Initialize view for larger screens 11 | updateView({ 12 | width: window.innerWidth, 13 | height: window.innerHeight - 56, 14 | x: window.innerWidth / 2, 15 | y: (window.innerHeight - 56) / 2, 16 | scale: 1, 17 | }); 18 | 19 | // Set up visibility with a slight delay 20 | const visibilityTimeout = setTimeout(() => { 21 | setVisibility("visible"); 22 | }, 100); 23 | 24 | // Cleanup 25 | return () => { 26 | clearTimeout(visibilityTimeout); 27 | }; 28 | } catch (error) { 29 | console.error("Error initializing canvas:", error); 30 | } 31 | return void 0; 32 | }, [updateView, setVisibility]); 33 | 34 | return null; 35 | }; 36 | 37 | export default CanvasInitialization; 38 | -------------------------------------------------------------------------------- /src/Canvas/Components/CanvasBackground.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type CanvasBackgroundProps = Record; 4 | 5 | /** 6 | * Component for rendering the canvas background 7 | * Includes gradient and background rectangle 8 | */ 9 | const CanvasBackground: React.FC = () => { 10 | return ( 11 | <> 12 | 13 | 22 | 23 | 24 | 25 | 26 | 34 | 35 | ); 36 | }; 37 | 38 | export default React.memo(CanvasBackground); 39 | -------------------------------------------------------------------------------- /src/Canvas/Components/CanvasSVG.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "../Canvas.module.scss"; 3 | import type { CanvasSVGProps } from "../../types"; 4 | import { CanvasBackground, CanvasContent } from "."; 5 | 6 | /** 7 | * Main SVG container component for the canvas 8 | */ 9 | const CanvasSVG: React.FC = ({ 10 | view, 11 | svgRef, 12 | handleWheel, 13 | visibility, 14 | children, 15 | }) => { 16 | // Calculate the transform value based on current view 17 | const transformValue = `matrix(${view.scale},0,0,${view.scale},${view.x},${view.y})`; 18 | 19 | return ( 20 | 32 | 33 | 34 | {children} 35 | 36 | ); 37 | }; 38 | 39 | export default CanvasSVG; 40 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import svgr from "@svgr/rollup"; 3 | import commonjs from "rollup-plugin-commonjs"; 4 | import external from "rollup-plugin-peer-deps-external"; 5 | // import postcss from 'rollup-plugin-postcss-modules' 6 | import postcss from "rollup-plugin-postcss"; 7 | import { terser } from "rollup-plugin-terser"; 8 | import typescript from "rollup-plugin-typescript2"; 9 | import url from "rollup-plugin-url"; 10 | import pkg from "./package.json"; 11 | 12 | export default { 13 | input: "src/index.tsx", 14 | output: [ 15 | { 16 | file: pkg.main, 17 | format: "cjs", 18 | exports: "named", 19 | sourcemap: true, 20 | }, 21 | { 22 | file: pkg.module, 23 | format: "es", 24 | exports: "named", 25 | sourcemap: true, 26 | }, 27 | ], 28 | plugins: [ 29 | external(), 30 | postcss({ 31 | modules: true, 32 | extensions: [".css", ".sass"], 33 | use: ["sass"], 34 | }), 35 | url(), 36 | svgr(), 37 | resolve(), 38 | typescript({ 39 | rollupCommonJSResolveHack: true, 40 | clean: true, 41 | }), 42 | commonjs(), 43 | terser(), 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /example/src/Components/Button/Buttons.module.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | $bg: #b3b3b3; 3 | $bg-off: #a3a3ac; 4 | $bg-on: #484867; 5 | 6 | $icon: #616a74; 7 | $icon-off: #cfcfcf; 8 | $icon-on: #9fcc00; 9 | 10 | .Group { 11 | position: absolute; 12 | bottom: 1px; 13 | left: 1px; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: flex-end; 17 | align-items: center; 18 | margin-bottom: 10px; 19 | } 20 | 21 | .Button { 22 | width: 40px; 23 | height: 40px; 24 | background-color: $bg; 25 | border-radius: 3px; 26 | margin: 10px 20px; 27 | display: flex; 28 | align-items: center; 29 | justify-content: center; 30 | cursor: pointer; 31 | 32 | &:hover { 33 | background-color: color.adjust($bg, $lightness: -5%); 34 | } 35 | } 36 | 37 | .ToggleButtonOff { 38 | @extend .Button; 39 | background-color: $bg-off; 40 | &:hover { 41 | background-color: color.adjust($bg-off, $lightness: -5%); 42 | } 43 | } 44 | .ToggleButtonOn { 45 | @extend .ToggleButtonOff; 46 | background-color: $bg-on; 47 | &:hover { 48 | background-color: color.adjust($bg-on, $lightness: -5%); 49 | } 50 | } 51 | .Icon { 52 | fill: $icon; 53 | } 54 | .IconOn { 55 | fill: $icon-on; 56 | } 57 | .IconOff { 58 | fill: $icon-off; 59 | } 60 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import { resolve } from "node:path"; 4 | import dts from "vite-plugin-dts"; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | react({ 9 | jsxRuntime: "classic", 10 | include: ["**/*.jsx", "**/*.tsx"], 11 | }), 12 | dts({ 13 | insertTypesEntry: true, 14 | }), 15 | ], 16 | build: { 17 | lib: { 18 | entry: resolve(__dirname, "src/index.tsx"), 19 | name: "SVGWorkflowCanvas", 20 | fileName: (format) => `svg-workflow-canvas.${format}.js`, 21 | formats: ["es", "umd"], 22 | }, 23 | rollupOptions: { 24 | // Externalize React to avoid bundling it 25 | external: ["react", "react-dom"], 26 | output: { 27 | // Global variables to use in UMD build 28 | globals: { 29 | react: "React", 30 | "react-dom": "ReactDOM", 31 | }, 32 | // Preserve modules for better tree-shaking in consuming projects 33 | preserveModules: false, 34 | preserveModulesRoot: "src", 35 | // Fix the named exports warning 36 | exports: "named", 37 | }, 38 | }, 39 | // Properly extract CSS 40 | cssCodeSplit: true, 41 | // Avoid minification for better debugging 42 | minify: false, 43 | sourcemap: true, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/Canvas/Node/ShapeEnd.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FunctionComponent } from "react"; 2 | import Icon from "../Icon/Icon"; 3 | import type { Node } from "../../types"; 4 | import styles from "./Node.module.scss"; 5 | import { InPort } from "./Ports"; 6 | 7 | export type ShapeProps = { 8 | node: Node; 9 | selected: boolean; 10 | unselected: boolean; 11 | dragging: boolean; 12 | onConnectionDrag: () => void; 13 | onConnectionEnd: () => void; 14 | connectionCandidate: boolean; 15 | }; 16 | 17 | export const ShapeEnd: FunctionComponent = ({ node, unselected, connectionCandidate }) => { 18 | const className = unselected ? styles.unselectedEnd : styles.end; 19 | return ( 20 | <> 21 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/Canvas/Node/ShapeStart.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FunctionComponent } from "react"; 2 | import Icon from "../Icon/Icon"; 3 | import type { Node } from "../../types"; 4 | import styles from "./Node.module.scss"; 5 | import { OutPort } from "./Ports"; 6 | 7 | export type ShapeProps = { 8 | node: Node; 9 | selected: boolean; 10 | unselected: boolean; 11 | dragging: boolean; 12 | onConnectionDrag: () => void; 13 | onConnectionEnd: () => void; 14 | connectionCandidate: boolean; 15 | }; 16 | 17 | export const ShapeStart: FunctionComponent = ({ node, unselected, onConnectionDrag, onConnectionEnd }) => { 18 | const className = unselected ? styles.unselectedStart : styles.start; 19 | 20 | return ( 21 | <> 22 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svg-workflow-canvas 2 | 3 | > React component to for workflow building using an SVG based canvas 4 | 5 | ![Alt text](/images/image.png?1 "Optional Title") 6 | 7 | [![NPM](https://img.shields.io/npm/v/svg-workflow-canvas.svg)](https://www.npmjs.com/package/svg-workflow-canvas) 8 | 9 | ## Install 10 | 11 | ```bash 12 | yarn add svg-workflow-canvas 13 | 14 | ``` 15 | 16 | ## Usage 17 | Below is a simple example of how to use this component. You can also check out the playground folder for a more interactive demo. 18 | 19 | ```tsx 20 | import React from "react"; 21 | import { Workflow } from "svg-workflow-canvas"; 22 | 23 | export default () => { 24 | const w1 = { 25 | nodes: [ 26 | { name: 'add-new-item', id: '1', icon: 'plus-circle', position: { x: 3, y: 7 } }, 27 | { name: 'create-user', id: '2', icon: 'user', position: { x: 254, y: 130 } }, 28 | { name: 'upload-stuff', id: '3', icon: 'home', position: { x: -137, y: -32 } }, 29 | { name: 'train-jedi', id: '4', icon: 'jedi', position: { x: 254, y: -105 } } 30 | ], 31 | connections: [{ from: '1', to: '2', id: '1' }] 32 | }; 33 | 34 | return ( 35 |
36 | 37 |
38 | ); 39 | }; 40 | 41 | ``` 42 | 43 | ## Development 44 | 45 | This project uses Vite for both building the library and for development: 46 | 47 | ```bash 48 | # Start the development playground 49 | yarn dev 50 | 51 | # Build the library 52 | yarn build 53 | 54 | # Run type checking 55 | yarn typecheck 56 | ``` 57 | 58 | ## License 59 | 60 | MIT © [astanciu](https://github.com/astanciu) 61 | -------------------------------------------------------------------------------- /src/types/workflow.ts: -------------------------------------------------------------------------------- 1 | import type { Node, Connection } from "./models"; 2 | import type { Point } from "./base"; 3 | 4 | /** 5 | * Workflow state 6 | */ 7 | export interface WorkflowState { 8 | id: string; 9 | workflowName: string; 10 | workflowDescription: string; 11 | nodes: Node[]; 12 | connections: Connection[]; 13 | selectedNode: Node | null; 14 | selectedConnection: Connection | null | "all-disabled"; 15 | } 16 | 17 | /** 18 | * Workflow component props 19 | */ 20 | export interface WorkflowProps { 21 | workflow: SerializedWorkflow; 22 | workflowChanged?: (workflow: SerializedWorkflow) => void; 23 | scale?: number; 24 | snapToGrid?: boolean; 25 | showGrid?: boolean; 26 | // biome-ignore lint/suspicious/noExplicitAny: 27 | render?: (add: any, save: any, updateNode: any, removeNode: any, selectedNode: Node | null) => React.ReactNode; 28 | } 29 | 30 | /** 31 | * Serialized point for storage/transfer 32 | */ 33 | export interface SerializedPoint { 34 | x: number; 35 | y: number; 36 | } 37 | 38 | /** 39 | * Serialized node for storage/transfer 40 | */ 41 | export interface SerializedNode { 42 | name: string; 43 | id: string; 44 | icon: string; 45 | position?: SerializedPoint; 46 | scale?: number; 47 | } 48 | 49 | /** 50 | * Serialized connection for storage/transfer 51 | */ 52 | export interface SerializedConnection { 53 | from: string; 54 | to: string; 55 | id: string; 56 | } 57 | 58 | /** 59 | * Serialized workflow for storage/transfer 60 | */ 61 | export interface SerializedWorkflow { 62 | id: string; 63 | name: string; 64 | description: string; 65 | nodes: SerializedNode[]; 66 | connections: SerializedConnection[]; 67 | } 68 | -------------------------------------------------------------------------------- /src/Canvas/Grid/Grid.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react"; 2 | import styles from "./Grid.module.scss"; 3 | import { useCanvas } from "../CanvasContext"; 4 | 5 | interface GridProps { 6 | show: boolean; 7 | } 8 | 9 | /** 10 | * Grid component renders the background grid pattern 11 | * Shows a small grid and a larger grid pattern 12 | */ 13 | const Grid: React.FC = ({ show }) => { 14 | // Constants for grid dimensions 15 | const SIZE = 100000; 16 | const SMALL_SPACING = 50; 17 | const BIG_SPACING = 250; 18 | 19 | // Creating the grid pattern with SVG patterns 20 | const gridPattern = useMemo( 21 | () => ( 22 | <> 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ), 32 | [], 33 | ); 34 | 35 | // Don't render anything if grid is hidden 36 | if (!show) { 37 | return null; 38 | } 39 | 40 | return ( 41 | 42 | {gridPattern} 43 | 44 | 45 | ); 46 | }; 47 | 48 | // Use memo to prevent unnecessary re-renders 49 | export default React.memo(Grid); 50 | -------------------------------------------------------------------------------- /src/Canvas/Hooks/useCanvasSize.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, type RefObject } from "react"; 2 | import debounce from "lodash/debounce"; 3 | import type { ViewType } from "../../types"; 4 | 5 | interface UseCanvasSizeProps { 6 | svgRef: RefObject; 7 | onViewChange: (view: Partial) => void; 8 | } 9 | 10 | /** 11 | * Hook to manage canvas sizing 12 | * Handles resize events and calculates the offset of the canvas 13 | */ 14 | const useCanvasSize = ({ svgRef, onViewChange }: UseCanvasSizeProps) => { 15 | // Function to update the canvas size 16 | const setCanvasSize = useCallback( 17 | debounce(() => { 18 | if (!svgRef.current) return; 19 | 20 | const parent = svgRef.current.parentElement as HTMLElement; 21 | const bb = svgRef.current.getBoundingClientRect(); 22 | 23 | onViewChange({ 24 | width: parent.offsetWidth, 25 | height: parent.offsetHeight, 26 | x: parent.offsetWidth / 2, 27 | y: parent.offsetHeight / 2, 28 | offsetTop: bb.top, 29 | offsetLeft: bb.left, 30 | }); 31 | }, 50), 32 | [], 33 | ); 34 | 35 | // Set up event listeners 36 | useEffect(() => { 37 | // Initial canvas sizing 38 | setCanvasSize(); 39 | 40 | // Add resize event listeners 41 | window.addEventListener("resize", setCanvasSize); 42 | window.addEventListener("orientationchange", () => { 43 | setTimeout(setCanvasSize, 300); 44 | }); 45 | 46 | // Clean up on unmount 47 | return () => { 48 | setCanvasSize.cancel(); 49 | window.removeEventListener("resize", setCanvasSize); 50 | }; 51 | }, [setCanvasSize]); 52 | 53 | return { setCanvasSize }; 54 | }; 55 | 56 | export default useCanvasSize; 57 | -------------------------------------------------------------------------------- /src/Canvas/Models/Node.ts: -------------------------------------------------------------------------------- 1 | import type { SerializedNode } from "../../types/workflow"; 2 | import { IconLibrary } from "../Assets/icon-library"; 3 | import { generateId } from "../Util/Utils"; 4 | import { Point } from "./Point"; 5 | 6 | /** 7 | * Node class representing a workflow node 8 | * This class is used as both the implementation and the type 9 | */ 10 | export class Node { 11 | public name = "Node"; 12 | public id: string = generateId(); 13 | public icon: string; 14 | public position: Point = new Point(); 15 | public selected = false; 16 | public highlightInPort = false; 17 | public scale = 1; 18 | public outPortOffset: Point; 19 | public inPortOffset: Point; 20 | public width = 80; 21 | public height = 79; 22 | 23 | constructor(node: SerializedNode) { 24 | Object.assign(this, node); 25 | this.position = new Point(node.position?.x || 0, node.position?.y || 0); 26 | this.outPortOffset = new Point(45 * this.scale, 0); 27 | this.inPortOffset = new Point(-45 * this.scale, 0); 28 | if (!this.icon) { 29 | this.icon = this.getRandomIcon(); 30 | } 31 | } 32 | 33 | get outPortPosition(): Point { 34 | if (!this.position) return new Point(); 35 | return this.position.add(this.outPortOffset); 36 | } 37 | 38 | get inPortPosition(): Point { 39 | if (!this.position) return new Point(); 40 | return this.position.add(this.inPortOffset); 41 | } 42 | 43 | public clone(): Node { 44 | return new Node(this); 45 | } 46 | 47 | toString(): string { 48 | return `${this.id}:${this.name}`; 49 | } 50 | 51 | getRandomIcon(): string { 52 | const icons = Object.keys(IconLibrary); 53 | const iconName = icons[(icons.length * Math.random()) << 0]; 54 | 55 | return iconName; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/types/components.ts: -------------------------------------------------------------------------------- 1 | import type { Node, Connection } from "./models"; 2 | import type { ViewType } from "./base"; 3 | import type { EventManagerApi } from "../Canvas/Util/useEventManager"; 4 | 5 | /** 6 | * Canvas component props 7 | */ 8 | export interface CanvasProps { 9 | nodes: Node[]; 10 | connections: Connection[]; 11 | selectedNode: Node | null; 12 | selectNode: (node: Node | null) => void; 13 | updateNode: (node: Node) => void; 14 | selectConnection: (conn: Connection | null) => void; 15 | selectedConnection: Connection | null | "all-disabled"; 16 | createConnection: (fromNode: Node, toNode: Node) => void; 17 | removeConnection: (conn: Connection) => void; 18 | snapToGrid: boolean; 19 | showGrid: boolean; 20 | } 21 | 22 | /** 23 | * Canvas initialization component props 24 | */ 25 | export interface CanvasInitializationProps { 26 | updateView: (newViewData: Partial) => void; 27 | setVisibility: (visibility: string) => void; 28 | } 29 | 30 | /** 31 | * Canvas events component props 32 | */ 33 | export interface CanvasEventsProps { 34 | eventManager: EventManagerApi; // Type this properly based on your EventManager implementation 35 | handleTap: (e: CustomEvent) => void; 36 | handleMove: (e: CustomEvent) => void; 37 | handleMoveEnd: (e: CustomEvent) => void; 38 | handlePinch: (e: CustomEvent) => void; 39 | cancelAnimation: () => void; 40 | } 41 | 42 | /** 43 | * Canvas SVG component props 44 | */ 45 | export interface CanvasSVGProps { 46 | view: ViewType; 47 | svgRef: React.RefObject; 48 | handleWheel: (e: React.WheelEvent) => void; 49 | visibility: string; 50 | } 51 | 52 | /** 53 | * Canvas content component props 54 | */ 55 | export interface CanvasContentProps { 56 | transform: string; 57 | } 58 | -------------------------------------------------------------------------------- /src/Canvas/Node/NodeTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import styles from "./NodeTitle.module.scss"; 3 | 4 | type TitleProps = { 5 | name: string; 6 | }; 7 | 8 | export const NodeTitle = ({ node, unselected }) => { 9 | const [className, setClass] = useState(unselected ? styles.UnselectedTitle : styles.Title); 10 | const [labelClass, setLabel] = useState(unselected ? styles.unselectedLabel : styles.label); 11 | // // Ugh, this is so dumb, but there's a rendering bug with Safari 12 | // // where the 'paint-order' isn't being respected on first render. This trick causes 13 | // // it to re-draw which causes the text outline to show up properly. 14 | // useEffect(() => { 15 | // const timer = setTimeout(() => { 16 | // setClass(className + ' ' + styles.TitleHack); 17 | // }, 100); 18 | // return () => clearTimeout(timer); 19 | // }, []); 20 | 21 | useEffect(() => { 22 | setClass(unselected ? styles.UnselectedTitle : styles.Title); 23 | setLabel(unselected ? styles.unselectedLabel : styles.label); 24 | }, [unselected]); 25 | 26 | const x = 0; 27 | const y = 58 * node.scale; 28 | const fontSize = 12 * node.scale; 29 | 30 | if (["START", "END"].includes(node.id)) { 31 | node.name = node.id.toLowerCase(); 32 | } 33 | 34 | const labelHeight = 18; 35 | const labelWidth = node.name.length * 7 + 30; 36 | const labelX = x - labelWidth / 2; 37 | const labelY = y - 12; 38 | const radius = 10; 39 | 40 | return ( 41 | 42 | 51 | 52 | {node.name} 53 | 54 | 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/Canvas/Node/Node.module.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | @use '../styles/variables'; 3 | 4 | $node-selected-bg: color.adjust(variables.$node-bg, $lightness: -10%); //95999e 5 | $node-unselected-bg: color.mix(variables.$node-bg, variables.$background, 25%); //95999e 6 | 7 | .normal { 8 | fill: variables.$node-bg; 9 | cursor: pointer; 10 | stroke: none; 11 | transition: fill 0.05s linear; 12 | } 13 | 14 | .normalOutline { 15 | fill: none; 16 | stroke: #95999e; 17 | stroke-width: 1px; 18 | } 19 | 20 | .normalIcon { 21 | fill: variables.$node-icon; 22 | stroke: none; 23 | cursor: pointer; 24 | } 25 | 26 | /* Selected */ 27 | .selected { 28 | composes: normal; 29 | fill: $node-selected-bg; //#24272b; 30 | stroke: none; 31 | } 32 | 33 | .selectedOutline { 34 | composes: normalOutline; 35 | stroke: #95999e; 36 | stroke-width: 2px; 37 | } 38 | 39 | .selectedIcon { 40 | composes: normalIcon; 41 | fill: #d1d7e4; 42 | } 43 | 44 | .selectedHighlight { 45 | fill: #c4ebff; 46 | opacity: 0.0514605978; 47 | } 48 | 49 | /* UnSelected */ 50 | .unselected { 51 | composes: normal; 52 | fill: $node-unselected-bg; //#b5b5b5; 53 | stroke: none; 54 | } 55 | 56 | .unselectedOutline { 57 | composes: normalOutline; 58 | stroke: #b5b5b5; 59 | } 60 | 61 | .unselectedIcon { 62 | composes: normalIcon; 63 | fill: #dedede; 64 | } 65 | 66 | .nocursor { 67 | cursor: none; 68 | } 69 | 70 | .dragging { 71 | cursor: grabbing !important; 72 | } 73 | 74 | /* Specials */ 75 | .start { 76 | composes: normal; 77 | fill: variables.$magenta; 78 | } 79 | .end { 80 | composes: normal; 81 | fill: variables.$green; 82 | } 83 | 84 | .specialIcon { 85 | composes: normalIcon; 86 | fill: #eceff4 87 | } 88 | 89 | .unselectedStart { 90 | composes: normal; 91 | fill: color.mix(variables.$magenta, variables.$background, 25%); 92 | } 93 | 94 | .unselectedEnd { 95 | composes: normal; 96 | fill: color.mix(variables.$green, variables.$background, 25%); 97 | } 98 | 99 | -------------------------------------------------------------------------------- /src/Canvas/Connections/util.ts: -------------------------------------------------------------------------------- 1 | import { Point } from "../Models"; 2 | 3 | // Credit for these methods goes to 4 | // https://github.com/flowhub/the-graph/blob/master/the-graph/the-graph-edge.js 5 | 6 | const NODE_SIZE = 110; 7 | const CURVE = 90; 8 | 9 | export const makeSVGPath = (start: Point, end: Point) => { 10 | const c1 = new Point(); 11 | const c2 = new Point(); 12 | 13 | if (end.x - 5 < start.x) { 14 | const curveFactor = ((start.x - end.x) * CURVE) / 200; 15 | if (Math.abs(end.y - start.y) < NODE_SIZE / 2) { 16 | // Loopback 17 | c1.x = start.x + curveFactor; 18 | c1.y = start.y - curveFactor; 19 | c2.x = end.x - curveFactor; 20 | c2.y = end.y - curveFactor; 21 | } else { 22 | // Stick out some 23 | c1.x = start.x + curveFactor; 24 | c1.y = start.y + (end.y > start.y ? curveFactor : -curveFactor); 25 | c2.x = end.x - curveFactor; 26 | c2.y = end.y + (end.y > start.y ? -curveFactor : curveFactor); 27 | } 28 | } else { 29 | // Controls halfway between 30 | c1.x = start.x + (end.x - start.x) / 2; 31 | c1.y = start.y; 32 | c2.x = c1.x; 33 | c2.y = end.y; 34 | } 35 | 36 | const path = `M ${start.x} ${start.y} C ${c1.x} ${c1.y} ${c2.x} ${c2.y} ${end.x} ${end.y}`; 37 | 38 | return { path, c1, c2 }; 39 | }; 40 | 41 | export const findPointOnCurve = (p: number, start: Point, c1: Point, c2: Point, end: Point): Point => { 42 | // p is percentage from 0 to 1 43 | const op = 1 - p; 44 | // 3 green points between 4 points that define curve 45 | const g1x = start.x * p + c1.x * op; 46 | const g1y = start.y * p + c1.y * op; 47 | const g2x = c1.x * p + c2.x * op; 48 | const g2y = c1.y * p + c2.y * op; 49 | const g3x = c2.x * p + end.x * op; 50 | const g3y = c2.y * p + end.y * op; 51 | // 2 blue points between green points 52 | const b1x = g1x * p + g2x * op; 53 | const b1y = g1y * p + g2y * op; 54 | const b2x = g2x * p + g3x * op; 55 | const b2y = g2y * p + g3y * op; 56 | // Point on the curve between blue points 57 | const x = b1x * p + b2x * op; 58 | const y = b1y * p + b2y * op; 59 | 60 | return new Point(x, y); 61 | }; 62 | -------------------------------------------------------------------------------- /src/Workflow/useWorkflow.tsx: -------------------------------------------------------------------------------- 1 | import { type Dispatch, useReducer } from "react"; 2 | import type { SerializedWorkflow, WorkflowState } from "../types/workflow"; 3 | import type { Connection, Node } from "../types"; 4 | import { WorkflowData } from "./WorkflowData"; 5 | 6 | // Alias for backward compatibility 7 | type State = WorkflowState; 8 | 9 | type WorkflowAction = 10 | | { type: "selectNode"; node: Node | null } 11 | | { type: "updateNode"; node: Node } 12 | | { type: "insertNode"; node: { name: string; id: string; icon: string } } 13 | | { type: "removeNode"; node: Node } 14 | | { type: "selectConnection"; connection: Connection | null | "all-disabled" } 15 | | { type: "createConnection"; from: Node; to: Node } 16 | | { type: "removeConnection"; connection: Connection } 17 | | { type: "reset" }; 18 | 19 | export const useWorkflow = ( 20 | jsonWorkflow: SerializedWorkflow, 21 | options: { scale?: number; workflowChanged?: (workflow: SerializedWorkflow) => void }, 22 | ): [State, Dispatch] => { 23 | const init = () => WorkflowData.loadState(jsonWorkflow, options); 24 | 25 | const reducer = (state: State, action: WorkflowAction): State => { 26 | switch (action.type) { 27 | case "selectNode": 28 | return WorkflowData.selectNode(state, action.node); 29 | case "updateNode": 30 | return WorkflowData.updateNode(state, action.node); 31 | case "insertNode": 32 | return WorkflowData.insertNode(state, action.node); 33 | case "removeNode": 34 | return WorkflowData.removeNode(state, action.node); 35 | case "selectConnection": 36 | return WorkflowData.selectConnection(state, action.connection); 37 | case "createConnection": 38 | console.log("createConnection", action.from, action.to); 39 | return WorkflowData.createConnection(state, action.from, action.to); 40 | case "removeConnection": 41 | return WorkflowData.removeConnection(state, action.connection); 42 | case "reset": 43 | return init(); 44 | default: 45 | return state; 46 | } 47 | }; 48 | 49 | return useReducer(reducer, {}, init); 50 | }; 51 | -------------------------------------------------------------------------------- /src/Canvas/Node/Ports.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | import type { Node } from "../Models"; 3 | import useEventManager from "../Util/useEventManager"; 4 | import styles from "./Port.module.scss"; 5 | 6 | type InPortProps = { 7 | node: Node; 8 | unselected: boolean; 9 | highlight: boolean; 10 | }; 11 | 12 | export const InPort = React.memo(({ node, unselected, highlight }: InPortProps) => { 13 | let className = styles.Port; 14 | if (unselected) { 15 | className = styles.PortUnselected; 16 | } 17 | if (highlight) { 18 | className = styles.PortHighlight; 19 | } 20 | 21 | return ( 22 | 28 | ); 29 | }); 30 | 31 | type OutPortProps = { 32 | node: Node; 33 | unselected: boolean; 34 | onConnectionDrag: (node: Node, e: CustomEvent) => void; 35 | onConnectionEnd: (node: Node) => void; 36 | }; 37 | 38 | export const OutPort = React.memo(({ node, unselected, onConnectionDrag, onConnectionEnd }: OutPortProps) => { 39 | const nodeDomRef = useRef(null); 40 | const nodeRef = useRef(node); 41 | 42 | // Use the new hook instead of creating a new EventManager instance directly 43 | const eventManager = useEventManager(nodeDomRef, false); 44 | 45 | useEffect(() => { 46 | // Register event handlers 47 | const handleMove = (e: CustomEvent) => { 48 | e.stopPropagation(); 49 | onConnectionDrag(nodeRef.current, e); 50 | }; 51 | 52 | const handleMoveEnd = () => { 53 | onConnectionEnd(nodeRef.current); 54 | }; 55 | 56 | eventManager.onMove(handleMove); 57 | eventManager.onMoveEnd(handleMoveEnd); 58 | 59 | // Cleanup function to unregister the events 60 | return () => { 61 | eventManager.offMove(handleMove); 62 | eventManager.offMoveEnd(handleMoveEnd); 63 | }; 64 | }, [eventManager, onConnectionDrag, onConnectionEnd]); 65 | 66 | useEffect(() => { 67 | nodeRef.current = node; 68 | }); 69 | 70 | return ( 71 | 72 | 73 | 79 | 80 | ); 81 | }); 82 | -------------------------------------------------------------------------------- /src/Canvas/Node/Shape.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FunctionComponent } from "react"; 2 | import Icon from "../Icon/Icon"; 3 | import type { Node } from "../../types"; 4 | import styles from "./Node.module.scss"; 5 | import { InPort, OutPort } from "./Ports"; 6 | 7 | export type ShapeProps = { 8 | node: Node; 9 | selected: boolean; 10 | unselected: boolean; 11 | dragging: boolean; 12 | onConnectionDrag: (node: Node, e: CustomEvent) => void; 13 | onConnectionEnd: (node: Node) => void; 14 | connectionCandidate: boolean; 15 | }; 16 | 17 | export const Shape: FunctionComponent = ({ 18 | node, 19 | selected, 20 | unselected, 21 | dragging, 22 | onConnectionDrag, 23 | onConnectionEnd, 24 | connectionCandidate, 25 | }) => { 26 | let nodeClass = styles.normal; 27 | let nodeOutline = styles.normalOutline; 28 | let nodeIconClass = styles.normalIcon; 29 | 30 | if (unselected) { 31 | nodeClass = styles.unselected; 32 | nodeOutline = styles.unselectedOutline; 33 | nodeIconClass = styles.unselectedIcon; 34 | } 35 | 36 | if (selected) { 37 | nodeClass = styles.selected; 38 | nodeOutline = styles.selectedOutline; 39 | nodeIconClass = styles.selectedIcon; 40 | } 41 | 42 | // Apply grabbing cursor class if node is actively being dragged 43 | if (dragging) { 44 | nodeClass += ` ${styles.dragging}`; 45 | nodeIconClass += ` ${styles.dragging}`; 46 | } 47 | 48 | return ( 49 | <> 50 | 56 | {/* {selected && } */} 61 | 65 | 66 | 67 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | ); 80 | }; 81 | -------------------------------------------------------------------------------- /src/Canvas/Components/CanvasContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react"; 2 | import Grid from "../Grid/Grid"; 3 | import ConnectionComponent from "../Connections/ConnectionComponent"; 4 | import ConnectionPreview from "../Connections/ConnectionPreview"; 5 | import NodeComponent from "../Node/Node"; 6 | import { useCanvas } from "../CanvasContext"; 7 | 8 | interface CanvasContentProps { 9 | transform: string; 10 | } 11 | 12 | /** 13 | * Component for rendering the main canvas content 14 | * Uses Context API to access canvas state 15 | */ 16 | const CanvasContent: React.FC = ({ transform }) => { 17 | const { 18 | nodes, 19 | connections, 20 | connectionInProgress, 21 | showGrid, 22 | snapToGrid, 23 | selectedNode, 24 | selectedConnection, 25 | selectNode, 26 | selectConnection, 27 | updateNode, 28 | removeConnection, 29 | handleConnectionDrag, 30 | handleConnectionEnd, 31 | isConnectionCandidate, 32 | view, 33 | } = useCanvas(); 34 | 35 | // Memoize the node components to prevent unnecessary re-renders 36 | const nodeComponents = useMemo(() => { 37 | return nodes.map((node) => ( 38 | 50 | )); 51 | }, [ 52 | nodes, 53 | updateNode, 54 | view, 55 | handleConnectionDrag, 56 | handleConnectionEnd, 57 | isConnectionCandidate, 58 | selectNode, 59 | selectedNode, 60 | snapToGrid, 61 | ]); 62 | 63 | // Memoize the connection components to prevent unnecessary re-renders 64 | const connectionComponents = useMemo(() => { 65 | return connections.map((conn) => ); 66 | }, [connections]); 67 | 68 | return ( 69 | 70 | 71 | 72 | {/* Render connections */} 73 | {connectionComponents} 74 | 75 | {/* Render connection preview during drag */} 76 | {connectionInProgress && ( 77 | 78 | )} 79 | 80 | {/* Render nodes */} 81 | {nodeComponents} 82 | 83 | ); 84 | }; 85 | 86 | export default React.memo(CanvasContent); 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svg-workflow-canvas", 3 | "version": "1.2.1", 4 | "description": "Generic workflow builder react component using an SVG canvas", 5 | "keywords": [ 6 | "workflow", 7 | "workflow component", 8 | "workflow builder", 9 | "workflow designer", 10 | "pipeline", 11 | "component", 12 | "ui", 13 | "svg", 14 | "react" 15 | ], 16 | "author": "Alex Stanciu (http://github.com/astanciu)", 17 | "license": "MIT", 18 | "repository": "astanciu/svg-workflow-canvas", 19 | "homepage": "https://astanciu.github.io/svg-workflow-canvas", 20 | "main": "dist/svg-workflow-canvas.umd.js", 21 | "module": "dist/svg-workflow-canvas.es.js", 22 | "types": "dist/index.d.ts", 23 | "exports": { 24 | ".": { 25 | "import": "./dist/svg-workflow-canvas.es.js", 26 | "require": "./dist/svg-workflow-canvas.umd.js" 27 | }, 28 | "./styles.css": "./dist/style.css" 29 | }, 30 | "engines": { 31 | "node": ">=8", 32 | "npm": ">=5" 33 | }, 34 | "scripts": { 35 | "test": "cross-env CI=1 react-scripts-ts test --env=jsdom", 36 | "test:watch": "react-scripts-ts test --env=jsdom", 37 | "build": "vite build", 38 | "build:legacy": "rollup -c", 39 | "dev": "cd playground && vite", 40 | "start": "yarn dev", 41 | "typecheck": "tsc --noEmit", 42 | "prepare": "yarn run build", 43 | "predeploy": "cd playground && yarn install && yarn run build", 44 | "deploy": "gh-pages -d playground/dist", 45 | "biome": "biome" 46 | }, 47 | "dependencies": { 48 | "lodash": "^4.17.21" 49 | }, 50 | "peerDependencies": { 51 | "react": "^17.0.0 || ^18.0.0", 52 | "react-dom": "^17.0.0 || ^18.0.0" 53 | }, 54 | "devDependencies": { 55 | "@biomejs/biome": "^1.9.4", 56 | "@rollup/plugin-node-resolve": "^9.0.0", 57 | "@svgr/rollup": "^8.1.0", 58 | "@types/jest": "^26.0.10", 59 | "@types/node": "^22.13.10", 60 | "@types/react": "^18.3.18", 61 | "@types/react-dom": "^18.3.5", 62 | "@vitejs/plugin-react": "^4.3.4", 63 | "babel-core": "^6.26.3", 64 | "babel-jest": "^26.3.0", 65 | "babel-runtime": "^6.26.0", 66 | "cross-env": "^7.0.3", 67 | "gh-pages": "^6.3.0", 68 | "react-scripts-ts": "^3.1.0", 69 | "rollup": "^2.26.8", 70 | "rollup-plugin-babel": "^4.4.0", 71 | "rollup-plugin-commonjs": "^10.1.0", 72 | "rollup-plugin-peer-deps-external": "^2.2.3", 73 | "rollup-plugin-postcss": "^3.1.8", 74 | "rollup-plugin-terser": "^7.0.1", 75 | "rollup-plugin-typescript2": "^0.27.2", 76 | "rollup-plugin-url": "^3.0.1", 77 | "sass": "^1.85.1", 78 | "tsup": "^8.4.0", 79 | "vite": "^6.2.2", 80 | "vite-plugin-dts": "^4.5.3" 81 | }, 82 | "files": ["dist"] 83 | } 84 | -------------------------------------------------------------------------------- /src/Canvas/Hooks/usePanning.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useCallback } from "react"; 2 | import type { ViewType } from "../Types"; 3 | 4 | interface UsePanningProps { 5 | initialView: ViewType; 6 | } 7 | 8 | /** 9 | * Hook to manage canvas panning 10 | * Handles moving the canvas and momentum scrolling 11 | */ 12 | const usePanning = ({ initialView }: UsePanningProps) => { 13 | const [view, setView] = useState(initialView); 14 | const velocityRef = useRef({ x: 0, y: 0 }); 15 | const frictionRef = useRef(1); 16 | const animationFrameRef = useRef(); 17 | 18 | // Update the view state 19 | const updateView = useCallback((newViewData: Partial) => { 20 | setView((prev) => ({ ...prev, ...newViewData })); 21 | }, []); 22 | 23 | // Handle movement events 24 | const handleMove = useCallback((e: Event) => { 25 | const customEvent = e as CustomEvent; 26 | 27 | // Add the delta to the current view position 28 | setView((prev) => ({ 29 | ...prev, 30 | x: prev.x + customEvent.detail.delta.x, 31 | y: prev.y + customEvent.detail.delta.y, 32 | })); 33 | }, []); 34 | 35 | // Handle the end of a movement (start glide animation) 36 | const handleMoveEnd = useCallback((e: Event) => { 37 | if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current); 38 | 39 | const customEvent = e as CustomEvent; 40 | velocityRef.current = customEvent.detail.delta; 41 | frictionRef.current = 0.85; 42 | 43 | animationFrameRef.current = requestAnimationFrame(glideCanvas); 44 | }, []); 45 | 46 | // Physics-based animation for momentum scrolling 47 | const glideCanvas = useCallback(() => { 48 | frictionRef.current -= 0.01; 49 | if (frictionRef.current < 0.01) frictionRef.current = 0.01; 50 | 51 | velocityRef.current = { 52 | x: velocityRef.current.x * frictionRef.current, 53 | y: velocityRef.current.y * frictionRef.current, 54 | }; 55 | 56 | // Stop the animation if the velocity is very low 57 | if ( 58 | velocityRef.current.x < 0.02 && 59 | velocityRef.current.x > -0.02 && 60 | velocityRef.current.y < 0.02 && 61 | velocityRef.current.y > -0.02 62 | ) { 63 | frictionRef.current = 1.0; 64 | return; 65 | } 66 | 67 | setView((prev) => ({ 68 | ...prev, 69 | x: prev.x + velocityRef.current.x, 70 | y: prev.y + velocityRef.current.y, 71 | })); 72 | 73 | animationFrameRef.current = requestAnimationFrame(glideCanvas); 74 | }, []); 75 | 76 | // Utility to cancel any running animations 77 | const cancelAnimation = useCallback(() => { 78 | if (animationFrameRef.current) { 79 | cancelAnimationFrame(animationFrameRef.current); 80 | } 81 | }, []); 82 | 83 | return { 84 | view, 85 | updateView, 86 | handleMove, 87 | handleMoveEnd, 88 | cancelAnimation, 89 | }; 90 | }; 91 | 92 | export default usePanning; 93 | -------------------------------------------------------------------------------- /src/Workflow/Workflow.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useCallback, useEffect } from "react"; 2 | import Canvas from "../Canvas/Canvas"; 3 | import type { SerializedWorkflow, WorkflowProps } from "../types/workflow"; 4 | import { useWorkflow } from "./useWorkflow"; 5 | import styles from "./Workflow.module.scss"; 6 | import { WorkflowData } from "./WorkflowData"; 7 | 8 | export const WorkflowContext = createContext(null); 9 | 10 | const emptyWorkflow: SerializedWorkflow = { 11 | id: "new-workflow", 12 | name: "New Workflow", 13 | description: "New Workflow", 14 | nodes: [], 15 | connections: [], 16 | }; 17 | 18 | export const Workflow = ({ 19 | workflow = emptyWorkflow, 20 | workflowChanged = () => {}, 21 | scale = 1, 22 | snapToGrid = false, 23 | showGrid = true, 24 | render, 25 | }: WorkflowProps) => { 26 | const [state, dispatch] = useWorkflow(workflow, { scale, workflowChanged }); 27 | 28 | // Reset the reducer if we receive a new workflow json in props 29 | useEffect(() => { 30 | dispatch({ type: "reset" }); 31 | }, [dispatch]); 32 | 33 | // We need to use callbacks on all these otherwise it 34 | // forces all similar components to re-render, when just one 35 | // item (node, connection) is actually changed. Alternative 36 | // would be to pass dispatch as a prop. 37 | const selectNode = useCallback((node) => dispatch({ type: "selectNode", node }), [dispatch]); 38 | const updateNode = useCallback((node) => dispatch({ type: "updateNode", node }), [dispatch]); 39 | const removeNode = useCallback((node) => dispatch({ type: "removeNode", node }), [dispatch]); 40 | const removeConnection = useCallback((connection) => dispatch({ type: "removeConnection", connection }), [dispatch]); 41 | const createConnection = useCallback( 42 | (from, to) => { 43 | // console.log("callback createConnection", from, to); 44 | dispatch({ type: "createConnection", from, to }); 45 | }, 46 | [dispatch], 47 | ); 48 | const selectConnection = useCallback((conn) => dispatch({ type: "selectConnection", connection: conn }), [dispatch]); 49 | const insertNode = useCallback((node) => dispatch({ type: "insertNode", node }), [dispatch]); 50 | 51 | const addNode = (node) => { 52 | insertNode(node.name); 53 | }; 54 | const saveWorkflow = () => { 55 | return WorkflowData.export(state); 56 | }; 57 | 58 | return ( 59 |
60 | {render?.(addNode, saveWorkflow, updateNode, removeNode, state.selectedNode)} 61 | 74 |
75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /src/Canvas/Hooks/useZoom.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef } from "react"; 2 | import type { ViewType, ScaleConstraints } from "../Types"; 3 | 4 | interface UseZoomProps { 5 | constraints: ScaleConstraints; 6 | onViewChange: (view: Partial) => void; 7 | view: ViewType; 8 | } 9 | 10 | /** 11 | * Hook to manage canvas zooming 12 | * Handles wheel zoom, pinch zoom, and zoom animations 13 | */ 14 | const useZoom = ({ constraints, onViewChange, view }: UseZoomProps) => { 15 | const animationFrameRef = useRef(); 16 | 17 | // Set scale while maintaining position relative to a specific point 18 | const setScaleWithLocation = useCallback( 19 | (scale: number, location: { x: number; y: number }) => { 20 | let newScale = scale; 21 | 22 | // Apply constraints 23 | if (newScale < constraints.min) { 24 | newScale = constraints.min; 25 | } else if (newScale > constraints.max) { 26 | newScale = constraints.max; 27 | } 28 | 29 | const xFactor = newScale / view.scale - 1; 30 | const posDelta = { 31 | x: location.x - view.x, 32 | y: location.y - view.y, 33 | }; 34 | 35 | onViewChange({ 36 | scale: newScale, 37 | x: view.x + -1 * posDelta.x * xFactor, 38 | y: view.y + -1 * posDelta.y * xFactor, 39 | }); 40 | }, 41 | [constraints, view.scale, view.x, view.y, onViewChange], 42 | ); 43 | 44 | // Handle mouse wheel events for zooming 45 | const handleWheel = useCallback( 46 | (event: React.WheelEvent) => { 47 | const size = event.deltaY; 48 | if (Number.isNaN(size) || !size) return; 49 | 50 | const scale = view.scale + (-1 * size) / 200; 51 | const center = { 52 | x: event.clientX, 53 | y: event.clientY, 54 | }; 55 | 56 | setScaleWithLocation(scale, center); 57 | }, 58 | [view.scale, setScaleWithLocation], 59 | ); 60 | 61 | // Handle pinch events for zooming (from touch gestures) 62 | const handlePinch = useCallback( 63 | (e: Event) => { 64 | const customEvent = e as CustomEvent; 65 | const center = { x: customEvent.detail.x, y: customEvent.detail.y }; 66 | setScaleWithLocation(customEvent.detail.scale, center); 67 | }, 68 | [setScaleWithLocation], 69 | ); 70 | 71 | // Animated zoom-to-fit function 72 | const zoomToFit = useCallback(() => { 73 | const ease = (t: number) => t * (2 - t); 74 | const start = 2; 75 | const end = 1; 76 | const duration = 15; // animation frames 77 | const t = animationFrameRef.current || 1; 78 | 79 | if (view.scale <= end) { 80 | if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current); 81 | return; 82 | } 83 | 84 | const delta = (end - start) * ease(t / duration); 85 | 86 | onViewChange({ 87 | scale: start + delta, 88 | }); 89 | 90 | animationFrameRef.current = requestAnimationFrame(zoomToFit); 91 | }, [view.scale, onViewChange]); 92 | 93 | return { 94 | handleWheel, 95 | handlePinch, 96 | zoomToFit, 97 | }; 98 | }; 99 | 100 | export default useZoom; 101 | -------------------------------------------------------------------------------- /example/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Workflow from "svg-workflow-canvas"; 3 | import { Button } from "./Components/Button/Button"; 4 | import { ButtonToggle } from "./Components/Button/ButtonToggle"; 5 | import { ButtonGroup } from "./Components/Button/ButtonGroup"; 6 | import { Panel } from "./Components/Panel/Panel"; 7 | import { useLocalStorage } from "./Components/useLocalStorage"; 8 | 9 | const App = () => { 10 | const [showGrid, setShowGrid] = useLocalStorage("settings.showGrid", true); 11 | const w1 = { 12 | id: "jzh31fs", 13 | name: "Workflow", 14 | description: "Generic workflow", 15 | nodes: [ 16 | { id: "START", icon: "sign-in-alt", position: { x: -400, y: 0 } }, 17 | { id: "END", icon: "sign-out-alt", position: { x: 350, y: 0 } }, 18 | { 19 | name: "add-new-item", 20 | id: "1", 21 | icon: "plus-circle", 22 | position: { x: 0, y: 0 }, 23 | }, 24 | { 25 | name: "create-user", 26 | id: "2", 27 | icon: "user", 28 | position: { x: 250, y: 165 }, 29 | }, 30 | { 31 | name: "upload-stuff", 32 | id: "3", 33 | icon: "home", 34 | position: { x: -200, y: 0 }, 35 | }, 36 | { 37 | name: "train-jedi", 38 | id: "4", 39 | icon: "jedi", 40 | position: { x: 250, y: -165 }, 41 | }, 42 | ], 43 | connections: [ 44 | { from: "1", to: "2", id: "h1zt2" }, 45 | { from: "3", to: "1", id: "0.10135201500322455" }, 46 | { from: "1", to: "4", id: "0.8044895443880992" }, 47 | { from: "START", to: "3", id: "0.5510817403732952" }, 48 | { from: "1", to: "END", id: "0.21708917364861713" }, 49 | ], 50 | }; 51 | 52 | const w2 = { 53 | id: "jzh31fs", 54 | name: "Workflow", 55 | description: "Generic workflow", 56 | nodes: [ 57 | { 58 | name: "create-user", 59 | id: "2", 60 | icon: "user", 61 | position: { x: 250, y: 165 }, 62 | }, 63 | { 64 | name: "upload-stuff", 65 | id: "3", 66 | icon: "home", 67 | position: { x: -200, y: 0 }, 68 | }, 69 | { 70 | name: "train-jedi", 71 | id: "4", 72 | icon: "jedi", 73 | position: { x: 250, y: -165 }, 74 | }, 75 | ], 76 | connections: [], 77 | }; 78 | 79 | const addNode = (addNodeWorkflowFn) => () => { 80 | addNodeWorkflowFn({ name: "Alex" }); 81 | }; 82 | 83 | const saveWorkflow = (fn) => () => { 84 | const workflow = fn(); 85 | console.log("Store this in DB: ", workflow); 86 | alert("In this demo, data was dumped to console"); 87 | }; 88 | 89 | const toggleGrid = () => { 90 | setShowGrid(!showGrid); 91 | }; 92 | 93 | return ( 94 |
95 | ( 101 | <> 102 | {/* */} 103 | 104 | 105 |
113 | ); 114 | }; 115 | 116 | export default App; 117 | -------------------------------------------------------------------------------- /src/Canvas/Canvas.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useCallback } from "react"; 2 | import type { CanvasProps, ScaleConstraints } from "../types"; 3 | import { useCanvasSize, usePanning, useZoom, useConnections } from "./Hooks"; 4 | import useEventManager from "./Util/useEventManager"; 5 | import { CanvasInitialization, CanvasEvents, CanvasSVG } from "./Components"; 6 | import { CanvasProvider } from "./CanvasContext"; 7 | 8 | /** 9 | * Canvas Component - SVG-based interactive workflow canvas 10 | * 11 | * Handles rendering and interaction for a zoomable, pannable canvas 12 | * with nodes and connections. 13 | */ 14 | const Canvas: React.FC = ({ 15 | nodes, 16 | connections, 17 | selectedNode, 18 | selectNode, 19 | updateNode, 20 | selectConnection, 21 | selectedConnection, 22 | createConnection, 23 | removeConnection, 24 | snapToGrid, 25 | showGrid, 26 | }) => { 27 | // Scale constraints 28 | const scaleConstraints: ScaleConstraints = { 29 | min: 0.25, 30 | max: 3, 31 | }; 32 | 33 | // Initialize refs and state 34 | const svgRef = useRef(null); 35 | const [visibility, setVisibility] = useState("hidden"); 36 | 37 | // Use panning hook for view state management 38 | const { view, updateView, handleMove, handleMoveEnd, cancelAnimation } = usePanning({ 39 | initialView: { 40 | width: 600, 41 | height: 400, 42 | x: 300, 43 | y: 200 / 2, 44 | scale: 1, 45 | }, 46 | }); 47 | 48 | // Use zoom hook for handling zoom interactions 49 | const { handleWheel, handlePinch, zoomToFit } = useZoom({ 50 | constraints: scaleConstraints, 51 | onViewChange: updateView, 52 | view, 53 | }); 54 | 55 | // Use canvas size hook for handling resize 56 | const { setCanvasSize } = useCanvasSize({ 57 | svgRef, 58 | onViewChange: updateView, 59 | }); 60 | 61 | // Use connections hook for handling node connections 62 | const { connectionInProgress, closestNode, handleConnectionDrag, handleConnectionEnd, isConnectionCandidate } = 63 | useConnections({ 64 | view, 65 | nodes, 66 | createConnection, 67 | }); 68 | 69 | // Set up event manager 70 | const eventManager = useEventManager(svgRef); 71 | 72 | // Handle tap events (selection) 73 | const handleTap = useCallback(() => { 74 | selectNode(null); 75 | selectConnection(null); 76 | }, [selectNode, selectConnection]); 77 | 78 | return ( 79 | 99 | {/* Handle canvas initialization */} 100 | 101 | 102 | {/* Handle canvas events */} 103 | 111 | 112 | {/* Render the SVG canvas */} 113 | 114 | 115 | ); 116 | }; 117 | 118 | export default Canvas; 119 | -------------------------------------------------------------------------------- /src/Canvas/Hooks/useConnections.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react"; 2 | import type { Node, ViewType, ConnectionInProgress } from "../../types"; 3 | import { Point } from "../Models"; 4 | interface UseConnectionsProps { 5 | view: ViewType; 6 | nodes: Node[]; 7 | createConnection: (fromNode: Node, toNode: Node) => void; 8 | } 9 | 10 | /** 11 | * Hook to manage canvas connections 12 | * Handles finding closest nodes, connection previews, and creating connections 13 | */ 14 | const useConnections = ({ view, nodes, createConnection }: UseConnectionsProps) => { 15 | const [connectionInProgress, setConnectionInProgress] = useState(null); 16 | const [closestNode, setClosestNode] = useState(undefined); 17 | 18 | // Convert page coordinates to SVG coordinates 19 | const convertCoordsToSVG = useCallback( 20 | (x: number, y: number): Point => { 21 | return new Point((x - view.x) / view.scale, (y - view.y) / view.scale); 22 | }, 23 | [view.x, view.y, view.scale], 24 | ); 25 | 26 | // Find the closest node to a given point with an active input port 27 | const getClosestInPortNode = useCallback( 28 | (loc: Point): Node | undefined => { 29 | const minDist: number = 60; 30 | let closestNode: Node | undefined; 31 | let closestDist: number = Number.POSITIVE_INFINITY; 32 | 33 | for (const node of nodes) { 34 | const distToMouse = node.inPortPosition.distanceTo(loc); 35 | 36 | if (distToMouse <= minDist) { 37 | if (!closestNode) { 38 | closestNode = node.clone(); 39 | closestDist = distToMouse; 40 | } else { 41 | if (distToMouse < closestDist) { 42 | closestNode = node.clone(); 43 | closestDist = distToMouse; 44 | } 45 | } 46 | } 47 | } 48 | 49 | return closestNode; 50 | }, 51 | [nodes], 52 | ); 53 | 54 | // Update the closest node based on mouse position 55 | const setClosestNodeToMouse = useCallback( 56 | (mouse: Point, sourceNode: Node): void => { 57 | const closest = getClosestInPortNode(mouse); 58 | // Don't allow connecting to self (same node) 59 | if (closest && closest.id === sourceNode.id) { 60 | setClosestNode(undefined); 61 | } else { 62 | setClosestNode(closest); 63 | } 64 | }, 65 | [getClosestInPortNode], 66 | ); 67 | 68 | // Handle connection dragging from a node 69 | const handleConnectionDrag = useCallback( 70 | (node: Node, e: CustomEvent) => { 71 | const mousePosition = convertCoordsToSVG( 72 | e.detail.x - (view?.offsetLeft ?? 0), 73 | e.detail.y - (view?.offsetTop ?? 0), 74 | ); 75 | 76 | setClosestNodeToMouse(mousePosition, node); 77 | 78 | setConnectionInProgress({ 79 | from: node, 80 | to: closestNode ? closestNode.inPortPosition : mousePosition, 81 | }); 82 | }, 83 | [convertCoordsToSVG, view.offsetLeft, view.offsetTop, closestNode, setClosestNodeToMouse], 84 | ); 85 | 86 | // Handle connection creation when dragging ends 87 | const handleConnectionEnd = useCallback( 88 | (node: Node) => { 89 | // Only create connection if we have a valid target node 90 | // and it's not the same as the source node (prevent self-connections) 91 | if (closestNode && node.id !== closestNode.id) { 92 | createConnection(node, closestNode); 93 | } 94 | 95 | setConnectionInProgress(null); 96 | setClosestNode(undefined); 97 | }, 98 | [closestNode, createConnection], 99 | ); 100 | 101 | // Check if a node is a potential connection candidate 102 | const isConnectionCandidate = useCallback( 103 | (nodeId: string): boolean => { 104 | // A node is a connection candidate if it's the closest node 105 | // AND not the source node of the connection (prevent self-connections) 106 | return Boolean( 107 | closestNode && closestNode.id === nodeId && connectionInProgress && connectionInProgress.from.id !== nodeId, 108 | ); 109 | }, 110 | [closestNode, connectionInProgress], 111 | ); 112 | 113 | return { 114 | connectionInProgress, 115 | closestNode, 116 | handleConnectionDrag, 117 | handleConnectionEnd, 118 | isConnectionCandidate, 119 | }; 120 | }; 121 | 122 | export default useConnections; 123 | -------------------------------------------------------------------------------- /src/Canvas/CanvasContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useMemo } from "react"; 2 | import type { Node, Connection, Point, ViewType } from "../types"; 3 | 4 | /** 5 | * Canvas context type definition 6 | */ 7 | interface CanvasContextType { 8 | // State 9 | nodes: Node[]; 10 | connections: Connection[]; 11 | selectedNode: Node | null; 12 | selectedConnection: Connection | null | "all-disabled"; 13 | view: ViewType; 14 | connectionInProgress: { from: Node; to: Point } | null; 15 | closestNode: Node | undefined; 16 | 17 | // Actions 18 | selectNode: (node: Node | null) => void; 19 | updateNode: (node: Node) => void; 20 | selectConnection: (conn: Connection | null) => void; 21 | createConnection: (fromNode: Node, toNode: Node) => void; 22 | removeConnection: (conn: Connection) => void; 23 | updateView: (newViewData: Partial) => void; 24 | handleConnectionDrag: (node: Node, e: CustomEvent) => void; 25 | handleConnectionEnd: (node: Node) => void; 26 | isConnectionCandidate: (nodeId: string) => boolean; 27 | 28 | // Settings 29 | snapToGrid: boolean; 30 | showGrid: boolean; 31 | } 32 | 33 | /** 34 | * Canvas provider props 35 | */ 36 | interface CanvasProviderProps { 37 | children: React.ReactNode; 38 | nodes: Node[]; 39 | connections: Connection[]; 40 | selectedNode: Node | null; 41 | selectNode: (node: Node | null) => void; 42 | updateNode: (node: Node) => void; 43 | selectConnection: (conn: Connection | null) => void; 44 | selectedConnection: Connection | null | "all-disabled"; 45 | createConnection: (fromNode: Node, toNode: Node) => void; 46 | removeConnection: (conn: Connection) => void; 47 | snapToGrid: boolean; 48 | showGrid: boolean; 49 | view: ViewType; 50 | updateView: (newViewData: Partial) => void; 51 | connectionInProgress: { from: Node; to: Point } | null; 52 | closestNode: Node | undefined; 53 | handleConnectionDrag: (node: Node, e: CustomEvent) => void; 54 | handleConnectionEnd: (node: Node) => void; 55 | isConnectionCandidate: (nodeId: string) => boolean; 56 | } 57 | 58 | // Create the context with a default undefined value 59 | const CanvasContext = createContext(undefined); 60 | 61 | /** 62 | * Canvas Provider component 63 | * Manages canvas state and provides it to all child components 64 | */ 65 | export const CanvasProvider: React.FC = ({ 66 | children, 67 | nodes, 68 | connections, 69 | selectedNode, 70 | selectNode, 71 | updateNode, 72 | selectConnection, 73 | selectedConnection, 74 | createConnection, 75 | removeConnection, 76 | snapToGrid, 77 | showGrid, 78 | view, 79 | updateView, 80 | connectionInProgress, 81 | closestNode, 82 | handleConnectionDrag, 83 | handleConnectionEnd, 84 | isConnectionCandidate, 85 | }) => { 86 | // Memoize the context value to prevent unnecessary re-renders 87 | const contextValue = useMemo( 88 | () => ({ 89 | // State 90 | nodes, 91 | connections, 92 | selectedNode, 93 | selectedConnection, 94 | view, 95 | connectionInProgress, 96 | closestNode, 97 | 98 | // Actions 99 | selectNode, 100 | updateNode, 101 | selectConnection, 102 | createConnection, 103 | removeConnection, 104 | updateView, 105 | handleConnectionDrag, 106 | handleConnectionEnd, 107 | isConnectionCandidate, 108 | 109 | // Settings 110 | snapToGrid, 111 | showGrid, 112 | }), 113 | [ 114 | nodes, 115 | connections, 116 | selectedNode, 117 | selectedConnection, 118 | view, 119 | connectionInProgress, 120 | closestNode, 121 | selectNode, 122 | updateNode, 123 | selectConnection, 124 | createConnection, 125 | removeConnection, 126 | updateView, 127 | handleConnectionDrag, 128 | handleConnectionEnd, 129 | isConnectionCandidate, 130 | snapToGrid, 131 | showGrid, 132 | ], 133 | ); 134 | 135 | return {children}; 136 | }; 137 | 138 | /** 139 | * Custom hook to use the canvas context 140 | * Ensures the context is used within a CanvasProvider 141 | */ 142 | export const useCanvas = (): CanvasContextType => { 143 | const context = useContext(CanvasContext); 144 | 145 | if (context === undefined) { 146 | throw new Error("useCanvas must be used within a CanvasProvider"); 147 | } 148 | 149 | return context; 150 | }; 151 | -------------------------------------------------------------------------------- /src/Canvas/Connections/ConnectionComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import type { Connection } from "../../types"; 3 | import useEventManager from "../Util/useEventManager"; 4 | import styles from "./Connections.module.scss"; 5 | import { findPointOnCurve, makeSVGPath } from "./util"; 6 | import { useCanvas } from "../CanvasContext"; 7 | 8 | type Props = { 9 | connection: Connection; 10 | }; 11 | 12 | /** 13 | * ConnectionComponent renders a bezier curve connection between two nodes 14 | * Includes selection and removal functionality 15 | */ 16 | const ConnectionComponent: React.FC = ({ connection }) => { 17 | const { selectedConnection, selectConnection, removeConnection } = useCanvas(); 18 | 19 | const connectionDom = useRef(null); 20 | const closeDom = useRef(null); 21 | const connectionRef = useRef(connection); 22 | const [error, setError] = useState(null); 23 | 24 | // Use the event manager hooks 25 | const connectionEventManager = useEventManager(connectionDom); 26 | const closeButtonEventManager = useEventManager(closeDom); 27 | 28 | // Set up event handlers 29 | useEffect(() => { 30 | try { 31 | // Main connection tap handler 32 | const connectionHandler = (e: CustomEvent) => { 33 | e.stopPropagation(); 34 | selectConnection(connectionRef.current); 35 | }; 36 | 37 | // Close button tap handler 38 | const closeHandler = (e: CustomEvent) => { 39 | e.stopPropagation(); 40 | removeConnection(connectionRef.current); 41 | }; 42 | 43 | if (connectionEventManager) { 44 | connectionEventManager.onTap(connectionHandler); 45 | } 46 | 47 | if (closeButtonEventManager) { 48 | closeButtonEventManager.onTap(closeHandler); 49 | } 50 | 51 | // Cleanup function to unregister handlers 52 | return () => { 53 | if (connectionEventManager) { 54 | connectionEventManager.offTap(connectionHandler); 55 | } 56 | if (closeButtonEventManager) { 57 | closeButtonEventManager.offTap(closeHandler); 58 | } 59 | }; 60 | } catch (err) { 61 | console.error("Error setting up connection event handlers:", err); 62 | setError("Failed to set up event handlers"); 63 | } 64 | return void 0; 65 | }, [connectionEventManager, closeButtonEventManager, selectConnection, removeConnection]); 66 | 67 | // Keep connectionRef.current updated for event handlers 68 | useEffect(() => { 69 | connectionRef.current = connection; 70 | }, [connection]); 71 | 72 | // If there was an error setting up event handlers, render a simplified version 73 | if (error) { 74 | console.warn(`Connection rendering with reduced functionality: ${error}`); 75 | } 76 | 77 | // Get path data 78 | const start = connection.from.outPortPosition; 79 | const end = connection.to.inPortPosition; 80 | 81 | const { path, c1, c2 } = makeSVGPath(start, end); 82 | const center = findPointOnCurve(0.5, start, c1, c2, end); 83 | 84 | // Determine CSS classes based on selection state 85 | let selected = false; 86 | let unselected = false; 87 | if (selectedConnection === "all-disabled") { 88 | selected = false; 89 | unselected = true; 90 | } else { 91 | selected = Boolean(selectedConnection && selectedConnection.id === connection.id); 92 | unselected = Boolean(selectedConnection && selectedConnection.id !== connection.id); 93 | } 94 | 95 | let className = styles.Connection; 96 | if (selected) { 97 | className = styles.ConnectionSelected; 98 | } 99 | if (unselected) { 100 | className = styles.ConnectionUnselected; 101 | } 102 | 103 | const scale = connection.from.scale; 104 | 105 | return ( 106 | 107 | {/* Touch-friendly hit area */} 108 | 109 | 110 | {/* Visible path */} 111 | 117 | 118 | {/* Close/delete button */} 119 | 120 | 121 | 122 | 123 | 133 | 134 | 135 | 136 | 137 | ); 138 | }; 139 | 140 | export default React.memo(ConnectionComponent); 141 | -------------------------------------------------------------------------------- /src/Workflow/WorkflowData.ts: -------------------------------------------------------------------------------- 1 | import isEqual from "lodash/isEqual"; 2 | import { Connection, Node } from "../Canvas/Models"; 3 | import { Point } from "../Canvas/Models/Point"; 4 | import { generateId } from "../Canvas/Util/Utils"; 5 | import type { WorkflowState, SerializedWorkflow, SerializedNode } from "../types/workflow"; 6 | 7 | export const WorkflowData = { 8 | loadState(jsonWorkflow: SerializedWorkflow, options: { scale?: number }): WorkflowState { 9 | if (!jsonWorkflow) { 10 | return { 11 | id: generateId(), 12 | workflowName: "Workflow", 13 | workflowDescription: "Generic workflow", 14 | nodes: [], 15 | connections: [], 16 | selectedNode: null, 17 | selectedConnection: null, 18 | }; 19 | } 20 | 21 | const state: WorkflowState = { 22 | id: jsonWorkflow.id || generateId(), 23 | workflowName: jsonWorkflow.name || "Workflow", 24 | workflowDescription: jsonWorkflow.description || "Generic workflow", 25 | nodes: [], 26 | connections: [], 27 | selectedNode: null, 28 | selectedConnection: null, 29 | }; 30 | 31 | state.nodes = jsonWorkflow.nodes.map((n) => { 32 | const data = { ...n, scale: options.scale || 1 }; 33 | return new Node(data); 34 | }); 35 | 36 | state.connections = jsonWorkflow.connections 37 | .map((c) => { 38 | const from = state.nodes.find((n) => n.id === c.from); 39 | const to = state.nodes.find((n) => n.id === c.to); 40 | if (!from || !to) return null; 41 | return new Connection(from, to, c.id); 42 | }) 43 | .filter((c): c is Connection => c !== null); 44 | 45 | return state; 46 | }, 47 | 48 | selectNode(state: WorkflowState, node: Node | null): WorkflowState { 49 | return { 50 | ...state, 51 | selectedConnection: "all-disabled", 52 | selectedNode: node, 53 | }; 54 | }, 55 | 56 | selectConnection(state: WorkflowState, conn: Connection | null | "all-disabled"): WorkflowState { 57 | return { ...state, selectedNode: null, selectedConnection: conn }; 58 | }, 59 | 60 | updateNode(state: WorkflowState, node: Node): WorkflowState { 61 | let posChanged = false; 62 | const nodes = state.nodes.map((n: Node) => { 63 | if (n.id === node.id) { 64 | posChanged = !isEqual(n.position, node.position); 65 | return node.clone(); 66 | } 67 | return n; 68 | }); 69 | 70 | const selectedNode = state.selectedNode?.id === node.id ? node : state.selectedNode; 71 | 72 | if (posChanged) { 73 | const connections = state.connections.map((conn) => { 74 | if (conn.from.id === node.id || conn.to.id === node.id) { 75 | const newConn = conn.clone(); 76 | if (conn.from.id === node.id) newConn.from = node; 77 | if (conn.to.id === node.id) newConn.to = node; 78 | return newConn; 79 | } 80 | return conn; 81 | }); 82 | 83 | return { ...state, nodes, connections, selectedNode }; 84 | } 85 | 86 | return { ...state, nodes, selectedNode }; 87 | }, 88 | 89 | removeNode(state: WorkflowState, node: Node): WorkflowState { 90 | const nodes = state.nodes.filter((n) => n.id !== node.id); 91 | const connections = state.connections.filter((c) => c.from.id !== node.id && c.to.id !== node.id); 92 | return { ...state, nodes, connections, selectedNode: null }; 93 | }, 94 | 95 | removeConnection(state: WorkflowState, conn: Connection): WorkflowState { 96 | const connections = state.connections.filter((c) => c.id !== conn.id); 97 | console.log("connections", connections); 98 | return { ...state, connections, selectedConnection: null }; 99 | }, 100 | 101 | createConnection(state: WorkflowState, from: Node, to: Node): WorkflowState { 102 | if (state.connections.some((c) => c.from.id === from.id && c.to.id === to.id)) { 103 | return state; 104 | } 105 | 106 | const connections = [...state.connections, new Connection(from, to)]; 107 | return { ...state, connections }; 108 | }, 109 | 110 | insertNode(state: WorkflowState, data: SerializedNode): WorkflowState { 111 | const node = new Node(data); 112 | node.position = WorkflowData.getNewPosition(state); 113 | 114 | const nodes = [...state.nodes, node]; 115 | return { ...state, nodes }; 116 | }, 117 | 118 | getNewPosition(state: WorkflowState): Point { 119 | const minDistance = 85; 120 | let angle = 0; 121 | let radius = 100; 122 | let c = 0; 123 | 124 | const getNextPoint = () => { 125 | const x = Math.cos(angle) * radius; 126 | const y = Math.sin(angle) * radius; 127 | angle += (Math.PI * 2) / 12; 128 | if (angle > Math.PI * 2) { 129 | angle = 0; 130 | radius += 100; 131 | } 132 | return new Point(x, y); 133 | }; 134 | 135 | let possible = new Point(0, 0); 136 | 137 | while (state.nodes.some((node) => node.position.distanceTo(possible) <= minDistance)) { 138 | possible = getNextPoint(); 139 | c++; 140 | if (c > 100) break; 141 | } 142 | 143 | return possible; 144 | }, 145 | 146 | export(state: WorkflowState): SerializedWorkflow { 147 | return { 148 | id: state.id, 149 | name: state.workflowName, 150 | description: state.workflowDescription, 151 | nodes: state.nodes.map((n) => ({ 152 | name: n.name, 153 | id: n.id, 154 | icon: n.icon, 155 | position: { 156 | x: n.position.x, 157 | y: n.position.y, 158 | }, 159 | })), 160 | connections: state.connections.map((c) => ({ 161 | from: c.from.id, 162 | to: c.to.id, 163 | id: c.id, 164 | })), 165 | }; 166 | }, 167 | }; 168 | -------------------------------------------------------------------------------- /src/Canvas/Node/Node.tsx: -------------------------------------------------------------------------------- 1 | import isEqual from "lodash/isEqual"; 2 | import React, { useEffect, useRef, useState, useCallback } from "react"; 3 | import { Node, Point } from "../Models"; 4 | import useEventManager from "../Util/useEventManager"; 5 | import { NodeTitle } from "./NodeTitle"; 6 | import { Shape } from "./Shape"; 7 | import { ShapeEnd } from "./ShapeEnd"; 8 | import { ShapeStart } from "./ShapeStart"; 9 | 10 | interface NodeProps { 11 | node: Node; 12 | updateNode: (node: Node) => void; 13 | selectedNode: Node | null; 14 | selectNode: (node: Node | null) => void; 15 | canvasView: { 16 | scale: number; 17 | }; 18 | onConnectionDrag: (node: Node, e: CustomEvent) => void; 19 | onConnectionEnd: (node: Node) => void; 20 | snapToGrid: boolean; 21 | connectionCandidate: boolean; 22 | } 23 | 24 | const NodeComponent: React.FC = React.memo( 25 | ({ 26 | node, 27 | updateNode, 28 | selectedNode, 29 | selectNode, 30 | canvasView, 31 | onConnectionDrag, 32 | onConnectionEnd, 33 | snapToGrid = true, 34 | connectionCandidate, 35 | }) => { 36 | const [, forceUpdate] = useState(0); 37 | const nodeRef = useRef(null); 38 | const draggingRef = useRef(false); 39 | 40 | const eventManager = useEventManager(nodeRef, false); 41 | 42 | const snapNodeToGrid = useCallback(() => { 43 | if (!snapToGrid) { 44 | // If not snapping to grid, still force a re-render to update UI 45 | forceUpdate(Math.random()); 46 | return; 47 | } 48 | 49 | const updatedNode = new Node(node); 50 | const hgrid = 100 * 0.5; 51 | const vgrid = 110 * 0.75; 52 | const target = { 53 | x: Math.round(updatedNode.position.x / hgrid) * hgrid, 54 | y: Math.round(updatedNode.position.y / vgrid) * vgrid, 55 | }; 56 | 57 | updatedNode.position = new Point(target.x, target.y); 58 | updateNode(updatedNode); 59 | 60 | // // Only update if the position would actually change 61 | // if (updatedNode.position.x !== target.x || updatedNode.position.y !== target.y) { 62 | // updatedNode.position = new Point(target.x, target.y); 63 | // updateNode(updatedNode); 64 | // } else { 65 | // // Still force a re-render to update UI state (cursor, etc.) 66 | // forceUpdate(Math.random()); 67 | // } 68 | }, [node, snapToGrid, updateNode]); 69 | 70 | const handleTap = useCallback( 71 | (e: Event) => { 72 | e.stopPropagation(); 73 | snapNodeToGrid(); 74 | 75 | // Always select the node on tap, the dragging state is reset in handleMoveEnd 76 | const clonedNode = new Node(node); 77 | selectNode(clonedNode); 78 | }, 79 | [node, selectNode, snapNodeToGrid], 80 | ); 81 | 82 | const handleMove = useCallback( 83 | (e: Event) => { 84 | e.stopPropagation(); 85 | draggingRef.current = true; 86 | 87 | const clonedNode = node.clone(); 88 | const scaleFactor = canvasView?.scale || 1; 89 | const customEvent = e as CustomEvent; 90 | 91 | clonedNode.position = new Point( 92 | clonedNode.position.x + (customEvent.detail.delta.x * 1) / scaleFactor, 93 | clonedNode.position.y + (customEvent.detail.delta.y * 1) / scaleFactor, 94 | ); 95 | 96 | updateNode(clonedNode); 97 | }, 98 | [node, canvasView, updateNode], 99 | ); 100 | 101 | const handleMoveEnd = useCallback(() => { 102 | // Reset dragging state 103 | draggingRef.current = false; 104 | // Apply grid snapping 105 | snapNodeToGrid(); 106 | 107 | // Use setTimeout to ensure the dragging state is fully reset 108 | // This allows the component to receive tap events after dragging 109 | setTimeout(() => { 110 | forceUpdate(Math.random()); 111 | }, 0); 112 | }, [snapNodeToGrid]); 113 | 114 | useEffect(() => { 115 | eventManager.onTap(handleTap); 116 | eventManager.onMove(handleMove); 117 | eventManager.onMoveEnd(handleMoveEnd); 118 | 119 | // Cleanup function to unregister the events 120 | return () => { 121 | eventManager.offTap(handleTap); 122 | eventManager.offMove(handleMove); 123 | eventManager.offMoveEnd(handleMoveEnd); 124 | }; 125 | }, [eventManager, handleTap, handleMove, handleMoveEnd]); 126 | 127 | // Only run snapToGrid on first mount or when relevant props change 128 | // biome-ignore lint/correctness/useExhaustiveDependencies: 129 | useEffect(() => { 130 | if (snapToGrid) { 131 | // Check if position is already aligned to grid to avoid loops 132 | const hgrid = 100 * 0.5; 133 | const vgrid = 110 * 0.75; 134 | 135 | const currentX = node.position.x; 136 | const currentY = node.position.y; 137 | 138 | const gridX = Math.round(currentX / hgrid) * hgrid; 139 | const gridY = Math.round(currentY / vgrid) * vgrid; 140 | 141 | // Only update if not already aligned to grid 142 | if (currentX !== gridX || currentY !== gridY) { 143 | const updatedNode = node.clone(); 144 | updatedNode.position = new Point(gridX, gridY); 145 | updateNode(updatedNode); 146 | } 147 | } 148 | }, [node.id]); // Only run on node id change, not on position changes 149 | 150 | const getTransform = () => { 151 | return `translate(${node.position.x},${node.position.y})`; 152 | }; 153 | 154 | const selected = selectedNode ? selectedNode.id === node.id : false; 155 | const unselected = selectedNode ? selectedNode.id !== node.id : false; 156 | 157 | // Set cursor style based on drag state 158 | // We use a ref value to determine the current drag state 159 | const dragStyle = { 160 | cursor: "grab", // Default cursor shows grab (can drag) 161 | }; 162 | 163 | let ShapeComponent = Shape; 164 | if (node.id === "START") { 165 | ShapeComponent = ShapeStart; 166 | } 167 | if (node.id === "END") { 168 | ShapeComponent = ShapeEnd; 169 | } 170 | 171 | return ( 172 | 173 | onConnectionDrag(node, e)} 179 | onConnectionEnd={(node) => onConnectionEnd(node)} 180 | connectionCandidate={connectionCandidate} 181 | /> 182 | {canvasView.scale > 0.7 && } 183 | 184 | ); 185 | }, 186 | (prevProps, nextProps) => { 187 | // Custom comparison logic 188 | return isEqual(prevProps, nextProps); 189 | }, 190 | ); 191 | 192 | NodeComponent.displayName = "NodeComponent"; 193 | 194 | export default NodeComponent; 195 | -------------------------------------------------------------------------------- /src/Canvas/Util/useEventManager.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useCallback } from "react"; 2 | import type { SerializedPoint } from "../../Workflow/Types"; 3 | 4 | // Event Types 5 | export type EventHandler = (e: Event) => void; 6 | export type EventType = "tap" | "move" | "moveEnd" | "pinch"; 7 | 8 | // Custom Event Detail Types 9 | export interface TapEventDetail { 10 | x: number; 11 | y: number; 12 | } 13 | 14 | export interface MoveEventDetail { 15 | x: number; 16 | y: number; 17 | delta: SerializedPoint; 18 | } 19 | 20 | export interface PinchEventDetail { 21 | x: number; 22 | y: number; 23 | scale: number; 24 | delta: SerializedPoint; 25 | } 26 | 27 | type TinyTouch = { 28 | identifier: number; 29 | pageX: number; 30 | pageY: number; 31 | }; 32 | 33 | interface EventManagerState { 34 | currentTouches: TinyTouch[]; 35 | startDistance: number; 36 | scale: number; 37 | previousScale: number; 38 | isClick: boolean; 39 | isDragging: boolean; 40 | isMouseDown: boolean; 41 | dragTravelDistance: number; 42 | previousCenter: SerializedPoint | null; 43 | touchIdentifier: number | null; 44 | previousLocation: SerializedPoint | null; 45 | delta: SerializedPoint | null; 46 | previousDelta: SerializedPoint | null; 47 | handlers: Record; 48 | } 49 | 50 | export interface EventManagerApi { 51 | onTap: (handler: EventHandler) => void; 52 | offTap: (handler: EventHandler) => void; 53 | onMove: (handler: EventHandler) => void; 54 | offMove: (handler: EventHandler) => void; 55 | onMoveEnd: (handler: EventHandler) => void; 56 | offMoveEnd: (handler: EventHandler) => void; 57 | onPinch: (handler: EventHandler) => void; 58 | offPinch: (handler: EventHandler) => void; 59 | } 60 | 61 | /** 62 | * React hook for managing interactive events on elements (tap, move, pinch) 63 | * @param elementRef - React ref to the DOM element to attach events to 64 | * @param debug - Whether to log debug information 65 | * @returns Object with methods to attach event handlers 66 | */ 67 | function useEventManager(elementRef: React.RefObject, debug = false): EventManagerApi { 68 | // Use a ref to hold mutable state that persists between renders 69 | // but doesn't cause re-renders when it changes 70 | const stateRef = useRef({ 71 | currentTouches: [], 72 | startDistance: 0, 73 | scale: 1, 74 | previousScale: 1, 75 | isClick: false, 76 | isDragging: false, 77 | isMouseDown: false, 78 | previousCenter: null, 79 | touchIdentifier: null, 80 | previousLocation: null, 81 | delta: null, 82 | previousDelta: null, 83 | dragTravelDistance: 0, 84 | handlers: { 85 | tap: [], 86 | move: [], 87 | moveEnd: [], 88 | pinch: [], 89 | }, 90 | }); 91 | 92 | /** 93 | * Helper to log debug information 94 | */ 95 | const debugEvent = useCallback( 96 | (e: TouchEvent | MouseEvent) => { 97 | if (debug && elementRef.current) { 98 | const elementName = elementRef.current.id || elementRef.current.tagName; 99 | console.log(`${elementName} event:`, e); 100 | } 101 | }, 102 | [debug, elementRef], 103 | ); 104 | 105 | /** 106 | * Registers an event handler for a specific event type 107 | */ 108 | const addHandler = useCallback((eventType: EventType, fn: EventHandler) => { 109 | stateRef.current.handlers[eventType].push(fn); 110 | }, []); 111 | 112 | /** 113 | * Removes an event handler from a specific event type 114 | */ 115 | const removeHandler = useCallback((eventType: EventType, fn: EventHandler) => { 116 | const handlers = stateRef.current.handlers[eventType]; 117 | const index = handlers.indexOf(fn); 118 | if (index !== -1) { 119 | handlers.splice(index, 1); 120 | } 121 | }, []); 122 | 123 | /** 124 | * Triggers all handlers for a specific event type 125 | */ 126 | const callHandler = useCallback((eventType: EventType, event: CustomEvent) => { 127 | if (stateRef.current.handlers[eventType].length) { 128 | for (const fn of stateRef.current.handlers[eventType]) { 129 | fn(event); 130 | } 131 | } 132 | }, []); 133 | 134 | /** 135 | * Calculates distance between two touch points 136 | */ 137 | const calculateDistance = useCallback((touch1: Touch | TinyTouch, touch2: Touch | TinyTouch) => { 138 | const first = { x: touch1.pageX, y: touch1.pageY }; 139 | const second = { x: touch2.pageX, y: touch2.pageY }; 140 | const a = Math.abs(first.x - second.x); 141 | const b = Math.abs(first.y - second.y); 142 | return Math.sqrt(a ** 2 + b ** 2); 143 | }, []); 144 | 145 | /** 146 | * Sets the starting distance for pinch calculations 147 | */ 148 | const setStartDistance = useCallback(() => { 149 | const { currentTouches } = stateRef.current; 150 | if (currentTouches.length >= 2) { 151 | const [first, second] = currentTouches; 152 | stateRef.current.startDistance = calculateDistance(first, second); 153 | } 154 | }, [calculateDistance]); 155 | 156 | /** 157 | * Extract relevant touch/mouse event details 158 | */ 159 | const getEvent = useCallback((e: TouchEvent | MouseEvent): Touch | MouseEvent | null => { 160 | const { touchIdentifier } = stateRef.current; 161 | 162 | if (window.TouchEvent && e instanceof TouchEvent) { 163 | if (touchIdentifier === null) return null; 164 | 165 | let event = Array.from(e.touches).find((t: Touch) => t.identifier === touchIdentifier); 166 | 167 | if (!event) { 168 | event = Array.from(e.changedTouches).find((t: Touch) => t.identifier === touchIdentifier); 169 | } 170 | 171 | return event || null; 172 | } 173 | return e as MouseEvent; 174 | }, []); 175 | 176 | /** 177 | * Handle pinch start event 178 | */ 179 | const handlePinchStart = useCallback( 180 | (e: TouchEvent) => { 181 | e.preventDefault(); 182 | const touches = e.changedTouches; 183 | 184 | for (const touch of Array.from(touches)) { 185 | const { identifier, pageX, pageY } = touch; 186 | stateRef.current.currentTouches.push({ identifier, pageX, pageY }); 187 | } 188 | 189 | setStartDistance(); 190 | }, 191 | [setStartDistance], 192 | ); 193 | 194 | /** 195 | * Handle pinch end event 196 | */ 197 | const handlePinchEnd = useCallback( 198 | (e: TouchEvent) => { 199 | e.preventDefault(); 200 | 201 | for (const finishedTouch of Array.from(e.changedTouches)) { 202 | const idx = stateRef.current.currentTouches.findIndex( 203 | (t: TinyTouch) => t.identifier === finishedTouch.identifier, 204 | ); 205 | 206 | if (idx !== -1) { 207 | stateRef.current.currentTouches.splice(idx, 1); 208 | } 209 | } 210 | 211 | setStartDistance(); 212 | 213 | if (stateRef.current.currentTouches.length < 2) { 214 | stateRef.current.previousScale = stateRef.current.scale; 215 | } 216 | 217 | stateRef.current.previousCenter = null; 218 | }, 219 | [setStartDistance], 220 | ); 221 | 222 | /** 223 | * Handle pinch cancel event 224 | */ 225 | const handlePinchCancel = useCallback((e: TouchEvent) => { 226 | e.preventDefault(); 227 | // Reset state similar to pinch end 228 | stateRef.current.currentTouches = []; 229 | stateRef.current.previousCenter = null; 230 | }, []); 231 | 232 | /** 233 | * Handle pinch move event 234 | */ 235 | const handlePinchMove = useCallback( 236 | (e: TouchEvent) => { 237 | e.preventDefault(); 238 | if (e.changedTouches.length < 2) return; 239 | 240 | const { currentTouches, previousScale, previousCenter } = stateRef.current; 241 | const touches: Touch[] = Array.from(e.touches); 242 | 243 | // Find the first two touch points based on our stored identifiers 244 | if (currentTouches.length < 2) return; 245 | 246 | const first = touches.find((t: Touch) => t.identifier === currentTouches[0].identifier); 247 | 248 | const second = touches.find((t: Touch) => t.identifier === currentTouches[1].identifier); 249 | 250 | if (!first || !second) return; 251 | 252 | const newDistance = calculateDistance(first, second); 253 | const newScale = (previousScale * newDistance) / stateRef.current.startDistance; 254 | stateRef.current.scale = newScale; 255 | 256 | const center: SerializedPoint = { 257 | x: (first.pageX + second.pageX) / 2, 258 | y: (first.pageY + second.pageY) / 2, 259 | }; 260 | 261 | if (!previousCenter) { 262 | stateRef.current.previousCenter = center; 263 | return; 264 | } 265 | 266 | const customEvent = new CustomEvent("pinch", { 267 | detail: { 268 | x: center.x, 269 | y: center.y, 270 | scale: newScale, 271 | delta: { 272 | x: center.x - previousCenter.x, 273 | y: center.y - previousCenter.y, 274 | }, 275 | }, 276 | }); 277 | 278 | callHandler("pinch", customEvent); 279 | stateRef.current.previousCenter = center; 280 | }, 281 | [calculateDistance, callHandler], 282 | ); 283 | 284 | /** 285 | * Handle mousedown/touchstart event 286 | */ 287 | const handleMouseDown = useCallback( 288 | (e: TouchEvent | MouseEvent) => { 289 | e.preventDefault(); 290 | e.stopPropagation(); 291 | debugEvent(e); 292 | 293 | // Ignore multiple touches 294 | if (window.TouchEvent && e instanceof TouchEvent && e.touches.length > 1) return; 295 | 296 | // Setup global event listeners 297 | if ("ontouchstart" in window) { 298 | window.addEventListener("touchmove", handleMouseMove); 299 | window.addEventListener("touchend", handleMouseUp); 300 | } else { 301 | window.addEventListener("mousemove", handleMouseMove); 302 | window.addEventListener("mouseup", handleMouseUp); 303 | } 304 | 305 | stateRef.current.isMouseDown = true; 306 | stateRef.current.isClick = true; 307 | stateRef.current.dragTravelDistance = 0; 308 | if (window.TouchEvent && e instanceof TouchEvent && e.changedTouches?.[0]) { 309 | stateRef.current.touchIdentifier = e.changedTouches[0].identifier; 310 | } 311 | }, 312 | [debugEvent], 313 | ); 314 | 315 | /** 316 | * Handle mousemove/touchmove event 317 | */ 318 | const handleMouseMove = useCallback( 319 | (e: TouchEvent | MouseEvent) => { 320 | e.preventDefault(); 321 | e.stopPropagation(); 322 | 323 | if (window.TouchEvent && e instanceof TouchEvent && e.touches.length > 1) return; 324 | if (!stateRef.current.isMouseDown) return; 325 | 326 | debugEvent(e); 327 | 328 | const touchOrClick = getEvent(e); 329 | if (!touchOrClick) return; 330 | 331 | const { previousLocation } = stateRef.current; 332 | 333 | if (!previousLocation) { 334 | stateRef.current.previousLocation = { 335 | x: touchOrClick.pageX, 336 | y: touchOrClick.pageY, 337 | }; 338 | return; 339 | } 340 | 341 | const delta = { 342 | x: touchOrClick.pageX - previousLocation.x, 343 | y: touchOrClick.pageY - previousLocation.y, 344 | }; 345 | 346 | stateRef.current.delta = delta; 347 | stateRef.current.dragTravelDistance += Math.abs(Math.sqrt(delta.x ** 2 + delta.y ** 2)); 348 | 349 | // if (Math.abs(delta.x) <= 2 && Math.abs(delta.y) <= 2) { 350 | // stateRef.current.isClick = true; 351 | 352 | // return; 353 | // } 354 | 355 | stateRef.current.isDragging = true; 356 | stateRef.current.isClick = false; 357 | 358 | const customEvent = new CustomEvent("move", { 359 | detail: { 360 | x: touchOrClick.pageX, 361 | y: touchOrClick.pageY, 362 | delta, 363 | }, 364 | }); 365 | 366 | stateRef.current.previousLocation = { 367 | x: touchOrClick.pageX, 368 | y: touchOrClick.pageY, 369 | }; 370 | 371 | callHandler("move", customEvent); 372 | }, 373 | [debugEvent, getEvent, callHandler], 374 | ); 375 | 376 | /** 377 | * Handle mouseup/touchend event 378 | */ 379 | const handleMouseUp = useCallback( 380 | (e: TouchEvent | MouseEvent) => { 381 | e.preventDefault(); 382 | e.stopPropagation(); 383 | // Remove global event listeners 384 | if ("ontouchstart" in window) { 385 | window.removeEventListener("touchmove", handleMouseMove); 386 | window.removeEventListener("touchend", handleMouseUp); 387 | } else { 388 | window.removeEventListener("mousemove", handleMouseMove); 389 | window.removeEventListener("mouseup", handleMouseUp); 390 | } 391 | 392 | // Ignore 2nd, 3rd, etc. fingers' 'touchend' 393 | if (window.TouchEvent && e instanceof TouchEvent && e.touches.length > 1) return; 394 | if (!stateRef.current.isMouseDown) return; 395 | 396 | debugEvent(e); 397 | 398 | // Reset tracking state 399 | stateRef.current.isMouseDown = false; 400 | stateRef.current.previousLocation = null; 401 | 402 | const event = getEvent(e); 403 | if (!event) return; 404 | 405 | const { isDragging, delta, dragTravelDistance } = stateRef.current; 406 | const isClick = dragTravelDistance <= 10; 407 | 408 | // Handle tap event 409 | if (isClick) { 410 | const customEvent = new CustomEvent("tap", { 411 | detail: { 412 | x: event.pageX, 413 | y: event.pageY, 414 | }, 415 | }); 416 | callHandler("tap", customEvent); 417 | } 418 | 419 | // Handle moveEnd event 420 | if (isDragging && !isClick && delta) { 421 | const customEvent = new CustomEvent("moveEnd", { 422 | detail: { 423 | x: event.pageX, 424 | y: event.pageY, 425 | delta, 426 | }, 427 | }); 428 | callHandler("moveEnd", customEvent); 429 | } 430 | 431 | // Reset dragging state 432 | stateRef.current.delta = stateRef.current.previousDelta = { x: 0, y: 0 }; 433 | stateRef.current.isClick = false; 434 | stateRef.current.isDragging = false; 435 | }, 436 | [debugEvent, getEvent, callHandler, handleMouseMove], 437 | ); 438 | 439 | /** 440 | * Setup event listeners when component mounts 441 | * and remove them when component unmounts 442 | */ 443 | useEffect(() => { 444 | const element = elementRef.current; 445 | if (!element) return; 446 | 447 | // Setup basic mouse/touch events 448 | const setupBasicEvents = () => { 449 | if ("ontouchstart" in window) { 450 | element.addEventListener("touchstart", handleMouseDown); 451 | } else { 452 | element.addEventListener("mousedown", handleMouseDown); 453 | } 454 | }; 455 | 456 | // Setup pinch events 457 | const setupPinchEvents = () => { 458 | if ("ontouchstart" in window) { 459 | element.addEventListener("touchstart", handlePinchStart); 460 | element.addEventListener("touchend", handlePinchEnd); 461 | element.addEventListener("touchcancel", handlePinchCancel); 462 | element.addEventListener("touchmove", handlePinchMove); 463 | } 464 | }; 465 | 466 | // Setup initial events 467 | setupBasicEvents(); 468 | 469 | // Set up debug name 470 | if (debug) { 471 | } 472 | 473 | // Cleanup function 474 | return () => { 475 | // Remove all event listeners 476 | if ("ontouchstart" in window) { 477 | element.removeEventListener("touchstart", handleMouseDown); 478 | element.removeEventListener("touchstart", handlePinchStart); 479 | element.removeEventListener("touchend", handlePinchEnd); 480 | element.removeEventListener("touchcancel", handlePinchCancel); 481 | element.removeEventListener("touchmove", handlePinchMove); 482 | window.removeEventListener("touchmove", handleMouseMove); 483 | window.removeEventListener("touchend", handleMouseUp); 484 | } else { 485 | element.removeEventListener("mousedown", handleMouseDown); 486 | window.removeEventListener("mousemove", handleMouseMove); 487 | window.removeEventListener("mouseup", handleMouseUp); 488 | } 489 | }; 490 | }, [ 491 | elementRef, 492 | debug, 493 | handleMouseDown, 494 | handleMouseMove, 495 | handleMouseUp, 496 | handlePinchStart, 497 | handlePinchEnd, 498 | handlePinchCancel, 499 | handlePinchMove, 500 | ]); 501 | 502 | // API for registering event handlers 503 | const onTap = useCallback( 504 | (fn: EventHandler) => { 505 | addHandler("tap", fn); 506 | }, 507 | [addHandler], 508 | ); 509 | 510 | const offTap = useCallback( 511 | (fn: EventHandler) => { 512 | removeHandler("tap", fn); 513 | }, 514 | [removeHandler], 515 | ); 516 | 517 | const onMove = useCallback( 518 | (fn: EventHandler) => { 519 | addHandler("move", fn); 520 | }, 521 | [addHandler], 522 | ); 523 | 524 | const offMove = useCallback( 525 | (fn: EventHandler) => { 526 | removeHandler("move", fn); 527 | }, 528 | [removeHandler], 529 | ); 530 | 531 | const onMoveEnd = useCallback( 532 | (fn: EventHandler) => { 533 | addHandler("moveEnd", fn); 534 | }, 535 | [addHandler], 536 | ); 537 | 538 | const offMoveEnd = useCallback( 539 | (fn: EventHandler) => { 540 | removeHandler("moveEnd", fn); 541 | }, 542 | [removeHandler], 543 | ); 544 | 545 | const onPinch = useCallback( 546 | (fn: EventHandler) => { 547 | // Setup pinch events if not already set up 548 | if (elementRef.current && "ontouchstart" in window) { 549 | elementRef.current.addEventListener("touchstart", handlePinchStart); 550 | elementRef.current.addEventListener("touchend", handlePinchEnd); 551 | elementRef.current.addEventListener("touchcancel", handlePinchCancel); 552 | elementRef.current.addEventListener("touchmove", handlePinchMove); 553 | } 554 | addHandler("pinch", fn); 555 | }, 556 | [elementRef, addHandler, handlePinchStart, handlePinchEnd, handlePinchCancel, handlePinchMove], 557 | ); 558 | 559 | const offPinch = useCallback( 560 | (fn: EventHandler) => { 561 | removeHandler("pinch", fn); 562 | }, 563 | [removeHandler], 564 | ); 565 | 566 | return { 567 | onTap, 568 | offTap, 569 | onMove, 570 | offMove, 571 | onMoveEnd, 572 | offMoveEnd, 573 | onPinch, 574 | offPinch, 575 | }; 576 | } 577 | 578 | export default useEventManager; 579 | -------------------------------------------------------------------------------- /example/yarn.lock: -------------------------------------------------------------------------------- 1 | # This file is generated by running "yarn install" inside your project. 2 | # Manual changes might be lost - proceed with caution! 3 | 4 | __metadata: 5 | version: 8 6 | cacheKey: 10c0 7 | 8 | "@ampproject/remapping@npm:^2.2.0": 9 | version: 2.3.0 10 | resolution: "@ampproject/remapping@npm:2.3.0" 11 | dependencies: 12 | "@jridgewell/gen-mapping": "npm:^0.3.5" 13 | "@jridgewell/trace-mapping": "npm:^0.3.24" 14 | checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed 15 | languageName: node 16 | linkType: hard 17 | 18 | "@babel/code-frame@npm:^7.26.2": 19 | version: 7.26.2 20 | resolution: "@babel/code-frame@npm:7.26.2" 21 | dependencies: 22 | "@babel/helper-validator-identifier": "npm:^7.25.9" 23 | js-tokens: "npm:^4.0.0" 24 | picocolors: "npm:^1.0.0" 25 | checksum: 10c0/7d79621a6849183c415486af99b1a20b84737e8c11cd55b6544f688c51ce1fd710e6d869c3dd21232023da272a79b91efb3e83b5bc2dc65c1187c5fcd1b72ea8 26 | languageName: node 27 | linkType: hard 28 | 29 | "@babel/compat-data@npm:^7.26.5": 30 | version: 7.26.8 31 | resolution: "@babel/compat-data@npm:7.26.8" 32 | checksum: 10c0/66408a0388c3457fff1c2f6c3a061278dd7b3d2f0455ea29bb7b187fa52c60ae8b4054b3c0a184e21e45f0eaac63cf390737bc7504d1f4a088a6e7f652c068ca 33 | languageName: node 34 | linkType: hard 35 | 36 | "@babel/core@npm:^7.26.0": 37 | version: 7.26.10 38 | resolution: "@babel/core@npm:7.26.10" 39 | dependencies: 40 | "@ampproject/remapping": "npm:^2.2.0" 41 | "@babel/code-frame": "npm:^7.26.2" 42 | "@babel/generator": "npm:^7.26.10" 43 | "@babel/helper-compilation-targets": "npm:^7.26.5" 44 | "@babel/helper-module-transforms": "npm:^7.26.0" 45 | "@babel/helpers": "npm:^7.26.10" 46 | "@babel/parser": "npm:^7.26.10" 47 | "@babel/template": "npm:^7.26.9" 48 | "@babel/traverse": "npm:^7.26.10" 49 | "@babel/types": "npm:^7.26.10" 50 | convert-source-map: "npm:^2.0.0" 51 | debug: "npm:^4.1.0" 52 | gensync: "npm:^1.0.0-beta.2" 53 | json5: "npm:^2.2.3" 54 | semver: "npm:^6.3.1" 55 | checksum: 10c0/e046e0e988ab53841b512ee9d263ca409f6c46e2a999fe53024688b92db394346fa3aeae5ea0866331f62133982eee05a675d22922a4603c3f603aa09a581d62 56 | languageName: node 57 | linkType: hard 58 | 59 | "@babel/generator@npm:^7.26.10": 60 | version: 7.26.10 61 | resolution: "@babel/generator@npm:7.26.10" 62 | dependencies: 63 | "@babel/parser": "npm:^7.26.10" 64 | "@babel/types": "npm:^7.26.10" 65 | "@jridgewell/gen-mapping": "npm:^0.3.5" 66 | "@jridgewell/trace-mapping": "npm:^0.3.25" 67 | jsesc: "npm:^3.0.2" 68 | checksum: 10c0/88b3b3ea80592fc89349c4e1a145e1386e4042866d2507298adf452bf972f68d13bf699a845e6ab8c028bd52c2247013eb1221b86e1db5c9779faacba9c4b10e 69 | languageName: node 70 | linkType: hard 71 | 72 | "@babel/helper-compilation-targets@npm:^7.26.5": 73 | version: 7.26.5 74 | resolution: "@babel/helper-compilation-targets@npm:7.26.5" 75 | dependencies: 76 | "@babel/compat-data": "npm:^7.26.5" 77 | "@babel/helper-validator-option": "npm:^7.25.9" 78 | browserslist: "npm:^4.24.0" 79 | lru-cache: "npm:^5.1.1" 80 | semver: "npm:^6.3.1" 81 | checksum: 10c0/9da5c77e5722f1a2fcb3e893049a01d414124522bbf51323bb1a0c9dcd326f15279836450fc36f83c9e8a846f3c40e88be032ed939c5a9840922bed6073edfb4 82 | languageName: node 83 | linkType: hard 84 | 85 | "@babel/helper-module-imports@npm:^7.25.9": 86 | version: 7.25.9 87 | resolution: "@babel/helper-module-imports@npm:7.25.9" 88 | dependencies: 89 | "@babel/traverse": "npm:^7.25.9" 90 | "@babel/types": "npm:^7.25.9" 91 | checksum: 10c0/078d3c2b45d1f97ffe6bb47f61961be4785d2342a4156d8b42c92ee4e1b7b9e365655dd6cb25329e8fe1a675c91eeac7e3d04f0c518b67e417e29d6e27b6aa70 92 | languageName: node 93 | linkType: hard 94 | 95 | "@babel/helper-module-transforms@npm:^7.26.0": 96 | version: 7.26.0 97 | resolution: "@babel/helper-module-transforms@npm:7.26.0" 98 | dependencies: 99 | "@babel/helper-module-imports": "npm:^7.25.9" 100 | "@babel/helper-validator-identifier": "npm:^7.25.9" 101 | "@babel/traverse": "npm:^7.25.9" 102 | peerDependencies: 103 | "@babel/core": ^7.0.0 104 | checksum: 10c0/ee111b68a5933481d76633dad9cdab30c41df4479f0e5e1cc4756dc9447c1afd2c9473b5ba006362e35b17f4ebddd5fca090233bef8dfc84dca9d9127e56ec3a 105 | languageName: node 106 | linkType: hard 107 | 108 | "@babel/helper-plugin-utils@npm:^7.25.9": 109 | version: 7.26.5 110 | resolution: "@babel/helper-plugin-utils@npm:7.26.5" 111 | checksum: 10c0/cdaba71d4b891aa6a8dfbe5bac2f94effb13e5fa4c2c487667fdbaa04eae059b78b28d85a885071f45f7205aeb56d16759e1bed9c118b94b16e4720ef1ab0f65 112 | languageName: node 113 | linkType: hard 114 | 115 | "@babel/helper-string-parser@npm:^7.25.9": 116 | version: 7.25.9 117 | resolution: "@babel/helper-string-parser@npm:7.25.9" 118 | checksum: 10c0/7244b45d8e65f6b4338a6a68a8556f2cb161b782343e97281a5f2b9b93e420cad0d9f5773a59d79f61d0c448913d06f6a2358a87f2e203cf112e3c5b53522ee6 119 | languageName: node 120 | linkType: hard 121 | 122 | "@babel/helper-validator-identifier@npm:^7.25.9": 123 | version: 7.25.9 124 | resolution: "@babel/helper-validator-identifier@npm:7.25.9" 125 | checksum: 10c0/4fc6f830177b7b7e887ad3277ddb3b91d81e6c4a24151540d9d1023e8dc6b1c0505f0f0628ae653601eb4388a8db45c1c14b2c07a9173837aef7e4116456259d 126 | languageName: node 127 | linkType: hard 128 | 129 | "@babel/helper-validator-option@npm:^7.25.9": 130 | version: 7.25.9 131 | resolution: "@babel/helper-validator-option@npm:7.25.9" 132 | checksum: 10c0/27fb195d14c7dcb07f14e58fe77c44eea19a6a40a74472ec05c441478fa0bb49fa1c32b2d64be7a38870ee48ef6601bdebe98d512f0253aea0b39756c4014f3e 133 | languageName: node 134 | linkType: hard 135 | 136 | "@babel/helpers@npm:^7.26.10": 137 | version: 7.26.10 138 | resolution: "@babel/helpers@npm:7.26.10" 139 | dependencies: 140 | "@babel/template": "npm:^7.26.9" 141 | "@babel/types": "npm:^7.26.10" 142 | checksum: 10c0/f99e1836bcffce96db43158518bb4a24cf266820021f6461092a776cba2dc01d9fc8b1b90979d7643c5c2ab7facc438149064463a52dd528b21c6ab32509784f 143 | languageName: node 144 | linkType: hard 145 | 146 | "@babel/parser@npm:^7.1.0": 147 | version: 7.8.8 148 | resolution: "@babel/parser@npm:7.8.8" 149 | bin: 150 | parser: ./bin/babel-parser.js 151 | checksum: 10c0/3da7f95ec3360332ebc71ec4ec0935432af047bff1a8bfe008b7ac209878740b409558fc061b927a28f3503f50e244a06622613ecb993cf3638f0d6fe722ec18 152 | languageName: node 153 | linkType: hard 154 | 155 | "@babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.26.9": 156 | version: 7.26.10 157 | resolution: "@babel/parser@npm:7.26.10" 158 | dependencies: 159 | "@babel/types": "npm:^7.26.10" 160 | bin: 161 | parser: ./bin/babel-parser.js 162 | checksum: 10c0/c47f5c0f63cd12a663e9dc94a635f9efbb5059d98086a92286d7764357c66bceba18ccbe79333e01e9be3bfb8caba34b3aaebfd8e62c3d5921c8cf907267be75 163 | languageName: node 164 | linkType: hard 165 | 166 | "@babel/plugin-transform-react-jsx-self@npm:^7.25.9": 167 | version: 7.25.9 168 | resolution: "@babel/plugin-transform-react-jsx-self@npm:7.25.9" 169 | dependencies: 170 | "@babel/helper-plugin-utils": "npm:^7.25.9" 171 | peerDependencies: 172 | "@babel/core": ^7.0.0-0 173 | checksum: 10c0/ce0e289f6af93d7c4dc6b385512199c5bb138ae61507b4d5117ba88b6a6b5092f704f1bdf80080b7d69b1b8c36649f2a0b250e8198667d4d30c08bbb1546bd99 174 | languageName: node 175 | linkType: hard 176 | 177 | "@babel/plugin-transform-react-jsx-source@npm:^7.25.9": 178 | version: 7.25.9 179 | resolution: "@babel/plugin-transform-react-jsx-source@npm:7.25.9" 180 | dependencies: 181 | "@babel/helper-plugin-utils": "npm:^7.25.9" 182 | peerDependencies: 183 | "@babel/core": ^7.0.0-0 184 | checksum: 10c0/fc9ee08efc9be7cbd2cc6788bbf92579adf3cab37912481f1b915221be3d22b0613b5b36a721df5f4c0ab65efe8582fcf8673caab83e6e1ce4cc04ceebf57dfa 185 | languageName: node 186 | linkType: hard 187 | 188 | "@babel/template@npm:^7.26.9": 189 | version: 7.26.9 190 | resolution: "@babel/template@npm:7.26.9" 191 | dependencies: 192 | "@babel/code-frame": "npm:^7.26.2" 193 | "@babel/parser": "npm:^7.26.9" 194 | "@babel/types": "npm:^7.26.9" 195 | checksum: 10c0/019b1c4129cc01ad63e17529089c2c559c74709d225f595eee017af227fee11ae8a97a6ab19ae6768b8aa22d8d75dcb60a00b28f52e9fa78140672d928bc1ae9 196 | languageName: node 197 | linkType: hard 198 | 199 | "@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10": 200 | version: 7.26.10 201 | resolution: "@babel/traverse@npm:7.26.10" 202 | dependencies: 203 | "@babel/code-frame": "npm:^7.26.2" 204 | "@babel/generator": "npm:^7.26.10" 205 | "@babel/parser": "npm:^7.26.10" 206 | "@babel/template": "npm:^7.26.9" 207 | "@babel/types": "npm:^7.26.10" 208 | debug: "npm:^4.3.1" 209 | globals: "npm:^11.1.0" 210 | checksum: 10c0/4e86bb4e3c30a6162bb91df86329df79d96566c3e2d9ccba04f108c30473a3a4fd360d9990531493d90f6a12004f10f616bf9b9229ca30c816b708615e9de2ac 211 | languageName: node 212 | linkType: hard 213 | 214 | "@babel/types@npm:^7.0.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.8.3": 215 | version: 7.8.7 216 | resolution: "@babel/types@npm:7.8.7" 217 | dependencies: 218 | esutils: "npm:^2.0.2" 219 | lodash: "npm:^4.17.13" 220 | to-fast-properties: "npm:^2.0.0" 221 | checksum: 10c0/e3d8c25f23836e5ad7dc70ec7c631c2cbc72f8ca32b3c2e48c16e5a7aee812a5455807c0f487fbc892afb41114298fb053bdf119c5ed9c61e205a60088996c21 222 | languageName: node 223 | linkType: hard 224 | 225 | "@babel/types@npm:^7.20.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.26.9": 226 | version: 7.26.10 227 | resolution: "@babel/types@npm:7.26.10" 228 | dependencies: 229 | "@babel/helper-string-parser": "npm:^7.25.9" 230 | "@babel/helper-validator-identifier": "npm:^7.25.9" 231 | checksum: 10c0/7a7f83f568bfc3dfabfaf9ae3a97ab5c061726c0afa7dcd94226d4f84a81559da368ed79671e3a8039d16f12476cf110381a377ebdea07587925f69628200dac 232 | languageName: node 233 | linkType: hard 234 | 235 | "@esbuild/aix-ppc64@npm:0.25.1": 236 | version: 0.25.1 237 | resolution: "@esbuild/aix-ppc64@npm:0.25.1" 238 | conditions: os=aix & cpu=ppc64 239 | languageName: node 240 | linkType: hard 241 | 242 | "@esbuild/android-arm64@npm:0.25.1": 243 | version: 0.25.1 244 | resolution: "@esbuild/android-arm64@npm:0.25.1" 245 | conditions: os=android & cpu=arm64 246 | languageName: node 247 | linkType: hard 248 | 249 | "@esbuild/android-arm@npm:0.25.1": 250 | version: 0.25.1 251 | resolution: "@esbuild/android-arm@npm:0.25.1" 252 | conditions: os=android & cpu=arm 253 | languageName: node 254 | linkType: hard 255 | 256 | "@esbuild/android-x64@npm:0.25.1": 257 | version: 0.25.1 258 | resolution: "@esbuild/android-x64@npm:0.25.1" 259 | conditions: os=android & cpu=x64 260 | languageName: node 261 | linkType: hard 262 | 263 | "@esbuild/darwin-arm64@npm:0.25.1": 264 | version: 0.25.1 265 | resolution: "@esbuild/darwin-arm64@npm:0.25.1" 266 | conditions: os=darwin & cpu=arm64 267 | languageName: node 268 | linkType: hard 269 | 270 | "@esbuild/darwin-x64@npm:0.25.1": 271 | version: 0.25.1 272 | resolution: "@esbuild/darwin-x64@npm:0.25.1" 273 | conditions: os=darwin & cpu=x64 274 | languageName: node 275 | linkType: hard 276 | 277 | "@esbuild/freebsd-arm64@npm:0.25.1": 278 | version: 0.25.1 279 | resolution: "@esbuild/freebsd-arm64@npm:0.25.1" 280 | conditions: os=freebsd & cpu=arm64 281 | languageName: node 282 | linkType: hard 283 | 284 | "@esbuild/freebsd-x64@npm:0.25.1": 285 | version: 0.25.1 286 | resolution: "@esbuild/freebsd-x64@npm:0.25.1" 287 | conditions: os=freebsd & cpu=x64 288 | languageName: node 289 | linkType: hard 290 | 291 | "@esbuild/linux-arm64@npm:0.25.1": 292 | version: 0.25.1 293 | resolution: "@esbuild/linux-arm64@npm:0.25.1" 294 | conditions: os=linux & cpu=arm64 295 | languageName: node 296 | linkType: hard 297 | 298 | "@esbuild/linux-arm@npm:0.25.1": 299 | version: 0.25.1 300 | resolution: "@esbuild/linux-arm@npm:0.25.1" 301 | conditions: os=linux & cpu=arm 302 | languageName: node 303 | linkType: hard 304 | 305 | "@esbuild/linux-ia32@npm:0.25.1": 306 | version: 0.25.1 307 | resolution: "@esbuild/linux-ia32@npm:0.25.1" 308 | conditions: os=linux & cpu=ia32 309 | languageName: node 310 | linkType: hard 311 | 312 | "@esbuild/linux-loong64@npm:0.25.1": 313 | version: 0.25.1 314 | resolution: "@esbuild/linux-loong64@npm:0.25.1" 315 | conditions: os=linux & cpu=loong64 316 | languageName: node 317 | linkType: hard 318 | 319 | "@esbuild/linux-mips64el@npm:0.25.1": 320 | version: 0.25.1 321 | resolution: "@esbuild/linux-mips64el@npm:0.25.1" 322 | conditions: os=linux & cpu=mips64el 323 | languageName: node 324 | linkType: hard 325 | 326 | "@esbuild/linux-ppc64@npm:0.25.1": 327 | version: 0.25.1 328 | resolution: "@esbuild/linux-ppc64@npm:0.25.1" 329 | conditions: os=linux & cpu=ppc64 330 | languageName: node 331 | linkType: hard 332 | 333 | "@esbuild/linux-riscv64@npm:0.25.1": 334 | version: 0.25.1 335 | resolution: "@esbuild/linux-riscv64@npm:0.25.1" 336 | conditions: os=linux & cpu=riscv64 337 | languageName: node 338 | linkType: hard 339 | 340 | "@esbuild/linux-s390x@npm:0.25.1": 341 | version: 0.25.1 342 | resolution: "@esbuild/linux-s390x@npm:0.25.1" 343 | conditions: os=linux & cpu=s390x 344 | languageName: node 345 | linkType: hard 346 | 347 | "@esbuild/linux-x64@npm:0.25.1": 348 | version: 0.25.1 349 | resolution: "@esbuild/linux-x64@npm:0.25.1" 350 | conditions: os=linux & cpu=x64 351 | languageName: node 352 | linkType: hard 353 | 354 | "@esbuild/netbsd-arm64@npm:0.25.1": 355 | version: 0.25.1 356 | resolution: "@esbuild/netbsd-arm64@npm:0.25.1" 357 | conditions: os=netbsd & cpu=arm64 358 | languageName: node 359 | linkType: hard 360 | 361 | "@esbuild/netbsd-x64@npm:0.25.1": 362 | version: 0.25.1 363 | resolution: "@esbuild/netbsd-x64@npm:0.25.1" 364 | conditions: os=netbsd & cpu=x64 365 | languageName: node 366 | linkType: hard 367 | 368 | "@esbuild/openbsd-arm64@npm:0.25.1": 369 | version: 0.25.1 370 | resolution: "@esbuild/openbsd-arm64@npm:0.25.1" 371 | conditions: os=openbsd & cpu=arm64 372 | languageName: node 373 | linkType: hard 374 | 375 | "@esbuild/openbsd-x64@npm:0.25.1": 376 | version: 0.25.1 377 | resolution: "@esbuild/openbsd-x64@npm:0.25.1" 378 | conditions: os=openbsd & cpu=x64 379 | languageName: node 380 | linkType: hard 381 | 382 | "@esbuild/sunos-x64@npm:0.25.1": 383 | version: 0.25.1 384 | resolution: "@esbuild/sunos-x64@npm:0.25.1" 385 | conditions: os=sunos & cpu=x64 386 | languageName: node 387 | linkType: hard 388 | 389 | "@esbuild/win32-arm64@npm:0.25.1": 390 | version: 0.25.1 391 | resolution: "@esbuild/win32-arm64@npm:0.25.1" 392 | conditions: os=win32 & cpu=arm64 393 | languageName: node 394 | linkType: hard 395 | 396 | "@esbuild/win32-ia32@npm:0.25.1": 397 | version: 0.25.1 398 | resolution: "@esbuild/win32-ia32@npm:0.25.1" 399 | conditions: os=win32 & cpu=ia32 400 | languageName: node 401 | linkType: hard 402 | 403 | "@esbuild/win32-x64@npm:0.25.1": 404 | version: 0.25.1 405 | resolution: "@esbuild/win32-x64@npm:0.25.1" 406 | conditions: os=win32 & cpu=x64 407 | languageName: node 408 | linkType: hard 409 | 410 | "@isaacs/cliui@npm:^8.0.2": 411 | version: 8.0.2 412 | resolution: "@isaacs/cliui@npm:8.0.2" 413 | dependencies: 414 | string-width: "npm:^5.1.2" 415 | string-width-cjs: "npm:string-width@^4.2.0" 416 | strip-ansi: "npm:^7.0.1" 417 | strip-ansi-cjs: "npm:strip-ansi@^6.0.1" 418 | wrap-ansi: "npm:^8.1.0" 419 | wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" 420 | checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e 421 | languageName: node 422 | linkType: hard 423 | 424 | "@isaacs/fs-minipass@npm:^4.0.0": 425 | version: 4.0.1 426 | resolution: "@isaacs/fs-minipass@npm:4.0.1" 427 | dependencies: 428 | minipass: "npm:^7.0.4" 429 | checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 430 | languageName: node 431 | linkType: hard 432 | 433 | "@jridgewell/gen-mapping@npm:^0.3.5": 434 | version: 0.3.8 435 | resolution: "@jridgewell/gen-mapping@npm:0.3.8" 436 | dependencies: 437 | "@jridgewell/set-array": "npm:^1.2.1" 438 | "@jridgewell/sourcemap-codec": "npm:^1.4.10" 439 | "@jridgewell/trace-mapping": "npm:^0.3.24" 440 | checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a 441 | languageName: node 442 | linkType: hard 443 | 444 | "@jridgewell/resolve-uri@npm:^3.1.0": 445 | version: 3.1.2 446 | resolution: "@jridgewell/resolve-uri@npm:3.1.2" 447 | checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e 448 | languageName: node 449 | linkType: hard 450 | 451 | "@jridgewell/set-array@npm:^1.2.1": 452 | version: 1.2.1 453 | resolution: "@jridgewell/set-array@npm:1.2.1" 454 | checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 455 | languageName: node 456 | linkType: hard 457 | 458 | "@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": 459 | version: 1.5.0 460 | resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" 461 | checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 462 | languageName: node 463 | linkType: hard 464 | 465 | "@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": 466 | version: 0.3.25 467 | resolution: "@jridgewell/trace-mapping@npm:0.3.25" 468 | dependencies: 469 | "@jridgewell/resolve-uri": "npm:^3.1.0" 470 | "@jridgewell/sourcemap-codec": "npm:^1.4.14" 471 | checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 472 | languageName: node 473 | linkType: hard 474 | 475 | "@npmcli/agent@npm:^3.0.0": 476 | version: 3.0.0 477 | resolution: "@npmcli/agent@npm:3.0.0" 478 | dependencies: 479 | agent-base: "npm:^7.1.0" 480 | http-proxy-agent: "npm:^7.0.0" 481 | https-proxy-agent: "npm:^7.0.1" 482 | lru-cache: "npm:^10.0.1" 483 | socks-proxy-agent: "npm:^8.0.3" 484 | checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 485 | languageName: node 486 | linkType: hard 487 | 488 | "@npmcli/fs@npm:^4.0.0": 489 | version: 4.0.0 490 | resolution: "@npmcli/fs@npm:4.0.0" 491 | dependencies: 492 | semver: "npm:^7.3.5" 493 | checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 494 | languageName: node 495 | linkType: hard 496 | 497 | "@parcel/watcher-android-arm64@npm:2.5.1": 498 | version: 2.5.1 499 | resolution: "@parcel/watcher-android-arm64@npm:2.5.1" 500 | conditions: os=android & cpu=arm64 501 | languageName: node 502 | linkType: hard 503 | 504 | "@parcel/watcher-darwin-arm64@npm:2.5.1": 505 | version: 2.5.1 506 | resolution: "@parcel/watcher-darwin-arm64@npm:2.5.1" 507 | conditions: os=darwin & cpu=arm64 508 | languageName: node 509 | linkType: hard 510 | 511 | "@parcel/watcher-darwin-x64@npm:2.5.1": 512 | version: 2.5.1 513 | resolution: "@parcel/watcher-darwin-x64@npm:2.5.1" 514 | conditions: os=darwin & cpu=x64 515 | languageName: node 516 | linkType: hard 517 | 518 | "@parcel/watcher-freebsd-x64@npm:2.5.1": 519 | version: 2.5.1 520 | resolution: "@parcel/watcher-freebsd-x64@npm:2.5.1" 521 | conditions: os=freebsd & cpu=x64 522 | languageName: node 523 | linkType: hard 524 | 525 | "@parcel/watcher-linux-arm-glibc@npm:2.5.1": 526 | version: 2.5.1 527 | resolution: "@parcel/watcher-linux-arm-glibc@npm:2.5.1" 528 | conditions: os=linux & cpu=arm & libc=glibc 529 | languageName: node 530 | linkType: hard 531 | 532 | "@parcel/watcher-linux-arm-musl@npm:2.5.1": 533 | version: 2.5.1 534 | resolution: "@parcel/watcher-linux-arm-musl@npm:2.5.1" 535 | conditions: os=linux & cpu=arm & libc=musl 536 | languageName: node 537 | linkType: hard 538 | 539 | "@parcel/watcher-linux-arm64-glibc@npm:2.5.1": 540 | version: 2.5.1 541 | resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.5.1" 542 | conditions: os=linux & cpu=arm64 & libc=glibc 543 | languageName: node 544 | linkType: hard 545 | 546 | "@parcel/watcher-linux-arm64-musl@npm:2.5.1": 547 | version: 2.5.1 548 | resolution: "@parcel/watcher-linux-arm64-musl@npm:2.5.1" 549 | conditions: os=linux & cpu=arm64 & libc=musl 550 | languageName: node 551 | linkType: hard 552 | 553 | "@parcel/watcher-linux-x64-glibc@npm:2.5.1": 554 | version: 2.5.1 555 | resolution: "@parcel/watcher-linux-x64-glibc@npm:2.5.1" 556 | conditions: os=linux & cpu=x64 & libc=glibc 557 | languageName: node 558 | linkType: hard 559 | 560 | "@parcel/watcher-linux-x64-musl@npm:2.5.1": 561 | version: 2.5.1 562 | resolution: "@parcel/watcher-linux-x64-musl@npm:2.5.1" 563 | conditions: os=linux & cpu=x64 & libc=musl 564 | languageName: node 565 | linkType: hard 566 | 567 | "@parcel/watcher-win32-arm64@npm:2.5.1": 568 | version: 2.5.1 569 | resolution: "@parcel/watcher-win32-arm64@npm:2.5.1" 570 | conditions: os=win32 & cpu=arm64 571 | languageName: node 572 | linkType: hard 573 | 574 | "@parcel/watcher-win32-ia32@npm:2.5.1": 575 | version: 2.5.1 576 | resolution: "@parcel/watcher-win32-ia32@npm:2.5.1" 577 | conditions: os=win32 & cpu=ia32 578 | languageName: node 579 | linkType: hard 580 | 581 | "@parcel/watcher-win32-x64@npm:2.5.1": 582 | version: 2.5.1 583 | resolution: "@parcel/watcher-win32-x64@npm:2.5.1" 584 | conditions: os=win32 & cpu=x64 585 | languageName: node 586 | linkType: hard 587 | 588 | "@parcel/watcher@npm:^2.4.1": 589 | version: 2.5.1 590 | resolution: "@parcel/watcher@npm:2.5.1" 591 | dependencies: 592 | "@parcel/watcher-android-arm64": "npm:2.5.1" 593 | "@parcel/watcher-darwin-arm64": "npm:2.5.1" 594 | "@parcel/watcher-darwin-x64": "npm:2.5.1" 595 | "@parcel/watcher-freebsd-x64": "npm:2.5.1" 596 | "@parcel/watcher-linux-arm-glibc": "npm:2.5.1" 597 | "@parcel/watcher-linux-arm-musl": "npm:2.5.1" 598 | "@parcel/watcher-linux-arm64-glibc": "npm:2.5.1" 599 | "@parcel/watcher-linux-arm64-musl": "npm:2.5.1" 600 | "@parcel/watcher-linux-x64-glibc": "npm:2.5.1" 601 | "@parcel/watcher-linux-x64-musl": "npm:2.5.1" 602 | "@parcel/watcher-win32-arm64": "npm:2.5.1" 603 | "@parcel/watcher-win32-ia32": "npm:2.5.1" 604 | "@parcel/watcher-win32-x64": "npm:2.5.1" 605 | detect-libc: "npm:^1.0.3" 606 | is-glob: "npm:^4.0.3" 607 | micromatch: "npm:^4.0.5" 608 | node-addon-api: "npm:^7.0.0" 609 | node-gyp: "npm:latest" 610 | dependenciesMeta: 611 | "@parcel/watcher-android-arm64": 612 | optional: true 613 | "@parcel/watcher-darwin-arm64": 614 | optional: true 615 | "@parcel/watcher-darwin-x64": 616 | optional: true 617 | "@parcel/watcher-freebsd-x64": 618 | optional: true 619 | "@parcel/watcher-linux-arm-glibc": 620 | optional: true 621 | "@parcel/watcher-linux-arm-musl": 622 | optional: true 623 | "@parcel/watcher-linux-arm64-glibc": 624 | optional: true 625 | "@parcel/watcher-linux-arm64-musl": 626 | optional: true 627 | "@parcel/watcher-linux-x64-glibc": 628 | optional: true 629 | "@parcel/watcher-linux-x64-musl": 630 | optional: true 631 | "@parcel/watcher-win32-arm64": 632 | optional: true 633 | "@parcel/watcher-win32-ia32": 634 | optional: true 635 | "@parcel/watcher-win32-x64": 636 | optional: true 637 | checksum: 10c0/8f35073d0c0b34a63d4c8d2213482f0ebc6a25de7b2cdd415d19cb929964a793cb285b68d1d50bfb732b070b3c82a2fdb4eb9c250eab709a1cd9d63345455a82 638 | languageName: node 639 | linkType: hard 640 | 641 | "@pkgjs/parseargs@npm:^0.11.0": 642 | version: 0.11.0 643 | resolution: "@pkgjs/parseargs@npm:0.11.0" 644 | checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd 645 | languageName: node 646 | linkType: hard 647 | 648 | "@rollup/rollup-android-arm-eabi@npm:4.35.0": 649 | version: 4.35.0 650 | resolution: "@rollup/rollup-android-arm-eabi@npm:4.35.0" 651 | conditions: os=android & cpu=arm 652 | languageName: node 653 | linkType: hard 654 | 655 | "@rollup/rollup-android-arm64@npm:4.35.0": 656 | version: 4.35.0 657 | resolution: "@rollup/rollup-android-arm64@npm:4.35.0" 658 | conditions: os=android & cpu=arm64 659 | languageName: node 660 | linkType: hard 661 | 662 | "@rollup/rollup-darwin-arm64@npm:4.35.0": 663 | version: 4.35.0 664 | resolution: "@rollup/rollup-darwin-arm64@npm:4.35.0" 665 | conditions: os=darwin & cpu=arm64 666 | languageName: node 667 | linkType: hard 668 | 669 | "@rollup/rollup-darwin-x64@npm:4.35.0": 670 | version: 4.35.0 671 | resolution: "@rollup/rollup-darwin-x64@npm:4.35.0" 672 | conditions: os=darwin & cpu=x64 673 | languageName: node 674 | linkType: hard 675 | 676 | "@rollup/rollup-freebsd-arm64@npm:4.35.0": 677 | version: 4.35.0 678 | resolution: "@rollup/rollup-freebsd-arm64@npm:4.35.0" 679 | conditions: os=freebsd & cpu=arm64 680 | languageName: node 681 | linkType: hard 682 | 683 | "@rollup/rollup-freebsd-x64@npm:4.35.0": 684 | version: 4.35.0 685 | resolution: "@rollup/rollup-freebsd-x64@npm:4.35.0" 686 | conditions: os=freebsd & cpu=x64 687 | languageName: node 688 | linkType: hard 689 | 690 | "@rollup/rollup-linux-arm-gnueabihf@npm:4.35.0": 691 | version: 4.35.0 692 | resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.35.0" 693 | conditions: os=linux & cpu=arm & libc=glibc 694 | languageName: node 695 | linkType: hard 696 | 697 | "@rollup/rollup-linux-arm-musleabihf@npm:4.35.0": 698 | version: 4.35.0 699 | resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.35.0" 700 | conditions: os=linux & cpu=arm & libc=musl 701 | languageName: node 702 | linkType: hard 703 | 704 | "@rollup/rollup-linux-arm64-gnu@npm:4.35.0": 705 | version: 4.35.0 706 | resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.35.0" 707 | conditions: os=linux & cpu=arm64 & libc=glibc 708 | languageName: node 709 | linkType: hard 710 | 711 | "@rollup/rollup-linux-arm64-musl@npm:4.35.0": 712 | version: 4.35.0 713 | resolution: "@rollup/rollup-linux-arm64-musl@npm:4.35.0" 714 | conditions: os=linux & cpu=arm64 & libc=musl 715 | languageName: node 716 | linkType: hard 717 | 718 | "@rollup/rollup-linux-loongarch64-gnu@npm:4.35.0": 719 | version: 4.35.0 720 | resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.35.0" 721 | conditions: os=linux & cpu=loong64 & libc=glibc 722 | languageName: node 723 | linkType: hard 724 | 725 | "@rollup/rollup-linux-powerpc64le-gnu@npm:4.35.0": 726 | version: 4.35.0 727 | resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.35.0" 728 | conditions: os=linux & cpu=ppc64 & libc=glibc 729 | languageName: node 730 | linkType: hard 731 | 732 | "@rollup/rollup-linux-riscv64-gnu@npm:4.35.0": 733 | version: 4.35.0 734 | resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.35.0" 735 | conditions: os=linux & cpu=riscv64 & libc=glibc 736 | languageName: node 737 | linkType: hard 738 | 739 | "@rollup/rollup-linux-s390x-gnu@npm:4.35.0": 740 | version: 4.35.0 741 | resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.35.0" 742 | conditions: os=linux & cpu=s390x & libc=glibc 743 | languageName: node 744 | linkType: hard 745 | 746 | "@rollup/rollup-linux-x64-gnu@npm:4.35.0": 747 | version: 4.35.0 748 | resolution: "@rollup/rollup-linux-x64-gnu@npm:4.35.0" 749 | conditions: os=linux & cpu=x64 & libc=glibc 750 | languageName: node 751 | linkType: hard 752 | 753 | "@rollup/rollup-linux-x64-musl@npm:4.35.0": 754 | version: 4.35.0 755 | resolution: "@rollup/rollup-linux-x64-musl@npm:4.35.0" 756 | conditions: os=linux & cpu=x64 & libc=musl 757 | languageName: node 758 | linkType: hard 759 | 760 | "@rollup/rollup-win32-arm64-msvc@npm:4.35.0": 761 | version: 4.35.0 762 | resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.35.0" 763 | conditions: os=win32 & cpu=arm64 764 | languageName: node 765 | linkType: hard 766 | 767 | "@rollup/rollup-win32-ia32-msvc@npm:4.35.0": 768 | version: 4.35.0 769 | resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.35.0" 770 | conditions: os=win32 & cpu=ia32 771 | languageName: node 772 | linkType: hard 773 | 774 | "@rollup/rollup-win32-x64-msvc@npm:4.35.0": 775 | version: 4.35.0 776 | resolution: "@rollup/rollup-win32-x64-msvc@npm:4.35.0" 777 | conditions: os=win32 & cpu=x64 778 | languageName: node 779 | linkType: hard 780 | 781 | "@types/babel__core@npm:^7.20.5": 782 | version: 7.20.5 783 | resolution: "@types/babel__core@npm:7.20.5" 784 | dependencies: 785 | "@babel/parser": "npm:^7.20.7" 786 | "@babel/types": "npm:^7.20.7" 787 | "@types/babel__generator": "npm:*" 788 | "@types/babel__template": "npm:*" 789 | "@types/babel__traverse": "npm:*" 790 | checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff 791 | languageName: node 792 | linkType: hard 793 | 794 | "@types/babel__generator@npm:*": 795 | version: 7.6.1 796 | resolution: "@types/babel__generator@npm:7.6.1" 797 | dependencies: 798 | "@babel/types": "npm:^7.0.0" 799 | checksum: 10c0/11776ebbdb60a56583210ebd1474cf88dd3c8c61e3930ad337b18ee2b231e50f6c514c0ebfefb70311116bc7d4146abc2860d36f2046fdbccfadb9161aa288e5 800 | languageName: node 801 | linkType: hard 802 | 803 | "@types/babel__template@npm:*": 804 | version: 7.0.2 805 | resolution: "@types/babel__template@npm:7.0.2" 806 | dependencies: 807 | "@babel/parser": "npm:^7.1.0" 808 | "@babel/types": "npm:^7.0.0" 809 | checksum: 10c0/8da8d45b97eb420976811a168d3ace44ffa0d98fea8ec204c8502331ce94f2b05b7be15357b741cffe2ef8bbfa419e841af9faafe07dc5049b3a6f71efd6a41e 810 | languageName: node 811 | linkType: hard 812 | 813 | "@types/babel__traverse@npm:*": 814 | version: 7.0.9 815 | resolution: "@types/babel__traverse@npm:7.0.9" 816 | dependencies: 817 | "@babel/types": "npm:^7.3.0" 818 | checksum: 10c0/1a923b1576704ef901a35f29dfcb274437ab3afa5248932dd772d887e9ef515ec31fa0cbb75c0d453d49b9719bab67c441ee0fc2e0c130f4174e5e542c4b220c 819 | languageName: node 820 | linkType: hard 821 | 822 | "@types/estree@npm:1.0.6": 823 | version: 1.0.6 824 | resolution: "@types/estree@npm:1.0.6" 825 | checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a 826 | languageName: node 827 | linkType: hard 828 | 829 | "@types/prop-types@npm:*": 830 | version: 15.7.14 831 | resolution: "@types/prop-types@npm:15.7.14" 832 | checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1 833 | languageName: node 834 | linkType: hard 835 | 836 | "@types/react-dom@npm:^18.2.7": 837 | version: 18.3.5 838 | resolution: "@types/react-dom@npm:18.3.5" 839 | peerDependencies: 840 | "@types/react": ^18.0.0 841 | checksum: 10c0/b163d35a6b32a79f5782574a7aeb12a31a647e248792bf437e6d596e2676961c394c5e3c6e91d1ce44ae90441dbaf93158efb4f051c0d61e2612f1cb04ce4faa 842 | languageName: node 843 | linkType: hard 844 | 845 | "@types/react@npm:^18.2.15": 846 | version: 18.3.18 847 | resolution: "@types/react@npm:18.3.18" 848 | dependencies: 849 | "@types/prop-types": "npm:*" 850 | csstype: "npm:^3.0.2" 851 | checksum: 10c0/8fb2b00672072135d0858dc9db07873ea107cc238b6228aaa2a9afd1ef7a64a7074078250db38afbeb19064be8ea6af5eac32d404efdd5f45e093cc4829d87f8 852 | languageName: node 853 | linkType: hard 854 | 855 | "@vitejs/plugin-react@npm:^4.0.3": 856 | version: 4.3.4 857 | resolution: "@vitejs/plugin-react@npm:4.3.4" 858 | dependencies: 859 | "@babel/core": "npm:^7.26.0" 860 | "@babel/plugin-transform-react-jsx-self": "npm:^7.25.9" 861 | "@babel/plugin-transform-react-jsx-source": "npm:^7.25.9" 862 | "@types/babel__core": "npm:^7.20.5" 863 | react-refresh: "npm:^0.14.2" 864 | peerDependencies: 865 | vite: ^4.2.0 || ^5.0.0 || ^6.0.0 866 | checksum: 10c0/38a47a1dbafae0b97142943d83ee3674cb3331153a60b1a3fd29d230c12c9dfe63b7c345b231a3450168ed8a9375a9a1a253c3d85e9efdc19478c0d56b98496c 867 | languageName: node 868 | linkType: hard 869 | 870 | "abbrev@npm:^3.0.0": 871 | version: 3.0.0 872 | resolution: "abbrev@npm:3.0.0" 873 | checksum: 10c0/049704186396f571650eb7b22ed3627b77a5aedf98bb83caf2eac81ca2a3e25e795394b0464cfb2d6076df3db6a5312139eac5b6a126ca296ac53c5008069c28 874 | languageName: node 875 | linkType: hard 876 | 877 | "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": 878 | version: 7.1.3 879 | resolution: "agent-base@npm:7.1.3" 880 | checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 881 | languageName: node 882 | linkType: hard 883 | 884 | "ansi-regex@npm:^5.0.0": 885 | version: 5.0.0 886 | resolution: "ansi-regex@npm:5.0.0" 887 | checksum: 10c0/4c711eeec7ab00c1869e926ae78758abd10137047cbb08b6fda499be2dc39c2d5f21e15c7279dbb222de523b53834b54043d4997191f62372d5e2250edcbc83a 888 | languageName: node 889 | linkType: hard 890 | 891 | "ansi-regex@npm:^5.0.1": 892 | version: 5.0.1 893 | resolution: "ansi-regex@npm:5.0.1" 894 | checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 895 | languageName: node 896 | linkType: hard 897 | 898 | "ansi-regex@npm:^6.0.1": 899 | version: 6.1.0 900 | resolution: "ansi-regex@npm:6.1.0" 901 | checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc 902 | languageName: node 903 | linkType: hard 904 | 905 | "ansi-styles@npm:^4.0.0": 906 | version: 4.3.0 907 | resolution: "ansi-styles@npm:4.3.0" 908 | dependencies: 909 | color-convert: "npm:^2.0.1" 910 | checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 911 | languageName: node 912 | linkType: hard 913 | 914 | "ansi-styles@npm:^6.1.0": 915 | version: 6.2.1 916 | resolution: "ansi-styles@npm:6.2.1" 917 | checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c 918 | languageName: node 919 | linkType: hard 920 | 921 | "balanced-match@npm:^1.0.0": 922 | version: 1.0.0 923 | resolution: "balanced-match@npm:1.0.0" 924 | checksum: 10c0/d45f1aeec59d87562cd65415e2890b9fd6ab7fa89941a46fb2eb505e2165158680ee1be7110586cf86f3a9599f1b88ec4a7fcf57594560ca37814a560ab95f41 925 | languageName: node 926 | linkType: hard 927 | 928 | "brace-expansion@npm:^2.0.1": 929 | version: 2.0.1 930 | resolution: "brace-expansion@npm:2.0.1" 931 | dependencies: 932 | balanced-match: "npm:^1.0.0" 933 | checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f 934 | languageName: node 935 | linkType: hard 936 | 937 | "braces@npm:^3.0.3": 938 | version: 3.0.3 939 | resolution: "braces@npm:3.0.3" 940 | dependencies: 941 | fill-range: "npm:^7.1.1" 942 | checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 943 | languageName: node 944 | linkType: hard 945 | 946 | "browserslist@npm:^4.24.0": 947 | version: 4.24.4 948 | resolution: "browserslist@npm:4.24.4" 949 | dependencies: 950 | caniuse-lite: "npm:^1.0.30001688" 951 | electron-to-chromium: "npm:^1.5.73" 952 | node-releases: "npm:^2.0.19" 953 | update-browserslist-db: "npm:^1.1.1" 954 | bin: 955 | browserslist: cli.js 956 | checksum: 10c0/db7ebc1733cf471e0b490b4f47e3e2ea2947ce417192c9246644e92c667dd56a71406cc58f62ca7587caf828364892e9952904a02b7aead752bc65b62a37cfe9 957 | languageName: node 958 | linkType: hard 959 | 960 | "cacache@npm:^19.0.1": 961 | version: 19.0.1 962 | resolution: "cacache@npm:19.0.1" 963 | dependencies: 964 | "@npmcli/fs": "npm:^4.0.0" 965 | fs-minipass: "npm:^3.0.0" 966 | glob: "npm:^10.2.2" 967 | lru-cache: "npm:^10.0.1" 968 | minipass: "npm:^7.0.3" 969 | minipass-collect: "npm:^2.0.1" 970 | minipass-flush: "npm:^1.0.5" 971 | minipass-pipeline: "npm:^1.2.4" 972 | p-map: "npm:^7.0.2" 973 | ssri: "npm:^12.0.0" 974 | tar: "npm:^7.4.3" 975 | unique-filename: "npm:^4.0.0" 976 | checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c 977 | languageName: node 978 | linkType: hard 979 | 980 | "caniuse-lite@npm:^1.0.30001688": 981 | version: 1.0.30001704 982 | resolution: "caniuse-lite@npm:1.0.30001704" 983 | checksum: 10c0/4efa0ece51ef58e7ce7e7c8cd7b50372bcb910581a47397be5c086c046c3cd436d123b734351fb20f638c322b339198edf89b5b632ff59bdd171c74ff7f4efcf 984 | languageName: node 985 | linkType: hard 986 | 987 | "chokidar@npm:^4.0.0": 988 | version: 4.0.3 989 | resolution: "chokidar@npm:4.0.3" 990 | dependencies: 991 | readdirp: "npm:^4.0.1" 992 | checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad 993 | languageName: node 994 | linkType: hard 995 | 996 | "chownr@npm:^3.0.0": 997 | version: 3.0.0 998 | resolution: "chownr@npm:3.0.0" 999 | checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 1000 | languageName: node 1001 | linkType: hard 1002 | 1003 | "color-convert@npm:^2.0.1": 1004 | version: 2.0.1 1005 | resolution: "color-convert@npm:2.0.1" 1006 | dependencies: 1007 | color-name: "npm:~1.1.4" 1008 | checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 1009 | languageName: node 1010 | linkType: hard 1011 | 1012 | "color-name@npm:~1.1.4": 1013 | version: 1.1.4 1014 | resolution: "color-name@npm:1.1.4" 1015 | checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 1016 | languageName: node 1017 | linkType: hard 1018 | 1019 | "convert-source-map@npm:^2.0.0": 1020 | version: 2.0.0 1021 | resolution: "convert-source-map@npm:2.0.0" 1022 | checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b 1023 | languageName: node 1024 | linkType: hard 1025 | 1026 | "cross-spawn@npm:^7.0.6": 1027 | version: 7.0.6 1028 | resolution: "cross-spawn@npm:7.0.6" 1029 | dependencies: 1030 | path-key: "npm:^3.1.0" 1031 | shebang-command: "npm:^2.0.0" 1032 | which: "npm:^2.0.1" 1033 | checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 1034 | languageName: node 1035 | linkType: hard 1036 | 1037 | "csstype@npm:^3.0.2": 1038 | version: 3.1.3 1039 | resolution: "csstype@npm:3.1.3" 1040 | checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 1041 | languageName: node 1042 | linkType: hard 1043 | 1044 | "debug@npm:4, debug@npm:^4.3.1, debug@npm:^4.3.4": 1045 | version: 4.4.0 1046 | resolution: "debug@npm:4.4.0" 1047 | dependencies: 1048 | ms: "npm:^2.1.3" 1049 | peerDependenciesMeta: 1050 | supports-color: 1051 | optional: true 1052 | checksum: 10c0/db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de 1053 | languageName: node 1054 | linkType: hard 1055 | 1056 | "debug@npm:^4.1.0": 1057 | version: 4.1.1 1058 | resolution: "debug@npm:4.1.1" 1059 | dependencies: 1060 | ms: "npm:^2.1.1" 1061 | checksum: 10c0/399e98c779ba4e71537497f10caf6390646b69330ff8a455427acce04f1ce8d8cd018e1804fdcca772177666fdcd888dd3672bb8f9986c1d785a6c2b9805f370 1062 | languageName: node 1063 | linkType: hard 1064 | 1065 | "detect-libc@npm:^1.0.3": 1066 | version: 1.0.3 1067 | resolution: "detect-libc@npm:1.0.3" 1068 | bin: 1069 | detect-libc: ./bin/detect-libc.js 1070 | checksum: 10c0/4da0deae9f69e13bc37a0902d78bf7169480004b1fed3c19722d56cff578d16f0e11633b7fbf5fb6249181236c72e90024cbd68f0b9558ae06e281f47326d50d 1071 | languageName: node 1072 | linkType: hard 1073 | 1074 | "eastasianwidth@npm:^0.2.0": 1075 | version: 0.2.0 1076 | resolution: "eastasianwidth@npm:0.2.0" 1077 | checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 1078 | languageName: node 1079 | linkType: hard 1080 | 1081 | "electron-to-chromium@npm:^1.5.73": 1082 | version: 1.5.116 1083 | resolution: "electron-to-chromium@npm:1.5.116" 1084 | checksum: 10c0/27f117cd18b17b6a7c4ddf82db5f5093c0eaa37bab27798f544648bf6408ef7d7c938251b50210eb1fa98d1bcf0a65b4302f926a147896f8a3dea3c0fc9a45f6 1085 | languageName: node 1086 | linkType: hard 1087 | 1088 | "emoji-regex@npm:^8.0.0": 1089 | version: 8.0.0 1090 | resolution: "emoji-regex@npm:8.0.0" 1091 | checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 1092 | languageName: node 1093 | linkType: hard 1094 | 1095 | "emoji-regex@npm:^9.2.2": 1096 | version: 9.2.2 1097 | resolution: "emoji-regex@npm:9.2.2" 1098 | checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 1099 | languageName: node 1100 | linkType: hard 1101 | 1102 | "encoding@npm:^0.1.13": 1103 | version: 0.1.13 1104 | resolution: "encoding@npm:0.1.13" 1105 | dependencies: 1106 | iconv-lite: "npm:^0.6.2" 1107 | checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 1108 | languageName: node 1109 | linkType: hard 1110 | 1111 | "env-paths@npm:^2.2.0": 1112 | version: 2.2.1 1113 | resolution: "env-paths@npm:2.2.1" 1114 | checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 1115 | languageName: node 1116 | linkType: hard 1117 | 1118 | "err-code@npm:^2.0.2": 1119 | version: 2.0.3 1120 | resolution: "err-code@npm:2.0.3" 1121 | checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 1122 | languageName: node 1123 | linkType: hard 1124 | 1125 | "esbuild@npm:^0.25.0": 1126 | version: 0.25.1 1127 | resolution: "esbuild@npm:0.25.1" 1128 | dependencies: 1129 | "@esbuild/aix-ppc64": "npm:0.25.1" 1130 | "@esbuild/android-arm": "npm:0.25.1" 1131 | "@esbuild/android-arm64": "npm:0.25.1" 1132 | "@esbuild/android-x64": "npm:0.25.1" 1133 | "@esbuild/darwin-arm64": "npm:0.25.1" 1134 | "@esbuild/darwin-x64": "npm:0.25.1" 1135 | "@esbuild/freebsd-arm64": "npm:0.25.1" 1136 | "@esbuild/freebsd-x64": "npm:0.25.1" 1137 | "@esbuild/linux-arm": "npm:0.25.1" 1138 | "@esbuild/linux-arm64": "npm:0.25.1" 1139 | "@esbuild/linux-ia32": "npm:0.25.1" 1140 | "@esbuild/linux-loong64": "npm:0.25.1" 1141 | "@esbuild/linux-mips64el": "npm:0.25.1" 1142 | "@esbuild/linux-ppc64": "npm:0.25.1" 1143 | "@esbuild/linux-riscv64": "npm:0.25.1" 1144 | "@esbuild/linux-s390x": "npm:0.25.1" 1145 | "@esbuild/linux-x64": "npm:0.25.1" 1146 | "@esbuild/netbsd-arm64": "npm:0.25.1" 1147 | "@esbuild/netbsd-x64": "npm:0.25.1" 1148 | "@esbuild/openbsd-arm64": "npm:0.25.1" 1149 | "@esbuild/openbsd-x64": "npm:0.25.1" 1150 | "@esbuild/sunos-x64": "npm:0.25.1" 1151 | "@esbuild/win32-arm64": "npm:0.25.1" 1152 | "@esbuild/win32-ia32": "npm:0.25.1" 1153 | "@esbuild/win32-x64": "npm:0.25.1" 1154 | dependenciesMeta: 1155 | "@esbuild/aix-ppc64": 1156 | optional: true 1157 | "@esbuild/android-arm": 1158 | optional: true 1159 | "@esbuild/android-arm64": 1160 | optional: true 1161 | "@esbuild/android-x64": 1162 | optional: true 1163 | "@esbuild/darwin-arm64": 1164 | optional: true 1165 | "@esbuild/darwin-x64": 1166 | optional: true 1167 | "@esbuild/freebsd-arm64": 1168 | optional: true 1169 | "@esbuild/freebsd-x64": 1170 | optional: true 1171 | "@esbuild/linux-arm": 1172 | optional: true 1173 | "@esbuild/linux-arm64": 1174 | optional: true 1175 | "@esbuild/linux-ia32": 1176 | optional: true 1177 | "@esbuild/linux-loong64": 1178 | optional: true 1179 | "@esbuild/linux-mips64el": 1180 | optional: true 1181 | "@esbuild/linux-ppc64": 1182 | optional: true 1183 | "@esbuild/linux-riscv64": 1184 | optional: true 1185 | "@esbuild/linux-s390x": 1186 | optional: true 1187 | "@esbuild/linux-x64": 1188 | optional: true 1189 | "@esbuild/netbsd-arm64": 1190 | optional: true 1191 | "@esbuild/netbsd-x64": 1192 | optional: true 1193 | "@esbuild/openbsd-arm64": 1194 | optional: true 1195 | "@esbuild/openbsd-x64": 1196 | optional: true 1197 | "@esbuild/sunos-x64": 1198 | optional: true 1199 | "@esbuild/win32-arm64": 1200 | optional: true 1201 | "@esbuild/win32-ia32": 1202 | optional: true 1203 | "@esbuild/win32-x64": 1204 | optional: true 1205 | bin: 1206 | esbuild: bin/esbuild 1207 | checksum: 10c0/80fca30dd0f21aec23fdfab34f0a8d5f55df5097dd7f475f2ab561d45662c32ee306f5649071cd1a0ba0614b164c48ca3dc3ee1551a4daf204b8af90e4d893f5 1208 | languageName: node 1209 | linkType: hard 1210 | 1211 | "escalade@npm:^3.2.0": 1212 | version: 3.2.0 1213 | resolution: "escalade@npm:3.2.0" 1214 | checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 1215 | languageName: node 1216 | linkType: hard 1217 | 1218 | "esutils@npm:^2.0.2": 1219 | version: 2.0.3 1220 | resolution: "esutils@npm:2.0.3" 1221 | checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 1222 | languageName: node 1223 | linkType: hard 1224 | 1225 | "exponential-backoff@npm:^3.1.1": 1226 | version: 3.1.2 1227 | resolution: "exponential-backoff@npm:3.1.2" 1228 | checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 1229 | languageName: node 1230 | linkType: hard 1231 | 1232 | "fill-range@npm:^7.1.1": 1233 | version: 7.1.1 1234 | resolution: "fill-range@npm:7.1.1" 1235 | dependencies: 1236 | to-regex-range: "npm:^5.0.1" 1237 | checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 1238 | languageName: node 1239 | linkType: hard 1240 | 1241 | "foreground-child@npm:^3.1.0": 1242 | version: 3.3.1 1243 | resolution: "foreground-child@npm:3.3.1" 1244 | dependencies: 1245 | cross-spawn: "npm:^7.0.6" 1246 | signal-exit: "npm:^4.0.1" 1247 | checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 1248 | languageName: node 1249 | linkType: hard 1250 | 1251 | "fs-minipass@npm:^3.0.0": 1252 | version: 3.0.3 1253 | resolution: "fs-minipass@npm:3.0.3" 1254 | dependencies: 1255 | minipass: "npm:^7.0.3" 1256 | checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 1257 | languageName: node 1258 | linkType: hard 1259 | 1260 | "fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": 1261 | version: 2.3.3 1262 | resolution: "fsevents@npm:2.3.3" 1263 | dependencies: 1264 | node-gyp: "npm:latest" 1265 | checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 1266 | conditions: os=darwin 1267 | languageName: node 1268 | linkType: hard 1269 | 1270 | "fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": 1271 | version: 2.3.3 1272 | resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" 1273 | dependencies: 1274 | node-gyp: "npm:latest" 1275 | conditions: os=darwin 1276 | languageName: node 1277 | linkType: hard 1278 | 1279 | "gensync@npm:^1.0.0-beta.2": 1280 | version: 1.0.0-beta.2 1281 | resolution: "gensync@npm:1.0.0-beta.2" 1282 | checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 1283 | languageName: node 1284 | linkType: hard 1285 | 1286 | "glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": 1287 | version: 10.4.5 1288 | resolution: "glob@npm:10.4.5" 1289 | dependencies: 1290 | foreground-child: "npm:^3.1.0" 1291 | jackspeak: "npm:^3.1.2" 1292 | minimatch: "npm:^9.0.4" 1293 | minipass: "npm:^7.1.2" 1294 | package-json-from-dist: "npm:^1.0.0" 1295 | path-scurry: "npm:^1.11.1" 1296 | bin: 1297 | glob: dist/esm/bin.mjs 1298 | checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e 1299 | languageName: node 1300 | linkType: hard 1301 | 1302 | "globals@npm:^11.1.0": 1303 | version: 11.12.0 1304 | resolution: "globals@npm:11.12.0" 1305 | checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 1306 | languageName: node 1307 | linkType: hard 1308 | 1309 | "graceful-fs@npm:^4.2.6": 1310 | version: 4.2.11 1311 | resolution: "graceful-fs@npm:4.2.11" 1312 | checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 1313 | languageName: node 1314 | linkType: hard 1315 | 1316 | "http-cache-semantics@npm:^4.1.1": 1317 | version: 4.1.1 1318 | resolution: "http-cache-semantics@npm:4.1.1" 1319 | checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc 1320 | languageName: node 1321 | linkType: hard 1322 | 1323 | "http-proxy-agent@npm:^7.0.0": 1324 | version: 7.0.2 1325 | resolution: "http-proxy-agent@npm:7.0.2" 1326 | dependencies: 1327 | agent-base: "npm:^7.1.0" 1328 | debug: "npm:^4.3.4" 1329 | checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 1330 | languageName: node 1331 | linkType: hard 1332 | 1333 | "https-proxy-agent@npm:^7.0.1": 1334 | version: 7.0.6 1335 | resolution: "https-proxy-agent@npm:7.0.6" 1336 | dependencies: 1337 | agent-base: "npm:^7.1.2" 1338 | debug: "npm:4" 1339 | checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac 1340 | languageName: node 1341 | linkType: hard 1342 | 1343 | "iconv-lite@npm:^0.6.2": 1344 | version: 0.6.3 1345 | resolution: "iconv-lite@npm:0.6.3" 1346 | dependencies: 1347 | safer-buffer: "npm:>= 2.1.2 < 3.0.0" 1348 | checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 1349 | languageName: node 1350 | linkType: hard 1351 | 1352 | "immutable@npm:^5.0.2": 1353 | version: 5.0.3 1354 | resolution: "immutable@npm:5.0.3" 1355 | checksum: 10c0/3269827789e1026cd25c2ea97f0b2c19be852ffd49eda1b674b20178f73d84fa8d945ad6f5ac5bc4545c2b4170af9f6e1f77129bc1cae7974a4bf9b04a9cdfb9 1356 | languageName: node 1357 | linkType: hard 1358 | 1359 | "imurmurhash@npm:^0.1.4": 1360 | version: 0.1.4 1361 | resolution: "imurmurhash@npm:0.1.4" 1362 | checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 1363 | languageName: node 1364 | linkType: hard 1365 | 1366 | "ip-address@npm:^9.0.5": 1367 | version: 9.0.5 1368 | resolution: "ip-address@npm:9.0.5" 1369 | dependencies: 1370 | jsbn: "npm:1.1.0" 1371 | sprintf-js: "npm:^1.1.3" 1372 | checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc 1373 | languageName: node 1374 | linkType: hard 1375 | 1376 | "is-extglob@npm:^2.1.1": 1377 | version: 2.1.1 1378 | resolution: "is-extglob@npm:2.1.1" 1379 | checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 1380 | languageName: node 1381 | linkType: hard 1382 | 1383 | "is-fullwidth-code-point@npm:^3.0.0": 1384 | version: 3.0.0 1385 | resolution: "is-fullwidth-code-point@npm:3.0.0" 1386 | checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc 1387 | languageName: node 1388 | linkType: hard 1389 | 1390 | "is-glob@npm:^4.0.3": 1391 | version: 4.0.3 1392 | resolution: "is-glob@npm:4.0.3" 1393 | dependencies: 1394 | is-extglob: "npm:^2.1.1" 1395 | checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a 1396 | languageName: node 1397 | linkType: hard 1398 | 1399 | "is-number@npm:^7.0.0": 1400 | version: 7.0.0 1401 | resolution: "is-number@npm:7.0.0" 1402 | checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 1403 | languageName: node 1404 | linkType: hard 1405 | 1406 | "isexe@npm:^2.0.0": 1407 | version: 2.0.0 1408 | resolution: "isexe@npm:2.0.0" 1409 | checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d 1410 | languageName: node 1411 | linkType: hard 1412 | 1413 | "isexe@npm:^3.1.1": 1414 | version: 3.1.1 1415 | resolution: "isexe@npm:3.1.1" 1416 | checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 1417 | languageName: node 1418 | linkType: hard 1419 | 1420 | "jackspeak@npm:^3.1.2": 1421 | version: 3.4.3 1422 | resolution: "jackspeak@npm:3.4.3" 1423 | dependencies: 1424 | "@isaacs/cliui": "npm:^8.0.2" 1425 | "@pkgjs/parseargs": "npm:^0.11.0" 1426 | dependenciesMeta: 1427 | "@pkgjs/parseargs": 1428 | optional: true 1429 | checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 1430 | languageName: node 1431 | linkType: hard 1432 | 1433 | "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": 1434 | version: 4.0.0 1435 | resolution: "js-tokens@npm:4.0.0" 1436 | checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed 1437 | languageName: node 1438 | linkType: hard 1439 | 1440 | "jsbn@npm:1.1.0": 1441 | version: 1.1.0 1442 | resolution: "jsbn@npm:1.1.0" 1443 | checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 1444 | languageName: node 1445 | linkType: hard 1446 | 1447 | "jsesc@npm:^3.0.2": 1448 | version: 3.1.0 1449 | resolution: "jsesc@npm:3.1.0" 1450 | bin: 1451 | jsesc: bin/jsesc 1452 | checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 1453 | languageName: node 1454 | linkType: hard 1455 | 1456 | "json5@npm:^2.2.3": 1457 | version: 2.2.3 1458 | resolution: "json5@npm:2.2.3" 1459 | bin: 1460 | json5: lib/cli.js 1461 | checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c 1462 | languageName: node 1463 | linkType: hard 1464 | 1465 | "lodash@npm:^4.17.13": 1466 | version: 4.17.15 1467 | resolution: "lodash@npm:4.17.15" 1468 | checksum: 10c0/c029ab298357d007252d5802348f1deb343099ba734ae6e6776a908e4995db5b8ed4ae51ff83a2937a1f6e3c41a0c8941eb941926e1a7f2ddfc3d235ec1e8aa7 1469 | languageName: node 1470 | linkType: hard 1471 | 1472 | "loose-envify@npm:^1.1.0": 1473 | version: 1.4.0 1474 | resolution: "loose-envify@npm:1.4.0" 1475 | dependencies: 1476 | js-tokens: "npm:^3.0.0 || ^4.0.0" 1477 | bin: 1478 | loose-envify: cli.js 1479 | checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e 1480 | languageName: node 1481 | linkType: hard 1482 | 1483 | "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": 1484 | version: 10.4.3 1485 | resolution: "lru-cache@npm:10.4.3" 1486 | checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb 1487 | languageName: node 1488 | linkType: hard 1489 | 1490 | "lru-cache@npm:^5.1.1": 1491 | version: 5.1.1 1492 | resolution: "lru-cache@npm:5.1.1" 1493 | dependencies: 1494 | yallist: "npm:^3.0.2" 1495 | checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 1496 | languageName: node 1497 | linkType: hard 1498 | 1499 | "make-fetch-happen@npm:^14.0.3": 1500 | version: 14.0.3 1501 | resolution: "make-fetch-happen@npm:14.0.3" 1502 | dependencies: 1503 | "@npmcli/agent": "npm:^3.0.0" 1504 | cacache: "npm:^19.0.1" 1505 | http-cache-semantics: "npm:^4.1.1" 1506 | minipass: "npm:^7.0.2" 1507 | minipass-fetch: "npm:^4.0.0" 1508 | minipass-flush: "npm:^1.0.5" 1509 | minipass-pipeline: "npm:^1.2.4" 1510 | negotiator: "npm:^1.0.0" 1511 | proc-log: "npm:^5.0.0" 1512 | promise-retry: "npm:^2.0.1" 1513 | ssri: "npm:^12.0.0" 1514 | checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 1515 | languageName: node 1516 | linkType: hard 1517 | 1518 | "micromatch@npm:^4.0.5": 1519 | version: 4.0.8 1520 | resolution: "micromatch@npm:4.0.8" 1521 | dependencies: 1522 | braces: "npm:^3.0.3" 1523 | picomatch: "npm:^2.3.1" 1524 | checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 1525 | languageName: node 1526 | linkType: hard 1527 | 1528 | "minimatch@npm:^9.0.4": 1529 | version: 9.0.5 1530 | resolution: "minimatch@npm:9.0.5" 1531 | dependencies: 1532 | brace-expansion: "npm:^2.0.1" 1533 | checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed 1534 | languageName: node 1535 | linkType: hard 1536 | 1537 | "minipass-collect@npm:^2.0.1": 1538 | version: 2.0.1 1539 | resolution: "minipass-collect@npm:2.0.1" 1540 | dependencies: 1541 | minipass: "npm:^7.0.3" 1542 | checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e 1543 | languageName: node 1544 | linkType: hard 1545 | 1546 | "minipass-fetch@npm:^4.0.0": 1547 | version: 4.0.1 1548 | resolution: "minipass-fetch@npm:4.0.1" 1549 | dependencies: 1550 | encoding: "npm:^0.1.13" 1551 | minipass: "npm:^7.0.3" 1552 | minipass-sized: "npm:^1.0.3" 1553 | minizlib: "npm:^3.0.1" 1554 | dependenciesMeta: 1555 | encoding: 1556 | optional: true 1557 | checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c 1558 | languageName: node 1559 | linkType: hard 1560 | 1561 | "minipass-flush@npm:^1.0.5": 1562 | version: 1.0.5 1563 | resolution: "minipass-flush@npm:1.0.5" 1564 | dependencies: 1565 | minipass: "npm:^3.0.0" 1566 | checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd 1567 | languageName: node 1568 | linkType: hard 1569 | 1570 | "minipass-pipeline@npm:^1.2.4": 1571 | version: 1.2.4 1572 | resolution: "minipass-pipeline@npm:1.2.4" 1573 | dependencies: 1574 | minipass: "npm:^3.0.0" 1575 | checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 1576 | languageName: node 1577 | linkType: hard 1578 | 1579 | "minipass-sized@npm:^1.0.3": 1580 | version: 1.0.3 1581 | resolution: "minipass-sized@npm:1.0.3" 1582 | dependencies: 1583 | minipass: "npm:^3.0.0" 1584 | checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb 1585 | languageName: node 1586 | linkType: hard 1587 | 1588 | "minipass@npm:^3.0.0": 1589 | version: 3.1.1 1590 | resolution: "minipass@npm:3.1.1" 1591 | dependencies: 1592 | yallist: "npm:^4.0.0" 1593 | checksum: 10c0/5ff6b2ec1dd2f76572a82e4e08fe5ad8c37e4c4a9ae21e20cc6cb802ccc99f77116490e73bde81857ff90c1af32681b9ef659f1c02133d9709e713262c1b859e 1594 | languageName: node 1595 | linkType: hard 1596 | 1597 | "minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": 1598 | version: 7.1.2 1599 | resolution: "minipass@npm:7.1.2" 1600 | checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 1601 | languageName: node 1602 | linkType: hard 1603 | 1604 | "minizlib@npm:^3.0.1": 1605 | version: 3.0.1 1606 | resolution: "minizlib@npm:3.0.1" 1607 | dependencies: 1608 | minipass: "npm:^7.0.4" 1609 | rimraf: "npm:^5.0.5" 1610 | checksum: 10c0/82f8bf70da8af656909a8ee299d7ed3b3372636749d29e105f97f20e88971be31f5ed7642f2e898f00283b68b701cc01307401cdc209b0efc5dd3818220e5093 1611 | languageName: node 1612 | linkType: hard 1613 | 1614 | "mkdirp@npm:^3.0.1": 1615 | version: 3.0.1 1616 | resolution: "mkdirp@npm:3.0.1" 1617 | bin: 1618 | mkdirp: dist/cjs/src/bin.js 1619 | checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d 1620 | languageName: node 1621 | linkType: hard 1622 | 1623 | "ms@npm:^2.1.1": 1624 | version: 2.1.2 1625 | resolution: "ms@npm:2.1.2" 1626 | checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc 1627 | languageName: node 1628 | linkType: hard 1629 | 1630 | "ms@npm:^2.1.3": 1631 | version: 2.1.3 1632 | resolution: "ms@npm:2.1.3" 1633 | checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 1634 | languageName: node 1635 | linkType: hard 1636 | 1637 | "nanoid@npm:^3.3.8": 1638 | version: 3.3.9 1639 | resolution: "nanoid@npm:3.3.9" 1640 | bin: 1641 | nanoid: bin/nanoid.cjs 1642 | checksum: 10c0/4515abe53db7b150cf77074558efc20d8e916d6910d557b5ce72e8bbf6f8e7554d3d7a0d180bfa65e5d8e99aa51b207aa8a3bf5f3b56233897b146d592e30b24 1643 | languageName: node 1644 | linkType: hard 1645 | 1646 | "negotiator@npm:^1.0.0": 1647 | version: 1.0.0 1648 | resolution: "negotiator@npm:1.0.0" 1649 | checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b 1650 | languageName: node 1651 | linkType: hard 1652 | 1653 | "node-addon-api@npm:^7.0.0": 1654 | version: 7.1.1 1655 | resolution: "node-addon-api@npm:7.1.1" 1656 | dependencies: 1657 | node-gyp: "npm:latest" 1658 | checksum: 10c0/fb32a206276d608037fa1bcd7e9921e177fe992fc610d098aa3128baca3c0050fc1e014fa007e9b3874cf865ddb4f5bd9f43ccb7cbbbe4efaff6a83e920b17e9 1659 | languageName: node 1660 | linkType: hard 1661 | 1662 | "node-gyp@npm:latest": 1663 | version: 11.1.0 1664 | resolution: "node-gyp@npm:11.1.0" 1665 | dependencies: 1666 | env-paths: "npm:^2.2.0" 1667 | exponential-backoff: "npm:^3.1.1" 1668 | glob: "npm:^10.3.10" 1669 | graceful-fs: "npm:^4.2.6" 1670 | make-fetch-happen: "npm:^14.0.3" 1671 | nopt: "npm:^8.0.0" 1672 | proc-log: "npm:^5.0.0" 1673 | semver: "npm:^7.3.5" 1674 | tar: "npm:^7.4.3" 1675 | which: "npm:^5.0.0" 1676 | bin: 1677 | node-gyp: bin/node-gyp.js 1678 | checksum: 10c0/c38977ce502f1ea41ba2b8721bd5b49bc3d5b3f813eabfac8414082faf0620ccb5211e15c4daecc23ed9f5e3e9cc4da00e575a0bcfc2a95a069294f2afa1e0cd 1679 | languageName: node 1680 | linkType: hard 1681 | 1682 | "node-releases@npm:^2.0.19": 1683 | version: 2.0.19 1684 | resolution: "node-releases@npm:2.0.19" 1685 | checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa 1686 | languageName: node 1687 | linkType: hard 1688 | 1689 | "nopt@npm:^8.0.0": 1690 | version: 8.1.0 1691 | resolution: "nopt@npm:8.1.0" 1692 | dependencies: 1693 | abbrev: "npm:^3.0.0" 1694 | bin: 1695 | nopt: bin/nopt.js 1696 | checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef 1697 | languageName: node 1698 | linkType: hard 1699 | 1700 | "p-map@npm:^7.0.2": 1701 | version: 7.0.3 1702 | resolution: "p-map@npm:7.0.3" 1703 | checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c 1704 | languageName: node 1705 | linkType: hard 1706 | 1707 | "package-json-from-dist@npm:^1.0.0": 1708 | version: 1.0.1 1709 | resolution: "package-json-from-dist@npm:1.0.1" 1710 | checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b 1711 | languageName: node 1712 | linkType: hard 1713 | 1714 | "path-key@npm:^3.1.0": 1715 | version: 3.1.1 1716 | resolution: "path-key@npm:3.1.1" 1717 | checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c 1718 | languageName: node 1719 | linkType: hard 1720 | 1721 | "path-scurry@npm:^1.11.1": 1722 | version: 1.11.1 1723 | resolution: "path-scurry@npm:1.11.1" 1724 | dependencies: 1725 | lru-cache: "npm:^10.2.0" 1726 | minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" 1727 | checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d 1728 | languageName: node 1729 | linkType: hard 1730 | 1731 | "picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": 1732 | version: 1.1.1 1733 | resolution: "picocolors@npm:1.1.1" 1734 | checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 1735 | languageName: node 1736 | linkType: hard 1737 | 1738 | "picomatch@npm:^2.3.1": 1739 | version: 2.3.1 1740 | resolution: "picomatch@npm:2.3.1" 1741 | checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be 1742 | languageName: node 1743 | linkType: hard 1744 | 1745 | "postcss@npm:^8.5.3": 1746 | version: 8.5.3 1747 | resolution: "postcss@npm:8.5.3" 1748 | dependencies: 1749 | nanoid: "npm:^3.3.8" 1750 | picocolors: "npm:^1.1.1" 1751 | source-map-js: "npm:^1.2.1" 1752 | checksum: 10c0/b75510d7b28c3ab728c8733dd01538314a18c52af426f199a3c9177e63eb08602a3938bfb66b62dc01350b9aed62087eabbf229af97a1659eb8d3513cec823b3 1753 | languageName: node 1754 | linkType: hard 1755 | 1756 | "proc-log@npm:^5.0.0": 1757 | version: 5.0.0 1758 | resolution: "proc-log@npm:5.0.0" 1759 | checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 1760 | languageName: node 1761 | linkType: hard 1762 | 1763 | "promise-retry@npm:^2.0.1": 1764 | version: 2.0.1 1765 | resolution: "promise-retry@npm:2.0.1" 1766 | dependencies: 1767 | err-code: "npm:^2.0.2" 1768 | retry: "npm:^0.12.0" 1769 | checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 1770 | languageName: node 1771 | linkType: hard 1772 | 1773 | "react-dom@npm:^18.2.0": 1774 | version: 18.3.1 1775 | resolution: "react-dom@npm:18.3.1" 1776 | dependencies: 1777 | loose-envify: "npm:^1.1.0" 1778 | scheduler: "npm:^0.23.2" 1779 | peerDependencies: 1780 | react: ^18.3.1 1781 | checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 1782 | languageName: node 1783 | linkType: hard 1784 | 1785 | "react-refresh@npm:^0.14.2": 1786 | version: 0.14.2 1787 | resolution: "react-refresh@npm:0.14.2" 1788 | checksum: 10c0/875b72ef56b147a131e33f2abd6ec059d1989854b3ff438898e4f9310bfcc73acff709445b7ba843318a953cb9424bcc2c05af2b3d80011cee28f25aef3e2ebb 1789 | languageName: node 1790 | linkType: hard 1791 | 1792 | "react@npm:^18.2.0": 1793 | version: 18.3.1 1794 | resolution: "react@npm:18.3.1" 1795 | dependencies: 1796 | loose-envify: "npm:^1.1.0" 1797 | checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 1798 | languageName: node 1799 | linkType: hard 1800 | 1801 | "readdirp@npm:^4.0.1": 1802 | version: 4.1.2 1803 | resolution: "readdirp@npm:4.1.2" 1804 | checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 1805 | languageName: node 1806 | linkType: hard 1807 | 1808 | "retry@npm:^0.12.0": 1809 | version: 0.12.0 1810 | resolution: "retry@npm:0.12.0" 1811 | checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe 1812 | languageName: node 1813 | linkType: hard 1814 | 1815 | "rimraf@npm:^5.0.5": 1816 | version: 5.0.10 1817 | resolution: "rimraf@npm:5.0.10" 1818 | dependencies: 1819 | glob: "npm:^10.3.7" 1820 | bin: 1821 | rimraf: dist/esm/bin.mjs 1822 | checksum: 10c0/7da4fd0e15118ee05b918359462cfa1e7fe4b1228c7765195a45b55576e8c15b95db513b8466ec89129666f4af45ad978a3057a02139afba1a63512a2d9644cc 1823 | languageName: node 1824 | linkType: hard 1825 | 1826 | "rollup@npm:^4.30.1": 1827 | version: 4.35.0 1828 | resolution: "rollup@npm:4.35.0" 1829 | dependencies: 1830 | "@rollup/rollup-android-arm-eabi": "npm:4.35.0" 1831 | "@rollup/rollup-android-arm64": "npm:4.35.0" 1832 | "@rollup/rollup-darwin-arm64": "npm:4.35.0" 1833 | "@rollup/rollup-darwin-x64": "npm:4.35.0" 1834 | "@rollup/rollup-freebsd-arm64": "npm:4.35.0" 1835 | "@rollup/rollup-freebsd-x64": "npm:4.35.0" 1836 | "@rollup/rollup-linux-arm-gnueabihf": "npm:4.35.0" 1837 | "@rollup/rollup-linux-arm-musleabihf": "npm:4.35.0" 1838 | "@rollup/rollup-linux-arm64-gnu": "npm:4.35.0" 1839 | "@rollup/rollup-linux-arm64-musl": "npm:4.35.0" 1840 | "@rollup/rollup-linux-loongarch64-gnu": "npm:4.35.0" 1841 | "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.35.0" 1842 | "@rollup/rollup-linux-riscv64-gnu": "npm:4.35.0" 1843 | "@rollup/rollup-linux-s390x-gnu": "npm:4.35.0" 1844 | "@rollup/rollup-linux-x64-gnu": "npm:4.35.0" 1845 | "@rollup/rollup-linux-x64-musl": "npm:4.35.0" 1846 | "@rollup/rollup-win32-arm64-msvc": "npm:4.35.0" 1847 | "@rollup/rollup-win32-ia32-msvc": "npm:4.35.0" 1848 | "@rollup/rollup-win32-x64-msvc": "npm:4.35.0" 1849 | "@types/estree": "npm:1.0.6" 1850 | fsevents: "npm:~2.3.2" 1851 | dependenciesMeta: 1852 | "@rollup/rollup-android-arm-eabi": 1853 | optional: true 1854 | "@rollup/rollup-android-arm64": 1855 | optional: true 1856 | "@rollup/rollup-darwin-arm64": 1857 | optional: true 1858 | "@rollup/rollup-darwin-x64": 1859 | optional: true 1860 | "@rollup/rollup-freebsd-arm64": 1861 | optional: true 1862 | "@rollup/rollup-freebsd-x64": 1863 | optional: true 1864 | "@rollup/rollup-linux-arm-gnueabihf": 1865 | optional: true 1866 | "@rollup/rollup-linux-arm-musleabihf": 1867 | optional: true 1868 | "@rollup/rollup-linux-arm64-gnu": 1869 | optional: true 1870 | "@rollup/rollup-linux-arm64-musl": 1871 | optional: true 1872 | "@rollup/rollup-linux-loongarch64-gnu": 1873 | optional: true 1874 | "@rollup/rollup-linux-powerpc64le-gnu": 1875 | optional: true 1876 | "@rollup/rollup-linux-riscv64-gnu": 1877 | optional: true 1878 | "@rollup/rollup-linux-s390x-gnu": 1879 | optional: true 1880 | "@rollup/rollup-linux-x64-gnu": 1881 | optional: true 1882 | "@rollup/rollup-linux-x64-musl": 1883 | optional: true 1884 | "@rollup/rollup-win32-arm64-msvc": 1885 | optional: true 1886 | "@rollup/rollup-win32-ia32-msvc": 1887 | optional: true 1888 | "@rollup/rollup-win32-x64-msvc": 1889 | optional: true 1890 | fsevents: 1891 | optional: true 1892 | bin: 1893 | rollup: dist/bin/rollup 1894 | checksum: 10c0/5a04add5a48173b1d95deb5422a96833b7df91b14ccec462c048be48241a79ecee2c1b843511b91ca8b6124bdbae134ccfebe80d4222a93e98e73795d161d3cc 1895 | languageName: node 1896 | linkType: hard 1897 | 1898 | "safer-buffer@npm:>= 2.1.2 < 3.0.0": 1899 | version: 2.1.2 1900 | resolution: "safer-buffer@npm:2.1.2" 1901 | checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 1902 | languageName: node 1903 | linkType: hard 1904 | 1905 | "sass@npm:^1.69.5": 1906 | version: 1.85.1 1907 | resolution: "sass@npm:1.85.1" 1908 | dependencies: 1909 | "@parcel/watcher": "npm:^2.4.1" 1910 | chokidar: "npm:^4.0.0" 1911 | immutable: "npm:^5.0.2" 1912 | source-map-js: "npm:>=0.6.2 <2.0.0" 1913 | dependenciesMeta: 1914 | "@parcel/watcher": 1915 | optional: true 1916 | bin: 1917 | sass: sass.js 1918 | checksum: 10c0/f843aa1df1dca2f0e9cb2fb247e4939fd514ae4c182cdd1900a0622c0d71b40dfb1c4225f78b78e165a318287ca137ec597695db3e496408bd16a921a2bc2b3f 1919 | languageName: node 1920 | linkType: hard 1921 | 1922 | "scheduler@npm:^0.23.2": 1923 | version: 0.23.2 1924 | resolution: "scheduler@npm:0.23.2" 1925 | dependencies: 1926 | loose-envify: "npm:^1.1.0" 1927 | checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 1928 | languageName: node 1929 | linkType: hard 1930 | 1931 | "semver@npm:^6.3.1": 1932 | version: 6.3.1 1933 | resolution: "semver@npm:6.3.1" 1934 | bin: 1935 | semver: bin/semver.js 1936 | checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d 1937 | languageName: node 1938 | linkType: hard 1939 | 1940 | "semver@npm:^7.3.5": 1941 | version: 7.7.1 1942 | resolution: "semver@npm:7.7.1" 1943 | bin: 1944 | semver: bin/semver.js 1945 | checksum: 10c0/fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958 1946 | languageName: node 1947 | linkType: hard 1948 | 1949 | "shebang-command@npm:^2.0.0": 1950 | version: 2.0.0 1951 | resolution: "shebang-command@npm:2.0.0" 1952 | dependencies: 1953 | shebang-regex: "npm:^3.0.0" 1954 | checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e 1955 | languageName: node 1956 | linkType: hard 1957 | 1958 | "shebang-regex@npm:^3.0.0": 1959 | version: 3.0.0 1960 | resolution: "shebang-regex@npm:3.0.0" 1961 | checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 1962 | languageName: node 1963 | linkType: hard 1964 | 1965 | "signal-exit@npm:^4.0.1": 1966 | version: 4.1.0 1967 | resolution: "signal-exit@npm:4.1.0" 1968 | checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 1969 | languageName: node 1970 | linkType: hard 1971 | 1972 | "smart-buffer@npm:^4.2.0": 1973 | version: 4.2.0 1974 | resolution: "smart-buffer@npm:4.2.0" 1975 | checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 1976 | languageName: node 1977 | linkType: hard 1978 | 1979 | "socks-proxy-agent@npm:^8.0.3": 1980 | version: 8.0.5 1981 | resolution: "socks-proxy-agent@npm:8.0.5" 1982 | dependencies: 1983 | agent-base: "npm:^7.1.2" 1984 | debug: "npm:^4.3.4" 1985 | socks: "npm:^2.8.3" 1986 | checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 1987 | languageName: node 1988 | linkType: hard 1989 | 1990 | "socks@npm:^2.8.3": 1991 | version: 2.8.4 1992 | resolution: "socks@npm:2.8.4" 1993 | dependencies: 1994 | ip-address: "npm:^9.0.5" 1995 | smart-buffer: "npm:^4.2.0" 1996 | checksum: 10c0/00c3271e233ccf1fb83a3dd2060b94cc37817e0f797a93c560b9a7a86c4a0ec2961fb31263bdd24a3c28945e24868b5f063cd98744171d9e942c513454b50ae5 1997 | languageName: node 1998 | linkType: hard 1999 | 2000 | "source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.2.1": 2001 | version: 1.2.1 2002 | resolution: "source-map-js@npm:1.2.1" 2003 | checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf 2004 | languageName: node 2005 | linkType: hard 2006 | 2007 | "sprintf-js@npm:^1.1.3": 2008 | version: 1.1.3 2009 | resolution: "sprintf-js@npm:1.1.3" 2010 | checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec 2011 | languageName: node 2012 | linkType: hard 2013 | 2014 | "ssri@npm:^12.0.0": 2015 | version: 12.0.0 2016 | resolution: "ssri@npm:12.0.0" 2017 | dependencies: 2018 | minipass: "npm:^7.0.3" 2019 | checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d 2020 | languageName: node 2021 | linkType: hard 2022 | 2023 | "string-width-cjs@npm:string-width@^4.2.0": 2024 | version: 4.2.3 2025 | resolution: "string-width@npm:4.2.3" 2026 | dependencies: 2027 | emoji-regex: "npm:^8.0.0" 2028 | is-fullwidth-code-point: "npm:^3.0.0" 2029 | strip-ansi: "npm:^6.0.1" 2030 | checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b 2031 | languageName: node 2032 | linkType: hard 2033 | 2034 | "string-width@npm:^4.1.0": 2035 | version: 4.2.0 2036 | resolution: "string-width@npm:4.2.0" 2037 | dependencies: 2038 | emoji-regex: "npm:^8.0.0" 2039 | is-fullwidth-code-point: "npm:^3.0.0" 2040 | strip-ansi: "npm:^6.0.0" 2041 | checksum: 10c0/54d6451abfdca5f147f930fe4eb1bbdf409ba115042146bb93dae309342124afcfeb776149075f60a6f8d6b2d3b68f48702fc96144b37789dd7a4752284b9648 2042 | languageName: node 2043 | linkType: hard 2044 | 2045 | "string-width@npm:^5.0.1, string-width@npm:^5.1.2": 2046 | version: 5.1.2 2047 | resolution: "string-width@npm:5.1.2" 2048 | dependencies: 2049 | eastasianwidth: "npm:^0.2.0" 2050 | emoji-regex: "npm:^9.2.2" 2051 | strip-ansi: "npm:^7.0.1" 2052 | checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca 2053 | languageName: node 2054 | linkType: hard 2055 | 2056 | "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.1": 2057 | version: 6.0.1 2058 | resolution: "strip-ansi@npm:6.0.1" 2059 | dependencies: 2060 | ansi-regex: "npm:^5.0.1" 2061 | checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 2062 | languageName: node 2063 | linkType: hard 2064 | 2065 | "strip-ansi@npm:^6.0.0": 2066 | version: 6.0.0 2067 | resolution: "strip-ansi@npm:6.0.0" 2068 | dependencies: 2069 | ansi-regex: "npm:^5.0.0" 2070 | checksum: 10c0/85257c80250541cc0e65088c7dc768563bdbd1bf7120471d6d3a73cdc60e8149a50038c12a6fd4a30b674587f306ae42e2cc73ac3095daf193633daa0bd8f928 2071 | languageName: node 2072 | linkType: hard 2073 | 2074 | "strip-ansi@npm:^7.0.1": 2075 | version: 7.1.0 2076 | resolution: "strip-ansi@npm:7.1.0" 2077 | dependencies: 2078 | ansi-regex: "npm:^6.0.1" 2079 | checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 2080 | languageName: node 2081 | linkType: hard 2082 | 2083 | "svg-workflow-canvas-example@workspace:.": 2084 | version: 0.0.0-use.local 2085 | resolution: "svg-workflow-canvas-example@workspace:." 2086 | dependencies: 2087 | "@types/react": "npm:^18.2.15" 2088 | "@types/react-dom": "npm:^18.2.7" 2089 | "@vitejs/plugin-react": "npm:^4.0.3" 2090 | react: "npm:^18.2.0" 2091 | react-dom: "npm:^18.2.0" 2092 | sass: "npm:^1.69.5" 2093 | svg-workflow-canvas: "link:.." 2094 | typescript: "npm:^5.0.2" 2095 | vite: "npm:^6.2.2" 2096 | languageName: unknown 2097 | linkType: soft 2098 | 2099 | "svg-workflow-canvas@link:..::locator=svg-workflow-canvas-example%40workspace%3A.": 2100 | version: 0.0.0-use.local 2101 | resolution: "svg-workflow-canvas@link:..::locator=svg-workflow-canvas-example%40workspace%3A." 2102 | languageName: node 2103 | linkType: soft 2104 | 2105 | "tar@npm:^7.4.3": 2106 | version: 7.4.3 2107 | resolution: "tar@npm:7.4.3" 2108 | dependencies: 2109 | "@isaacs/fs-minipass": "npm:^4.0.0" 2110 | chownr: "npm:^3.0.0" 2111 | minipass: "npm:^7.1.2" 2112 | minizlib: "npm:^3.0.1" 2113 | mkdirp: "npm:^3.0.1" 2114 | yallist: "npm:^5.0.0" 2115 | checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d 2116 | languageName: node 2117 | linkType: hard 2118 | 2119 | "to-fast-properties@npm:^2.0.0": 2120 | version: 2.0.0 2121 | resolution: "to-fast-properties@npm:2.0.0" 2122 | checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 2123 | languageName: node 2124 | linkType: hard 2125 | 2126 | "to-regex-range@npm:^5.0.1": 2127 | version: 5.0.1 2128 | resolution: "to-regex-range@npm:5.0.1" 2129 | dependencies: 2130 | is-number: "npm:^7.0.0" 2131 | checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 2132 | languageName: node 2133 | linkType: hard 2134 | 2135 | "typescript@npm:^5.0.2": 2136 | version: 5.8.2 2137 | resolution: "typescript@npm:5.8.2" 2138 | bin: 2139 | tsc: bin/tsc 2140 | tsserver: bin/tsserver 2141 | checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 2142 | languageName: node 2143 | linkType: hard 2144 | 2145 | "typescript@patch:typescript@npm%3A^5.0.2#optional!builtin": 2146 | version: 5.8.2 2147 | resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=8c6c40" 2148 | bin: 2149 | tsc: bin/tsc 2150 | tsserver: bin/tsserver 2151 | checksum: 10c0/8a6cd29dfb59bd5a978407b93ae0edb530ee9376a5b95a42ad057a6f80ffb0c410489ccd6fe48d1d0dfad6e8adf5d62d3874bbd251f488ae30e11a1ce6dabd28 2152 | languageName: node 2153 | linkType: hard 2154 | 2155 | "unique-filename@npm:^4.0.0": 2156 | version: 4.0.0 2157 | resolution: "unique-filename@npm:4.0.0" 2158 | dependencies: 2159 | unique-slug: "npm:^5.0.0" 2160 | checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc 2161 | languageName: node 2162 | linkType: hard 2163 | 2164 | "unique-slug@npm:^5.0.0": 2165 | version: 5.0.0 2166 | resolution: "unique-slug@npm:5.0.0" 2167 | dependencies: 2168 | imurmurhash: "npm:^0.1.4" 2169 | checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 2170 | languageName: node 2171 | linkType: hard 2172 | 2173 | "update-browserslist-db@npm:^1.1.1": 2174 | version: 1.1.3 2175 | resolution: "update-browserslist-db@npm:1.1.3" 2176 | dependencies: 2177 | escalade: "npm:^3.2.0" 2178 | picocolors: "npm:^1.1.1" 2179 | peerDependencies: 2180 | browserslist: ">= 4.21.0" 2181 | bin: 2182 | update-browserslist-db: cli.js 2183 | checksum: 10c0/682e8ecbf9de474a626f6462aa85927936cdd256fe584c6df2508b0df9f7362c44c957e9970df55dfe44d3623807d26316ea2c7d26b80bb76a16c56c37233c32 2184 | languageName: node 2185 | linkType: hard 2186 | 2187 | "vite@npm:^6.2.2": 2188 | version: 6.2.2 2189 | resolution: "vite@npm:6.2.2" 2190 | dependencies: 2191 | esbuild: "npm:^0.25.0" 2192 | fsevents: "npm:~2.3.3" 2193 | postcss: "npm:^8.5.3" 2194 | rollup: "npm:^4.30.1" 2195 | peerDependencies: 2196 | "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 2197 | jiti: ">=1.21.0" 2198 | less: "*" 2199 | lightningcss: ^1.21.0 2200 | sass: "*" 2201 | sass-embedded: "*" 2202 | stylus: "*" 2203 | sugarss: "*" 2204 | terser: ^5.16.0 2205 | tsx: ^4.8.1 2206 | yaml: ^2.4.2 2207 | dependenciesMeta: 2208 | fsevents: 2209 | optional: true 2210 | peerDependenciesMeta: 2211 | "@types/node": 2212 | optional: true 2213 | jiti: 2214 | optional: true 2215 | less: 2216 | optional: true 2217 | lightningcss: 2218 | optional: true 2219 | sass: 2220 | optional: true 2221 | sass-embedded: 2222 | optional: true 2223 | stylus: 2224 | optional: true 2225 | sugarss: 2226 | optional: true 2227 | terser: 2228 | optional: true 2229 | tsx: 2230 | optional: true 2231 | yaml: 2232 | optional: true 2233 | bin: 2234 | vite: bin/vite.js 2235 | checksum: 10c0/52f5b1c10cfe5e3b6382c6de1811ebbf76df9b5a8bab3d65169446c6b54a5f1528f775b1548009a6d8aad11def20fba046bb3e9abb10c0c2c9ccd78118623bb8 2236 | languageName: node 2237 | linkType: hard 2238 | 2239 | "which@npm:^2.0.1": 2240 | version: 2.0.2 2241 | resolution: "which@npm:2.0.2" 2242 | dependencies: 2243 | isexe: "npm:^2.0.0" 2244 | bin: 2245 | node-which: ./bin/node-which 2246 | checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f 2247 | languageName: node 2248 | linkType: hard 2249 | 2250 | "which@npm:^5.0.0": 2251 | version: 5.0.0 2252 | resolution: "which@npm:5.0.0" 2253 | dependencies: 2254 | isexe: "npm:^3.1.1" 2255 | bin: 2256 | node-which: bin/which.js 2257 | checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b 2258 | languageName: node 2259 | linkType: hard 2260 | 2261 | "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": 2262 | version: 7.0.0 2263 | resolution: "wrap-ansi@npm:7.0.0" 2264 | dependencies: 2265 | ansi-styles: "npm:^4.0.0" 2266 | string-width: "npm:^4.1.0" 2267 | strip-ansi: "npm:^6.0.0" 2268 | checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da 2269 | languageName: node 2270 | linkType: hard 2271 | 2272 | "wrap-ansi@npm:^8.1.0": 2273 | version: 8.1.0 2274 | resolution: "wrap-ansi@npm:8.1.0" 2275 | dependencies: 2276 | ansi-styles: "npm:^6.1.0" 2277 | string-width: "npm:^5.0.1" 2278 | strip-ansi: "npm:^7.0.1" 2279 | checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 2280 | languageName: node 2281 | linkType: hard 2282 | 2283 | "yallist@npm:^3.0.2": 2284 | version: 3.1.1 2285 | resolution: "yallist@npm:3.1.1" 2286 | checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 2287 | languageName: node 2288 | linkType: hard 2289 | 2290 | "yallist@npm:^4.0.0": 2291 | version: 4.0.0 2292 | resolution: "yallist@npm:4.0.0" 2293 | checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a 2294 | languageName: node 2295 | linkType: hard 2296 | 2297 | "yallist@npm:^5.0.0": 2298 | version: 5.0.0 2299 | resolution: "yallist@npm:5.0.0" 2300 | checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 2301 | languageName: node 2302 | linkType: hard 2303 | --------------------------------------------------------------------------------