├── .nvmrc ├── .prettierrc ├── packages ├── examples │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── index.css │ │ ├── views │ │ │ ├── SimpleExample.tsx │ │ │ ├── util.ts │ │ │ ├── ColorCube.tsx │ │ │ └── DancingPoints.tsx │ │ └── index.tsx │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── dist │ │ ├── assets │ │ │ └── index-b2114c28.css │ │ └── index.html │ ├── index.html │ ├── tsconfig.json │ └── package.json ├── mathbox-react │ ├── babel.config.js │ ├── src │ │ ├── index.ts │ │ ├── testSetup.ts │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── MathboxAPIContext.ts │ │ │ ├── ContainedMathbox.tsx │ │ │ ├── Mathbox.tsx │ │ │ ├── util.ts │ │ │ ├── components.spec.tsx │ │ │ └── components.tsx │ │ ├── threestrap │ │ │ ├── index.ts │ │ │ ├── ThreestrapContext.ts │ │ │ ├── Renderer.ts │ │ │ ├── threestrap.ts │ │ │ └── Controls.ts │ │ ├── stories │ │ │ ├── Mathbox.stories.tsx │ │ │ ├── Grid.stories.tsx │ │ │ ├── utils.tsx │ │ │ ├── Cartesian.stories.tsx │ │ │ ├── Surface.stories.tsx │ │ │ ├── Area.stories.tsx │ │ │ ├── Play.stories.tsx │ │ │ └── Point.stories.tsx │ │ └── testUtils.ts │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── .storybook │ │ ├── preview.js │ │ └── main.js │ ├── jest.config.js │ ├── README.md │ └── package.json ├── tsconfig │ ├── package.json │ └── base.json └── eslint-config │ ├── package.json │ └── index.js ├── .eslintrc.js ├── .vscode └── settings.json ├── .yarnrc.yml ├── turbo.json ├── scripts └── run_examples.sh ├── .github ├── renovate.json └── workflows │ └── tests.yaml ├── .gitignore ├── package.json ├── README.md ├── LICENSE ├── .pre-commit-config.yaml ├── .secrets.baseline └── .yarn └── plugins └── @yarnpkg └── plugin-workspace-tools.cjs /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.12.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | -------------------------------------------------------------------------------- /packages/examples/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["@mathbox-react/eslint-config"], 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.alwaysShowStatus": true, 3 | "eslint.workingDirectories": [{ "pattern": "./packages/*/" }] 4 | } 5 | -------------------------------------------------------------------------------- /packages/mathbox-react/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [["@babel/preset-env", { targets: { node: "current" } }]], 3 | } 4 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Threestrap from "./threestrap" 2 | 3 | export * from "./components" 4 | export { Threestrap } 5 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/testSetup.ts: -------------------------------------------------------------------------------- 1 | import { getMockContext } from "./testUtils" 2 | 3 | HTMLCanvasElement.prototype.getContext = getMockContext 4 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mathbox-react/tsconfig", 3 | "private": true, 4 | "files": [ 5 | "base.json" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/mathbox-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mathbox-react/tsconfig/base.json", 3 | "compilerOptions": { 4 | "outDir": "./build/esm" 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/examples/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import react from "@vitejs/plugin-react" 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /packages/mathbox-react/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mathbox-react/tsconfig/base.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "./build/cjs" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./components" 2 | export { default as Mathbox } from "./Mathbox" 3 | export { default as ContainedMathbox } from "./ContainedMathbox" 4 | 5 | export { useMathbox } from "./MathboxAPIContext" 6 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | enableScripts: false 4 | 5 | plugins: 6 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 7 | spec: "@yarnpkg/plugin-workspace-tools" 8 | 9 | yarnPath: .yarn/releases/yarn-3.3.1.cjs 10 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/threestrap/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Renderer } from "./Renderer" 2 | export { default as Controls } from "./Controls" 3 | export { useThreestrap } from "./ThreestrapContext" 4 | export type { Threestrap } from "./threestrap" 5 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "lint": {}, 5 | "test": {}, 6 | "build": { 7 | "dependsOn": ["^build"], 8 | "outputs": ["dist/**", "build/**"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/examples/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /scripts/run_examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | yarn workspace mathbox-react build 3 | 4 | concurrently \ 5 | --names "mathbox-react,examples" \ 6 | --prefix-colors "cyan,magenta" \ 7 | "yarn workspace mathbox-react build-watch" \ 8 | "yarn workspace examples dev" 9 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "labels": ["dependencies"], 4 | "packageRules": [ 5 | { 6 | "matchDepTypes": ["devDependencies"], 7 | "automerge": true, 8 | "automergeType": "branch" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/mathbox-react/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mathbox-react/tsconfig/base.json", 3 | "compilerOptions": { 4 | "outDir": "./build/esm" 5 | }, 6 | "include": ["src/**/*"], 7 | "exclude": ["src/stories/", "src/**/*.test.tsx", "src/**/*.test.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | 5 | # See https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/sdks 12 | !.yarn/versions 13 | 14 | # turborepo 15 | .turbo 16 | -------------------------------------------------------------------------------- /packages/mathbox-react/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import "mathbox/mathbox.css" 2 | 3 | export const parameters = { 4 | actions: { argTypesRegex: "^on[A-Z].*" }, 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/, 9 | }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/types.ts: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react" 2 | import type { MathboxSelection, NodeType } from "mathbox" 3 | import React from "react" 4 | 5 | export type WithChildren = { 6 | children?: ReactNode | ReactNode[] 7 | } & T 8 | 9 | export type MathboxRef = React.Ref> 10 | -------------------------------------------------------------------------------- /packages/examples/dist/assets/index-b2114c28.css: -------------------------------------------------------------------------------- 1 | body, 2 | html, 3 | #root, 4 | .h-100 { 5 | height: 100%; 6 | } 7 | form { 8 | width: 400px; 9 | z-index: 10; 10 | position: absolute; 11 | top: 0px; 12 | left: 0px; 13 | background-color: #eee; 14 | display: grid; 15 | grid-template-columns: auto auto 1fr; 16 | column-gap: 1rem; 17 | } 18 | -------------------------------------------------------------------------------- /packages/mathbox-react/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 3 | addons: [ 4 | "@storybook/addon-links", 5 | "@storybook/addon-essentials", 6 | "@storybook/addon-interactions", 7 | ], 8 | framework: "@storybook/react", 9 | core: { 10 | builder: "webpack5", 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /packages/mathbox-react/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | transform: { 4 | "^.+\\.(ts|tsx)?$": "ts-jest", 5 | "^.+\\.(js|jsx)$": "babel-jest", 6 | }, 7 | transformIgnorePatterns: [ 8 | "node_modules/(?!(mathbox|threestrap|three|shadergraph)/)", 9 | ], 10 | testEnvironment: "jsdom", 11 | setupFiles: ["./src/testSetup.ts"], 12 | } 13 | -------------------------------------------------------------------------------- /packages/examples/src/index.css: -------------------------------------------------------------------------------- 1 | body, 2 | html, 3 | #root { 4 | height: 100%; 5 | } 6 | 7 | .h-100 { 8 | height: 100%; 9 | } 10 | 11 | form { 12 | width: 400px; 13 | z-index: 10; 14 | 15 | position: absolute; 16 | top: 0px; 17 | left: 0px; 18 | 19 | background-color: #eee; 20 | 21 | display: grid; 22 | grid-template-columns: auto auto 1fr; 23 | column-gap: 1rem; 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | linting_and_tests: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: "18.12.1" 15 | - run: yarn install --immutable 16 | - run: yarn lint 17 | - run: yarn test 18 | -------------------------------------------------------------------------------- /packages/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "skipLibCheck": true, 11 | "allowSyntheticDefaultImports": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true 14 | }, 15 | "exclude": ["build", "node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/Mathbox.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Story, Meta } from "@storybook/react" 3 | 4 | import { ContainedMathbox } from "../components" 5 | 6 | export default { 7 | title: "Mathbox", 8 | component: ContainedMathbox, 9 | argTypes: {}, 10 | } as Meta 11 | 12 | const Template: Story> = () => ( 13 | 14 | ) 15 | 16 | export const HelloWorld = Template.bind({}) 17 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/threestrap/ThreestrapContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react" 2 | import type { Threestrap } from "./threestrap" 3 | 4 | const ThreestrapContext = createContext(null) 5 | 6 | export default ThreestrapContext 7 | 8 | const useThreestrap = () => { 9 | const threestrap = useContext(ThreestrapContext) 10 | if (!threestrap) { 11 | throw new Error("Threestrap context is not available") 12 | } 13 | return threestrap 14 | } 15 | 16 | export { useThreestrap } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mathbox-react-root", 3 | "private": true, 4 | "license": "ISC", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "devDependencies": { 9 | "concurrently": "^8.0.0" 10 | }, 11 | "scripts": { 12 | "examples": "./scripts/run_examples.sh", 13 | "lint": "turbo run lint", 14 | "test": "turbo run test", 15 | "build": "turbo run build" 16 | }, 17 | "packageManager": "yarn@3.3.1", 18 | "dependencies": { 19 | "prettier": "^2.8.8", 20 | "turbo": "^1.7.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/examples/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/threestrap/Renderer.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react" 2 | import type { ColorRepresentation } from "three" 3 | import { useThreestrap } from "./ThreestrapContext" 4 | 5 | type RendererProps = { 6 | clearColor?: ColorRepresentation 7 | } 8 | 9 | const Renderer: React.FC = (props) => { 10 | const threestrap = useThreestrap() 11 | useEffect(() => { 12 | if (props.clearColor) { 13 | threestrap.renderer.setClearColor(props.clearColor) 14 | } 15 | }) 16 | return null 17 | } 18 | 19 | export default Renderer 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mathbox React 2 | 3 | React bindings for [Mathbox](https://github.com/unconed/mathbox). 4 | 5 | ## Development 6 | 7 | The project uses [Yarn](https://yarnpkg.com/getting-started/install) and includes two [Yarn Workspaces](https://yarnpkg.com/features/workspaces): 8 | 9 | - [mathbox-react](./mathbox-react/) The actual package code, pbulished to NPM 10 | - [examples](./example/) Examples using `mathbox-react`. 11 | 12 | See individual `package.json` files for available commands. In general, commands should be run via `yarn`, not `npm`. E.g., `yarn install` or `yarn lint`. 13 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/threestrap/threestrap.ts: -------------------------------------------------------------------------------- 1 | import type { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 2 | import type { FirstPersonControls } from "three/examples/jsm/controls/FirstPersonControls" 3 | import type { WebGLRenderer } from "three" 4 | 5 | export interface ThreestrapControls { 6 | orbit: OrbitControls 7 | firstPerson: FirstPersonControls 8 | } 9 | 10 | type Controls = ThreestrapControls[keyof ThreestrapControls] 11 | 12 | // TODO: This should be in threestrap package 13 | export interface Threestrap { 14 | renderer: WebGLRenderer 15 | controls: Controls | null 16 | } 17 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mathbox-react/eslint-config", 3 | "exports": "./index.js", 4 | "main": "./index.js", 5 | "dependencies": { 6 | "@typescript-eslint/eslint-plugin": "^5.47.1", 7 | "@typescript-eslint/parser": "^5.47.1", 8 | "eslint-config-airbnb": "^19.0.4", 9 | "eslint-config-airbnb-typescript": "^17.0.0", 10 | "eslint-config-prettier": "^8.5.0", 11 | "eslint-config-react-app": "^7.0.1", 12 | "eslint-import-resolver-typescript": "^3.5.2", 13 | "eslint-plugin-prettier": "^4.2.1" 14 | }, 15 | "peerDependencies": { 16 | "eslint": "^8.31.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/MathboxAPIContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react" 2 | import { MathboxSelection, NodeType } from "mathbox" 3 | 4 | const MathboxAPIContext = createContext | null>(null) 5 | 6 | /** 7 | * Returns Mathbox API Selection object for the nearest enclosing Mathbox node. 8 | */ 9 | const useMathbox = () => { 10 | const selection = useContext(MathboxAPIContext) 11 | if (!selection) { 12 | throw new Error( 13 | "useMathbox must be used within Mathbox or ContainedMathbox" 14 | ) 15 | } 16 | return selection 17 | } 18 | 19 | export default MathboxAPIContext 20 | 21 | export { useMathbox } 22 | -------------------------------------------------------------------------------- /packages/examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src", "vite.config.ts"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /packages/examples/src/views/SimpleExample.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { ContainedMathbox, Axis, Grid, Cartesian } from "mathbox-react" 3 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 4 | 5 | const SimpleExample: React.FC = () => ( 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | 22 | export default SimpleExample 23 | -------------------------------------------------------------------------------- /packages/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "private": true, 4 | "dependencies": { 5 | "mathbox": "^2.3.1", 6 | "mathbox-react": "workspace:*", 7 | "mathjs": "10.5.2", 8 | "memoize-one": "6.0.0", 9 | "react": "18.1.0", 10 | "react-dom": "18.1.0", 11 | "react-hook-form": "7.31.1", 12 | "react-router-dom": "6.3.0", 13 | "three": "0.148.0", 14 | "typescript": "^5.2.2" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.26", 18 | "@types/react-dom": "^18.0.9", 19 | "@types/three": "0.148.0", 20 | "@vitejs/plugin-react": "^3.0.0", 21 | "eslint": "^8.32.0", 22 | "vite": "^4.0.0" 23 | }, 24 | "scripts": { 25 | "dev": "vite", 26 | "preview": "vite preview", 27 | "lint": "eslint \"**/*.ts?(x)\"" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/examples/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import { BrowserRouter, Routes, Route } from "react-router-dom" 4 | import DancingPoints from "./views/DancingPoints" 5 | import ColorCube from "./views/ColorCube" 6 | import SimpleExample from "./views/SimpleExample" 7 | import "./index.css" 8 | 9 | const rootElement = document.getElementById("root") 10 | if (!rootElement) { 11 | throw new Error("No root element found") 12 | } 13 | 14 | const root = ReactDOM.createRoot(rootElement) 15 | 16 | root.render( 17 | 18 | 19 | } /> 20 | } /> 21 | } /> 22 | 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /packages/mathbox-react/README.md: -------------------------------------------------------------------------------- 1 | # Mathbox React 2 | 3 | React bindings for [Mathbox](https://github.com/unconed/mathbox). 4 | 5 | ## Example 6 | 7 | ```tsx 8 | import React from "react" 9 | import { ContainedMathbox, Axis, Grid, Cartesian } from "mathbox-react" 10 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 11 | 12 | const SimpleExample: React.FC = () => ( 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | 29 | export default SimpleExample 30 | ``` 31 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/Grid.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Story, Meta } from "@storybook/react" 3 | 4 | import { CustomMathbox as Mathbox } from "./utils" 5 | import { Grid } from "../components" 6 | 7 | export default { 8 | title: "Grid", 9 | component: Grid, 10 | argTypes: { 11 | color: { 12 | type: "string", 13 | defaultValue: "rgb(128, 128, 128)", 14 | }, 15 | axes: { 16 | type: "string", 17 | defaultValue: "xy", 18 | }, 19 | }, 20 | } as Meta 21 | 22 | const Template: Story> = (args) => ( 23 | 24 | 25 | 26 | ) 27 | 28 | export const DefaultGrid = Template.bind({}) 29 | DefaultGrid.args = {} 30 | 31 | export const GridXY = Template.bind({}) 32 | GridXY.args = { axes: "xy" } 33 | 34 | export const GridYZ = Template.bind({}) 35 | GridYZ.args = { axes: "yz" } 36 | 37 | export const GridZX = Template.bind({}) 38 | GridZX.args = { axes: "zx" } 39 | -------------------------------------------------------------------------------- /packages/examples/src/views/util.ts: -------------------------------------------------------------------------------- 1 | import { AreaEmitter } from "mathbox" 2 | import * as math from "mathjs" 3 | import memoizeOne from "memoize-one" 4 | 5 | export const isEvaluateableFunc = (argSymbols: string[], dimsOut: number) => { 6 | if (dimsOut !== 1) throw new Error("Not implemented.") 7 | 8 | return (text: string) => { 9 | try { 10 | const funcText = `f(${argSymbols}) = ${text}` 11 | const f = math.evaluate(funcText) 12 | const args = argSymbols.map(() => 0) 13 | const out = f(...args) 14 | return typeof out === "number" 15 | } catch (err) { 16 | return false 17 | } 18 | } 19 | } 20 | 21 | const compile = memoizeOne( 22 | (text: string): math.EvalFunction => math.compile(text) 23 | ) 24 | 25 | export const textToAreaEmitter = ( 26 | text: string, 27 | scope: Record 28 | ): AreaEmitter => { 29 | const funcText = `f(x,y,t) = ${text}` 30 | const compiled = compile(funcText) 31 | const f = compiled.evaluate(scope) 32 | return (emit, x, y, i, j, t) => { 33 | emit(x, f(x, y, t), y) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/utils.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useCallback } from "react" 2 | import { MathboxSelection } from "mathbox" 3 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 4 | import { ContainedMathbox } from "../components" 5 | 6 | type MathboxProps = React.ComponentProps 7 | 8 | const storybookDefaultMathboxOptions = { 9 | plugins: ["core", "controls", "cursor"], 10 | controls: { 11 | klass: OrbitControls, 12 | }, 13 | } 14 | 15 | export const CustomMathbox = (props: MathboxProps) => { 16 | const { options: overrides, ...others } = props 17 | const options = useMemo( 18 | () => ({ 19 | ...storybookDefaultMathboxOptions, 20 | ...(overrides ?? {}), 21 | }), 22 | [overrides] 23 | ) 24 | const setup = useCallback((mathbox: MathboxSelection<"root"> | null) => { 25 | if (mathbox === null) return 26 | mathbox.three.camera.position.set(1, 1, 2) 27 | }, []) 28 | return ( 29 | 30 | {props.children} 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/Cartesian.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Story, Meta } from "@storybook/react" 3 | 4 | import { CustomMathbox as Mathbox } from "./utils" 5 | import { Cartesian, Grid, Axis } from "../components/components" 6 | 7 | export default { 8 | title: "Cartesian", 9 | component: Cartesian, 10 | argTypes: { 11 | range: { 12 | type: "string", 13 | defaultValue: [ 14 | [-1, 1], 15 | [-1, 1], 16 | [-1, 1], 17 | ], 18 | }, 19 | scale: { 20 | type: "string", 21 | defaultValue: [1, 1, 1], 22 | }, 23 | }, 24 | } as Meta 25 | 26 | const Template: Story> = (args) => ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | 37 | export const DefaultCartesian = Template.bind({}) 38 | DefaultCartesian.args = {} 39 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/ContainedMathbox.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, forwardRef } from "react" 2 | import { RootProps, MathBoxOptions, MathboxSelection } from "mathbox" 3 | import Mathbox from "./Mathbox" 4 | import { WithChildren } from "./types" 5 | 6 | type Props = WithChildren< 7 | { 8 | containerId?: string 9 | containerClass?: string 10 | containerStyle?: React.CSSProperties 11 | options?: MathBoxOptions 12 | } & RootProps 13 | > 14 | 15 | const ContainedMathbox = ( 16 | props: Props, 17 | ref: React.Ref> 18 | ) => { 19 | const { children, containerId, containerClass, containerStyle, ...others } = 20 | props 21 | const [container, setContainer] = useState(null) 22 | return ( 23 |
29 | {container && ( 30 | 31 | {children} 32 | 33 | )} 34 |
35 | ) 36 | } 37 | 38 | export default forwardRef(ContainedMathbox) 39 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/Surface.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Story, Meta } from "@storybook/react" 3 | 4 | import { CustomMathbox as Mathbox } from "./utils" 5 | import { Cartesian, Surface, Area, Grid } from "../components/components" 6 | 7 | export default { 8 | title: "Surface", 9 | component: Surface, 10 | argTypes: { 11 | color: { 12 | type: "string", 13 | defaultValue: "rgb(48,144,255)", 14 | }, 15 | shaded: { 16 | type: "boolean", 17 | defaultValue: true, 18 | }, 19 | }, 20 | } as Meta 21 | 22 | const Template: Story> = (args) => ( 23 | 24 | 31 | 32 | { 37 | const y = Math.sin(x - t) * Math.cos(z - t) 38 | emit(x, y, z) 39 | }} 40 | /> 41 | 42 | 43 | 44 | ) 45 | 46 | export const DefaultCartesian = Template.bind({}) 47 | DefaultCartesian.args = {} 48 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/Area.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Story, Meta } from "@storybook/react" 3 | 4 | import { CustomMathbox as Mathbox } from "./utils" 5 | import { Cartesian, Surface, Area, Grid } from "../components/components" 6 | 7 | export default { 8 | title: "Area", 9 | component: Area, 10 | argTypes: { 11 | width: { 12 | type: "number", 13 | defaultValue: 18, 14 | }, 15 | height: { 16 | type: "number", 17 | defaultValue: 18, 18 | }, 19 | live: { 20 | type: "boolean", 21 | defaultValue: true, 22 | }, 23 | }, 24 | } as Meta 25 | 26 | const Template: Story> = (args) => ( 27 | 28 | 35 | 36 | { 39 | const y = Math.sin(x - t) * Math.cos(z - t) 40 | emit(x, y, z) 41 | }} 42 | {...args} 43 | /> 44 | 45 | 46 | 47 | ) 48 | 49 | export const DefaultCartesian = Template.bind({}) 50 | DefaultCartesian.args = {} 51 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/Play.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Story, Meta } from "@storybook/react" 3 | 4 | import { CustomMathbox as Mathbox } from "./utils" 5 | import { Cartesian, Play, Interval, Line, Grid } from "../components/components" 6 | 7 | export default { 8 | title: "Play", 9 | component: Play, 10 | argTypes: {}, 11 | } as Meta 12 | 13 | const Template: Story> = () => ( 14 | 15 | 22 | 23 | { 27 | const y = Math.sin(x - t) 28 | emit(x, y) 29 | }} 30 | /> 31 | 36 | 37 | 47 | 48 | 49 | ) 50 | 51 | export const DefaultCartesian = Template.bind({}) 52 | DefaultCartesian.args = {} 53 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/testUtils.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | type Automock = any 3 | 4 | /** 5 | * A mock that uses proxies to create properties as they are accessed. Supports 6 | * function calls and toPrimitive. 7 | * 8 | * @param specifics Allows specifying specific values for properties. If a prop 9 | * exists on `specifics`, then that value will be used when the property is 10 | * accessed on automock. 11 | * @returns 12 | */ 13 | const getAutomock = >( 14 | specifics?: T 15 | ): Automock => { 16 | const autmockSeed = jest.fn() 17 | const handler = { 18 | get(target: Automock, prop: string | symbol) { 19 | if (prop === Symbol.toPrimitive) { 20 | return () => `Automock: ${JSON.stringify(specifics)}` 21 | } 22 | if (specifics && Reflect.has(specifics, prop)) { 23 | const value = Reflect.get(specifics, prop) 24 | return value 25 | } 26 | if (!Reflect.has(target, prop)) { 27 | Reflect.set(target, prop, getAutomock()) 28 | } 29 | return Reflect.get(target, prop) 30 | }, 31 | apply() { 32 | return getAutomock() 33 | }, 34 | } 35 | return new Proxy(autmockSeed, handler) 36 | } 37 | 38 | export const getMockContext = () => { 39 | const mockContext = getAutomock({ 40 | getParameter: jest.fn((name) => { 41 | if (name === mockContext.VERSION) return "Fake version!" 42 | return getAutomock() 43 | }), 44 | }) 45 | return mockContext 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Chris Chudzicki 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | skip: 3 | - prettier 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v5.0.0 7 | hooks: 8 | - id: trailing-whitespace 9 | - id: end-of-file-fixer 10 | exclude: ".css.d.ts" 11 | - id: check-yaml 12 | - id: check-added-large-files 13 | - id: check-merge-conflict 14 | - id: check-toml 15 | - id: debug-statements 16 | - repo: https://github.com/scop/pre-commit-shfmt 17 | rev: v3.11.0-1 18 | hooks: 19 | - id: shfmt 20 | - repo: https://github.com/adrienverge/yamllint.git 21 | rev: v1.37.1 22 | hooks: 23 | - id: yamllint 24 | args: [--format, parsable, -d, relaxed] 25 | - repo: https://github.com/Yelp/detect-secrets 26 | rev: v1.5.0 27 | hooks: 28 | - id: detect-secrets 29 | args: 30 | - --baseline 31 | - .secrets.baseline 32 | - repo: https://github.com/astral-sh/ruff-pre-commit 33 | rev: "v0.11.9" 34 | hooks: 35 | - id: ruff-format 36 | - id: ruff 37 | args: [--extend-ignore=D1, --fix] 38 | - repo: https://github.com/shellcheck-py/shellcheck-py 39 | rev: v0.10.0.1 40 | hooks: 41 | - id: shellcheck 42 | args: ["--severity=warning"] 43 | - repo: local 44 | hooks: 45 | - id: prettier 46 | name: prettier 47 | entry: npx prettier --write --no-error-on-unmatched-pattern 48 | language: node 49 | types_or: 50 | [ 51 | javascript, 52 | jsx, 53 | ts, 54 | tsx, 55 | json, 56 | scss, 57 | sass, 58 | css, 59 | yaml, 60 | markdown, 61 | html, 62 | ] 63 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/stories/Point.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react" 2 | import { Story, Meta } from "@storybook/react" 3 | import { MathboxSelection } from "mathbox" 4 | import { CustomMathbox as Mathbox } from "./utils" 5 | import { Cartesian, Grid, Point, Array as MBArray } from "../components" 6 | 7 | export default { 8 | title: "Point", 9 | component: Point, 10 | argTypes: { 11 | color: { 12 | type: "string", 13 | defaultValue: "rgb(128, 128, 128)", 14 | }, 15 | opacity: { 16 | type: "number", 17 | defaultValue: 1, 18 | }, 19 | points: { 20 | type: "array", 21 | defaultValue: [[1, 1, 1]], 22 | }, 23 | size: { 24 | type: "number", 25 | defaultValue: 4, 26 | }, 27 | }, 28 | } as Meta 29 | 30 | const Template: Story> = (args) => { 31 | const { points, ...otherArgs } = args 32 | const [data, setData] = useState | null>(null) 33 | return ( 34 | 35 | 42 | 43 | 44 | 53 | 61 | 62 | 63 | 64 | ) 65 | } 66 | 67 | export const DefaultPoint = Template.bind({}) 68 | DefaultPoint.args = {} 69 | 70 | export const LiveProps = Template.bind({}) 71 | LiveProps.args = { 72 | liveProps: { 73 | size: (t: number) => 20 * (1 + 0.5 * Math.sin(t)), 74 | }, 75 | } 76 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/Mathbox.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useEffect, 3 | useState, 4 | forwardRef, 5 | useImperativeHandle, 6 | } from "react" 7 | import { Color } from "three" 8 | import { mathBox, RootProps, MathboxSelection, MathBoxOptions } from "mathbox" 9 | import MathboxAPIContext from "./MathboxAPIContext" 10 | import { WithChildren } from "./types" 11 | import { useDeepCompareMemo } from "./util" 12 | import ThreestrapContext from "../threestrap/ThreestrapContext" 13 | 14 | type Props = WithChildren< 15 | { 16 | container: HTMLElement 17 | options?: MathBoxOptions 18 | } & RootProps 19 | > 20 | 21 | const Mathbox = ( 22 | props: Props, 23 | ref: React.Ref | null> 24 | ) => { 25 | const { container, children, options, ...rootProps } = props 26 | const mathboxOptions = useDeepCompareMemo(options ?? {}, {}) 27 | const [selection, setSelection] = useState | null>( 28 | null 29 | ) 30 | useEffect(() => { 31 | if (!container) return () => {} 32 | 33 | const mathbox = mathBox({ 34 | ...mathboxOptions, 35 | element: container, 36 | }) 37 | setSelection(mathbox) 38 | 39 | /** 40 | * TODO: Should Mathbox component allow setting these more easily? 41 | */ 42 | mathbox.three.renderer.setClearColor(new Color(0xffffff), 1.0) 43 | mathbox.three.camera.position.set(1, 1, 2) 44 | return () => { 45 | mathbox.select("*").remove() 46 | mathbox.three.destroy() 47 | setSelection(null) 48 | } 49 | }, [container, mathboxOptions]) 50 | 51 | useEffect(() => { 52 | if (!selection) return 53 | selection.set(rootProps) 54 | }, [selection, rootProps]) 55 | 56 | useImperativeHandle(ref, () => selection, [selection]) 57 | return ( 58 | selection && ( 59 | 60 | 61 | {children} 62 | 63 | 64 | ) 65 | ) 66 | } 67 | 68 | export default forwardRef(Mathbox) 69 | -------------------------------------------------------------------------------- /packages/mathbox-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mathbox-react", 3 | "version": "0.3.0", 4 | "description": "React wrapper for Mathbox", 5 | "license": "ISC", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/ChristopherChudzicki/mathbox-react", 9 | "directory": "mathbox-react" 10 | }, 11 | "author": "Chris Chudzicki", 12 | "main": "build/cjs/index.js", 13 | "exports": { 14 | ".": { 15 | "import": "./build/esm/index.js", 16 | "require": "./build/cjs/index.js" 17 | }, 18 | "./threestrap": { 19 | "import": "./build/esm/threestrap/index.js", 20 | "require": "./build/cjs/threestrap/index.js" 21 | } 22 | }, 23 | "files": [ 24 | "build" 25 | ], 26 | "scripts": { 27 | "build": "rm -rf ./build && yarn build-esm && yarn build-cjs", 28 | "build-esm": "tsc --project tsconfig.esm.json", 29 | "build-cjs": "tsc --project tsconfig.cjs.json", 30 | "build-watch": "tsc --watch", 31 | "storybook": "start-storybook -p 6006", 32 | "lint": "eslint \"**/*.ts?(x)\"", 33 | "prepack": "yarn build", 34 | "test": "jest" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "7.23.2", 38 | "@babel/preset-env": "7.23.2", 39 | "@mathbox-react/eslint-config": "workspace:*", 40 | "@mathbox-react/tsconfig": "workspace:*", 41 | "@storybook/addon-actions": "7.4.6", 42 | "@storybook/addon-essentials": "7.4.6", 43 | "@storybook/addon-interactions": "7.4.6", 44 | "@storybook/addon-links": "7.4.6", 45 | "@storybook/builder-webpack5": "^7.0.0", 46 | "@storybook/manager-webpack5": "^6.5.15", 47 | "@storybook/react": "7.4.6", 48 | "@storybook/testing-library": "0.2.2", 49 | "@testing-library/react": "14.0.0", 50 | "@types/jest": "29.5.5", 51 | "@types/lodash": "4.14.199", 52 | "@types/react": "18.2.28", 53 | "@types/three": "0.157.0", 54 | "babel-jest": "27.5.1", 55 | "babel-loader": "9.1.3", 56 | "eslint": "^8.32.0", 57 | "jest": "27.5.1", 58 | "mathbox": "2.3.1", 59 | "react": "18.2.0", 60 | "react-dom": "18.2.0", 61 | "three": "0.157.0", 62 | "ts-jest": "29.1.1", 63 | "typescript": "^5.2.2" 64 | }, 65 | "peerDependencies": { 66 | "mathbox": "^2.3.1", 67 | "react": "^17.0.2 || ^18.0.0", 68 | "three": ">=0.118.0" 69 | }, 70 | "dependencies": { 71 | "lodash": "^4.17.21" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/threestrap/Controls.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-unused-prop-types */ 2 | import React, { useEffect } from "react" 3 | import type { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 4 | import type { Vector3 } from "three" 5 | import { useThreestrap } from "./ThreestrapContext" 6 | import type { Threestrap } from "./threestrap" 7 | 8 | type OrbitControlsProps = { 9 | type: "orbit" 10 | enablePan?: boolean 11 | enableRotate?: boolean 12 | enableZoom?: boolean 13 | target?: Vector3 14 | onStart?: (event: { type: "end"; target: OrbitControls }) => void 15 | onEnd?: (event: { type: "end"; target: OrbitControls }) => void 16 | } 17 | type ControlsProps = OrbitControlsProps 18 | 19 | const ALLOWED_CONTROLS = ["orbit"] 20 | 21 | const useOrbitControls = ( 22 | threestrap: Threestrap, 23 | { 24 | type, 25 | enablePan, 26 | enableRotate, 27 | enableZoom, 28 | onStart, 29 | onEnd, 30 | target, 31 | }: ControlsProps 32 | ) => { 33 | useEffect(() => { 34 | if (type !== "orbit") return 35 | if (!threestrap.controls) return 36 | const controls = threestrap.controls as OrbitControls 37 | if (enablePan !== undefined) { 38 | controls.enablePan = enablePan 39 | } 40 | if (enableZoom !== undefined) { 41 | controls.enableZoom = enableZoom 42 | } 43 | if (enableRotate !== undefined) { 44 | controls.enableRotate = enableRotate 45 | } 46 | }, [type, enablePan, enableRotate, enableZoom, threestrap.controls]) 47 | 48 | useEffect(() => { 49 | if (type !== "orbit") return 50 | if (!threestrap.controls) return 51 | const controls = threestrap.controls as OrbitControls 52 | if (target) { 53 | controls.target = target 54 | } 55 | }, [type, threestrap.controls, target]) 56 | 57 | useEffect(() => { 58 | if (type !== "orbit") return () => {} 59 | if (!threestrap.controls) return () => {} 60 | const controls = threestrap.controls as OrbitControls 61 | if (onStart) { 62 | controls.addEventListener("start", onStart) 63 | } 64 | return () => { 65 | if (onStart) { 66 | controls.removeEventListener("start", onStart) 67 | } 68 | } 69 | }, [type, onStart, threestrap.controls]) 70 | 71 | useEffect(() => { 72 | if (type !== "orbit") return () => {} 73 | if (!threestrap.controls) return () => {} 74 | const controls = threestrap.controls as OrbitControls 75 | if (onEnd) { 76 | controls.addEventListener("end", onEnd) 77 | } 78 | return () => { 79 | if (onEnd) { 80 | controls.removeEventListener("end", onEnd) 81 | } 82 | } 83 | }, [type, onEnd, threestrap.controls]) 84 | } 85 | 86 | const Controls: React.FC = (props) => { 87 | if (!ALLOWED_CONTROLS.includes(props.type)) { 88 | throw new Error(`Invalid control type ${props.type}`) 89 | } 90 | const threestrap = useThreestrap() 91 | useOrbitControls(threestrap, props) 92 | return null 93 | } 94 | 95 | export default Controls 96 | -------------------------------------------------------------------------------- /packages/eslint-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "react-app", 4 | "airbnb", 5 | "airbnb-typescript", 6 | "eslint:recommended", 7 | "plugin:react/recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:react-hooks/recommended", 10 | "plugin:prettier/recommended", // this should come last 11 | ], 12 | plugins: ["react", "@typescript-eslint"], 13 | rules: { 14 | "@typescript-eslint/naming-convention": [ 15 | "warn", 16 | { 17 | selector: "parameter", 18 | modifiers: ["unused"], 19 | format: ["camelCase"], 20 | leadingUnderscore: "allow", // do not require... it's annoying when required for object destructuring. 21 | }, 22 | ], 23 | "@typescript-eslint/no-unused-vars": [ 24 | "error", 25 | { 26 | varsIgnorePattern: "^_", 27 | argsIgnorePattern: "^_", 28 | destructuredArrayIgnorePattern: "^_", 29 | ignoreRestSiblings: true, 30 | }, 31 | ], 32 | "max-classes-per-file": "off", 33 | "react/function-component-definition": [ 34 | "error", 35 | { 36 | namedComponents: "arrow-function", 37 | unnamedComponents: "arrow-function", 38 | }, 39 | ], 40 | "import/prefer-default-export": "off", 41 | "react/destructuring-assignment": "off", 42 | "no-param-reassign": [ 43 | "error", 44 | { props: true, ignorePropertyModificationsFor: ["state"] }, 45 | ], 46 | "import/no-extraneous-dependencies": [ 47 | "error", 48 | { 49 | devDependencies: [ 50 | "**/__tests__/**/*.ts", 51 | "**/*.spec.ts", 52 | "**/*.spec.tsx", 53 | "**/playwright.config.ts", 54 | "**/vite.config.ts", 55 | "**/*.stories.tsx", 56 | "**/src/setupTests.ts", 57 | "**/src/playwright/**", 58 | "**/src/test_util/**/*.ts", 59 | "**/src/test_util/**/*.tsx", 60 | ], 61 | }, 62 | ], 63 | "jsx-a11y/label-has-associated-control": [ 64 | 2, 65 | { 66 | assert: "either", 67 | }, 68 | ], 69 | }, 70 | settings: { 71 | "import/parsers": { 72 | "@typescript-eslint/parser": [".ts", ".tsx"], 73 | }, 74 | "import/resolver": { 75 | typescript: { 76 | project: ["tsconfig.json"], 77 | }, 78 | node: { 79 | project: ["tsconfig.json"], 80 | }, 81 | }, 82 | }, 83 | overrides: [ 84 | { 85 | files: ["**/*.ts?(x)"], 86 | rules: { 87 | "react/prop-types": "off", 88 | "react/require-default-props": "off", 89 | "react/jsx-props-no-spreading": "off", 90 | }, 91 | }, 92 | { 93 | files: ["**/*.stories.*"], 94 | rules: { 95 | "import/no-anonymous-default-export": "off", 96 | }, 97 | }, 98 | { 99 | files: ["**/*.spec.*"], 100 | excludedFiles: ["**/src/playwright/**"], 101 | extends: ["react-app/jest"], 102 | }, 103 | ], 104 | parserOptions: { 105 | project: "./tsconfig.json", 106 | }, 107 | ignorePatterns: ["build/"], 108 | } 109 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import isEqual from "lodash/isEqual" 3 | import { useRef, useMemo } from "react" 4 | import { MathboxSelection, NodeType } from "mathbox" 5 | 6 | type WithPrivateUp = MathboxSelection & { _up?: WithPrivateUp } 7 | 8 | /** 9 | * Get root node given any node in Mathbox tree 10 | */ 11 | const getRoot = (selection: MathboxSelection) => { 12 | let current = selection as WithPrivateUp 13 | while (current._up) { 14 | current = current._up 15 | } 16 | return current 17 | } 18 | 19 | export const isRootDestroyed = (selection: MathboxSelection) => { 20 | const root = getRoot(selection) 21 | return root.three === undefined 22 | } 23 | 24 | /** 25 | * Check that parent is actually the selection's parent. 26 | * The mathbox components occasionally render with a selection value whose 27 | * parent does not match the parent specified in MathboxContext. 28 | * 29 | * I believe this fundamentally this is happening because 30 | * - we store parent value in context... 31 | * - we update the context parent value using useEffect hooks.. 32 | * - useEffect hooks are called children before parents. 33 | * 34 | * One wonders whether we really need to be using useEffect hooks for the 35 | * mathbox components, or if we can just put all the node creation/removal stuff 36 | * in a render function. Hooks seem nice for dependencies, but mathbox is pretty 37 | * smart about diffing out the actual changes. 38 | */ 39 | export const isSelectionParent = ( 40 | selection: MathboxSelection, 41 | parent: MathboxSelection 42 | ) => { 43 | const selectionParentNode = (selection as WithPrivateUp)._up?.[0] 44 | return selectionParentNode === parent[0] 45 | } 46 | 47 | const CAN_HAVE_CHILDREN = [ 48 | "view", 49 | "cartesian", 50 | "cartesian4", 51 | "polar", 52 | "spherical", 53 | "stereographic", 54 | "stereographic4", 55 | "transform", 56 | "transform4", 57 | "vertex", 58 | "fragment", 59 | "layer", 60 | "mask", 61 | "group", 62 | "inherit", 63 | "root", 64 | "unit", 65 | "rtt", 66 | "clock", 67 | "now", 68 | "move", 69 | "present", 70 | "reveal", 71 | "slide", 72 | ] as const 73 | 74 | export type ParentNodeTypes = (typeof CAN_HAVE_CHILDREN)[number] 75 | 76 | export const canNodeHaveChildren = (type: NodeType) => 77 | (CAN_HAVE_CHILDREN as readonly string[]).includes(type) 78 | 79 | export const capitalize = (s: string) => { 80 | if (!s) return "" 81 | return s[0].toLocaleUpperCase() + s.slice(1) 82 | } 83 | 84 | /** 85 | * Returns the same reference to `value` as long as the the given values are 86 | * deep equal. 87 | * 88 | * This is, in general, not a great idea. See 89 | * https://github.com/facebook/react/issues/14476#issuecomment-471199055 90 | * 91 | * This should work OK for mathbox options because the objects in question are 92 | * either shallow, or are deep equal at a fairly shallow level. 93 | * E.g., OrbitControls may not be a shall object, but if it's not, it will at 94 | * least be deep equal between renders. 95 | * 96 | * But should work OK for mathbox options. 97 | */ 98 | export const useDeepCompareMemo = (value: T, initial: T): T => { 99 | const oldValue = useRef(initial) 100 | const memoOptions = useMemo(() => { 101 | if (isEqual(value, oldValue.current)) return oldValue.current 102 | oldValue.current = value 103 | return value 104 | }, [value]) 105 | return memoOptions 106 | } 107 | -------------------------------------------------------------------------------- /packages/examples/src/views/ColorCube.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from "react" 2 | import { MathboxSelection } from "mathbox" 3 | import * as MB from "mathbox-react" 4 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 5 | import { useForm } from "react-hook-form" 6 | 7 | const mathboxOptions = { 8 | plugins: ["core", "controls", "cursor"], 9 | controls: { 10 | klass: OrbitControls, 11 | }, 12 | } 13 | interface FormValues { 14 | opacity: number 15 | size: number 16 | width: number 17 | height: number 18 | depth: number 19 | } 20 | const defaultFormValues: FormValues = { 21 | opacity: 0.75, 22 | size: 4, 23 | width: 16, 24 | height: 16, 25 | depth: 16, 26 | } 27 | 28 | interface ControlsFormProps { 29 | onChange: (values: FormValues) => void 30 | } 31 | 32 | const ControlsForm: React.FC = ({ onChange }) => { 33 | const { register, watch } = useForm() 34 | const values = watch() 35 | const onFormChange = useCallback(() => onChange(watch()), [onChange, watch]) 36 | return ( 37 |
38 | 39 | {values.opacity} 40 | 49 | 50 | {values.size} 51 | 60 | 61 | {values.width} 62 | 71 | 72 | {values.height} 73 | 82 | 83 | 84 | {values.depth} 85 | 94 |
95 | ) 96 | } 97 | 98 | const ColorCube: React.FC = () => { 99 | const [formValues, setFormValues] = useState(defaultFormValues) 100 | const setup = useCallback((mathbox: MathboxSelection<"root"> | null) => { 101 | // @ts-expect-error useful for debugging 102 | window.mathbox = mathbox 103 | }, []) 104 | return ( 105 | <> 106 | 107 | 112 | 120 | { 128 | emit(x, y, z, formValues.opacity) 129 | }} 130 | /> 131 | 137 | 138 | 139 | 140 | ) 141 | } 142 | 143 | export default ColorCube 144 | -------------------------------------------------------------------------------- /.secrets.baseline: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.5.0", 3 | "plugins_used": [ 4 | { 5 | "name": "ArtifactoryDetector" 6 | }, 7 | { 8 | "name": "AWSKeyDetector" 9 | }, 10 | { 11 | "name": "AzureStorageKeyDetector" 12 | }, 13 | { 14 | "name": "Base64HighEntropyString", 15 | "limit": 4.5 16 | }, 17 | { 18 | "name": "BasicAuthDetector" 19 | }, 20 | { 21 | "name": "CloudantDetector" 22 | }, 23 | { 24 | "name": "DiscordBotTokenDetector" 25 | }, 26 | { 27 | "name": "GitHubTokenDetector" 28 | }, 29 | { 30 | "name": "GitLabTokenDetector" 31 | }, 32 | { 33 | "name": "HexHighEntropyString", 34 | "limit": 3.0 35 | }, 36 | { 37 | "name": "IbmCloudIamDetector" 38 | }, 39 | { 40 | "name": "IbmCosHmacDetector" 41 | }, 42 | { 43 | "name": "IPPublicDetector" 44 | }, 45 | { 46 | "name": "JwtTokenDetector" 47 | }, 48 | { 49 | "name": "KeywordDetector", 50 | "keyword_exclude": "" 51 | }, 52 | { 53 | "name": "MailchimpDetector" 54 | }, 55 | { 56 | "name": "NpmDetector" 57 | }, 58 | { 59 | "name": "OpenAIDetector" 60 | }, 61 | { 62 | "name": "PrivateKeyDetector" 63 | }, 64 | { 65 | "name": "PypiTokenDetector" 66 | }, 67 | { 68 | "name": "SendGridDetector" 69 | }, 70 | { 71 | "name": "SlackDetector" 72 | }, 73 | { 74 | "name": "SoftlayerDetector" 75 | }, 76 | { 77 | "name": "SquareOAuthDetector" 78 | }, 79 | { 80 | "name": "StripeDetector" 81 | }, 82 | { 83 | "name": "TelegramBotTokenDetector" 84 | }, 85 | { 86 | "name": "TwilioKeyDetector" 87 | } 88 | ], 89 | "filters_used": [ 90 | { 91 | "path": "detect_secrets.filters.allowlist.is_line_allowlisted" 92 | }, 93 | { 94 | "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", 95 | "min_level": 2 96 | }, 97 | { 98 | "path": "detect_secrets.filters.heuristic.is_indirect_reference" 99 | }, 100 | { 101 | "path": "detect_secrets.filters.heuristic.is_likely_id_string" 102 | }, 103 | { 104 | "path": "detect_secrets.filters.heuristic.is_lock_file" 105 | }, 106 | { 107 | "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" 108 | }, 109 | { 110 | "path": "detect_secrets.filters.heuristic.is_potential_uuid" 111 | }, 112 | { 113 | "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" 114 | }, 115 | { 116 | "path": "detect_secrets.filters.heuristic.is_sequential_string" 117 | }, 118 | { 119 | "path": "detect_secrets.filters.heuristic.is_swagger_file" 120 | }, 121 | { 122 | "path": "detect_secrets.filters.heuristic.is_templated_secret" 123 | } 124 | ], 125 | "results": { 126 | ".yarn/releases/yarn-3.3.1.cjs": [ 127 | { 128 | "type": "Base64 High Entropy String", 129 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 130 | "hashed_secret": "1d278d3c888d1a2fa7eed622bfc02927ce4049af", 131 | "is_verified": false, 132 | "line_number": 261 133 | }, 134 | { 135 | "type": "Base64 High Entropy String", 136 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 137 | "hashed_secret": "578849bf67fb5b1ec70feadcf751c19863f52978", 138 | "is_verified": false, 139 | "line_number": 263 140 | }, 141 | { 142 | "type": "Base64 High Entropy String", 143 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 144 | "hashed_secret": "5f53eba35c75ae82841881fe17d89c81b0d471e0", 145 | "is_verified": false, 146 | "line_number": 299 147 | }, 148 | { 149 | "type": "Secret Keyword", 150 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 151 | "hashed_secret": "9af3842326eadff7d85a55edfec67f6d8a3a259f", 152 | "is_verified": false, 153 | "line_number": 299 154 | }, 155 | { 156 | "type": "Base64 High Entropy String", 157 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 158 | "hashed_secret": "a0e0de71f4ebf2bd8d9d8f6f4c9f0262286782e7", 159 | "is_verified": false, 160 | "line_number": 299 161 | }, 162 | { 163 | "type": "Secret Keyword", 164 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 165 | "hashed_secret": "3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1", 166 | "is_verified": false, 167 | "line_number": 391 168 | }, 169 | { 170 | "type": "Base64 High Entropy String", 171 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 172 | "hashed_secret": "253bcd633bc71327399349c393ab77368c6d825d", 173 | "is_verified": false, 174 | "line_number": 597 175 | }, 176 | { 177 | "type": "Base64 High Entropy String", 178 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 179 | "hashed_secret": "535fa6d9d262a7e8b003ca52be45d3f0db2d0866", 180 | "is_verified": false, 181 | "line_number": 597 182 | }, 183 | { 184 | "type": "Base64 High Entropy String", 185 | "filename": ".yarn/releases/yarn-3.3.1.cjs", 186 | "hashed_secret": "bb2493326db73320353978bee6d383385e49eca0", 187 | "is_verified": false, 188 | "line_number": 597 189 | } 190 | ] 191 | }, 192 | "generated_at": "2025-09-28T00:31:30Z" 193 | } 194 | -------------------------------------------------------------------------------- /packages/examples/src/views/DancingPoints.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useEffect } from "react" 2 | import { useForm } from "react-hook-form" 3 | import { AreaEmitter, MathboxSelection } from "mathbox" 4 | import * as MB from "mathbox-react" 5 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 6 | import { isEvaluateableFunc, textToAreaEmitter } from "./util" 7 | 8 | const mathboxOptions = { 9 | plugins: ["core", "controls", "cursor"], 10 | controls: { 11 | klass: OrbitControls, 12 | }, 13 | } 14 | 15 | interface FormValues { 16 | showPoints: boolean 17 | width: number 18 | height: number 19 | size: number 20 | expr: string 21 | A: number 22 | } 23 | const defaultFormValues: FormValues = { 24 | showPoints: true, 25 | width: 48, 26 | height: 64, 27 | size: 4, 28 | expr: "0.25*A*sin(10*x - t)*sin(2*y - t)", 29 | A: 0.5, 30 | } 31 | 32 | interface ControlsFormProps { 33 | onChange: (values: FormValues) => void 34 | } 35 | 36 | const ControlsForm = ({ onChange }: ControlsFormProps) => { 37 | const { register, watch } = useForm() 38 | const values = watch() 39 | const onFormChange = useCallback(() => onChange(watch()), [onChange, watch]) 40 | return ( 41 |
42 | 43 | {values.showPoints} 44 | 50 | 51 | {values.size} 52 | 60 | 61 | {values.width} 62 | 70 | 71 | {values.height} 72 | 80 | 81 | f(x, y, t, A) = 82 | 88 | 89 | {values.A} 90 | 99 |
100 | ) 101 | } 102 | 103 | interface PointsProps { 104 | size: number 105 | width: number 106 | height: number 107 | expr: AreaEmitter 108 | } 109 | 110 | const Points: React.FC = (props) => ( 111 | <> 112 | 119 | 120 | 121 | 127 | 128 | 129 | 130 | 131 | 132 | ) 133 | 134 | const DancingPoints: React.FC = () => { 135 | const [container, setContainer] = useState(null) 136 | const [formValues, setFormValues] = useState(defaultFormValues) 137 | const [emitter, setEmitter] = useState(() => {}) 138 | useEffect(() => { 139 | if (isEvaluateableFunc(["x", "y", "t", "A"], 1)(formValues.expr)) { 140 | const scope = { A: formValues.A } 141 | setEmitter(() => textToAreaEmitter(formValues.expr, scope)) 142 | } 143 | }, [formValues.expr, formValues.A]) 144 | 145 | const setup = useCallback((mathbox: MathboxSelection<"root"> | null) => { 146 | // @ts-expect-error Useful for debugging 147 | window.mathbox = mathbox 148 | }, []) 149 | 150 | return ( 151 | <> 152 | 153 |
154 | {container && ( 155 | 160 | 161 | 162 | {formValues.showPoints && ( 163 | 169 | )} 170 | 171 | 172 | )} 173 |
174 | 175 | ) 176 | } 177 | 178 | export default DancingPoints 179 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/components.spec.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react" 2 | import { render, act } from "@testing-library/react" 3 | import { MathboxSelection } from "mathbox" 4 | import ContainedMathbox from "./ContainedMathbox" 5 | import Mathbox from "./Mathbox" 6 | import { Cartesian, Grid, Voxel } from "./components" 7 | import { MathboxRef } from "./types" 8 | 9 | function assertNotNil(value: T): asserts value is NonNullable { 10 | if (value === undefined) { 11 | throw new Error("Unexpected undefined value") 12 | } 13 | if (value === null) { 14 | throw new Error("Unexpected null value") 15 | } 16 | } 17 | 18 | /** 19 | * Assert that two mathbox selections have the same nodes in the same order. 20 | */ 21 | const assertSelectionsEqual = (s1: MathboxSelection, s2: MathboxSelection) => { 22 | expect(s1.length).toBe(s2.length) 23 | Array(s1.length) 24 | .fill(null) 25 | .forEach((_, i) => { 26 | expect(s1[i]).toBe(s2[i]) 27 | }) 28 | } 29 | 30 | describe("ContainedMathbox", () => { 31 | it("makes a new mathbox instance only if the options have changed", () => { 32 | const mbRef: MathboxRef<"root"> = { current: null } 33 | const { rerender } = render() 34 | 35 | const mb0 = mbRef.current 36 | assertNotNil(mb0) 37 | rerender() 38 | const mb1 = mbRef.current 39 | expect(mb1).toBe(mb0) 40 | assertNotNil(mb1) 41 | rerender() 42 | const mb2 = mbRef.current 43 | assertNotNil(mb2) 44 | expect(mb2).not.toBe(mb1) 45 | }) 46 | }) 47 | 48 | describe("Cartesian", () => { 49 | it("exposes Mathbox instance via ref", () => { 50 | const mbRef: MathboxRef<"root"> = { current: null } 51 | const cartesianRef: MathboxRef<"cartesian"> = { current: null } 52 | render( 53 | 54 | 55 | 56 | ) 57 | 58 | expect(mbRef.current?.[0].type).toBe("root") 59 | expect(cartesianRef.current?.[0].type).toBe("cartesian") 60 | }) 61 | 62 | it("creates a cartesian instance as child of root", () => { 63 | const mbRef: MathboxRef<"root"> = { current: null } 64 | render( 65 | 66 | 67 | 68 | ) 69 | expect(mbRef.current?.select("cartesian").length).toBe(1) 70 | }) 71 | 72 | it("creates mathbox children as children of itself", () => { 73 | const mbRef: MathboxRef<"root"> = { current: null } 74 | render( 75 | 76 | 77 | 78 | 79 | 80 | 81 | ) 82 | mbRef.current?.print() 83 | expect(mbRef.current?.select("cartesian").length).toBe(1) 84 | expect(mbRef.current?.select("cartesian grid").length).toBe(2) 85 | }) 86 | 87 | it("removes its mathbox instance when unmounted", () => { 88 | const mbRef: MathboxRef<"root"> = { current: null } 89 | const { rerender } = render( 90 | 91 | 92 | 93 | ) 94 | expect(mbRef.current?.select("cartesian").length).toBe(1) 95 | rerender() 96 | expect(mbRef.current?.select("cartesian").length).toBe(0) 97 | }) 98 | 99 | it.each([ 100 | { props: { visible: true, scale: [3, 2, 1] } }, 101 | { props: { visible: false, scale: [1, 2, 3] } }, 102 | ])("passes appropriate props to its mathbox instance", ({ props }) => { 103 | const mbRef: MathboxRef<"root"> = { current: null } 104 | render( 105 | 106 | 107 | 108 | ) 109 | const cartesian = mbRef.current?.select<"cartesian">("cartesian") 110 | 111 | assertNotNil(cartesian) 112 | 113 | expect(cartesian.get("visible")).toBe(props.visible) 114 | // Mathbox converts scale to a ThreeJS Vec3 115 | expect(cartesian.get("scale").toArray()).toStrictEqual(props.scale) 116 | }) 117 | 118 | it("updates props on its mathbox instance when rerendered", () => { 119 | const mbRef: MathboxRef<"root"> = { current: null } 120 | const { rerender } = render( 121 | 122 | 123 | 124 | ) 125 | const cartesian = mbRef.current?.select<"cartesian">("cartesian") 126 | assertNotNil(cartesian) 127 | 128 | expect(cartesian.get("visible")).toBe(true) 129 | rerender( 130 | 131 | 132 | 133 | ) 134 | expect(cartesian.get("visible")).toBe(false) 135 | }) 136 | 137 | it("updates liveProps on its mathbox instance when rerendered", async () => { 138 | const mbRef: MathboxRef<"root"> = { current: null } 139 | const { rerender } = render( 140 | 141 | 142 | 1 + t, 145 | }} 146 | /> 147 | 148 | 149 | ) 150 | const grid = mbRef.current?.select<"grid">("grid") 151 | assertNotNil(grid) 152 | 153 | const w1 = grid.get("width") 154 | await new Promise((resolve) => { 155 | setTimeout(resolve, 500) 156 | }) 157 | const w2 = grid.get("width") 158 | expect(w1).toBe(1) 159 | // Mathbox uses seconds 160 | expect(w2 - w1).toBeGreaterThan(0.45) 161 | expect(w2 - w1).toBeLessThanOrEqual(0.5) 162 | rerender( 163 | 164 | 165 | 166 | 167 | 168 | ) 169 | expect(grid.get("width")).toBe(10) 170 | }) 171 | 172 | it("re-renders inside new instance when root changes", () => { 173 | const mbRef: MathboxRef<"root"> = { current: null } 174 | const cartesianRef: MathboxRef<"cartesian"> = { current: null } 175 | const gridRef: MathboxRef<"grid"> = { current: null } 176 | const options1 = {} 177 | const { rerender } = render( 178 | 179 | 180 | 181 | 182 | 183 | ) 184 | 185 | const mb1 = mbRef.current 186 | const cartesian1 = cartesianRef.current 187 | const grid1 = gridRef.current 188 | assertNotNil(mb1) 189 | assertNotNil(cartesian1) 190 | assertNotNil(grid1) 191 | 192 | expect(cartesian1).toHaveLength(1) 193 | assertSelectionsEqual(mb1.select("cartesian"), cartesian1) 194 | expect(grid1).toHaveLength(1) 195 | assertSelectionsEqual(mb1.select("grid"), grid1) 196 | 197 | /** 198 | * When re-rendered, this will create a new mathBox since the options 199 | * object prop will have changed. 200 | */ 201 | const options2 = { plugins: ["core"] } 202 | expect(options1).not.toBe(options2) 203 | 204 | rerender( 205 | 206 | 207 | 208 | 209 | 210 | ) 211 | 212 | const mb2 = mbRef.current 213 | const cartesian2 = cartesianRef.current 214 | const grid2 = gridRef.current 215 | assertNotNil(mb2) 216 | assertNotNil(cartesian2) 217 | assertNotNil(grid2) 218 | 219 | // The options have changed (via deep equal) so a new instance is created 220 | expect(mb1).not.toBe(mb2) 221 | 222 | expect(cartesian2).toHaveLength(1) 223 | assertSelectionsEqual(mb2.select("cartesian"), cartesian2) 224 | expect(grid2).toHaveLength(1) 225 | assertSelectionsEqual(mb2.select("grid"), grid2) 226 | }) 227 | 228 | it("Can render a new instance without error", async () => { 229 | const mbRef: MathboxRef<"root"> = { current: null } 230 | let containerDiv: HTMLDivElement | null = null 231 | let setKey: (key: string) => void 232 | const KeyedMathbox = () => { 233 | const [container, setContainer] = useState(null) 234 | const [key, setKeyState] = useState("key-0") 235 | setKey = setKeyState 236 | containerDiv = container 237 | return ( 238 |
239 | {container && ( 240 | 241 | 242 | 243 | )} 244 |
245 | ) 246 | } 247 | render() 248 | 249 | assertNotNil(mbRef.current) 250 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 251 | act(() => setKey!("key-2")) 252 | 253 | assertNotNil(containerDiv) 254 | expect(mbRef.current.three.element).toBe(containerDiv) 255 | expect(mbRef.current.select("cartesian").length).toBe(1) 256 | }) 257 | 258 | it("Can add and remove children without error", async () => { 259 | const mbRef: MathboxRef<"root"> = { current: null } 260 | 261 | const { rerender } = render( 262 | 263 | 264 | 265 | 266 | 267 | ) 268 | 269 | assertNotNil(mbRef.current) 270 | expect(mbRef.current.select("grid").length).toBe(1) 271 | 272 | rerender( 273 | 274 | 275 | 276 | 277 | 278 | 279 | ) 280 | 281 | assertNotNil(mbRef.current) 282 | expect(mbRef.current.select("grid").length).toBe(2) 283 | 284 | rerender( 285 | 286 | 287 | 288 | ) 289 | 290 | assertNotNil(mbRef.current) 291 | expect(mbRef.current.select("grid").length).toBe(0) 292 | }) 293 | }) 294 | 295 | describe("", () => { 296 | it("throws a runtime error if it has children", () => { 297 | const willThrow = () => 298 | render( 299 | 300 | 301 | {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} 302 | {/* @ts-expect-error */} 303 | 304 | 305 | 306 | 307 | 308 | ) 309 | expect(willThrow).toThrow("Component cannot have children.") 310 | }) 311 | }) 312 | -------------------------------------------------------------------------------- /packages/mathbox-react/src/components/components.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | forwardRef, 3 | useRef, 4 | useContext, 5 | useEffect, 6 | useReducer, 7 | useImperativeHandle, 8 | } from "react" 9 | import { Props, NodeType, MathboxSelection } from "mathbox" 10 | import MathboxAPIContext from "./MathboxAPIContext" 11 | import { WithChildren } from "./types" 12 | import { 13 | isRootDestroyed, 14 | isSelectionParent, 15 | canNodeHaveChildren, 16 | ParentNodeTypes, 17 | capitalize, 18 | } from "./util" 19 | 20 | type LiveProps

= { 21 | [K in keyof P]: (t: number, dt: number) => P[K] 22 | } 23 | type MathboxComponent = React.ForwardRefExoticComponent< 24 | (T extends ParentNodeTypes ? WithChildren : Props[T]) & 25 | React.RefAttributes> & { 26 | liveProps?: LiveProps 27 | } 28 | > 29 | 30 | const mathboxComponentFactory = ( 31 | type: T 32 | ): MathboxComponent => { 33 | const canHaveChildren = canNodeHaveChildren(type) 34 | const componentName = capitalize(type) 35 | const Comp = ( 36 | props: WithChildren & { liveProps?: LiveProps }, 37 | ref: React.Ref | null> 38 | ) => { 39 | const [_ignored, forceUpdate] = useReducer((x) => x + 1, 0) 40 | const parent = useContext(MathboxAPIContext) 41 | const selection = useRef | null>(null) 42 | const prevLiveProps = useRef | undefined>(undefined) 43 | useEffect( 44 | () => () => { 45 | if (selection.current) { 46 | selection.current.remove() 47 | selection.current = null 48 | } 49 | }, 50 | [] 51 | ) 52 | useImperativeHandle(ref, () => selection.current) 53 | 54 | const { children, liveProps, ...others } = props 55 | useEffect(() => { 56 | if (!parent) return 57 | if (isRootDestroyed(parent)) { 58 | forceUpdate() 59 | return 60 | } 61 | if (selection.current) { 62 | if (!isSelectionParent(selection.current, parent)) { 63 | selection.current.remove() 64 | selection.current = null 65 | forceUpdate() 66 | } 67 | } 68 | if (!selection.current) { 69 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 70 | // @ts-expect-error 71 | selection.current = parent[type](others, liveProps) 72 | forceUpdate() 73 | } else { 74 | /** 75 | * If liveProps have changed, remove all the old liveProps. 76 | * (The same prop cannot be re-assigned live without being un-assigned 77 | * first.) 78 | * 79 | * We could unbind just the ones that have changed, but simpler to 80 | * unbind them all. 81 | */ 82 | if (prevLiveProps.current && liveProps !== prevLiveProps.current) { 83 | Object.keys(prevLiveProps.current).forEach((key) => { 84 | // @ts-expect-error this is not in ts yet 85 | selection.current.unbind(key) 86 | }) 87 | } 88 | if (liveProps && liveProps !== prevLiveProps.current) { 89 | selection.current.bind(liveProps) 90 | } 91 | selection.current.set(others) 92 | } 93 | prevLiveProps.current = liveProps 94 | }, [parent, others, liveProps]) 95 | if (!canHaveChildren) { 96 | if (props.children) { 97 | throw new Error(`Component <${componentName} /> cannot have children.`) 98 | } 99 | return null 100 | } 101 | return ( 102 | 103 | {props.children} 104 | 105 | ) 106 | } 107 | /** 108 | * The line below gives a TS error without explicit any. 109 | * But if you replace each generic T above with a specific instance, e.g., 110 | * 'cartesian', there is no error. 111 | * 112 | * So an alternative without "any" would be be to copy the definition of Comp 113 | * with all ~64 different node types. 114 | * 115 | * I'm not 100% sure why there's a ts error here, but the above indicates to 116 | * me that this is actually type-safe. And I do not want that much copy pasta. 117 | * So instead, use the any and put explicit return type on the factory. 118 | */ 119 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 120 | return forwardRef(Comp) as any 121 | } 122 | 123 | /** 124 | * Component wrapper for mathbox [`area`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#dataarea). 125 | */ 126 | export const Area = mathboxComponentFactory("area") 127 | 128 | /** 129 | * Component wrapper for mathbox [`array`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#dataarray). 130 | */ 131 | const MBArray = mathboxComponentFactory("array") 132 | 133 | export { MBArray as Array } 134 | 135 | /** 136 | * Component wrapper for mathbox [`axis`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawaxis). 137 | */ 138 | export const Axis = mathboxComponentFactory("axis") 139 | 140 | /** 141 | * Component wrapper for mathbox [`camera`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#cameracamera). 142 | */ 143 | export const Camera = mathboxComponentFactory("camera") 144 | 145 | /** 146 | * Component wrapper for mathbox [`cartesian`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#viewcartesian). 147 | */ 148 | export const Cartesian = mathboxComponentFactory("cartesian") 149 | 150 | /** 151 | * Component wrapper for mathbox [`cartesian4`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#viewcartesian4). 152 | */ 153 | export const Cartesian4 = mathboxComponentFactory("cartesian4") 154 | 155 | /** 156 | * Component wrapper for mathbox [`clamp`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorclamp). 157 | */ 158 | export const Clamp = mathboxComponentFactory("clamp") 159 | 160 | /** 161 | * Component wrapper for mathbox [`clock`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#timeclock). 162 | */ 163 | export const Clock = mathboxComponentFactory("clock") 164 | 165 | /** 166 | * Component wrapper for mathbox [`compose`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#rttcompose). 167 | */ 168 | export const Compose = mathboxComponentFactory("compose") 169 | 170 | /** 171 | * Component wrapper for mathbox [`dom`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#overlaydom). 172 | */ 173 | export const Dom = mathboxComponentFactory("dom") 174 | 175 | /** 176 | * Component wrapper for mathbox [`face`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawface). 177 | */ 178 | export const Face = mathboxComponentFactory("face") 179 | 180 | /** 181 | * Component wrapper for mathbox [`format`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#textformat). 182 | */ 183 | export const Format = mathboxComponentFactory("format") 184 | 185 | /** 186 | * Component wrapper for mathbox [`fragment`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#transformfragment). 187 | */ 188 | export const Fragment = mathboxComponentFactory("fragment") 189 | 190 | /** 191 | * Component wrapper for mathbox [`grid`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawgrid). 192 | */ 193 | export const Grid = mathboxComponentFactory("grid") 194 | 195 | /** 196 | * Component wrapper for mathbox [`group`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#basegroup). 197 | */ 198 | export const Group = mathboxComponentFactory("group") 199 | 200 | /** 201 | * Component wrapper for mathbox [`grow`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorgrow). 202 | */ 203 | export const Grow = mathboxComponentFactory("grow") 204 | 205 | /** 206 | * Component wrapper for mathbox [`html`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#overlayhtml). 207 | */ 208 | export const Html = mathboxComponentFactory("html") 209 | 210 | /** 211 | * Component wrapper for mathbox [`inherit`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#baseinherit). 212 | */ 213 | export const Inherit = mathboxComponentFactory("inherit") 214 | 215 | /** 216 | * Component wrapper for mathbox [`interval`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#datainterval). 217 | */ 218 | export const Interval = mathboxComponentFactory("interval") 219 | 220 | /** 221 | * Component wrapper for mathbox [`join`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorjoin). 222 | */ 223 | export const Join = mathboxComponentFactory("join") 224 | 225 | /** 226 | * Component wrapper for mathbox [`label`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#textlabel). 227 | */ 228 | export const Label = mathboxComponentFactory("label") 229 | 230 | /** 231 | * Component wrapper for mathbox [`layer`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#transformlayer). 232 | */ 233 | export const Layer = mathboxComponentFactory("layer") 234 | 235 | /** 236 | * Component wrapper for mathbox [`lerp`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorlerp). 237 | */ 238 | export const Lerp = mathboxComponentFactory("lerp") 239 | 240 | /** 241 | * Component wrapper for mathbox [`line`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawline). 242 | */ 243 | export const Line = mathboxComponentFactory("line") 244 | 245 | /** 246 | * Component wrapper for mathbox [`mask`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#transformmask). 247 | */ 248 | export const Mask = mathboxComponentFactory("mask") 249 | 250 | /** 251 | * Component wrapper for mathbox [`matrix`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#datamatrix). 252 | */ 253 | export const Matrix = mathboxComponentFactory("matrix") 254 | 255 | /** 256 | * Component wrapper for mathbox [`memo`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatormemo). 257 | */ 258 | export const Memo = mathboxComponentFactory("memo") 259 | 260 | /** 261 | * Component wrapper for mathbox [`move`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#presentmove). 262 | */ 263 | export const Move = mathboxComponentFactory("move") 264 | 265 | /** 266 | * Component wrapper for mathbox [`now`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#timenow). 267 | */ 268 | export const Now = mathboxComponentFactory("now") 269 | 270 | /** 271 | * Component wrapper for mathbox [`play`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#presentplay). 272 | */ 273 | export const Play = mathboxComponentFactory("play") 274 | 275 | /** 276 | * Component wrapper for mathbox [`point`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawpoint). 277 | */ 278 | export const Point = mathboxComponentFactory("point") 279 | 280 | /** 281 | * Component wrapper for mathbox [`polar`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#viewpolar). 282 | */ 283 | export const Polar = mathboxComponentFactory("polar") 284 | 285 | /** 286 | * Component wrapper for mathbox [`present`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#presentpresent). 287 | */ 288 | export const Present = mathboxComponentFactory("present") 289 | 290 | /** 291 | * Component wrapper for mathbox [`readback`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorreadback). 292 | */ 293 | export const Readback = mathboxComponentFactory("readback") 294 | 295 | /** 296 | * Component wrapper for mathbox [`repeat`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorrepeat). 297 | */ 298 | export const Repeat = mathboxComponentFactory("repeat") 299 | 300 | /** 301 | * Component wrapper for mathbox [`resample`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorresample). 302 | */ 303 | export const Resample = mathboxComponentFactory("resample") 304 | 305 | /** 306 | * Component wrapper for mathbox [`retext`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#textretext). 307 | */ 308 | export const Retext = mathboxComponentFactory("retext") 309 | 310 | /** 311 | * Component wrapper for mathbox [`reveal`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#presentreveal). 312 | */ 313 | export const Reveal = mathboxComponentFactory("reveal") 314 | 315 | /** 316 | * Component wrapper for mathbox [`rtt`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#rttrtt). 317 | */ 318 | export const Rtt = mathboxComponentFactory("rtt") 319 | 320 | /** 321 | * Component wrapper for mathbox [`scale`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#datascale). 322 | */ 323 | export const Scale = mathboxComponentFactory("scale") 324 | 325 | /** 326 | * Component wrapper for mathbox [`shader`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#shadershader). 327 | */ 328 | export const Shader = mathboxComponentFactory("shader") 329 | 330 | /** 331 | * Component wrapper for mathbox [`slice`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorslice). 332 | */ 333 | export const Slice = mathboxComponentFactory("slice") 334 | 335 | /** 336 | * Component wrapper for mathbox [`slide`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#presentslide). 337 | */ 338 | export const Slide = mathboxComponentFactory("slide") 339 | 340 | /** 341 | * Component wrapper for mathbox [`spherical`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#viewspherical). 342 | */ 343 | export const Spherical = mathboxComponentFactory("spherical") 344 | 345 | /** 346 | * Component wrapper for mathbox [`split`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorsplit). 347 | */ 348 | export const Split = mathboxComponentFactory("split") 349 | 350 | /** 351 | * Component wrapper for mathbox [`spread`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorspread). 352 | */ 353 | export const Spread = mathboxComponentFactory("spread") 354 | 355 | /** 356 | * Component wrapper for mathbox [`step`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#presentstep). 357 | */ 358 | export const Step = mathboxComponentFactory("step") 359 | 360 | /** 361 | * Component wrapper for mathbox [`stereographic`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#viewstereographic). 362 | */ 363 | export const Stereographic = mathboxComponentFactory("stereographic") 364 | 365 | /** 366 | * Component wrapper for mathbox [`stereographic4`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#viewstereographic4). 367 | */ 368 | export const Stereographic4 = mathboxComponentFactory("stereographic4") 369 | 370 | /** 371 | * Component wrapper for mathbox [`strip`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawstrip). 372 | */ 373 | export const Strip = mathboxComponentFactory("strip") 374 | 375 | /** 376 | * Component wrapper for mathbox [`subdivide`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorsubdivide). 377 | */ 378 | export const Subdivide = mathboxComponentFactory("subdivide") 379 | 380 | /** 381 | * Component wrapper for mathbox [`surface`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawsurface). 382 | */ 383 | export const Surface = mathboxComponentFactory("surface") 384 | 385 | /** 386 | * Component wrapper for mathbox [`swizzle`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatorswizzle). 387 | */ 388 | export const Swizzle = mathboxComponentFactory("swizzle") 389 | 390 | /** 391 | * Component wrapper for mathbox [`text`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#texttext). 392 | */ 393 | export const Text = mathboxComponentFactory("text") 394 | 395 | /** 396 | * Component wrapper for mathbox [`ticks`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawticks). 397 | */ 398 | export const Ticks = mathboxComponentFactory("ticks") 399 | 400 | /** 401 | * Component wrapper for mathbox [`transform`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#transformtransform). 402 | */ 403 | export const Transform = mathboxComponentFactory("transform") 404 | 405 | /** 406 | * Component wrapper for mathbox [`transform4`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#transformtransform4). 407 | */ 408 | export const Transform4 = mathboxComponentFactory("transform4") 409 | 410 | /** 411 | * Component wrapper for mathbox [`transpose`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#operatortranspose). 412 | */ 413 | export const Transpose = mathboxComponentFactory("transpose") 414 | 415 | /** 416 | * Component wrapper for mathbox [`unit`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#baseunit). 417 | */ 418 | export const Unit = mathboxComponentFactory("unit") 419 | 420 | /** 421 | * Component wrapper for mathbox [`vector`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#drawvector). 422 | */ 423 | export const Vector = mathboxComponentFactory("vector") 424 | 425 | /** 426 | * Component wrapper for mathbox [`vertex`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#transformvertex). 427 | */ 428 | export const Vertex = mathboxComponentFactory("vertex") 429 | 430 | /** 431 | * Component wrapper for mathbox [`view`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#viewview). 432 | */ 433 | export const View = mathboxComponentFactory("view") 434 | 435 | /** 436 | * Component wrapper for mathbox [`volume`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#datavolume). 437 | */ 438 | export const Volume = mathboxComponentFactory("volume") 439 | 440 | /** 441 | * Component wrapper for mathbox [`voxel`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#datavoxel). 442 | */ 443 | export const Voxel = mathboxComponentFactory("voxel") 444 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-workspace-tools", 5 | factory: function (require) { 6 | var plugin=(()=>{var _r=Object.create;var we=Object.defineProperty;var Er=Object.getOwnPropertyDescriptor;var br=Object.getOwnPropertyNames;var xr=Object.getPrototypeOf,Cr=Object.prototype.hasOwnProperty;var W=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,t)=>(typeof require<"u"?require:r)[t]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var q=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),wr=(e,r)=>{for(var t in r)we(e,t,{get:r[t],enumerable:!0})},Je=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of br(r))!Cr.call(e,s)&&s!==t&&we(e,s,{get:()=>r[s],enumerable:!(n=Er(r,s))||n.enumerable});return e};var Be=(e,r,t)=>(t=e!=null?_r(xr(e)):{},Je(r||!e||!e.__esModule?we(t,"default",{value:e,enumerable:!0}):t,e)),Sr=e=>Je(we({},"__esModule",{value:!0}),e);var ve=q(ee=>{"use strict";ee.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;ee.find=(e,r)=>e.nodes.find(t=>t.type===r);ee.exceedsLimit=(e,r,t=1,n)=>n===!1||!ee.isInteger(e)||!ee.isInteger(r)?!1:(Number(r)-Number(e))/Number(t)>=n;ee.escapeNode=(e,r=0,t)=>{let n=e.nodes[r];!n||(t&&n.type===t||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};ee.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0===0?(e.invalid=!0,!0):!1;ee.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0===0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;ee.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;ee.reduce=e=>e.reduce((r,t)=>(t.type==="text"&&r.push(t.value),t.type==="range"&&(t.type="text"),r),[]);ee.flatten=(...e)=>{let r=[],t=n=>{for(let s=0;s{"use strict";var tt=ve();rt.exports=(e,r={})=>{let t=(n,s={})=>{let i=r.escapeInvalid&&tt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c="";if(n.value)return(i||a)&&tt.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let p of n.nodes)c+=t(p);return c};return t(e)}});var st=q((Jn,nt)=>{"use strict";nt.exports=function(e){return typeof e=="number"?e-e===0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var ht=q((es,pt)=>{"use strict";var at=st(),le=(e,r,t)=>{if(at(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(r===void 0||e===r)return String(e);if(at(r)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n={relaxZeros:!0,...t};typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),i=String(n.shorthand),a=String(n.capture),c=String(n.wrap),p=e+":"+r+"="+s+i+a+c;if(le.cache.hasOwnProperty(p))return le.cache[p].result;let m=Math.min(e,r),h=Math.max(e,r);if(Math.abs(m-h)===1){let y=e+"|"+r;return n.capture?`(${y})`:n.wrap===!1?y:`(?:${y})`}let R=ft(e)||ft(r),f={min:e,max:r,a:m,b:h},$=[],_=[];if(R&&(f.isPadded=R,f.maxLen=String(f.max).length),m<0){let y=h<0?Math.abs(h):1;_=it(y,Math.abs(m),f,n),m=f.a=0}return h>=0&&($=it(m,h,f,n)),f.negatives=_,f.positives=$,f.result=vr(_,$,n),n.capture===!0?f.result=`(${f.result})`:n.wrap!==!1&&$.length+_.length>1&&(f.result=`(?:${f.result})`),le.cache[p]=f,f.result};function vr(e,r,t){let n=Me(e,r,"-",!1,t)||[],s=Me(r,e,"",!1,t)||[],i=Me(e,r,"-?",!0,t)||[];return n.concat(i).concat(s).join("|")}function Hr(e,r){let t=1,n=1,s=ut(e,t),i=new Set([r]);for(;e<=s&&s<=r;)i.add(s),t+=1,s=ut(e,t);for(s=ct(r+1,n)-1;e1&&c.count.pop(),c.count.push(h.count[0]),c.string=c.pattern+lt(c.count),a=m+1;continue}t.isPadded&&(R=Or(m,t,n)),h.string=R+h.pattern+lt(h.count),i.push(h),a=m+1,c=h}return i}function Me(e,r,t,n,s){let i=[];for(let a of e){let{string:c}=a;!n&&!ot(r,"string",c)&&i.push(t+c),n&&ot(r,"string",c)&&i.push(t+c)}return i}function Tr(e,r){let t=[];for(let n=0;nr?1:r>e?-1:0}function ot(e,r,t){return e.some(n=>n[r]===t)}function ut(e,r){return Number(String(e).slice(0,-r)+"9".repeat(r))}function ct(e,r){return e-e%Math.pow(10,r)}function lt(e){let[r=0,t=""]=e;return t||r>1?`{${r+(t?","+t:"")}}`:""}function Lr(e,r,t){return`[${e}${r-e===1?"":"-"}${r}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Or(e,r,t){if(!r.isPadded)return e;let n=Math.abs(r.maxLen-String(e).length),s=t.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}le.cache={};le.clearCache=()=>le.cache={};pt.exports=le});var Ue=q((ts,Et)=>{"use strict";var Nr=W("util"),At=ht(),dt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Ir=e=>r=>e===!0?Number(r):String(r),Pe=e=>typeof e=="number"||typeof e=="string"&&e!=="",Ae=e=>Number.isInteger(+e),De=e=>{let r=`${e}`,t=-1;if(r[0]==="-"&&(r=r.slice(1)),r==="0")return!1;for(;r[++t]==="0";);return t>0},Br=(e,r,t)=>typeof e=="string"||typeof r=="string"?!0:t.stringify===!0,Mr=(e,r,t)=>{if(r>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?r-1:r,"0")}return t===!1?String(e):e},gt=(e,r)=>{let t=e[0]==="-"?"-":"";for(t&&(e=e.slice(1),r--);e.length{e.negatives.sort((a,c)=>ac?1:0),e.positives.sort((a,c)=>ac?1:0);let t=r.capture?"":"?:",n="",s="",i;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${t}${e.negatives.join("|")})`),n&&s?i=`${n}|${s}`:i=n||s,r.wrap?`(${t}${i})`:i},mt=(e,r,t,n)=>{if(t)return At(e,r,{wrap:!1,...n});let s=String.fromCharCode(e);if(e===r)return s;let i=String.fromCharCode(r);return`[${s}-${i}]`},Rt=(e,r,t)=>{if(Array.isArray(e)){let n=t.wrap===!0,s=t.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return At(e,r,t)},yt=(...e)=>new RangeError("Invalid range arguments: "+Nr.inspect(...e)),_t=(e,r,t)=>{if(t.strictRanges===!0)throw yt([e,r]);return[]},Dr=(e,r)=>{if(r.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Ur=(e,r,t=1,n={})=>{let s=Number(e),i=Number(r);if(!Number.isInteger(s)||!Number.isInteger(i)){if(n.strictRanges===!0)throw yt([e,r]);return[]}s===0&&(s=0),i===0&&(i=0);let a=s>i,c=String(e),p=String(r),m=String(t);t=Math.max(Math.abs(t),1);let h=De(c)||De(p)||De(m),R=h?Math.max(c.length,p.length,m.length):0,f=h===!1&&Br(e,r,n)===!1,$=n.transform||Ir(f);if(n.toRegex&&t===1)return mt(gt(e,R),gt(r,R),!0,n);let _={negatives:[],positives:[]},y=T=>_[T<0?"negatives":"positives"].push(Math.abs(T)),E=[],S=0;for(;a?s>=i:s<=i;)n.toRegex===!0&&t>1?y(s):E.push(Mr($(s,S),R,f)),s=a?s-t:s+t,S++;return n.toRegex===!0?t>1?Pr(_,n):Rt(E,null,{wrap:!1,...n}):E},Gr=(e,r,t=1,n={})=>{if(!Ae(e)&&e.length>1||!Ae(r)&&r.length>1)return _t(e,r,n);let s=n.transform||(f=>String.fromCharCode(f)),i=`${e}`.charCodeAt(0),a=`${r}`.charCodeAt(0),c=i>a,p=Math.min(i,a),m=Math.max(i,a);if(n.toRegex&&t===1)return mt(p,m,!1,n);let h=[],R=0;for(;c?i>=a:i<=a;)h.push(s(i,R)),i=c?i-t:i+t,R++;return n.toRegex===!0?Rt(h,null,{wrap:!1,options:n}):h},$e=(e,r,t,n={})=>{if(r==null&&Pe(e))return[e];if(!Pe(e)||!Pe(r))return _t(e,r,n);if(typeof t=="function")return $e(e,r,1,{transform:t});if(dt(t))return $e(e,r,0,t);let s={...n};return s.capture===!0&&(s.wrap=!0),t=t||s.step||1,Ae(t)?Ae(e)&&Ae(r)?Ur(e,r,t,s):Gr(e,r,Math.max(Math.abs(t),1),s):t!=null&&!dt(t)?Dr(t,s):$e(e,r,1,t)};Et.exports=$e});var Ct=q((rs,xt)=>{"use strict";var qr=Ue(),bt=ve(),Kr=(e,r={})=>{let t=(n,s={})=>{let i=bt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c=i===!0||a===!0,p=r.escapeInvalid===!0?"\\":"",m="";if(n.isOpen===!0||n.isClose===!0)return p+n.value;if(n.type==="open")return c?p+n.value:"(";if(n.type==="close")return c?p+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":c?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let h=bt.reduce(n.nodes),R=qr(...h,{...r,wrap:!1,toRegex:!0});if(R.length!==0)return h.length>1&&R.length>1?`(${R})`:R}if(n.nodes)for(let h of n.nodes)m+=t(h,n);return m};return t(e)};xt.exports=Kr});var vt=q((ns,St)=>{"use strict";var Wr=Ue(),wt=He(),he=ve(),fe=(e="",r="",t=!1)=>{let n=[];if(e=[].concat(e),r=[].concat(r),!r.length)return e;if(!e.length)return t?he.flatten(r).map(s=>`{${s}}`):r;for(let s of e)if(Array.isArray(s))for(let i of s)n.push(fe(i,r,t));else for(let i of r)t===!0&&typeof i=="string"&&(i=`{${i}}`),n.push(Array.isArray(i)?fe(s,i,t):s+i);return he.flatten(n)},jr=(e,r={})=>{let t=r.rangeLimit===void 0?1e3:r.rangeLimit,n=(s,i={})=>{s.queue=[];let a=i,c=i.queue;for(;a.type!=="brace"&&a.type!=="root"&&a.parent;)a=a.parent,c=a.queue;if(s.invalid||s.dollar){c.push(fe(c.pop(),wt(s,r)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){c.push(fe(c.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let R=he.reduce(s.nodes);if(he.exceedsLimit(...R,r.step,t))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let f=Wr(...R,r);f.length===0&&(f=wt(s,r)),c.push(fe(c.pop(),f)),s.nodes=[];return}let p=he.encloseBrace(s),m=s.queue,h=s;for(;h.type!=="brace"&&h.type!=="root"&&h.parent;)h=h.parent,m=h.queue;for(let R=0;R{"use strict";Ht.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` 7 | `,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Nt=q((as,Ot)=>{"use strict";var Fr=He(),{MAX_LENGTH:Tt,CHAR_BACKSLASH:Ge,CHAR_BACKTICK:Qr,CHAR_COMMA:Xr,CHAR_DOT:Zr,CHAR_LEFT_PARENTHESES:Yr,CHAR_RIGHT_PARENTHESES:zr,CHAR_LEFT_CURLY_BRACE:Vr,CHAR_RIGHT_CURLY_BRACE:Jr,CHAR_LEFT_SQUARE_BRACKET:kt,CHAR_RIGHT_SQUARE_BRACKET:Lt,CHAR_DOUBLE_QUOTE:en,CHAR_SINGLE_QUOTE:tn,CHAR_NO_BREAK_SPACE:rn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:nn}=$t(),sn=(e,r={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let t=r||{},n=typeof t.maxLength=="number"?Math.min(Tt,t.maxLength):Tt;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},i=[s],a=s,c=s,p=0,m=e.length,h=0,R=0,f,$={},_=()=>e[h++],y=E=>{if(E.type==="text"&&c.type==="dot"&&(c.type="text"),c&&c.type==="text"&&E.type==="text"){c.value+=E.value;return}return a.nodes.push(E),E.parent=a,E.prev=c,c=E,E};for(y({type:"bos"});h0){if(a.ranges>0){a.ranges=0;let E=a.nodes.shift();a.nodes=[E,{type:"text",value:Fr(a)}]}y({type:"comma",value:f}),a.commas++;continue}if(f===Zr&&R>0&&a.commas===0){let E=a.nodes;if(R===0||E.length===0){y({type:"text",value:f});continue}if(c.type==="dot"){if(a.range=[],c.value+=f,c.type="range",a.nodes.length!==3&&a.nodes.length!==5){a.invalid=!0,a.ranges=0,c.type="text";continue}a.ranges++,a.args=[];continue}if(c.type==="range"){E.pop();let S=E[E.length-1];S.value+=c.value+f,c=S,a.ranges--;continue}y({type:"dot",value:f});continue}y({type:"text",value:f})}do if(a=i.pop(),a.type!=="root"){a.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let E=i[i.length-1],S=E.nodes.indexOf(a);E.nodes.splice(S,1,...a.nodes)}while(i.length>0);return y({type:"eos"}),s};Ot.exports=sn});var Mt=q((is,Bt)=>{"use strict";var It=He(),an=Ct(),on=vt(),un=Nt(),X=(e,r={})=>{let t=[];if(Array.isArray(e))for(let n of e){let s=X.create(n,r);Array.isArray(s)?t.push(...s):t.push(s)}else t=[].concat(X.create(e,r));return r&&r.expand===!0&&r.nodupes===!0&&(t=[...new Set(t)]),t};X.parse=(e,r={})=>un(e,r);X.stringify=(e,r={})=>It(typeof e=="string"?X.parse(e,r):e,r);X.compile=(e,r={})=>(typeof e=="string"&&(e=X.parse(e,r)),an(e,r));X.expand=(e,r={})=>{typeof e=="string"&&(e=X.parse(e,r));let t=on(e,r);return r.noempty===!0&&(t=t.filter(Boolean)),r.nodupes===!0&&(t=[...new Set(t)]),t};X.create=(e,r={})=>e===""||e.length<3?[e]:r.expand!==!0?X.compile(e,r):X.expand(e,r);Bt.exports=X});var me=q((os,qt)=>{"use strict";var cn=W("path"),se="\\\\/",Pt=`[^${se}]`,ie="\\.",ln="\\+",fn="\\?",Te="\\/",pn="(?=.)",Dt="[^/]",qe=`(?:${Te}|$)`,Ut=`(?:^|${Te})`,Ke=`${ie}{1,2}${qe}`,hn=`(?!${ie})`,dn=`(?!${Ut}${Ke})`,gn=`(?!${ie}{0,1}${qe})`,An=`(?!${Ke})`,mn=`[^.${Te}]`,Rn=`${Dt}*?`,Gt={DOT_LITERAL:ie,PLUS_LITERAL:ln,QMARK_LITERAL:fn,SLASH_LITERAL:Te,ONE_CHAR:pn,QMARK:Dt,END_ANCHOR:qe,DOTS_SLASH:Ke,NO_DOT:hn,NO_DOTS:dn,NO_DOT_SLASH:gn,NO_DOTS_SLASH:An,QMARK_NO_DOT:mn,STAR:Rn,START_ANCHOR:Ut},yn={...Gt,SLASH_LITERAL:`[${se}]`,QMARK:Pt,STAR:`${Pt}*?`,DOTS_SLASH:`${ie}{1,2}(?:[${se}]|$)`,NO_DOT:`(?!${ie})`,NO_DOTS:`(?!(?:^|[${se}])${ie}{1,2}(?:[${se}]|$))`,NO_DOT_SLASH:`(?!${ie}{0,1}(?:[${se}]|$))`,NO_DOTS_SLASH:`(?!${ie}{1,2}(?:[${se}]|$))`,QMARK_NO_DOT:`[^.${se}]`,START_ANCHOR:`(?:^|[${se}])`,END_ANCHOR:`(?:[${se}]|$)`},_n={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:_n,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:cn.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?yn:Gt}}});var Re=q(F=>{"use strict";var En=W("path"),bn=process.platform==="win32",{REGEX_BACKSLASH:xn,REGEX_REMOVE_BACKSLASH:Cn,REGEX_SPECIAL_CHARS:wn,REGEX_SPECIAL_CHARS_GLOBAL:Sn}=me();F.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);F.hasRegexChars=e=>wn.test(e);F.isRegexChar=e=>e.length===1&&F.hasRegexChars(e);F.escapeRegex=e=>e.replace(Sn,"\\$1");F.toPosixSlashes=e=>e.replace(xn,"/");F.removeBackslashes=e=>e.replace(Cn,r=>r==="\\"?"":r);F.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};F.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:bn===!0||En.sep==="\\";F.escapeLast=(e,r,t)=>{let n=e.lastIndexOf(r,t);return n===-1?e:e[n-1]==="\\"?F.escapeLast(e,r,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};F.removePrefix=(e,r={})=>{let t=e;return t.startsWith("./")&&(t=t.slice(2),r.prefix="./"),t};F.wrapOutput=(e,r={},t={})=>{let n=t.contains?"":"^",s=t.contains?"":"$",i=`${n}(?:${e})${s}`;return r.negated===!0&&(i=`(?:^(?!${i}).*$)`),i}});var Yt=q((cs,Zt)=>{"use strict";var Kt=Re(),{CHAR_ASTERISK:We,CHAR_AT:vn,CHAR_BACKWARD_SLASH:ye,CHAR_COMMA:Hn,CHAR_DOT:je,CHAR_EXCLAMATION_MARK:Fe,CHAR_FORWARD_SLASH:Xt,CHAR_LEFT_CURLY_BRACE:Qe,CHAR_LEFT_PARENTHESES:Xe,CHAR_LEFT_SQUARE_BRACKET:$n,CHAR_PLUS:Tn,CHAR_QUESTION_MARK:Wt,CHAR_RIGHT_CURLY_BRACE:kn,CHAR_RIGHT_PARENTHESES:jt,CHAR_RIGHT_SQUARE_BRACKET:Ln}=me(),Ft=e=>e===Xt||e===ye,Qt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?1/0:1)},On=(e,r)=>{let t=r||{},n=e.length-1,s=t.parts===!0||t.scanToEnd===!0,i=[],a=[],c=[],p=e,m=-1,h=0,R=0,f=!1,$=!1,_=!1,y=!1,E=!1,S=!1,T=!1,L=!1,z=!1,I=!1,re=0,K,g,v={value:"",depth:0,isGlob:!1},k=()=>m>=n,l=()=>p.charCodeAt(m+1),H=()=>(K=g,p.charCodeAt(++m));for(;m0&&(B=p.slice(0,h),p=p.slice(h),R-=h),w&&_===!0&&R>0?(w=p.slice(0,R),o=p.slice(R)):_===!0?(w="",o=p):w=p,w&&w!==""&&w!=="/"&&w!==p&&Ft(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),t.unescape===!0&&(o&&(o=Kt.removeBackslashes(o)),w&&T===!0&&(w=Kt.removeBackslashes(w)));let u={prefix:B,input:e,start:h,base:w,glob:o,isBrace:f,isBracket:$,isGlob:_,isExtglob:y,isGlobstar:E,negated:L,negatedExtglob:z};if(t.tokens===!0&&(u.maxDepth=0,Ft(g)||a.push(v),u.tokens=a),t.parts===!0||t.tokens===!0){let M;for(let b=0;b{"use strict";var ke=me(),Z=Re(),{MAX_LENGTH:Le,POSIX_REGEX_SOURCE:Nn,REGEX_NON_SPECIAL_CHARS:In,REGEX_SPECIAL_CHARS_BACKREF:Bn,REPLACEMENTS:zt}=ke,Mn=(e,r)=>{if(typeof r.expandRange=="function")return r.expandRange(...e,r);e.sort();let t=`[${e.join("-")}]`;try{new RegExp(t)}catch{return e.map(s=>Z.escapeRegex(s)).join("..")}return t},de=(e,r)=>`Missing ${e}: "${r}" - use "\\\\${r}" to match literal characters`,Vt=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=zt[e]||e;let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let i={type:"bos",value:"",output:t.prepend||""},a=[i],c=t.capture?"":"?:",p=Z.isWindows(r),m=ke.globChars(p),h=ke.extglobChars(m),{DOT_LITERAL:R,PLUS_LITERAL:f,SLASH_LITERAL:$,ONE_CHAR:_,DOTS_SLASH:y,NO_DOT:E,NO_DOT_SLASH:S,NO_DOTS_SLASH:T,QMARK:L,QMARK_NO_DOT:z,STAR:I,START_ANCHOR:re}=m,K=A=>`(${c}(?:(?!${re}${A.dot?y:R}).)*?)`,g=t.dot?"":E,v=t.dot?L:z,k=t.bash===!0?K(t):I;t.capture&&(k=`(${k})`),typeof t.noext=="boolean"&&(t.noextglob=t.noext);let l={input:e,index:-1,start:0,dot:t.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:a};e=Z.removePrefix(e,l),s=e.length;let H=[],w=[],B=[],o=i,u,M=()=>l.index===s-1,b=l.peek=(A=1)=>e[l.index+A],V=l.advance=()=>e[++l.index]||"",J=()=>e.slice(l.index+1),Q=(A="",O=0)=>{l.consumed+=A,l.index+=O},Ee=A=>{l.output+=A.output!=null?A.output:A.value,Q(A.value)},Rr=()=>{let A=1;for(;b()==="!"&&(b(2)!=="("||b(3)==="?");)V(),l.start++,A++;return A%2===0?!1:(l.negated=!0,l.start++,!0)},be=A=>{l[A]++,B.push(A)},oe=A=>{l[A]--,B.pop()},C=A=>{if(o.type==="globstar"){let O=l.braces>0&&(A.type==="comma"||A.type==="brace"),d=A.extglob===!0||H.length&&(A.type==="pipe"||A.type==="paren");A.type!=="slash"&&A.type!=="paren"&&!O&&!d&&(l.output=l.output.slice(0,-o.output.length),o.type="star",o.value="*",o.output=k,l.output+=o.output)}if(H.length&&A.type!=="paren"&&(H[H.length-1].inner+=A.value),(A.value||A.output)&&Ee(A),o&&o.type==="text"&&A.type==="text"){o.value+=A.value,o.output=(o.output||"")+A.value;return}A.prev=o,a.push(A),o=A},xe=(A,O)=>{let d={...h[O],conditions:1,inner:""};d.prev=o,d.parens=l.parens,d.output=l.output;let x=(t.capture?"(":"")+d.open;be("parens"),C({type:A,value:O,output:l.output?"":_}),C({type:"paren",extglob:!0,value:V(),output:x}),H.push(d)},yr=A=>{let O=A.close+(t.capture?")":""),d;if(A.type==="negate"){let x=k;A.inner&&A.inner.length>1&&A.inner.includes("/")&&(x=K(t)),(x!==k||M()||/^\)+$/.test(J()))&&(O=A.close=`)$))${x}`),A.inner.includes("*")&&(d=J())&&/^\.[^\\/.]+$/.test(d)&&(O=A.close=`)${d})${x})`),A.prev.type==="bos"&&(l.negatedExtglob=!0)}C({type:"paren",extglob:!0,value:u,output:O}),oe("parens")};if(t.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let A=!1,O=e.replace(Bn,(d,x,P,j,G,Ie)=>j==="\\"?(A=!0,d):j==="?"?x?x+j+(G?L.repeat(G.length):""):Ie===0?v+(G?L.repeat(G.length):""):L.repeat(P.length):j==="."?R.repeat(P.length):j==="*"?x?x+j+(G?k:""):k:x?d:`\\${d}`);return A===!0&&(t.unescape===!0?O=O.replace(/\\/g,""):O=O.replace(/\\+/g,d=>d.length%2===0?"\\\\":d?"\\":"")),O===e&&t.contains===!0?(l.output=e,l):(l.output=Z.wrapOutput(O,l,r),l)}for(;!M();){if(u=V(),u==="\0")continue;if(u==="\\"){let d=b();if(d==="/"&&t.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",C({type:"text",value:u});continue}let x=/^\\+/.exec(J()),P=0;if(x&&x[0].length>2&&(P=x[0].length,l.index+=P,P%2!==0&&(u+="\\")),t.unescape===!0?u=V():u+=V(),l.brackets===0){C({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||o.value==="["||o.value==="[^")){if(t.posix!==!1&&u===":"){let d=o.value.slice(1);if(d.includes("[")&&(o.posix=!0,d.includes(":"))){let x=o.value.lastIndexOf("["),P=o.value.slice(0,x),j=o.value.slice(x+2),G=Nn[j];if(G){o.value=P+G,l.backtrack=!0,V(),!i.output&&a.indexOf(o)===1&&(i.output=_);continue}}}(u==="["&&b()!==":"||u==="-"&&b()==="]")&&(u=`\\${u}`),u==="]"&&(o.value==="["||o.value==="[^")&&(u=`\\${u}`),t.posix===!0&&u==="!"&&o.value==="["&&(u="^"),o.value+=u,Ee({value:u});continue}if(l.quotes===1&&u!=='"'){u=Z.escapeRegex(u),o.value+=u,Ee({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,t.keepQuotes===!0&&C({type:"text",value:u});continue}if(u==="("){be("parens"),C({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&t.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){yr(H.pop());continue}C({type:"paren",value:u,output:l.parens?")":"\\)"}),oe("parens");continue}if(u==="["){if(t.nobracket===!0||!J().includes("]")){if(t.nobracket!==!0&&t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else be("brackets");C({type:"bracket",value:u});continue}if(u==="]"){if(t.nobracket===!0||o&&o.type==="bracket"&&o.value.length===1){C({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(t.strictBrackets===!0)throw new SyntaxError(de("opening","["));C({type:"text",value:u,output:`\\${u}`});continue}oe("brackets");let d=o.value.slice(1);if(o.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),o.value+=u,Ee({value:u}),t.literalBrackets===!1||Z.hasRegexChars(d))continue;let x=Z.escapeRegex(o.value);if(l.output=l.output.slice(0,-o.value.length),t.literalBrackets===!0){l.output+=x,o.value=x;continue}o.value=`(${c}${x}|${o.value})`,l.output+=o.value;continue}if(u==="{"&&t.nobrace!==!0){be("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),C(d);continue}if(u==="}"){let d=w[w.length-1];if(t.nobrace===!0||!d){C({type:"text",value:u,output:u});continue}let x=")";if(d.dots===!0){let P=a.slice(),j=[];for(let G=P.length-1;G>=0&&(a.pop(),P[G].type!=="brace");G--)P[G].type!=="dots"&&j.unshift(P[G].value);x=Mn(j,t),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let P=l.output.slice(0,d.outputIndex),j=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=x="\\}",l.output=P;for(let G of j)l.output+=G.output||G.value}C({type:"brace",value:u,output:x}),oe("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,C({type:"text",value:u});continue}if(u===","){let d=u,x=w[w.length-1];x&&B[B.length-1]==="braces"&&(x.comma=!0,d="|"),C({type:"comma",value:u,output:d});continue}if(u==="/"){if(o.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",a.pop(),o=i;continue}C({type:"slash",value:u,output:$});continue}if(u==="."){if(l.braces>0&&o.type==="dot"){o.value==="."&&(o.output=R);let d=w[w.length-1];o.type="dots",o.output+=u,o.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&o.type!=="bos"&&o.type!=="slash"){C({type:"text",value:u,output:R});continue}C({type:"dot",value:u,output:R});continue}if(u==="?"){if(!(o&&o.value==="(")&&t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("qmark",u);continue}if(o&&o.type==="paren"){let x=b(),P=u;if(x==="<"&&!Z.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(o.value==="("&&!/[!=<:]/.test(x)||x==="<"&&!/<([!=]|\w+>)/.test(J()))&&(P=`\\${u}`),C({type:"text",value:u,output:P});continue}if(t.dot!==!0&&(o.type==="slash"||o.type==="bos")){C({type:"qmark",value:u,output:z});continue}C({type:"qmark",value:u,output:L});continue}if(u==="!"){if(t.noextglob!==!0&&b()==="("&&(b(2)!=="?"||!/[!=<:]/.test(b(3)))){xe("negate",u);continue}if(t.nonegate!==!0&&l.index===0){Rr();continue}}if(u==="+"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("plus",u);continue}if(o&&o.value==="("||t.regex===!1){C({type:"plus",value:u,output:f});continue}if(o&&(o.type==="bracket"||o.type==="paren"||o.type==="brace")||l.parens>0){C({type:"plus",value:u});continue}C({type:"plus",value:f});continue}if(u==="@"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){C({type:"at",extglob:!0,value:u,output:""});continue}C({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=In.exec(J());d&&(u+=d[0],l.index+=d[0].length),C({type:"text",value:u});continue}if(o&&(o.type==="globstar"||o.star===!0)){o.type="star",o.star=!0,o.value+=u,o.output=k,l.backtrack=!0,l.globstar=!0,Q(u);continue}let A=J();if(t.noextglob!==!0&&/^\([^?]/.test(A)){xe("star",u);continue}if(o.type==="star"){if(t.noglobstar===!0){Q(u);continue}let d=o.prev,x=d.prev,P=d.type==="slash"||d.type==="bos",j=x&&(x.type==="star"||x.type==="globstar");if(t.bash===!0&&(!P||A[0]&&A[0]!=="/")){C({type:"star",value:u,output:""});continue}let G=l.braces>0&&(d.type==="comma"||d.type==="brace"),Ie=H.length&&(d.type==="pipe"||d.type==="paren");if(!P&&d.type!=="paren"&&!G&&!Ie){C({type:"star",value:u,output:""});continue}for(;A.slice(0,3)==="/**";){let Ce=e[l.index+4];if(Ce&&Ce!=="/")break;A=A.slice(3),Q("/**",3)}if(d.type==="bos"&&M()){o.type="globstar",o.value+=u,o.output=K(t),l.output=o.output,l.globstar=!0,Q(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!j&&M()){l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=K(t)+(t.strictSlashes?")":"|$)"),o.value+=u,l.globstar=!0,l.output+=d.output+o.output,Q(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&A[0]==="/"){let Ce=A[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=`${K(t)}${$}|${$}${Ce})`,o.value+=u,l.output+=d.output+o.output,l.globstar=!0,Q(u+V()),C({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&A[0]==="/"){o.type="globstar",o.value+=u,o.output=`(?:^|${$}|${K(t)}${$})`,l.output=o.output,l.globstar=!0,Q(u+V()),C({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-o.output.length),o.type="globstar",o.output=K(t),o.value+=u,l.output+=o.output,l.globstar=!0,Q(u);continue}let O={type:"star",value:u,output:k};if(t.bash===!0){O.output=".*?",(o.type==="bos"||o.type==="slash")&&(O.output=g+O.output),C(O);continue}if(o&&(o.type==="bracket"||o.type==="paren")&&t.regex===!0){O.output=u,C(O);continue}(l.index===l.start||o.type==="slash"||o.type==="dot")&&(o.type==="dot"?(l.output+=S,o.output+=S):t.dot===!0?(l.output+=T,o.output+=T):(l.output+=g,o.output+=g),b()!=="*"&&(l.output+=_,o.output+=_)),C(O)}for(;l.brackets>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=Z.escapeLast(l.output,"["),oe("brackets")}for(;l.parens>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=Z.escapeLast(l.output,"("),oe("parens")}for(;l.braces>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=Z.escapeLast(l.output,"{"),oe("braces")}if(t.strictSlashes!==!0&&(o.type==="star"||o.type==="bracket")&&C({type:"maybe_slash",value:"",output:`${$}?`}),l.backtrack===!0){l.output="";for(let A of l.tokens)l.output+=A.output!=null?A.output:A.value,A.suffix&&(l.output+=A.suffix)}return l};Vt.fastpaths=(e,r)=>{let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=zt[e]||e;let i=Z.isWindows(r),{DOT_LITERAL:a,SLASH_LITERAL:c,ONE_CHAR:p,DOTS_SLASH:m,NO_DOT:h,NO_DOTS:R,NO_DOTS_SLASH:f,STAR:$,START_ANCHOR:_}=ke.globChars(i),y=t.dot?R:h,E=t.dot?f:h,S=t.capture?"":"?:",T={negated:!1,prefix:""},L=t.bash===!0?".*?":$;t.capture&&(L=`(${L})`);let z=g=>g.noglobstar===!0?L:`(${S}(?:(?!${_}${g.dot?m:a}).)*?)`,I=g=>{switch(g){case"*":return`${y}${p}${L}`;case".*":return`${a}${p}${L}`;case"*.*":return`${y}${L}${a}${p}${L}`;case"*/*":return`${y}${L}${c}${p}${E}${L}`;case"**":return y+z(t);case"**/*":return`(?:${y}${z(t)}${c})?${E}${p}${L}`;case"**/*.*":return`(?:${y}${z(t)}${c})?${E}${L}${a}${p}${L}`;case"**/.*":return`(?:${y}${z(t)}${c})?${a}${p}${L}`;default:{let v=/^(.*?)\.(\w+)$/.exec(g);if(!v)return;let k=I(v[1]);return k?k+a+v[2]:void 0}}},re=Z.removePrefix(e,T),K=I(re);return K&&t.strictSlashes!==!0&&(K+=`${c}?`),K};Jt.exports=Vt});var rr=q((fs,tr)=>{"use strict";var Pn=W("path"),Dn=Yt(),Ze=er(),Ye=Re(),Un=me(),Gn=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,r,t=!1)=>{if(Array.isArray(e)){let h=e.map(f=>D(f,r,t));return f=>{for(let $ of h){let _=$(f);if(_)return _}return!1}}let n=Gn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=r||{},i=Ye.isWindows(r),a=n?D.compileRe(e,r):D.makeRe(e,r,!1,!0),c=a.state;delete a.state;let p=()=>!1;if(s.ignore){let h={...r,ignore:null,onMatch:null,onResult:null};p=D(s.ignore,h,t)}let m=(h,R=!1)=>{let{isMatch:f,match:$,output:_}=D.test(h,a,r,{glob:e,posix:i}),y={glob:e,state:c,regex:a,posix:i,input:h,output:_,match:$,isMatch:f};return typeof s.onResult=="function"&&s.onResult(y),f===!1?(y.isMatch=!1,R?y:!1):p(h)?(typeof s.onIgnore=="function"&&s.onIgnore(y),y.isMatch=!1,R?y:!1):(typeof s.onMatch=="function"&&s.onMatch(y),R?y:!0)};return t&&(m.state=c),m};D.test=(e,r,t,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let i=t||{},a=i.format||(s?Ye.toPosixSlashes:null),c=e===n,p=c&&a?a(e):e;return c===!1&&(p=a?a(e):e,c=p===n),(c===!1||i.capture===!0)&&(i.matchBase===!0||i.basename===!0?c=D.matchBase(e,r,t,s):c=r.exec(p)),{isMatch:Boolean(c),match:c,output:p}};D.matchBase=(e,r,t,n=Ye.isWindows(t))=>(r instanceof RegExp?r:D.makeRe(r,t)).test(Pn.basename(e));D.isMatch=(e,r,t)=>D(r,t)(e);D.parse=(e,r)=>Array.isArray(e)?e.map(t=>D.parse(t,r)):Ze(e,{...r,fastpaths:!1});D.scan=(e,r)=>Dn(e,r);D.compileRe=(e,r,t=!1,n=!1)=>{if(t===!0)return e.output;let s=r||{},i=s.contains?"":"^",a=s.contains?"":"$",c=`${i}(?:${e.output})${a}`;e&&e.negated===!0&&(c=`^(?!${c}).*$`);let p=D.toRegex(c,r);return n===!0&&(p.state=e),p};D.makeRe=(e,r={},t=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return r.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ze.fastpaths(e,r)),s.output||(s=Ze(e,r)),D.compileRe(s,r,t,n)};D.toRegex=(e,r)=>{try{let t=r||{};return new RegExp(e,t.flags||(t.nocase?"i":""))}catch(t){if(r&&r.debug===!0)throw t;return/$^/}};D.constants=Un;tr.exports=D});var sr=q((ps,nr)=>{"use strict";nr.exports=rr()});var cr=q((hs,ur)=>{"use strict";var ir=W("util"),or=Mt(),ae=sr(),ze=Re(),ar=e=>e===""||e==="./",N=(e,r,t)=>{r=[].concat(r),e=[].concat(e);let n=new Set,s=new Set,i=new Set,a=0,c=h=>{i.add(h.output),t&&t.onResult&&t.onResult(h)};for(let h=0;h!n.has(h));if(t&&m.length===0){if(t.failglob===!0)throw new Error(`No matches found for "${r.join(", ")}"`);if(t.nonull===!0||t.nullglob===!0)return t.unescape?r.map(h=>h.replace(/\\/g,"")):r}return m};N.match=N;N.matcher=(e,r)=>ae(e,r);N.isMatch=(e,r,t)=>ae(r,t)(e);N.any=N.isMatch;N.not=(e,r,t={})=>{r=[].concat(r).map(String);let n=new Set,s=[],a=N(e,r,{...t,onResult:c=>{t.onResult&&t.onResult(c),s.push(c.output)}});for(let c of s)a.includes(c)||n.add(c);return[...n]};N.contains=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);if(Array.isArray(r))return r.some(n=>N.contains(e,n,t));if(typeof r=="string"){if(ar(e)||ar(r))return!1;if(e.includes(r)||e.startsWith("./")&&e.slice(2).includes(r))return!0}return N.isMatch(e,r,{...t,contains:!0})};N.matchKeys=(e,r,t)=>{if(!ze.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),r,t),s={};for(let i of n)s[i]=e[i];return s};N.some=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(n.some(a=>i(a)))return!0}return!1};N.every=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(!n.every(a=>i(a)))return!1}return!0};N.all=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);return[].concat(r).every(n=>ae(n,t)(e))};N.capture=(e,r,t)=>{let n=ze.isWindows(t),i=ae.makeRe(String(e),{...t,capture:!0}).exec(n?ze.toPosixSlashes(r):r);if(i)return i.slice(1).map(a=>a===void 0?"":a)};N.makeRe=(...e)=>ae.makeRe(...e);N.scan=(...e)=>ae.scan(...e);N.parse=(e,r)=>{let t=[];for(let n of[].concat(e||[]))for(let s of or(String(n),r))t.push(ae.parse(s,r));return t};N.braces=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return r&&r.nobrace===!0||!/\{.*\}/.test(e)?[e]:or(e,r)};N.braceExpand=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,{...r,expand:!0})};ur.exports=N});var fr=q((ds,lr)=>{"use strict";lr.exports=(e,...r)=>new Promise(t=>{t(e(...r))})});var hr=q((gs,Ve)=>{"use strict";var qn=fr(),pr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let r=[],t=0,n=()=>{t--,r.length>0&&r.shift()()},s=(c,p,...m)=>{t++;let h=qn(c,...m);p(h),h.then(n,n)},i=(c,p,...m)=>{tnew Promise(m=>i(c,m,...p));return Object.defineProperties(a,{activeCount:{get:()=>t},pendingCount:{get:()=>r.length}}),a};Ve.exports=pr;Ve.exports.default=pr});var Fn={};wr(Fn,{default:()=>jn});var Se=W("@yarnpkg/cli"),ne=W("@yarnpkg/core"),et=W("@yarnpkg/core"),ue=W("clipanion"),ce=class extends Se.BaseCommand{constructor(){super(...arguments);this.json=ue.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=ue.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=ue.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=ue.Option.Rest()}async execute(){let t=await ne.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ne.Project.find(t,this.context.cwd),i=await ne.Cache.find(t);await n.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(n.workspaces);else if(this.workspaces.length===0){if(!s)throw new Se.WorkspaceRequiredError(n.cwd,this.context.cwd);a=new Set([s])}else a=new Set(this.workspaces.map(p=>n.getWorkspaceByIdent(et.structUtils.parseIdent(p))));for(let p of a)for(let m of this.production?["dependencies"]:ne.Manifest.hardDependencies)for(let h of p.manifest.getForScope(m).values()){let R=n.tryWorkspaceByDescriptor(h);R!==null&&a.add(R)}for(let p of n.workspaces)a.has(p)?this.production&&p.manifest.devDependencies.clear():(p.manifest.installConfig=p.manifest.installConfig||{},p.manifest.installConfig.selfReferences=!1,p.manifest.dependencies.clear(),p.manifest.devDependencies.clear(),p.manifest.peerDependencies.clear(),p.manifest.scripts.clear());return(await ne.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async p=>{await n.install({cache:i,report:p,persistProject:!1})})).exitCode()}};ce.paths=[["workspaces","focus"]],ce.usage=ue.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var Ne=W("@yarnpkg/cli"),ge=W("@yarnpkg/core"),_e=W("@yarnpkg/core"),Y=W("@yarnpkg/core"),gr=W("@yarnpkg/plugin-git"),U=W("clipanion"),Oe=Be(cr()),Ar=W("os"),mr=Be(hr()),te=Be(W("typanion")),pe=class extends Ne.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:te.isOneOf([te.isEnum(["unlimited"]),te.applyCascade(te.isNumber(),[te.isInteger(),te.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!s)throw new Ne.WorkspaceRequiredError(n.cwd,this.context.cwd);await n.restoreInstallState();let i=this.cli.process([this.commandName,...this.args]),a=i.path.length===1&&i.path[0]==="run"&&typeof i.scriptName<"u"?i.scriptName:null;if(i.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let c=this.all?n.topLevelWorkspace:s,p=this.since?Array.from(await gr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:n})):[c,...this.from.length>0?c.getRecursiveWorkspaceChildren():[]],m=g=>Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.from),h=this.from.length>0?p.filter(m):p,R=new Set([...h,...h.map(g=>[...this.recursive?this.since?g.getRecursiveWorkspaceDependents():g.getRecursiveWorkspaceDependencies():g.getRecursiveWorkspaceChildren()]).flat()]),f=[],$=!1;if(a!=null&&a.includes(":")){for(let g of n.workspaces)if(g.manifest.scripts.has(a)&&($=!$,$===!1))break}for(let g of R)a&&!g.manifest.scripts.has(a)&&!$&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(g)).has(a)||a===process.env.npm_lifecycle_event&&g.cwd===s.cwd||this.include.length>0&&!Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.include)||this.exclude.length>0&&Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.exclude)||this.publicOnly&&g.manifest.private===!0||f.push(g);let _=this.parallel?this.jobs==="unlimited"?1/0:Number(this.jobs)||Math.max(1,(0,Ar.cpus)().length/2):1,y=_===1?!1:this.parallel,E=y?this.interlaced:!0,S=(0,mr.default)(_),T=new Map,L=new Set,z=0,I=null,re=!1,K=await _e.StreamReport.start({configuration:t,stdout:this.context.stdout},async g=>{let v=async(k,{commandIndex:l})=>{if(re)return-1;!y&&this.verbose&&l>1&&g.reportSeparator();let H=Kn(k,{configuration:t,verbose:this.verbose,commandIndex:l}),[w,B]=dr(g,{prefix:H,interlaced:E}),[o,u]=dr(g,{prefix:H,interlaced:E});try{this.verbose&&g.reportInfo(null,`${H} Process started`);let M=Date.now(),b=await this.cli.run([this.commandName,...this.args],{cwd:k.cwd,stdout:w,stderr:o})||0;w.end(),o.end(),await B,await u;let V=Date.now();if(this.verbose){let J=t.get("enableTimers")?`, completed in ${Y.formatUtils.pretty(t,V-M,Y.formatUtils.Type.DURATION)}`:"";g.reportInfo(null,`${H} Process exited (exit code ${b})${J}`)}return b===130&&(re=!0,I=b),b}catch(M){throw w.end(),o.end(),await B,await u,M}};for(let k of f)T.set(k.anchoredLocator.locatorHash,k);for(;T.size>0&&!g.hasErrors();){let k=[];for(let[w,B]of T){if(L.has(B.anchoredDescriptor.descriptorHash))continue;let o=!0;if(this.topological||this.topologicalDev){let u=this.topologicalDev?new Map([...B.manifest.dependencies,...B.manifest.devDependencies]):B.manifest.dependencies;for(let M of u.values()){let b=n.tryWorkspaceByDescriptor(M);if(o=b===null||!T.has(b.anchoredLocator.locatorHash),!o)break}}if(!!o&&(L.add(B.anchoredDescriptor.descriptorHash),k.push(S(async()=>{let u=await v(B,{commandIndex:++z});return T.delete(w),L.delete(B.anchoredDescriptor.descriptorHash),u})),!y))break}if(k.length===0){let w=Array.from(T.values()).map(B=>Y.structUtils.prettyLocator(t,B.anchoredLocator)).join(", ");g.reportError(_e.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${w})`);return}let H=(await Promise.all(k)).find(w=>w!==0);I===null&&(I=typeof H<"u"?1:I),(this.topological||this.topologicalDev)&&typeof H<"u"&&g.reportError(_e.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return I!==null?I:K.exitCode()}};pe.paths=[["workspaces","foreach"]],pe.usage=U.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});function dr(e,{prefix:r,interlaced:t}){let n=e.createStreamReporter(r),s=new Y.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let i=new Promise(c=>{n.on("finish",()=>{c(s.active)})});if(t)return[s,i];let a=new Y.miscUtils.BufferStream;return a.pipe(s,{end:!1}),a.on("finish",()=>{s.end()}),[a,i]}function Kn(e,{configuration:r,commandIndex:t,verbose:n}){if(!n)return null;let i=`[${Y.structUtils.stringifyIdent(e.locator)}]:`,a=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],c=a[t%a.length];return Y.formatUtils.pretty(r,i,c)}var Wn={commands:[ce,pe]},jn=Wn;return Sr(Fn);})(); 8 | /*! 9 | * fill-range 10 | * 11 | * Copyright (c) 2014-present, Jon Schlinkert. 12 | * Licensed under the MIT License. 13 | */ 14 | /*! 15 | * is-number 16 | * 17 | * Copyright (c) 2014-present, Jon Schlinkert. 18 | * Released under the MIT License. 19 | */ 20 | /*! 21 | * to-regex-range 22 | * 23 | * Copyright (c) 2015-present, Jon Schlinkert. 24 | * Released under the MIT License. 25 | */ 26 | return plugin; 27 | } 28 | }; 29 | --------------------------------------------------------------------------------