The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .all-contributorsrc
├── .changeset
    ├── README.md
    ├── config.json
    └── light-tigers-explode.md
├── .codesandbox
    └── ci.json
├── .eslintignore
├── .eslintrc.json
├── .github
    └── workflows
    │   ├── main.yml
    │   └── release.yml
├── .gitignore
├── .husky
    ├── .gitignore
    └── pre-commit
├── .prettierignore
├── .storybook
    ├── main.js
    ├── manager.js
    └── preview.js
├── .vscode
    └── settings.json
├── .yarn
    ├── plugins
    │   └── @yarnpkg
    │   │   ├── plugin-interactive-tools.cjs
    │   │   ├── plugin-version.cjs
    │   │   └── plugin-workspace-tools.cjs
    └── releases
    │   └── yarn-sources.cjs
├── .yarnrc.yml
├── LICENSE
├── README.md
├── babel.config.js
├── code_of_conduct.md
├── contributing.md
├── cypress.json
├── cypress
    ├── fixtures
    │   └── example.json
    ├── integration
    │   └── spec.js
    ├── plugins
    │   └── index.js
    └── support
    │   ├── commands.js
    │   └── index.js
├── demo
    ├── index.html
    ├── package.json
    ├── serve.json
    ├── src
    │   ├── App.jsx
    │   ├── index.css
    │   ├── index.jsx
    │   ├── sandboxes
    │   │   ├── leva-advanced-panels
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.jsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.jsx
    │   │   ├── leva-busy
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.tsx
    │   │   │   │   ├── index.css
    │   │   │   │   ├── index.tsx
    │   │   │   │   └── styles.module.css
    │   │   ├── leva-custom-plugin
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.tsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.tsx
    │   │   ├── leva-minimal
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.jsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.jsx
    │   │   ├── leva-plugin-bezier
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.tsx
    │   │   │   │   ├── index.css
    │   │   │   │   ├── index.tsx
    │   │   │   │   └── style.css
    │   │   ├── leva-plugin-dates
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.tsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.tsx
    │   │   ├── leva-plugin-plot
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.tsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.tsx
    │   │   ├── leva-plugin-spring
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.tsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.tsx
    │   │   ├── leva-scroll
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.jsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.jsx
    │   │   ├── leva-theme
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.jsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.jsx
    │   │   ├── leva-transient
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │   │   └── index.html
    │   │   │   └── src
    │   │   │   │   ├── App.jsx
    │   │   │   │   ├── index.css
    │   │   │   │   └── index.jsx
    │   │   └── leva-ui
    │   │   │   ├── package.json
    │   │   │   ├── public
    │   │   │       └── index.html
    │   │   │   └── src
    │   │   │       ├── App.jsx
    │   │   │       ├── index.css
    │   │   │       ├── index.jsx
    │   │   │       └── styles.css
    │   └── styles.module.css
    ├── tsconfig.json
    └── vite.config.js
├── docs
    ├── advanced
    │   ├── circle-drag.gif
    │   ├── controlled-inputs.md
    │   └── creating-plugins.md
    ├── configuration.md
    ├── getting-started.md
    ├── inputs.md
    ├── plugins.md
    ├── special-inputs.md
    ├── styling.md
    └── typescript.md
├── hero.png
├── package.json
├── packages
    ├── leva
    │   ├── .npmignore
    │   ├── CHANGELOG.md
    │   ├── README.md
    │   ├── package.json
    │   ├── plugin
    │   │   └── package.json
    │   ├── src
    │   │   ├── components
    │   │   │   ├── Boolean
    │   │   │   │   ├── Boolean.tsx
    │   │   │   │   ├── StyledBoolean.ts
    │   │   │   │   ├── boolean-plugin.ts
    │   │   │   │   ├── boolean-types.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── Button
    │   │   │   │   ├── Button.tsx
    │   │   │   │   ├── StyledButton.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── ButtonGroup
    │   │   │   │   ├── ButtonGroup.tsx
    │   │   │   │   ├── StyledButtonGroup.tsx
    │   │   │   │   ├── StyledButtonGroupButton.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── Color
    │   │   │   │   ├── Color.tsx
    │   │   │   │   ├── StyledColor.ts
    │   │   │   │   ├── color-plugin.ts
    │   │   │   │   ├── color-types.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── Control
    │   │   │   │   ├── Control.tsx
    │   │   │   │   ├── ControlInput.tsx
    │   │   │   │   └── index.ts
    │   │   │   ├── Folder
    │   │   │   │   ├── Folder.tsx
    │   │   │   │   ├── FolderTitle.tsx
    │   │   │   │   ├── StyledFolder.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── Image
    │   │   │   │   ├── Image.tsx
    │   │   │   │   ├── StyledImage.ts
    │   │   │   │   ├── image-plugin.ts
    │   │   │   │   ├── image-types.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── Interval
    │   │   │   │   ├── Interval.tsx
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── interval-plugin.ts
    │   │   │   │   └── interval-types.ts
    │   │   │   ├── Leva
    │   │   │   │   ├── Filter.tsx
    │   │   │   │   ├── Leva.tsx
    │   │   │   │   ├── LevaPanel.tsx
    │   │   │   │   ├── LevaRoot.tsx
    │   │   │   │   ├── StyledFilter.ts
    │   │   │   │   ├── StyledRoot.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   └── tree.ts
    │   │   │   ├── Monitor
    │   │   │   │   ├── Monitor.tsx
    │   │   │   │   ├── StyledMonitor.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── Number
    │   │   │   │   ├── Number.tsx
    │   │   │   │   ├── RangeSlider.tsx
    │   │   │   │   ├── StyledNumber.ts
    │   │   │   │   ├── StyledRange.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── number-plugin.ts
    │   │   │   │   └── number-types.ts
    │   │   │   ├── Select
    │   │   │   │   ├── Select.tsx
    │   │   │   │   ├── StyledSelect.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── select-plugin.ts
    │   │   │   │   └── select-types.ts
    │   │   │   ├── String
    │   │   │   │   ├── String.tsx
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── string-plugin.ts
    │   │   │   │   └── string-types.ts
    │   │   │   ├── UI
    │   │   │   │   ├── Chevron.tsx
    │   │   │   │   ├── Label.tsx
    │   │   │   │   ├── Misc.tsx
    │   │   │   │   ├── Row.tsx
    │   │   │   │   ├── StyledUI.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── ValueInput
    │   │   │   │   ├── StyledInput.ts
    │   │   │   │   ├── ValueInput.tsx
    │   │   │   │   └── index.ts
    │   │   │   ├── Vector
    │   │   │   │   ├── Vector.tsx
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── vector-plugin.ts
    │   │   │   │   ├── vector-types.ts
    │   │   │   │   └── vector-utils.ts
    │   │   │   ├── Vector2d
    │   │   │   │   ├── Joystick.tsx
    │   │   │   │   ├── StyledJoystick.ts
    │   │   │   │   ├── Vector2d.tsx
    │   │   │   │   ├── index.ts
    │   │   │   │   └── vector2d-types.ts
    │   │   │   └── Vector3d
    │   │   │   │   ├── Vector3d.tsx
    │   │   │   │   ├── index.ts
    │   │   │   │   └── vector3d-types.ts
    │   │   ├── context.tsx
    │   │   ├── eventEmitter.ts
    │   │   ├── helpers
    │   │   │   ├── button.ts
    │   │   │   ├── buttonGroup.ts
    │   │   │   ├── folder.ts
    │   │   │   ├── index.ts
    │   │   │   └── monitor.ts
    │   │   ├── hooks
    │   │   │   ├── index.ts
    │   │   │   ├── useCanvas.ts
    │   │   │   ├── useCompareMemoize.ts
    │   │   │   ├── useDeepMemo.ts
    │   │   │   ├── useDrag.ts
    │   │   │   ├── useInput.ts
    │   │   │   ├── useInputSetters.ts
    │   │   │   ├── usePopin.ts
    │   │   │   ├── useShallowMemo.ts
    │   │   │   ├── useToggle.ts
    │   │   │   ├── useTransform.ts
    │   │   │   ├── useValue.ts
    │   │   │   ├── useValuesForPath.ts
    │   │   │   └── useVisiblePaths.ts
    │   │   ├── index.ts
    │   │   ├── plugin.ts
    │   │   ├── plugin
    │   │   │   └── index.ts
    │   │   ├── store.ts
    │   │   ├── styles
    │   │   │   ├── index.ts
    │   │   │   └── stitches.config.ts
    │   │   ├── types
    │   │   │   ├── index.ts
    │   │   │   ├── internal.ts
    │   │   │   ├── public.test.ts
    │   │   │   ├── public.ts
    │   │   │   ├── utils.ts
    │   │   │   └── v8n.d.ts
    │   │   ├── useControls.ts
    │   │   └── utils
    │   │   │   ├── data.ts
    │   │   │   ├── event.ts
    │   │   │   ├── fn.ts
    │   │   │   ├── index.ts
    │   │   │   ├── input.ts
    │   │   │   ├── log.ts
    │   │   │   ├── math.ts
    │   │   │   ├── object.ts
    │   │   │   ├── path.ts
    │   │   │   └── react.ts
    │   └── stories
    │   │   ├── Folder.stories.tsx
    │   │   ├── caching.stories.tsx
    │   │   ├── components
    │   │       └── decorator-reset.tsx
    │   │   ├── controlled-inputs.stories.tsx
    │   │   ├── hook-dependencies.stories.tsx
    │   │   ├── input-options.stories.tsx
    │   │   ├── inputs
    │   │       ├── Boolean.stories.tsx
    │   │       ├── Button.stories.tsx
    │   │       ├── ButtonGroup.stories.tsx
    │   │       ├── Color.stories.tsx
    │   │       ├── Image.stories.tsx
    │   │       ├── Interval.stories.tsx
    │   │       ├── Number.stories.tsx
    │   │       ├── Select.stories.tsx
    │   │       ├── String.stories.tsx
    │   │       └── Vector.stories.tsx
    │   │   └── panel-options.stories.tsx
    ├── plugin-bezier
    │   ├── CHANGELOG.md
    │   ├── README.md
    │   ├── package.json
    │   └── src
    │   │   ├── Bezier.stories.css
    │   │   ├── Bezier.stories.tsx
    │   │   ├── Bezier.tsx
    │   │   ├── BezierPreview.tsx
    │   │   ├── BezierSvg.tsx
    │   │   ├── StyledBezier.ts
    │   │   ├── bezier-plugin.ts
    │   │   ├── bezier-types.ts
    │   │   ├── bezier-utils.ts
    │   │   └── index.ts
    ├── plugin-dates
    │   ├── CHANGELOG.md
    │   ├── README.md
    │   ├── package.json
    │   └── src
    │   │   ├── Date.stories.tsx
    │   │   ├── Date.tsx
    │   │   ├── StyledDate.ts
    │   │   ├── date-plugin.ts
    │   │   ├── date-types.ts
    │   │   ├── date-utils.ts
    │   │   └── index.ts
    ├── plugin-plot
    │   ├── CHANGELOG.md
    │   ├── README.md
    │   ├── package.json
    │   └── src
    │   │   ├── Plot.stories.tsx
    │   │   ├── Plot.tsx
    │   │   ├── PlotCanvas.tsx
    │   │   ├── StyledPlot.ts
    │   │   ├── index.ts
    │   │   ├── plot-plugin.ts
    │   │   ├── plot-types.ts
    │   │   └── plot-utils.ts
    └── plugin-spring
    │   ├── CHANGELOG.md
    │   ├── README.md
    │   ├── package.json
    │   └── src
    │       ├── Spring.stories.tsx
    │       ├── Spring.tsx
    │       ├── SpringCanvas.tsx
    │       ├── StyledSpring.ts
    │       ├── index.ts
    │       ├── math.ts
    │       ├── spring-plugin.ts
    │       └── spring-types.ts
├── tsconfig.json
└── yarn.lock


/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 | 
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 | 
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md)
9 | 


--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json",
 3 |   "changelog": "@changesets/cli/changelog",
 4 |   "commit": false,
 5 |   "access": "public",
 6 |   "baseBranch": "main",
 7 |   "updateInternalDependencies": "patch",
 8 |   "ignore": ["demo"]
 9 | }
10 | 


--------------------------------------------------------------------------------
/.changeset/light-tigers-explode.md:
--------------------------------------------------------------------------------
1 | ---
2 | "leva": patch
3 | ---
4 | 
5 | `@radix-ui/*` upgrades to prevent peerDeps warnings with React 19
6 | 


--------------------------------------------------------------------------------
/.codesandbox/ci.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "packages": [
 3 |     "packages/*"
 4 |   ],
 5 |   "sandboxes": [
 6 |     "/demo/src/sandboxes/leva-minimal",
 7 |     "/demo/src/sandboxes/leva-busy",
 8 |     "/demo/src/sandboxes/leva-scroll",
 9 |     "/demo/src/sandboxes/leva-advanced-panels",
10 |     "/demo/src/sandboxes/leva-ui",
11 |     "/demo/src/sandboxes/leva-theme",
12 |     "/demo/src/sandboxes/leva-transient",
13 |     "/demo/src/sandboxes/leva-plugin-plot",
14 |     "/demo/src/sandboxes/leva-plugin-bezier",
15 |     "/demo/src/sandboxes/leva-plugin-spring",
16 |     "/demo/src/sandboxes/leva-plugin-dates",
17 |     "/demo/src/sandboxes/leva-custom-plugin"
18 |   ],
19 |   "node": "14"
20 | }
21 | 


--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | .yarn/


--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "extends": "react-app",
 3 |   "rules": {
 4 |     "no-console": "warn"
 5 |   },
 6 |   "plugins": ["cypress"],
 7 |   "env": {
 8 |     "cypress/globals": true
 9 |   }
10 | }
11 | 


--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
 1 | name: CI
 2 | on:
 3 |   push:
 4 |     paths:
 5 |       - '.github/**'
 6 |       - 'packages/**'
 7 |       - 'package.json'
 8 |       - 'yarn.lock'
 9 |       - '!demo/**'
10 |       - '!docs/**'
11 |       - '!**.md'
12 |       - '!.changeset/**'
13 | jobs:
14 |   build:
15 |     name: Build, lint, and test
16 |     runs-on: ubuntu-latest
17 | 
18 |     steps:
19 |       - name: Checkout repo
20 |         uses: actions/checkout@v3
21 | 
22 |       - name: Use Node
23 |         uses: actions/setup-node@v3
24 |         with:
25 |           node-version: '18'
26 | 
27 |       - name: Get yarn cache directory path
28 |         id: yarn-cache-dir-path
29 |         run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
30 | 
31 |       - uses: actions/cache@v3
32 |         id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
33 |         with:
34 |           path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
35 |           key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
36 |           restore-keys: |
37 |             ${{ runner.os }}-yarn-
38 | 
39 |       - name: Install dependencies
40 |         run: yarn --silent
41 | 
42 |       - name: Check types
43 |         run: yarn tsc
44 | 
45 |       - name: Check formatting
46 |         run: yarn prettier
47 | 
48 |       - name: Lint files
49 |         run: yarn lint:full
50 | 
51 |       - uses: actions/cache@v3
52 |         name: Setup Yarn build cache
53 |         id: yarn-build-cache
54 |         with:
55 |           path: packages/**/dist
56 |           key: ${{ runner.os }}-yarn-build-${{ hashFiles('/packages/**/*') }}
57 |           restore-keys: |
58 |             ${{ runner.os }}-yarn-build-
59 | 
60 |       - name: Yarn build without cache
61 |         if: steps.yarn-build-cache.outputs.cache-hit != 'true'
62 |         run: yarn build
63 | 
64 |       - name: Cypress run
65 |         run: yarn ci:test
66 | 


--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
 1 | name: Release
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - main
 7 |     paths:
 8 |       - '**/package.json'
 9 |       - '.changeset/**'
10 |       - '.github/workflows/release.yml'
11 | 
12 | jobs:
13 |   release:
14 |     name: Release
15 |     runs-on: ubuntu-latest
16 |     steps:
17 |       - name: Checkout Repo
18 |         uses: actions/checkout@v3
19 |         with:
20 |           # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
21 |           fetch-depth: 0
22 | 
23 |       - name: Use Node
24 |         uses: actions/setup-node@v3
25 |         with:
26 |           node-version: '18'
27 | 
28 |       - name: Get yarn cache directory path
29 |         id: yarn-cache-dir-path
30 |         run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
31 | 
32 |       - uses: actions/cache@v3
33 |         id: yarn-cache
34 |         with:
35 |           path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
36 |           key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
37 |           restore-keys: |
38 |             ${{ runner.os }}-yarn-
39 | 
40 |       - name: Install dependencies
41 |         run: yarn install --silent
42 | 
43 |       - name: Create Release Pull Request or Publish to npm
44 |         id: changesets
45 |         uses: changesets/action@v1
46 |         with:
47 |           version: yarn ci:version
48 |           publish: yarn ci:release
49 |           commit: 'chore(release): update monorepo packages versions'
50 |           title: 'Upcoming Release Changes'
51 |         env:
52 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 |           NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
54 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 2 | 
 3 | # dependencies
 4 | node_modules/
 5 | .yarn/*
 6 | !.yarn/releases
 7 | !.yarn/plugins
 8 | !.yarn/sdks
 9 | !.yarn/versions
10 | .pnp.*
11 | 
12 | # testing
13 | /coverage
14 | 
15 | # production
16 | build/
17 | dist/
18 | .cache/
19 | .parcel-cache/
20 | 
21 | # misc
22 | .DS_Store
23 | .env.local
24 | .env.development.local
25 | .env.test.local
26 | .env.production.local
27 | 
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | 
32 | storybook-static/
33 | .idea
34 | cypress/screenshots
35 | 


--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 | 


--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 | 
4 | npm run lint
5 | 


--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
 1 | .changeset/
 2 | .codesandbox/
 3 | .github/
 4 | .husky/
 5 | .storybook/
 6 | .vscode/
 7 | .yarn/
 8 | dist/
 9 | node_modules/
10 | patches/
11 | storybook-static/
12 | 


--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
 1 | const path = require('path')
 2 | 
 3 | module.exports = {
 4 |   stories: ['../packages/**/*.stories.mdx', '../packages/**/*.stories.@(js|jsx|ts|tsx)'],
 5 |   addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-storysource'],
 6 |   webpackFinal: async (config, { configType }) => {
 7 |     // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
 8 |     // You can change the configuration based on that.
 9 |     // 'PRODUCTION' is used when building the static version of storybook.
10 | 
11 |     // manually resolve packages from other repos so that vercel builds properly
12 |     if (configType === 'PRODUCTION') {
13 |       config.resolve.alias['leva'] = path.resolve(__dirname, '../packages/leva/')
14 |     }
15 |     // Return the altered config
16 |     return config
17 |   },
18 |   typescript: {
19 |     reactDocgen: 'none', // temp fix for TS 4.3.2
20 |   },
21 | }
22 | 


--------------------------------------------------------------------------------
/.storybook/manager.js:
--------------------------------------------------------------------------------
1 | import { addons } from '@storybook/addons';
2 | import { themes } from '@storybook/theming';
3 | 
4 | addons.setConfig({
5 |   theme: themes.dark,
6 | });


--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
 1 | 
 2 | export const parameters = {
 3 |   actions: { argTypesRegex: "^on[A-Z].*" },
 4 |   "previewTabs": {
 5 |     'storybook/docs/panel': { hidden: true }
 6 |   },
 7 |   options: {
 8 |     storySort: {
 9 |       order: ["Inputs", ["String", "Boolean", "Number", "Interval"], "Misc", "Plugins"]
10 |     }
11 |   },
12 | }
13 | 
14 | export const  decorators = [(Story) => <div style={{ color: "white" }}><Story /></div>]
15 | 


--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 |   "typescript.tsdk": "node_modules/typescript/lib"
3 | }


--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
 1 | nodeLinker: node-modules
 2 | 
 3 | plugins:
 4 |   - path: .yarn/plugins/@yarnpkg/plugin-version.cjs
 5 |     spec: '@yarnpkg/plugin-version'
 6 |   - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
 7 |     spec: '@yarnpkg/plugin-interactive-tools'
 8 |   - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
 9 |     spec: '@yarnpkg/plugin-workspace-tools'
10 | 
11 | yarnPath: .yarn/releases/yarn-sources.cjs
12 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2021 Poimandres
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   comments: false,
 3 |   presets: [
 4 |     [
 5 |       '@babel/preset-env',
 6 |       {
 7 |         bugfixes: true,
 8 |         targets: {
 9 |           esmodules: true,
10 |         },
11 |       },
12 |     ],
13 |     '@babel/preset-react',
14 |     '@babel/preset-typescript',
15 |   ],
16 | }
17 | 


--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
 1 | # Contributing
 2 | 
 3 | ## What we are looking for
 4 | 
 5 | - Input Suggestions
 6 | - Plugin Suggestions
 7 | - Alternative Themes
 8 | - Unit Tests
 9 | - Docs
10 | 


--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 |   "baseUrl": "http://localhost:5000",
3 |   "defaultCommandTimeout": 10000,
4 |   "video": false
5 | }
6 | 


--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 |   "name": "Using fixtures to represent data",
3 |   "email": "hello@cypress.io",
4 |   "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 | 


--------------------------------------------------------------------------------
/cypress/integration/spec.js:
--------------------------------------------------------------------------------
 1 | describe('Number', () => {
 2 |   it('Works.', () => {
 3 |     cy.visit('/leva-minimal')
 4 |     cy.findByLabelText(/number/).should('exist')
 5 | 
 6 |     cy.findByLabelText(/number/)
 7 |       .clear()
 8 |       .type(123)
 9 |       .blur()
10 |     // expect 123 to be the value
11 |     cy.findByLabelText(/number/).should('have.value', 123)
12 |     // expect previous value when empty string is used
13 |     cy.findByLabelText(/number/)
14 |       .focus()
15 |       .clear()
16 |       .blur()
17 |   })
18 | 
19 |   it("Doesn't accept non-numbers.", () => {
20 |     cy.visit('/leva-minimal')
21 | 
22 |     // expect previous value when typing invalid characters
23 |     cy.findByLabelText(/number/)
24 |       .clear()
25 |       .type('ABC')
26 |       .blur()
27 |     cy.findByLabelText(/number/).should('have.value', 10)
28 |   })
29 | })
30 | 
31 | describe('MinMax', () => {
32 |   it('Works.', () => {
33 |     cy.visit('/leva-minimal')
34 |     cy.findByLabelText(/minmax/).should('exist')
35 | 
36 |     cy.findByLabelText(/number/)
37 |       .clear()
38 |       .type(13)
39 |       .blur()
40 |     cy.findByLabelText(/number/).should('have.value', 13)
41 |     // expect previous value when empty string is used
42 |     cy.findByLabelText(/number/)
43 |       .focus()
44 |       .clear()
45 |       .blur()
46 |   })
47 | 
48 |   it("Doesn't go over.", () => {
49 |     cy.visit('/leva-minimal')
50 | 
51 |     // since value is over max, it should reset to max
52 |     cy.findByLabelText(/minmax/)
53 |       .clear()
54 |       .type(123)
55 |       .blur()
56 |     cy.findByLabelText(/minmax/).should('have.value', 30.5)
57 |   })
58 | 
59 |   it("Doesn't go under.", () => {
60 |     cy.visit('/leva-minimal')
61 | 
62 |     // since value is under min, it should reset to initial
63 |     cy.findByLabelText(/minmax/)
64 |       .clear()
65 |       .type(1)
66 |       .blur()
67 |     cy.findByLabelText(/minmax/).should('have.value', 5.5)
68 |   })
69 | })
70 | 


--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
 1 | /// <reference types="cypress" />
 2 | // ***********************************************************
 3 | // This example plugins/index.js can be used to load plugins
 4 | //
 5 | // You can change the location of this file or turn off loading
 6 | // the plugins file with the 'pluginsFile' configuration option.
 7 | //
 8 | // You can read more here:
 9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 | 
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 | 
15 | /**
16 |  * @type {Cypress.PluginConfig}
17 |  */
18 | module.exports = (on, config) => {
19 |   // `on` is used to hook into various events Cypress emits
20 |   // `config` is the resolved Cypress config
21 | }
22 | 


--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | import '@testing-library/cypress/add-commands'
2 | 


--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
 1 | // ***********************************************************
 2 | // This example support/index.js is processed and
 3 | // loaded automatically before your test files.
 4 | //
 5 | // This is a great place to put global configuration and
 6 | // behavior that modifies Cypress.
 7 | //
 8 | // You can change the location of this file or turn off
 9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 | 
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 | 
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 | 


--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="UTF-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 6 |     <meta http-equiv="X-UA-Compatible" content="ie=edge" />
 7 |     <title>Sandboxes Leva</title>
 8 |   </head>
 9 | 
10 |   <body>
11 |     <div id="root"></div>
12 |     <script type="module" src="./src/index.jsx"></script>
13 |   </body>
14 | </html>
15 | 


--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "demo",
 3 |   "version": "1.0.0",
 4 |   "private": true,
 5 |   "scripts": {
 6 |     "dev": "vite --port 3300 --host",
 7 |     "build": "vite build",
 8 |     "serve": "vite preview"
 9 |   },
10 |   "dependencies": {
11 |     "@radix-ui/react-icons": "^1.0.3",
12 |     "@react-three/drei": "^8.8.3",
13 |     "@react-three/fiber": "^7.0.26",
14 |     "@stitches/react": "1.2.8",
15 |     "leva": "*",
16 |     "noisejs": "^2.1.0",
17 |     "react": "^18.0.0",
18 |     "react-dom": "^18.0.0",
19 |     "react-use": "^17.3.2",
20 |     "three": "^0.143.0",
21 |     "wouter": "^2.7.5"
22 |   },
23 |   "devDependencies": {
24 |     "@types/react": "^18.0.0",
25 |     "@types/react-dom": "^18.0.0",
26 |     "@vitejs/plugin-react-refresh": "^1.3.6",
27 |     "typescript": "^4.5.5",
28 |     "vite": "2.7.13"
29 |   }
30 | }
31 | 


--------------------------------------------------------------------------------
/demo/serve.json:
--------------------------------------------------------------------------------
1 | {
2 |   "rewrites": [{ "source": "/*", "destination": "/index.html" }]
3 | }
4 | 


--------------------------------------------------------------------------------
/demo/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/index.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-advanced-panels/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-advanced-panels",
 3 |   "version": "1.0.0",
 4 |   "main": "src/index.jsx",
 5 |   "dependencies": {
 6 |     "leva": "*",
 7 |     "react": "^18.0.0",
 8 |     "react-dom": "^18.0.0",
 9 |     "react-scripts": "4.0.3"
10 |   },
11 |   "scripts": {
12 |     "start": "react-scripts start",
13 |     "build": "react-scripts build",
14 |     "test": "react-scripts test --env=jsdom",
15 |     "eject": "react-scripts eject"
16 |   },
17 |   "browserslist": [
18 |     ">0.2%",
19 |     "not dead",
20 |     "not ie <= 11",
21 |     "not op_mini all"
22 |   ]
23 | }
24 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-advanced-panels/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-advanced-panels/src/App.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useControls, useStoreContext, useCreateStore, LevaPanel, LevaStoreProvider } from 'leva'
 3 | 
 4 | function MyComponent() {
 5 |   const store = useStoreContext()
 6 |   useControls({ point: [0, 0] }, { store })
 7 |   return null
 8 | }
 9 | 
10 | export default function App() {
11 |   const store1 = useCreateStore()
12 |   const store2 = useCreateStore()
13 |   useControls({ color: '#fff' }, { store: store1 })
14 |   useControls({ boolean: true }, { store: store2 })
15 |   return (
16 |     <div
17 |       style={{
18 |         display: 'grid',
19 |         width: 300,
20 |         gridRowGap: 10,
21 |         padding: 10,
22 |         background: '#fff',
23 |       }}>
24 |       <LevaPanel store={store1} fill flat titleBar={false} />
25 |       <LevaPanel store={store2} fill flat titleBar={false} />
26 |       <LevaStoreProvider store={store1}>
27 |         <MyComponent />
28 |       </LevaStoreProvider>
29 |     </div>
30 |   )
31 | }
32 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-advanced-panels/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-advanced-panels/src/index.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-busy/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-busy",
 3 |   "main": "src/index.jsx",
 4 |   "version": "1.0.0",
 5 |   "dependencies": {
 6 |     "@radix-ui/react-icons": "^1.0.2",
 7 |     "leva": "*",
 8 |     "noisejs": "2.1.0",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-scripts": "4.0.3",
12 |     "react-use": "^17.2.4"
13 |   },
14 |   "scripts": {
15 |     "start": "react-scripts start",
16 |     "build": "react-scripts build",
17 |     "test": "react-scripts test --env=jsdom",
18 |     "eject": "react-scripts eject"
19 |   },
20 |   "browserslist": [
21 |     ">0.2%",
22 |     "not dead",
23 |     "not ie <= 11",
24 |     "not op_mini all"
25 |   ],
26 |   "devDependencies": {
27 |     "@types/react": "^18.0.0",
28 |     "@types/react-dom": "^18.0.0",
29 |     "typescript": "^4.1.5"
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-busy/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-busy/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-busy/src/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-busy/src/styles.module.css:
--------------------------------------------------------------------------------
 1 | .buttons {
 2 |   display: flex;
 3 |   font-size: 14px;
 4 |   padding: 10px;
 5 |   align-items: center;
 6 | }
 7 | 
 8 | .buttons > * {
 9 |   margin-left: 4px;
10 | }
11 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-custom-plugin/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-custom-plugin",
 3 |   "version": "1.0.0",
 4 |   "main": "src/index.tsx",
 5 |   "dependencies": {
 6 |     "leva": "*",
 7 |     "react": "^18.0.0",
 8 |     "react-dom": "^18.0.0",
 9 |     "react-scripts": "4.0.3"
10 |   },
11 |   "devDependencies": {
12 |     "@types/react": "^18.0.0",
13 |     "@types/react-dom": "^18.0.0",
14 |     "typescript": "^4.1.5"
15 |   },
16 |   "scripts": {
17 |     "start": "react-scripts start",
18 |     "build": "react-scripts build",
19 |     "test": "react-scripts test --env=jsdom",
20 |     "eject": "react-scripts eject"
21 |   },
22 |   "browserslist": [
23 |     ">0.2%",
24 |     "not dead",
25 |     "not ie <= 11",
26 |     "not op_mini all"
27 |   ]
28 | }
29 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-custom-plugin/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-custom-plugin/src/App.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Leva, useControls } from 'leva'
 3 | import { createPlugin, useInputContext, LevaInputProps, Components } from 'leva/plugin'
 4 | 
 5 | const { Row, Label, String } = Components
 6 | 
 7 | type GreenOrBlueSettings = { alpha?: number }
 8 | type GreenOrBlueType = { color?: string; light: boolean }
 9 | type GreenOrBlueInput = GreenOrBlueType & GreenOrBlueSettings
10 | 
11 | type GreenOrBlueProps = LevaInputProps<GreenOrBlueType, GreenOrBlueSettings, string>
12 | 
13 | function GreenOrBlue() {
14 |   const props = useInputContext<GreenOrBlueProps>()
15 |   const { label, displayValue, onUpdate, onChange, settings } = props
16 |   const background = displayValue
17 | 
18 |   return (
19 |     <Row input>
20 |       <Label style={{ background, opacity: settings.alpha }}>{label}</Label>
21 |       <String displayValue={displayValue} onUpdate={onUpdate} onChange={onChange} />
22 |     </Row>
23 |   )
24 | }
25 | 
26 | const normalize = ({ color, light, alpha }: GreenOrBlueInput) => {
27 |   return { value: { color, light }, settings: { alpha } }
28 | }
29 | 
30 | const sanitize = (v: string): GreenOrBlueType => {
31 |   if (!['green', 'blue', 'lightgreen', 'lightblue'].includes(v)) throw Error('Invalid value')
32 |   // @ts-ignore
33 |   const [, isLight, color] = v.match(/(light)?(.*)/)
34 |   return { light: !!isLight, color }
35 | }
36 | 
37 | const format = (v: GreenOrBlueType) => (v.light ? 'light' : '') + v.color
38 | 
39 | const greenOrBlue = createPlugin({
40 |   sanitize,
41 |   format,
42 |   normalize,
43 |   component: GreenOrBlue,
44 | })
45 | 
46 | export default function App() {
47 |   const data = useControls({
48 |     myPlugin: greenOrBlue({ color: 'green', light: true, alpha: 0.5 }),
49 |   })
50 | 
51 |   return (
52 |     <>
53 |       <Leva titleBar={false} />
54 |       <pre>{JSON.stringify(data, null, '  ')}</pre>
55 |     </>
56 |   )
57 | }
58 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-custom-plugin/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-custom-plugin/src/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-minimal/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-minimal",
 3 |   "version": "1.0.0",
 4 |   "main": "src/index.jsx",
 5 |   "dependencies": {
 6 |     "@radix-ui/react-icons": "^1.0.2",
 7 |     "leva": "*",
 8 |     "react": "^18.0.0",
 9 |     "react-dom": "^18.0.0",
10 |     "react-scripts": "4.0.3"
11 |   },
12 |   "scripts": {
13 |     "start": "react-scripts start",
14 |     "build": "react-scripts build",
15 |     "test": "react-scripts test --env=jsdom",
16 |     "eject": "react-scripts eject"
17 |   },
18 |   "browserslist": [
19 |     ">0.2%",
20 |     "not dead",
21 |     "not ie <= 11",
22 |     "not op_mini all"
23 |   ]
24 | }
25 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-minimal/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-minimal/src/App.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useControls, Leva } from 'leva'
 3 | import { Half2Icon } from '@radix-ui/react-icons'
 4 | 
 5 | export default function App() {
 6 |   const data = useControls({
 7 |     number: 10,
 8 |     minmax: { value: 12.5, min: 5.5, max: 30.5, optional: true },
 9 |     printSize: { value: 100, min: 80, max: 140, step: 10 },
10 |     color: {
11 |       value: '#f00',
12 |       hint: 'Hey, we support icons and hinting values and long text will wrap!',
13 |       label: <Half2Icon />,
14 |     },
15 |   })
16 | 
17 |   return (
18 |     <>
19 |       <Leva titleBar={false} />
20 |       <pre>{JSON.stringify(data, null, '  ')}</pre>
21 |     </>
22 |   )
23 | }
24 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-minimal/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-minimal/src/index.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-bezier/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-plugin-bezier",
 3 |   "version": "1.0.0",
 4 |   "keywords": [],
 5 |   "main": "src/index.jsx",
 6 |   "dependencies": {
 7 |     "@leva-ui/plugin-bezier": "*",
 8 |     "leva": "*",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-scripts": "4.0.3"
12 |   },
13 |   "devDependencies": {
14 |     "@types/react": "^18.0.0",
15 |     "@types/react-dom": "^18.0.0",
16 |     "typescript": "^4.1.5"
17 |   },
18 |   "scripts": {
19 |     "start": "react-scripts start",
20 |     "build": "react-scripts build",
21 |     "test": "react-scripts test --env=jsdom",
22 |     "eject": "react-scripts eject"
23 |   },
24 |   "browserslist": [
25 |     ">0.2%",
26 |     "not dead",
27 |     "not ie <= 11",
28 |     "not op_mini all"
29 |   ]
30 | }
31 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-bezier/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-bezier/src/App.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useControls } from 'leva'
 3 | import { bezier } from '@leva-ui/plugin-bezier'
 4 | import './style.css'
 5 | 
 6 | export default function App() {
 7 |   const { curve } = useControls({ curve: bezier() })
 8 | 
 9 |   return (
10 |     <div className="App">
11 |       <div className="bezier-animated" style={{ animationTimingFunction: curve.cssEasing }} />
12 |       <pre>{JSON.stringify(curve, null, '  ')}</pre>
13 |     </div>
14 |   )
15 | }
16 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-bezier/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-bezier/src/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-bezier/src/style.css:
--------------------------------------------------------------------------------
 1 | @keyframes bezierStoryScale {
 2 |   0% {
 3 |     transform: scaleX(0);
 4 |   }
 5 | 
 6 |   100% {
 7 |     transform: scaleX(1);
 8 |   }
 9 | }
10 | 
11 | .bezier-animated {
12 |   height: 10px;
13 |   width: 200px;
14 |   background: indianred;
15 |   transform-origin: left;
16 |   animation: bezierStoryScale 1000ms infinite alternate both;
17 | }
18 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-dates/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-plugin-dates",
 3 |   "version": "1.0.0",
 4 |   "keywords": [],
 5 |   "main": "src/index.jsx",
 6 |   "dependencies": {
 7 |     "@leva-ui/plugin-dates": "*",
 8 |     "leva": "*",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-scripts": "4.0.3"
12 |   },
13 |   "devDependencies": {
14 |     "@types/react": "^18.0.0",
15 |     "@types/react-dom": "^18.0.0",
16 |     "typescript": "^4.1.5"
17 |   },
18 |   "scripts": {
19 |     "start": "react-scripts start",
20 |     "build": "react-scripts build",
21 |     "test": "react-scripts test --env=jsdom",
22 |     "eject": "react-scripts eject"
23 |   },
24 |   "browserslist": [
25 |     ">0.2%",
26 |     "not dead",
27 |     "not ie <= 11",
28 |     "not op_mini all"
29 |   ]
30 | }
31 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-dates/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-dates/src/App.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { date } from '@leva-ui/plugin-dates'
 3 | import { useControls } from 'leva'
 4 | 
 5 | export default function App() {
 6 |   const { birthday } = useControls({
 7 |     birthday: date({
 8 |       date: new Date(),
 9 |       locale: 'en-UK',
10 |       inputFormat: 'dd.MM.yyyy',
11 |     }),
12 |   })
13 | 
14 |   return <div className="App">{birthday.formattedDate}</div>
15 | }
16 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-dates/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-dates/src/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-plot/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-plugin-plot",
 3 |   "version": "1.0.0",
 4 |   "keywords": [],
 5 |   "main": "src/index.jsx",
 6 |   "dependencies": {
 7 |     "@leva-ui/plugin-plot": "*",
 8 |     "leva": "*",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-scripts": "4.0.3"
12 |   },
13 |   "devDependencies": {
14 |     "@types/react": "^18.0.0",
15 |     "@types/react-dom": "^18.0.0",
16 |     "typescript": "^4.1.5"
17 |   },
18 |   "scripts": {
19 |     "start": "react-scripts start",
20 |     "build": "react-scripts build",
21 |     "test": "react-scripts test --env=jsdom",
22 |     "eject": "react-scripts eject"
23 |   },
24 |   "browserslist": [
25 |     ">0.2%",
26 |     "not dead",
27 |     "not ie <= 11",
28 |     "not op_mini all"
29 |   ]
30 | }
31 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-plot/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-plot/src/App.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useControls, monitor } from 'leva'
 3 | import { plot } from '@leva-ui/plugin-plot'
 4 | 
 5 | export default function App() {
 6 |   const p = React.useRef(performance.now())
 7 |   const values = useControls({
 8 |     w: 1,
 9 |     y1: plot({ expression: 'cos(x*w)', boundsX: [-10, 10] }),
10 |     y2: plot({ expression: 'x * y1', boundsX: [-100, 100] }),
11 |     y3: plot({ expression: 'tan(y2)', boundsX: [-4, 4], boundsY: [-10, 10] }),
12 |   })
13 | 
14 |   useControls(
15 |     {
16 |       'y1(t)': monitor(
17 |         () => {
18 |           const t = performance.now() - p.current
19 |           return values.y1(t / 100)
20 |         },
21 |         { graph: true, interval: 30 }
22 |       ),
23 |     },
24 |     [values.y1]
25 |   )
26 | 
27 |   const t1 = values.y1(1)
28 |   const t2 = values.y2(1)
29 |   const t3 = values.y3(1)
30 |   return (
31 |     <div className="App">
32 |       <pre>y1(1) = {t1}</pre>
33 |       <pre>y2(1) = {t2}</pre>
34 |       <pre>y3(1) = {t3}</pre>
35 |     </div>
36 |   )
37 | }
38 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-plot/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-plot/src/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-spring/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-plugin-spring",
 3 |   "version": "1.0.0",
 4 |   "keywords": [],
 5 |   "main": "src/index.jsx",
 6 |   "dependencies": {
 7 |     "@leva-ui/plugin-spring": "*",
 8 |     "leva": "*",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-scripts": "4.0.3"
12 |   },
13 |   "devDependencies": {
14 |     "@types/react": "^18.0.0",
15 |     "@types/react-dom": "^18.0.0",
16 |     "typescript": "^4.1.5"
17 |   },
18 |   "scripts": {
19 |     "start": "react-scripts start",
20 |     "build": "react-scripts build",
21 |     "test": "react-scripts test --env=jsdom",
22 |     "eject": "react-scripts eject"
23 |   },
24 |   "browserslist": [
25 |     ">0.2%",
26 |     "not dead",
27 |     "not ie <= 11",
28 |     "not op_mini all"
29 |   ]
30 | }
31 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-spring/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-spring/src/App.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useControls } from 'leva'
 3 | import { spring } from '@leva-ui/plugin-spring'
 4 | 
 5 | export default function App() {
 6 |   const { mySpring } = useControls({
 7 |     mySpring: spring({ tension: 100, friction: 30, hint: 'spring to use with react-spring' }),
 8 |   })
 9 | 
10 |   return (
11 |     <div className="App">
12 |       <pre>{JSON.stringify(mySpring, null, '  ')}</pre>
13 |     </div>
14 |   )
15 | }
16 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-spring/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-plugin-spring/src/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-scroll/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-scroll",
 3 |   "version": "1.0.0",
 4 |   "description": "This sandbox has been generated!",
 5 |   "keywords": [],
 6 |   "main": "src/index.jsx",
 7 |   "dependencies": {
 8 |     "leva": "*",
 9 |     "noisejs": "2.1.0",
10 |     "react": "^18.0.0",
11 |     "react-dom": "^18.0.0",
12 |     "react-scripts": "4.0.3"
13 |   },
14 |   "scripts": {
15 |     "start": "react-scripts start",
16 |     "build": "react-scripts build",
17 |     "test": "react-scripts test --env=jsdom",
18 |     "eject": "react-scripts eject"
19 |   },
20 |   "browserslist": [
21 |     ">0.2%",
22 |     "not dead",
23 |     "not ie <= 11",
24 |     "not op_mini all"
25 |   ]
26 | }
27 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-scroll/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-scroll/src/App.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useControls, folder, button, monitor, Leva } from 'leva'
 3 | import { Noise } from 'noisejs'
 4 | 
 5 | const noise = new Noise(Math.random())
 6 | 
 7 | function frame() {
 8 |   const t = Date.now()
 9 |   return noise.simplex2(t / 1000, t / 100)
10 | }
11 | 
12 | export default function App() {
13 |   const data = useControls({
14 |     first: { value: 0, min: -10, max: 10 },
15 |     image: { image: undefined },
16 |     select: { options: ['x', 'y', ['x', 'y']] },
17 |     interval: { min: -100, max: 100, value: [-10, 10] },
18 |     color: '#ffffff',
19 |     refMonitor: monitor(frame, { graph: true, interval: 30 }),
20 |     number: { value: 1000, min: 3 },
21 |     folder2: folder({
22 |       boolean: false,
23 |       spring: { tension: 100, friction: 30 },
24 |       folder3: folder(
25 |         {
26 |           // eslint-disable-next-line no-console
27 |           'Hello Button': button(() => console.log('hello')),
28 |           folder4: folder({
29 |             pos2d: { x: 3, y: 4 },
30 |             pos2dArr: [100, 200],
31 |             pos3d: { x: 0.3, y: 0.1, z: 0.5 },
32 |             pos3dArr: [Math.PI / 2, 20, 4],
33 |           }),
34 |         },
35 |         { collapsed: false }
36 |       ),
37 |     }),
38 |     colorObj: { r: 1, g: 2, b: 3 },
39 |   })
40 | 
41 |   return (
42 |     <>
43 |       <Leva oneLineLabels />
44 | 
45 |       <div className="App">
46 |         <pre>{JSON.stringify(data, null, '  ')}</pre>
47 |       </div>
48 |     </>
49 |   )
50 | }
51 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-scroll/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-scroll/src/index.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-theme/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-theme",
 3 |   "version": "1.0.0",
 4 |   "main": "src/index.jsx",
 5 |   "dependencies": {
 6 |     "leva": "*",
 7 |     "@leva-ui/plugin-spring": "*",
 8 |     "noisejs": "2.1.0",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-scripts": "4.0.3"
12 |   },
13 |   "scripts": {
14 |     "start": "react-scripts start",
15 |     "build": "react-scripts build",
16 |     "test": "react-scripts test --env=jsdom",
17 |     "eject": "react-scripts eject"
18 |   },
19 |   "browserslist": [
20 |     ">0.2%",
21 |     "not dead",
22 |     "not ie <= 11",
23 |     "not op_mini all"
24 |   ]
25 | }
26 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-theme/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-theme/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-theme/src/index.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-transient/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-transient",
 3 |   "version": "1.0.0",
 4 |   "main": "src/index.jsx",
 5 |   "dependencies": {
 6 |     "@react-three/drei": "^4.3.3",
 7 |     "@react-three/fiber": "^6.0.21",
 8 |     "leva": "*",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-scripts": "4.0.3",
12 |     "three": "^0.143.0"
13 |   },
14 |   "scripts": {
15 |     "start": "react-scripts start",
16 |     "build": "react-scripts build",
17 |     "test": "react-scripts test --env=jsdom",
18 |     "eject": "react-scripts eject"
19 |   },
20 |   "browserslist": [
21 |     ">0.2%",
22 |     "not dead",
23 |     "not ie <= 11",
24 |     "not op_mini all"
25 |   ]
26 | }
27 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-transient/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-transient/src/App.jsx:
--------------------------------------------------------------------------------
 1 | import React, { useRef } from 'react'
 2 | import { useControls } from 'leva'
 3 | import { Canvas } from '@react-three/fiber'
 4 | import { OrbitControls } from '@react-three/drei'
 5 | 
 6 | import * as THREE from 'three'
 7 | 
 8 | const torusknot = new THREE.TorusKnotBufferGeometry(3, 0.8, 256, 16)
 9 | 
10 | const Mesh = () => {
11 |   const matRef = useRef()
12 |   useControls({ color: { value: 'indianred', onChange: (v) => matRef.current && matRef.current.color.set(v) } })
13 |   return (
14 |     <mesh geometry={torusknot}>
15 |       <meshPhysicalMaterial ref={matRef} attach="material" flatShading />
16 |     </mesh>
17 |   )
18 | }
19 | 
20 | export default function App() {
21 |   return (
22 |     <Canvas
23 |       pixelRatio={[1, 2]}
24 |       camera={{ position: [0, 0, 16], fov: 50 }}
25 |       style={{ background: 'dimgray', height: '100vh', width: '100vw' }}>
26 |       <OrbitControls />
27 |       <directionalLight />
28 |       <Mesh />
29 |     </Canvas>
30 |   )
31 | }
32 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-transient/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-transient/src/index.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-ui/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva-ui",
 3 |   "version": "1.0.0",
 4 |   "description": "This sandbox has been generated!",
 5 |   "keywords": [],
 6 |   "main": "src/index.jsx",
 7 |   "dependencies": {
 8 |     "leva": "*",
 9 |     "react": "^18.0.0",
10 |     "react-dom": "^18.0.0",
11 |     "react-dropzone": "11.3.1",
12 |     "react-scripts": "4.0.3",
13 |     "react-use-gesture": "^9.0.0"
14 |   },
15 |   "scripts": {
16 |     "start": "react-scripts start",
17 |     "build": "react-scripts build",
18 |     "test": "react-scripts test --env=jsdom",
19 |     "eject": "react-scripts eject"
20 |   },
21 |   "browserslist": [
22 |     ">0.2%",
23 |     "not dead",
24 |     "not ie <= 11",
25 |     "not op_mini all"
26 |   ]
27 | }
28 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-ui/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 |   <head>
 4 |     <meta charset="utf-8" />
 5 |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 6 |     <meta name="theme-color" content="#000000" />
 7 |     <!--
 8 |       manifest.json provides metadata used when your web app is added to the
 9 |       homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10 |     -->
11 |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 |     <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
13 |     <!--
14 |       Notice the use of %PUBLIC_URL% in the tags above.
15 |       It will be replaced with the URL of the `public` folder during the build.
16 |       Only files inside the `public` folder can be referenced from the HTML.
17 | 
18 |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 |       work correctly both with client-side routing and a non-root public URL.
20 |       Learn how to configure a non-root public URL by running `npm run build`.
21 |     -->
22 |     <title>Leva Sandbox</title>
23 |   </head>
24 | 
25 |   <body>
26 |     <noscript> You need to enable JavaScript to run this app. </noscript>
27 |     <div id="root"></div>
28 |     <!--
29 |       This HTML file is a template.
30 |       If you open it directly in the browser, you will see an empty page.
31 | 
32 |       You can add webfonts, meta tags, or analytics to this file.
33 |       The build step will place the bundled scripts into the <body> tag.
34 | 
35 |       To begin the development, run `npm start` or `yarn start`.
36 |       To create a production bundle, use `npm run build` or `yarn build`.
37 |     -->
38 |   </body>
39 | </html>
40 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-ui/src/index.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   font-family: system-ui, sans-serif;
 3 |   min-height: 100vh;
 4 |   background: linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248));
 5 |   margin: 0;
 6 | }
 7 | 
 8 | *,
 9 | *:after,
10 | *:before {
11 |   box-sizing: border-box;
12 | }
13 | 
14 | pre {
15 |   max-width: 720px;
16 |   margin: 20px;
17 |   padding: 20px;
18 |   border: 10px solid black;
19 | }
20 | 


--------------------------------------------------------------------------------
/demo/src/sandboxes/leva-ui/src/index.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import ReactDOM from 'react-dom'
 3 | import App from './App'
 4 | 
 5 | import './index.css'
 6 | 
 7 | const rootElement = document.getElementById('root')
 8 | ReactDOM.render(
 9 |   <React.StrictMode>
10 |     <App />
11 |   </React.StrictMode>,
12 |   rootElement
13 | )
14 | 


--------------------------------------------------------------------------------
/demo/src/styles.module.css:
--------------------------------------------------------------------------------
 1 | .back {
 2 |   position: fixed;
 3 |   left: 10px;
 4 |   bottom: 10px;
 5 |   z-index: 100;
 6 |   padding: 10px;
 7 |   background: #000;
 8 |   color: #fff;
 9 |   font-weight: 500;
10 |   font-size: 14px;
11 |   text-decoration: none;
12 |   border-radius: 2px;
13 |   border: 1px solid #333;
14 | }
15 | 
16 | .link {
17 |   color: inherit;
18 | }
19 | 
20 | .linkList {
21 |   display: grid;
22 |   gap: 10px;
23 | }
24 | 


--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ESNext",
 4 |     "lib": ["DOM", "DOM.Iterable", "ESNext"],
 5 |     "types": ["vite/client"],
 6 |     "allowJs": true,
 7 |     "skipLibCheck": false,
 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"
18 |   },
19 |   "include": ["./src"]
20 | }
21 | 


--------------------------------------------------------------------------------
/demo/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import reactRefresh from '@vitejs/plugin-react-refresh'
3 | 
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 |   plugins: [reactRefresh()],
7 | })
8 | 


--------------------------------------------------------------------------------
/docs/advanced/circle-drag.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/leva/f66726534e85e356af64c4556cb5c8b605175598/docs/advanced/circle-drag.gif


--------------------------------------------------------------------------------
/docs/advanced/creating-plugins.md:
--------------------------------------------------------------------------------
1 | # Creating Plugins
2 | 
3 | @TODO
4 | This page will contain info on how to create custom plugins,
5 | what even qualifies as a plugin, and an idea of what kinds of plugins could be included and maintained in this repo
6 | 


--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
 1 | ## Configuration
 2 | 
 3 | You can configure Leva by using the `<Leva>` component anywhere in your App:
 4 | 
 5 | ```jsx
 6 | import { Leva } from 'leva'
 7 | 
 8 | export default function MyApp() {
 9 |   return (
10 |     <>
11 |       <Leva
12 |         theme={myTheme} // you can pass a custom theme (see the styling section)
13 |         fill // default = false,  true makes the pane fill the parent dom node it's rendered in
14 |         flat // default = false,  true removes border radius and shadow
15 |         oneLineLabels // default = false, alternative layout for labels, with labels and fields on separate rows
16 |         hideTitleBar // default = false, hides the GUI header
17 |         collapsed // default = false, when true the GUI is collpased
18 |         hidden // default = false, when true the GUI is hidden
19 |       />
20 |     </>
21 |   )
22 | }
23 | ```
24 | 
25 | - TODO // Add default config for LevaPanel as well
26 | 
27 | ### Disabling the GUI
28 | 
29 | Each instance of the `useControls` hook will render the panel. If you want to completely disable the GUI based on preferences, you need to explicitly set `hidden` to false.
30 | 
31 | ```jsx
32 | import { Leva } from 'leva'
33 | 
34 | function MyComponent() {
35 |   const { myValue } = useControls({ myValue: 10 }) // Won't be visible because the panel will not render.
36 | 
37 |   return myValue
38 | }
39 | 
40 | export default function MyApp() {
41 |   return (
42 |     <>
43 |       <Leva {...config} hidden={false} />
44 |     </>
45 |   )
46 | }
47 | ```
48 | 


--------------------------------------------------------------------------------
/docs/plugins.md:
--------------------------------------------------------------------------------
 1 | # Plugins
 2 | 
 3 | What are plugins
 4 | 
 5 | ## How to find plugins
 6 | 
 7 | @TODO
 8 | 
 9 | ## How to use plugins
10 | 
11 | @TODO
12 | 
13 | ## Creating a plugin
14 | 


--------------------------------------------------------------------------------
/docs/special-inputs.md:
--------------------------------------------------------------------------------
 1 | # Special Inputs
 2 | 
 3 | ### Button
 4 | 
 5 | A simple button:
 6 | 
 7 | ```jsx
 8 | const button = useControls({
 9 |   foo: button(() => console.log('clicked')),
10 | })
11 | ```
12 | 
13 | ### Monitor
14 | 


--------------------------------------------------------------------------------
/docs/styling.md:
--------------------------------------------------------------------------------
1 | # Customizing Style
2 | 
3 | @todo
4 | Talk about how to use themes to customize styles
5 | 


--------------------------------------------------------------------------------
/docs/typescript.md:
--------------------------------------------------------------------------------
1 | # Using With Typescript
2 | 
3 | @TODO talk about ts specific issues
4 | 


--------------------------------------------------------------------------------
/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/leva/f66726534e85e356af64c4556cb5c8b605175598/hero.png


--------------------------------------------------------------------------------
/packages/leva/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/leva/f66726534e85e356af64c4556cb5c8b605175598/packages/leva/.npmignore


--------------------------------------------------------------------------------
/packages/leva/README.md:
--------------------------------------------------------------------------------
1 | ../../README.md
2 | 


--------------------------------------------------------------------------------
/packages/leva/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "leva",
 3 |   "version": "0.10.0",
 4 |   "main": "dist/leva.cjs.js",
 5 |   "module": "dist/leva.esm.js",
 6 |   "types": "dist/leva.cjs.d.ts",
 7 |   "license": "MIT",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "https://github.com/pmndrs/leva.git",
11 |     "directory": "packages/leva"
12 |   },
13 |   "bugs": "https://github.com/pmndrs/leva/issues",
14 |   "preconstruct": {
15 |     "entrypoints": [
16 |       "index.ts",
17 |       "plugin/index.ts"
18 |     ]
19 |   },
20 |   "peerDependencies": {
21 |     "react": "^18.0.0 || ^19.0.0",
22 |     "react-dom": "^18.0.0 || ^19.0.0"
23 |   },
24 |   "dependencies": {
25 |     "@radix-ui/react-portal": "^1.1.4",
26 |     "@radix-ui/react-tooltip": "^1.1.8",
27 |     "@stitches/react": "^1.2.8",
28 |     "@use-gesture/react": "^10.2.5",
29 |     "colord": "^2.9.2",
30 |     "dequal": "^2.0.2",
31 |     "merge-value": "^1.0.0",
32 |     "react-colorful": "^5.5.1",
33 |     "react-dropzone": "^12.0.0",
34 |     "v8n": "^1.3.3",
35 |     "zustand": "^3.6.9"
36 |   },
37 |   "devDependencies": {
38 |     "@welldone-software/why-did-you-render": "^6.2.3"
39 |   }
40 | }
41 | 


--------------------------------------------------------------------------------
/packages/leva/plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "main": "dist/leva-plugin.cjs.js",
3 |   "module": "dist/leva-plugin.esm.js"
4 | }
5 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Boolean/Boolean.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useInputContext } from '../../context'
 3 | import { Label, Row } from '../UI'
 4 | import { StyledInputWrapper } from './StyledBoolean'
 5 | import type { BooleanProps } from './boolean-types'
 6 | 
 7 | export function Boolean({
 8 |   value,
 9 |   onUpdate,
10 |   id,
11 |   disabled,
12 | }: Pick<BooleanProps, 'value' | 'onUpdate' | 'id' | 'disabled'>) {
13 |   return (
14 |     <StyledInputWrapper>
15 |       <input
16 |         id={id}
17 |         type="checkbox"
18 |         checked={value}
19 |         onChange={(e) => onUpdate(e.currentTarget.checked)}
20 |         disabled={disabled}
21 |       />
22 |       <label htmlFor={id}>
23 |         <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
24 |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
25 |         </svg>
26 |       </label>
27 |     </StyledInputWrapper>
28 |   )
29 | }
30 | 
31 | export function BooleanComponent() {
32 |   const { label, value, onUpdate, disabled, id } = useInputContext<BooleanProps>()
33 | 
34 |   return (
35 |     <Row input>
36 |       <Label>{label}</Label>
37 |       <Boolean value={value} onUpdate={onUpdate} id={id} disabled={disabled} />
38 |     </Row>
39 |   )
40 | }
41 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Boolean/StyledBoolean.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const StyledInputWrapper = styled('div', {
 4 |   position: 'relative',
 5 |   $flex: '',
 6 |   height: '$rowHeight',
 7 | 
 8 |   input: {
 9 |     $reset: '',
10 |     height: 0,
11 |     width: 0,
12 |     opacity: 0,
13 |     margin: 0,
14 |   },
15 | 
16 |   label: {
17 |     position: 'relative',
18 |     $flexCenter: '',
19 |     userSelect: 'none',
20 |     cursor: 'pointer',
21 |     height: '$checkboxSize',
22 |     width: '$checkboxSize',
23 |     backgroundColor: '$elevation3',
24 |     borderRadius: '$sm',
25 |     $hover: '',
26 |   },
27 | 
28 |   'input:focus + label': { $focusStyle: '' },
29 | 
30 |   'input:focus:checked + label, input:checked + label:hover': {
31 |     $hoverStyle: '$accent3',
32 |   },
33 | 
34 |   'input + label:active': {
35 |     backgroundColor: '$accent1',
36 |   },
37 | 
38 |   'input:checked + label:active': {
39 |     backgroundColor: '$accent1',
40 |   },
41 | 
42 |   'label > svg': {
43 |     display: 'none',
44 |     width: '90%',
45 |     height: '90%',
46 |     stroke: '$highlight3',
47 |   },
48 | 
49 |   'input:checked + label': {
50 |     backgroundColor: '$accent2',
51 |   },
52 | 
53 |   'input:checked + label > svg': {
54 |     display: 'block',
55 |   },
56 | })
57 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Boolean/boolean-plugin.ts:
--------------------------------------------------------------------------------
1 | import v8n from 'v8n'
2 | 
3 | export const schema = (o: any) => v8n().boolean().test(o)
4 | 
5 | export const sanitize = (v: any): boolean => {
6 |   if (typeof v !== 'boolean') throw Error('Invalid boolean')
7 |   return v
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Boolean/boolean-types.ts:
--------------------------------------------------------------------------------
1 | import type { LevaInputProps } from '../../types'
2 | 
3 | export type BooleanProps = LevaInputProps<boolean>
4 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Boolean/index.ts:
--------------------------------------------------------------------------------
 1 | import * as props from './boolean-plugin'
 2 | import { BooleanComponent } from './Boolean'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | export * from './Boolean'
 6 | 
 7 | export default createInternalPlugin({
 8 |   component: BooleanComponent,
 9 |   ...props,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Button/Button.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useStoreContext } from '../..'
 3 | import { ButtonInput } from '../../types'
 4 | import { Row } from '../UI'
 5 | import { StyledButton } from './StyledButton'
 6 | 
 7 | type ButtonProps = {
 8 |   label: string
 9 | } & Omit<ButtonInput, 'type'>
10 | 
11 | export function Button({ onClick, settings, label }: ButtonProps) {
12 |   const store = useStoreContext()
13 |   return (
14 |     <Row>
15 |       <StyledButton disabled={settings.disabled} onClick={() => onClick(store.get)}>
16 |         {label}
17 |       </StyledButton>
18 |     </Row>
19 |   )
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Button/StyledButton.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const StyledButton = styled('button', {
 4 |   display: 'block',
 5 |   $reset: '',
 6 |   fontWeight: '$button',
 7 |   height: '$rowHeight',
 8 |   borderStyle: 'none',
 9 |   borderRadius: '$sm',
10 |   backgroundColor: '$elevation1',
11 |   color: '$highlight1',
12 |   '&:not(:disabled)': {
13 |     color: '$highlight3',
14 |     backgroundColor: '$accent2',
15 |     cursor: 'pointer',
16 |     $hover: '$accent3',
17 |     $active: '$accent3 $accent1',
18 |     $focus: '',
19 |   },
20 | })
21 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Button'
2 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/ButtonGroup/ButtonGroup.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Row, Label } from '../UI'
 3 | import { StyledButtonGroup } from './StyledButtonGroup'
 4 | import { StyledButtonGroupButton } from './StyledButtonGroupButton'
 5 | import { ButtonGroupInputOpts, ButtonGroupOpts } from '../../types'
 6 | import { useStoreContext } from '../..'
 7 | 
 8 | export type ButtonGroupInternalOpts = {
 9 |   label: null | React.JSX.Element | string
10 |   opts: ButtonGroupInputOpts
11 | }
12 | 
13 | const getOpts = ({ label: _label, opts: _opts }: ButtonGroupInternalOpts) => {
14 |   let label = typeof _label === 'string' ? (_label.trim() === '' ? null : _label) : _label
15 |   let opts = _opts
16 |   if (typeof _opts.opts === 'object') {
17 |     if (opts.label !== undefined) {
18 |       label = _opts.label as any
19 |     }
20 |     opts = _opts.opts
21 |   }
22 | 
23 |   return { label, opts: opts as ButtonGroupOpts }
24 | }
25 | 
26 | export function ButtonGroup(props: ButtonGroupInternalOpts) {
27 |   const { label, opts } = getOpts(props)
28 |   const store = useStoreContext()
29 |   return (
30 |     <Row input={!!label}>
31 |       {label && <Label>{label}</Label>}
32 |       <StyledButtonGroup>
33 |         {Object.entries(opts).map(([label, onClick]) => (
34 |           <StyledButtonGroupButton key={label} onClick={() => onClick(store.get)}>
35 |             {label}
36 |           </StyledButtonGroupButton>
37 |         ))}
38 |       </StyledButtonGroup>
39 |     </Row>
40 |   )
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/ButtonGroup/StyledButtonGroup.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../styles'
2 | 
3 | export const StyledButtonGroup = styled('div', {
4 |   $flex: '',
5 |   justifyContent: 'flex-end',
6 |   gap: '$colGap',
7 | })
8 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/ButtonGroup/StyledButtonGroupButton.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const StyledButtonGroupButton = styled('button', {
 4 |   $reset: '',
 5 |   cursor: 'pointer',
 6 |   borderRadius: '$xs',
 7 |   '&:hover': {
 8 |     backgroundColor: '$elevation3',
 9 |   },
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/ButtonGroup/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ButtonGroup'
2 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Color/StyledColor.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const ColorPreview = styled('div', {
 4 |   position: 'relative',
 5 |   boxSizing: 'border-box',
 6 |   borderRadius: '$sm',
 7 |   overflow: 'hidden',
 8 |   cursor: 'pointer',
 9 |   height: '$rowHeight',
10 |   width: '$rowHeight',
11 |   backgroundColor: '#fff',
12 |   backgroundImage: `url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><path d="M8 0h8v8H8zM0 8h8v8H0z"/></svg>')`,
13 |   $inputStyle: '',
14 |   $hover: '',
15 |   zIndex: 1,
16 |   variants: {
17 |     active: { true: { $inputStyle: '$accent1' } },
18 |   },
19 |   '&::before': {
20 |     content: '""',
21 |     position: 'absolute',
22 |     top: 0,
23 |     bottom: 0,
24 |     right: 0,
25 |     left: 0,
26 |     backgroundColor: 'currentColor',
27 |     zIndex: 1,
28 |   },
29 | })
30 | 
31 | export const PickerContainer = styled('div', {
32 |   position: 'relative',
33 |   display: 'grid',
34 |   gridTemplateColumns: '$sizes$rowHeight auto',
35 |   columnGap: '$colGap',
36 |   alignItems: 'center',
37 | })
38 | 
39 | export const PickerWrapper = styled('div', {
40 |   width: '$colorPickerWidth',
41 |   height: '$colorPickerHeight',
42 | 
43 |   '.react-colorful': {
44 |     width: '100%',
45 |     height: '100%',
46 |     boxShadow: '$level2',
47 |     cursor: 'crosshair',
48 |   },
49 | 
50 |   '.react-colorful__saturation': {
51 |     borderRadius: '$sm $sm 0 0',
52 |   },
53 | 
54 |   '.react-colorful__alpha, .react-colorful__hue': {
55 |     height: 10,
56 |   },
57 | 
58 |   '.react-colorful__last-control': {
59 |     borderRadius: '0 0 $sm $sm',
60 |   },
61 | 
62 |   '.react-colorful__pointer': {
63 |     height: 12,
64 |     width: 12,
65 |   },
66 | })
67 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Color/color-plugin.ts:
--------------------------------------------------------------------------------
 1 | import v8n from 'v8n'
 2 | import { Colord, colord, extend, getFormat } from 'colord'
 3 | import namesPlugin from 'colord/plugins/names'
 4 | import { omit } from '../../utils'
 5 | import type { InternalColorSettings, Format, ColorInput } from './color-types'
 6 | 
 7 | extend([namesPlugin])
 8 | 
 9 | const convertMap = {
10 |   rgb: 'toRgb',
11 |   hsl: 'toHsl',
12 |   hsv: 'toHsv',
13 |   hex: 'toHex',
14 | }
15 | 
16 | v8n.extend({
17 |   color: () => (value: any) => colord(value).isValid(),
18 | })
19 | // prettier-ignore
20 | // @ts-expect-error
21 | export const schema = (o: any) => v8n().color().test(o)
22 | 
23 | function convert(color: Colord, { format, hasAlpha, isString }: InternalColorSettings) {
24 |   const convertFn = convertMap[format] + (isString && format !== 'hex' ? 'String' : '')
25 |   // @ts-ignore
26 |   const result = color[convertFn]()
27 |   return typeof result === 'object' && !hasAlpha ? omit(result, ['a']) : result
28 | }
29 | 
30 | export const sanitize = (v: any, settings: InternalColorSettings) => {
31 |   const color = colord(v)
32 |   if (!color.isValid()) throw Error('Invalid color')
33 |   return convert(color, settings)
34 | }
35 | 
36 | export const format = (v: any, settings: InternalColorSettings) => {
37 |   return convert(colord(v), { ...settings, isString: true, format: 'hex' })
38 | }
39 | 
40 | export const normalize = ({ value }: ColorInput) => {
41 |   const _f = getFormat(value)
42 |   const format = (_f === 'name' ? 'hex' : _f) as Format
43 |   const hasAlpha =
44 |     typeof value === 'object'
45 |       ? 'a' in value
46 |       : (_f === 'hex' && value.length === 8) || /^(rgba)|(hsla)|(hsva)/.test(value)
47 | 
48 |   const settings = { format, hasAlpha, isString: typeof value === 'string' }
49 | 
50 |   // by santizing the value we make sure the returned value is parsed and fixed,
51 |   // consistent with future updates.
52 |   return { value: sanitize(value, settings), settings }
53 | }
54 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Color/color-types.ts:
--------------------------------------------------------------------------------
 1 | import type { ColorVectorInput, InputWithSettings, LevaInputProps } from '../../types'
 2 | 
 3 | export type Format = 'hex' | 'rgb' | 'hsl' | 'hsv'
 4 | 
 5 | export type Color = string | ColorVectorInput
 6 | export type InternalColorSettings = { format: Format; hasAlpha: boolean; isString: boolean }
 7 | 
 8 | export type ColorInput = InputWithSettings<Color>
 9 | 
10 | export type ColorProps = LevaInputProps<Color, InternalColorSettings, string>
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Color/index.ts:
--------------------------------------------------------------------------------
 1 | import * as props from './color-plugin'
 2 | import { ColorComponent } from './Color'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | export * from './Color'
 6 | 
 7 | export default createInternalPlugin({
 8 |   component: ColorComponent,
 9 |   ...props,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Control/Control.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { ControlInput } from './ControlInput'
 3 | import { log, LevaErrors } from '../../utils/log'
 4 | import { Plugins } from '../../plugin'
 5 | import { Button } from '../Button'
 6 | import { ButtonGroup } from '../ButtonGroup'
 7 | import { Monitor } from '../Monitor'
 8 | import { useInput } from '../../hooks'
 9 | import { SpecialInputs } from '../../types'
10 | 
11 | type ControlProps = { path: string }
12 | 
13 | const specialComponents = {
14 |   [SpecialInputs.BUTTON]: Button,
15 |   [SpecialInputs.BUTTON_GROUP]: ButtonGroup,
16 |   [SpecialInputs.MONITOR]: Monitor,
17 | }
18 | 
19 | export const Control = React.memo(({ path }: ControlProps) => {
20 |   const [input, { set, setSettings, disable, storeId, emitOnEditStart, emitOnEditEnd }] = useInput(path)
21 |   if (!input) return null
22 | 
23 |   const { type, label, key, ...inputProps } = input
24 | 
25 |   if (type in SpecialInputs) {
26 |     // @ts-expect-error
27 |     const SpecialInputForType = specialComponents[type]
28 |     return <SpecialInputForType label={label} path={path} {...inputProps} />
29 |   }
30 | 
31 |   if (!(type in Plugins)) {
32 |     log(LevaErrors.UNSUPPORTED_INPUT, type, path)
33 |     return null
34 |   }
35 | 
36 |   return (
37 |     // @ts-expect-error
38 |     <ControlInput
39 |       key={storeId + path}
40 |       type={type}
41 |       label={label}
42 |       storeId={storeId}
43 |       path={path}
44 |       valueKey={key}
45 |       setValue={set}
46 |       setSettings={setSettings}
47 |       disable={disable}
48 |       emitOnEditStart={emitOnEditStart}
49 |       emitOnEditEnd={emitOnEditEnd}
50 |       {...inputProps}
51 |     />
52 |   )
53 | })
54 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Control/ControlInput.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Plugins } from '../../plugin'
 3 | import { warn, LevaErrors } from '../../utils/log'
 4 | import { InputContext } from '../../context'
 5 | import { useInputSetters } from '../../hooks'
 6 | import { StyledInputWrapper } from '../UI/StyledUI'
 7 | import type { DataInput } from '../../types'
 8 | 
 9 | type ControlInputProps = Omit<DataInput, '__refCount' | 'key'> & {
10 |   valueKey: string
11 |   path: string
12 |   storeId: string
13 |   setValue: (value: any) => void
14 |   setSettings: (settings: any) => void
15 |   disable: (flag: boolean) => void
16 |   emitOnEditStart?: (...args: any) => void
17 |   emitOnEditEnd?: (...args: any) => void
18 | }
19 | 
20 | export function ControlInput({
21 |   type,
22 |   label,
23 |   path,
24 |   valueKey,
25 |   value,
26 |   settings,
27 |   setValue,
28 |   disabled,
29 |   ...rest
30 | }: ControlInputProps) {
31 |   const { displayValue, onChange, onUpdate } = useInputSetters({ type, value, settings, setValue })
32 | 
33 |   const Input = Plugins[type].component
34 |   if (!Input) {
35 |     warn(LevaErrors.NO_COMPONENT_FOR_TYPE, type, path)
36 |     return null
37 |   }
38 | 
39 |   return (
40 |     <InputContext.Provider
41 |       value={{
42 |         key: valueKey,
43 |         path,
44 |         id: '' + path,
45 |         label,
46 |         displayValue,
47 |         value,
48 |         onChange,
49 |         onUpdate,
50 |         settings,
51 |         setValue,
52 |         disabled,
53 |         ...rest,
54 |       }}>
55 |       <StyledInputWrapper disabled={disabled}>
56 |         {/* @ts-ignore */}
57 |         <Input />
58 |       </StyledInputWrapper>
59 |     </InputContext.Provider>
60 |   )
61 | }
62 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Control/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Control'
2 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Folder/FolderTitle.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { StyledTitle } from './StyledFolder'
 3 | import { Chevron } from '../UI'
 4 | 
 5 | export type FolderTitleProps = {
 6 |   name?: string
 7 |   toggled: boolean
 8 |   toggle: (flag?: boolean) => void
 9 | }
10 | 
11 | export function FolderTitle({ toggle, toggled, name }: FolderTitleProps) {
12 |   return (
13 |     <StyledTitle onClick={() => toggle()}>
14 |       <Chevron toggled={toggled} />
15 |       <div>{name}</div>
16 |     </StyledTitle>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Folder/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Folder'
2 | export * from './FolderTitle'
3 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Image/Image.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useCallback } from 'react'
 2 | import { Label, Portal, Overlay, Row } from '../UI'
 3 | import { useDropzone } from 'react-dropzone'
 4 | import { DropZone, ImageContainer, ImagePreview, Instructions, ImageLargePreview, Remove } from './StyledImage'
 5 | import { useInputContext } from '../../context'
 6 | import { usePopin } from '../../hooks'
 7 | import type { ImageProps } from './image-types'
 8 | 
 9 | export function ImageComponent() {
10 |   const { label, value, onUpdate, disabled } = useInputContext<ImageProps>()
11 |   const { popinRef, wrapperRef, shown, show, hide } = usePopin()
12 | 
13 |   const onDrop = useCallback(
14 |     <T extends File>(acceptedFiles: T[]) => {
15 |       if (acceptedFiles.length) onUpdate(acceptedFiles[0])
16 |     },
17 |     [onUpdate]
18 |   )
19 | 
20 |   const clear = useCallback(
21 |     (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
22 |       e.stopPropagation()
23 |       onUpdate(undefined)
24 |     },
25 |     [onUpdate]
26 |   )
27 | 
28 |   const { getRootProps, getInputProps, isDragAccept } = useDropzone({
29 |     maxFiles: 1,
30 |     accept: 'image/*',
31 |     onDrop,
32 |     disabled,
33 |   })
34 | 
35 |   // TODO fix any in DropZone
36 |   return (
37 |     <Row input>
38 |       <Label>{label}</Label>
39 |       <ImageContainer>
40 |         <ImagePreview
41 |           ref={popinRef}
42 |           hasImage={!!value}
43 |           onPointerDown={() => !!value && show()}
44 |           onPointerUp={hide}
45 |           style={{ backgroundImage: value ? `url(${value})` : 'none' }}
46 |         />
47 |         {shown && !!value && (
48 |           <Portal>
49 |             <Overlay onPointerUp={hide} style={{ cursor: 'pointer' }} />
50 |             <ImageLargePreview ref={wrapperRef} style={{ backgroundImage: `url(${value})` }} />
51 |           </Portal>
52 |         )}
53 |         <DropZone {...(getRootProps({ isDragAccept }) as any)}>
54 |           <input {...getInputProps()} />
55 |           <Instructions>{isDragAccept ? 'drop image' : 'click or drop'}</Instructions>
56 |         </DropZone>
57 |         <Remove onClick={clear} disabled={!value} />
58 |       </ImageContainer>
59 |     </Row>
60 |   )
61 | }
62 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Image/image-plugin.ts:
--------------------------------------------------------------------------------
 1 | import type { ImageInput } from '../../types'
 2 | 
 3 | export const sanitize = (v: any): string | undefined => {
 4 |   if (v === undefined) return undefined
 5 |   if (v instanceof File) {
 6 |     try {
 7 |       return URL.createObjectURL(v)
 8 |     } catch (e) {
 9 |       return undefined
10 |     }
11 |   }
12 |   if (typeof v === 'string' && v.indexOf('blob:') === 0) return v
13 |   throw Error(`Invalid image format [undefined | blob | File].`)
14 | }
15 | 
16 | export const schema = (_o: any, s: any) => typeof s === 'object' && 'image' in s
17 | 
18 | export const normalize = ({ image }: ImageInput) => {
19 |   return { value: image }
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Image/image-types.ts:
--------------------------------------------------------------------------------
1 | import type { LevaInputProps } from '../../types'
2 | 
3 | export type ImageProps = LevaInputProps<string | undefined>
4 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Image/index.ts:
--------------------------------------------------------------------------------
 1 | import * as props from './image-plugin'
 2 | import { ImageComponent } from './Image'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | export * from './Image'
 6 | 
 7 | export default createInternalPlugin({
 8 |   component: ImageComponent,
 9 |   ...props,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Interval/index.ts:
--------------------------------------------------------------------------------
 1 | import * as props from './interval-plugin'
 2 | import { IntervalComponent } from './Interval'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | export * from './Interval'
 6 | 
 7 | export default createInternalPlugin({
 8 |   component: IntervalComponent,
 9 |   ...props,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Interval/interval-plugin.ts:
--------------------------------------------------------------------------------
 1 | import v8n from 'v8n'
 2 | import { clamp } from '../../utils'
 3 | import { normalizeKeyedNumberSettings } from '../Vector/vector-utils'
 4 | import type { IntervalInput } from '../../types'
 5 | import type { InternalInterval, InternalIntervalSettings, Interval } from './interval-types'
 6 | 
 7 | const number = v8n().number()
 8 | 
 9 | export const schema = (o: any, s: any) =>
10 |   v8n().array().length(2).every.number().test(o) && v8n().schema({ min: number, max: number }).test(s)
11 | 
12 | export const format = (v: Interval) => ({ min: v[0], max: v[1] })
13 | 
14 | export const sanitize = (
15 |   value: InternalInterval | Interval,
16 |   { bounds: [MIN, MAX] }: InternalIntervalSettings,
17 |   prevValue: any
18 | ): Interval => {
19 |   // value can be passed as an array externally
20 |   const _value: InternalInterval = Array.isArray(value) ? format(value as Interval) : value
21 |   const _newValue = { min: prevValue[0], max: prevValue[1] }
22 |   const { min, max } = { ..._newValue, ..._value }
23 |   return [clamp(Number(min), MIN, Math.max(MIN, max)), clamp(Number(max), Math.min(MAX, min), MAX)]
24 | }
25 | 
26 | export const normalize = ({ value, min, max }: IntervalInput) => {
27 |   const boundsSettings = { min, max }
28 |   const _settings = normalizeKeyedNumberSettings(format(value), { min: boundsSettings, max: boundsSettings })
29 |   const bounds: [number, number] = [min, max]
30 |   const settings = { ..._settings, bounds }
31 | 
32 |   // sanitizing value to make sure it's withing interval bounds
33 |   const _value = sanitize(format(value), settings, value)
34 |   return { value: _value, settings }
35 | }
36 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Interval/interval-types.ts:
--------------------------------------------------------------------------------
 1 | import type { LevaInputProps, IntervalInput } from '../../types'
 2 | import type { InternalNumberSettings } from '../Number/number-types'
 3 | 
 4 | export type Interval = IntervalInput['value']
 5 | export type InternalInterval = { min: number; max: number }
 6 | 
 7 | export type InternalIntervalSettings = {
 8 |   bounds: [number, number]
 9 |   min: InternalNumberSettings
10 |   max: InternalNumberSettings
11 | }
12 | 
13 | export type IntervalProps = LevaInputProps<Interval, InternalIntervalSettings, InternalInterval>
14 | 
15 | export type IntervalSliderProps = {
16 |   value: InternalInterval
17 |   onDrag: (v: Partial<InternalInterval>) => void
18 | } & InternalIntervalSettings
19 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Leva/Leva.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useEffect } from 'react'
 2 | import { createRoot } from 'react-dom/client'
 3 | import { levaStore } from '../../store'
 4 | import { LevaRoot, LevaRootProps } from './LevaRoot'
 5 | 
 6 | let rootInitialized = false
 7 | let rootEl: HTMLElement | null = null
 8 | 
 9 | type LevaProps = Omit<Partial<LevaRootProps>, 'store'> & { isRoot?: boolean }
10 | 
11 | // uses global store
12 | export function Leva({ isRoot = false, ...props }: LevaProps) {
13 |   useEffect(() => {
14 |     rootInitialized = true
15 |     // if this panel was attached somewhere in the app and there is already
16 |     // a floating panel, we remove it.
17 |     if (!isRoot && rootEl) {
18 |       rootEl.remove()
19 |       rootEl = null
20 |     }
21 |     return () => {
22 |       if (!isRoot) rootInitialized = false
23 |     }
24 |   }, [isRoot])
25 | 
26 |   return <LevaRoot store={levaStore} {...props} />
27 | }
28 | 
29 | /**
30 |  * This hook is used by Leva useControls, and ensures that we spawn a Leva Panel
31 |  * without the user having to put it into the component tree. This should only
32 |  * happen when using the global store
33 |  * @param isGlobalPanel
34 |  */
35 | export function useRenderRoot(isGlobalPanel: boolean) {
36 |   useEffect(() => {
37 |     if (isGlobalPanel && !rootInitialized) {
38 |       if (!rootEl) {
39 |         rootEl =
40 |           document.getElementById('leva__root') || Object.assign(document.createElement('div'), { id: 'leva__root' })
41 |         if (document.body) {
42 |           document.body.appendChild(rootEl)
43 |           createRoot(rootEl).render(<Leva isRoot />)
44 |         }
45 |       }
46 |       rootInitialized = true
47 |     }
48 |   }, [isGlobalPanel])
49 | }
50 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Leva/LevaPanel.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useStoreContext } from '../../context'
 3 | import { LevaRoot, LevaRootProps } from './LevaRoot'
 4 | 
 5 | type LevaPanelProps = Partial<LevaRootProps>
 6 | 
 7 | // uses custom store
 8 | export function LevaPanel({ store, ...props }: LevaPanelProps) {
 9 |   const parentStore = useStoreContext()
10 |   const _store = store === undefined ? parentStore : store
11 |   return <LevaRoot store={_store} {...props} />
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Leva/StyledRoot.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | import { StyledInputRow } from '../UI/StyledUI'
 3 | 
 4 | export const StyledRoot = styled('div', {
 5 |   /* position */
 6 |   position: 'relative',
 7 |   fontFamily: '$mono',
 8 |   fontSize: '$root',
 9 |   color: '$rootText',
10 |   backgroundColor: '$elevation1',
11 |   variants: {
12 |     fill: {
13 |       false: {
14 |         position: 'fixed',
15 |         top: '10px',
16 |         right: '10px',
17 |         zIndex: 1000,
18 |         width: '$rootWidth',
19 |       },
20 |       true: {
21 |         position: 'relative',
22 |         width: '100%',
23 |       },
24 |     },
25 |     flat: {
26 |       false: {
27 |         borderRadius: '$lg',
28 |         boxShadow: '$level1',
29 |       },
30 |     },
31 |     oneLineLabels: {
32 |       true: {
33 |         [`${StyledInputRow}`]: {
34 |           gridTemplateColumns: 'auto',
35 |           gridAutoColumns: 'minmax(max-content, 1fr)',
36 |           gridAutoRows: 'minmax($sizes$rowHeight), auto)',
37 |           rowGap: 0,
38 |           columnGap: 0,
39 |           marginTop: '$rowGap',
40 |         },
41 |       },
42 |     },
43 |     hideTitleBar: {
44 |       true: { $titleBarHeight: '0px' },
45 |       false: { $titleBarHeight: '$sizes$titleBarHeight' },
46 |     },
47 |   },
48 | 
49 |   '&,*,*:after,*:before': {
50 |     boxSizing: 'border-box',
51 |   },
52 | 
53 |   '*::selection': {
54 |     backgroundColor: '$accent2',
55 |   },
56 | })
57 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Leva/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Leva'
2 | export * from './LevaPanel'
3 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Leva/tree.ts:
--------------------------------------------------------------------------------
 1 | // @ts-expect-error
 2 | import merge from 'merge-value'
 3 | import { getKeyPath } from '../../utils'
 4 | import type { Tree } from '../../types'
 5 | 
 6 | export const isInput = (v: object) => '__levaInput' in v
 7 | 
 8 | export const buildTree = (paths: string[], filter?: string): Tree => {
 9 |   const tree = {}
10 |   const _filter = filter ? filter.toLowerCase() : null
11 |   paths.forEach((path) => {
12 |     const [valueKey, folderPath] = getKeyPath(path)
13 |     if (!_filter || valueKey.toLowerCase().indexOf(_filter) > -1) {
14 |       merge(tree, folderPath, {
15 |         [valueKey]: { __levaInput: true, path },
16 |       })
17 |     }
18 |   })
19 |   return tree
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Monitor/StyledMonitor.ts:
--------------------------------------------------------------------------------
1 | import { styled } from '../../styles'
2 | 
3 | export const Canvas = styled('canvas', {
4 |   height: '$monitorHeight',
5 |   width: '100%',
6 |   display: 'block',
7 |   borderRadius: '$sm',
8 | })
9 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Monitor/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Monitor'
2 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Number/RangeSlider.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useRef } from 'react'
 2 | import { RangeWrapper, Range, Scrubber, Indicator } from './StyledRange'
 3 | import { sanitizeStep } from './number-plugin'
 4 | import { useDrag } from '../../hooks'
 5 | import { invertedRange, range } from '../../utils'
 6 | import { useTh } from '../../styles'
 7 | import type { RangeSliderProps } from './number-types'
 8 | 
 9 | export function RangeSlider({ value, min, max, onDrag, step, initialValue }: RangeSliderProps) {
10 |   const ref = useRef<HTMLDivElement>(null)
11 |   const scrubberRef = useRef<HTMLDivElement>(null)
12 |   const rangeWidth = useRef<number>(0)
13 |   const scrubberWidth = useTh('sizes', 'scrubberWidth')
14 | 
15 |   const bind = useDrag(({ event, first, xy: [x], movement: [mx], memo }) => {
16 |     if (first) {
17 |       // rangeWidth is the width of the slider el minus the width of the scrubber el itself
18 |       const { width, left } = ref.current!.getBoundingClientRect()
19 |       rangeWidth.current = width - parseFloat(scrubberWidth)
20 | 
21 |       const targetIsScrub = event?.target === scrubberRef.current
22 |       // memo is the value where the user clicked on
23 |       memo = targetIsScrub ? value : invertedRange((x - left) / width, min, max)
24 |     }
25 |     const newValue = memo + invertedRange(mx / rangeWidth.current, 0, max - min)
26 |     onDrag(sanitizeStep(newValue, { step, initialValue }))
27 |     return memo
28 |   })
29 | 
30 |   const pos = range(value, min, max)
31 | 
32 |   return (
33 |     <RangeWrapper ref={ref} {...bind()}>
34 |       <Range>
35 |         <Indicator style={{ left: 0, right: `${(1 - pos) * 100}%` }} />
36 |       </Range>
37 |       <Scrubber ref={scrubberRef} style={{ left: `calc(${pos} * (100% - ${scrubberWidth}))` }} />
38 |     </RangeWrapper>
39 |   )
40 | }
41 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Number/StyledNumber.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const RangeGrid = styled('div', {
 4 |   variants: {
 5 |     hasRange: {
 6 |       true: {
 7 |         position: 'relative',
 8 |         display: 'grid',
 9 |         gridTemplateColumns: 'auto $sizes$numberInputMinWidth',
10 |         columnGap: '$colGap',
11 |         alignItems: 'center',
12 |       },
13 |     },
14 |   },
15 | })
16 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Number/StyledRange.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const Range = styled('div', {
 4 |   position: 'relative',
 5 |   width: '100%',
 6 |   height: 2,
 7 |   borderRadius: '$xs',
 8 |   backgroundColor: '$elevation1',
 9 | })
10 | 
11 | export const Scrubber = styled('div', {
12 |   position: 'absolute',
13 |   width: '$scrubberWidth',
14 |   height: '$scrubberHeight',
15 |   borderRadius: '$xs',
16 |   boxShadow: '0 0 0 2px $colors$elevation2',
17 |   backgroundColor: '$accent2',
18 |   cursor: 'pointer',
19 |   $active: 'none $accent1',
20 |   $hover: 'none $accent3',
21 |   variants: {
22 |     position: {
23 |       left: {
24 |         borderTopRightRadius: 0,
25 |         borderBottomRightRadius: 0,
26 |         transform: 'translateX(calc(-0.5 * ($sizes$scrubberWidth + 4px)))',
27 |       },
28 |       right: {
29 |         borderTopLeftRadius: 0,
30 |         borderBottomLeftRadius: 0,
31 |         transform: 'translateX(calc(0.5 * ($sizes$scrubberWidth + 4px)))',
32 |       },
33 |     },
34 |   },
35 | })
36 | 
37 | export const RangeWrapper = styled('div', {
38 |   position: 'relative',
39 |   $flex: '',
40 |   height: '100%',
41 |   cursor: 'pointer',
42 |   touchAction: 'none',
43 | })
44 | 
45 | export const Indicator = styled('div', {
46 |   position: 'absolute',
47 |   height: '100%',
48 |   backgroundColor: '$accent2',
49 | })
50 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Number/index.ts:
--------------------------------------------------------------------------------
 1 | import * as props from './number-plugin'
 2 | import { NumberComponent } from './Number'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | const { sanitizeStep, ...rest } = props
 6 | 
 7 | export * from './Number'
 8 | export * from './StyledNumber'
 9 | export * from './StyledRange'
10 | export { sanitizeStep }
11 | 
12 | export default createInternalPlugin({
13 |   component: NumberComponent,
14 |   ...rest,
15 | })
16 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Number/number-types.ts:
--------------------------------------------------------------------------------
 1 | import type { InputWithSettings, LevaInputProps, NumberSettings } from '../../types'
 2 | 
 3 | export type InternalNumberSettings = {
 4 |   min: number
 5 |   max: number
 6 |   step: number
 7 |   pad: number
 8 |   initialValue: number
 9 |   suffix?: string
10 | }
11 | export type NumberInput = InputWithSettings<number | string, NumberSettings>
12 | 
13 | export type NumberProps = LevaInputProps<number, InternalNumberSettings>
14 | 
15 | export type RangeSliderProps = { value: number; onDrag: (v: number) => void } & InternalNumberSettings
16 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Select/Select.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useRef } from 'react'
 2 | import { useInputContext } from '../../context'
 3 | import { Label, Row, Chevron } from '../UI'
 4 | import { NativeSelect, PresentationalSelect, SelectContainer } from './StyledSelect'
 5 | import type { SelectProps } from './select-types'
 6 | 
 7 | export function Select({
 8 |   displayValue,
 9 |   value,
10 |   onUpdate,
11 |   id,
12 |   settings,
13 |   disabled,
14 | }: Pick<SelectProps, 'value' | 'displayValue' | 'onUpdate' | 'id' | 'settings' | 'disabled'>) {
15 |   const { keys, values } = settings
16 |   const lastDisplayedValue = useRef<any>()
17 | 
18 |   // in case the value isn't present in values (possibly when changing options
19 |   // via deps), remember the last correct display value.
20 |   if (value === values[displayValue]) {
21 |     lastDisplayedValue.current = keys[displayValue]
22 |   }
23 | 
24 |   return (
25 |     <SelectContainer>
26 |       <NativeSelect
27 |         id={id}
28 |         value={displayValue}
29 |         onChange={(e) => onUpdate(values[Number(e.currentTarget.value)])}
30 |         disabled={disabled}>
31 |         {keys.map((key, index) => (
32 |           <option key={key} value={index}>
33 |             {key}
34 |           </option>
35 |         ))}
36 |       </NativeSelect>
37 |       <PresentationalSelect>{lastDisplayedValue.current}</PresentationalSelect>
38 |       <Chevron toggled />
39 |     </SelectContainer>
40 |   )
41 | }
42 | 
43 | export function SelectComponent() {
44 |   const { label, value, displayValue, onUpdate, id, disabled, settings } = useInputContext<SelectProps>()
45 |   return (
46 |     <Row input>
47 |       <Label>{label}</Label>
48 |       <Select
49 |         id={id}
50 |         value={value}
51 |         displayValue={displayValue}
52 |         onUpdate={onUpdate}
53 |         settings={settings}
54 |         disabled={disabled}
55 |       />
56 |     </Row>
57 |   )
58 | }
59 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Select/StyledSelect.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const SelectContainer = styled('div', {
 4 |   $flexCenter: '',
 5 |   position: 'relative',
 6 |   '> svg': {
 7 |     pointerEvents: 'none',
 8 |     position: 'absolute',
 9 |     right: '$md',
10 |   },
11 | })
12 | 
13 | export const NativeSelect = styled('select', {
14 |   position: 'absolute',
15 |   top: 0,
16 |   left: 0,
17 |   width: '100%',
18 |   height: '100%',
19 |   opacity: 0,
20 | })
21 | 
22 | export const PresentationalSelect = styled('div', {
23 |   display: 'flex',
24 |   alignItems: 'center',
25 |   width: '100%',
26 |   height: '$rowHeight',
27 |   backgroundColor: '$elevation3',
28 |   borderRadius: '$sm',
29 |   padding: '0 $sm',
30 |   cursor: 'pointer',
31 |   [`${NativeSelect}:focus + &`]: {
32 |     $focusStyle: '',
33 |   },
34 |   [`${NativeSelect}:hover + &`]: {
35 |     $hoverStyle: '',
36 |   },
37 | })
38 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Select/index.ts:
--------------------------------------------------------------------------------
 1 | import * as props from './select-plugin'
 2 | import { SelectComponent } from './Select'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | export * from './Select'
 6 | 
 7 | export default createInternalPlugin({
 8 |   component: SelectComponent,
 9 |   ...props,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Select/select-plugin.ts:
--------------------------------------------------------------------------------
 1 | import v8n from 'v8n'
 2 | import type { SelectInput, InternalSelectSettings } from './select-types'
 3 | 
 4 | // the options attribute is either an key value object or an array
 5 | export const schema = (_o: any, s: any) =>
 6 |   v8n()
 7 |     .schema({
 8 |       options: v8n().passesAnyOf(v8n().object(), v8n().array()),
 9 |     })
10 |     .test(s)
11 | 
12 | export const sanitize = (value: any, { values }: InternalSelectSettings) => {
13 |   if (values.indexOf(value) < 0) throw Error(`Selected value doesn't match Select options`)
14 |   return value
15 | }
16 | 
17 | export const format = (value: any, { values }: InternalSelectSettings) => {
18 |   return values.indexOf(value)
19 | }
20 | 
21 | export const normalize = (input: SelectInput) => {
22 |   let { value, options } = input
23 |   let keys
24 |   let values
25 | 
26 |   if (Array.isArray(options)) {
27 |     values = options
28 |     keys = options.map((o) => String(o))
29 |   } else {
30 |     values = Object.values(options)
31 |     keys = Object.keys(options)
32 |   }
33 | 
34 |   if (!('value' in input)) value = values[0]
35 |   else if (!values.includes(value)) {
36 |     keys.unshift(String(value))
37 |     values.unshift(value)
38 |   }
39 | 
40 |   if (!Object.values(options).includes(value)) (options as any)[String(value)] = value
41 |   return { value, settings: { keys, values } }
42 | }
43 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Select/select-types.ts:
--------------------------------------------------------------------------------
1 | import type { LevaInputProps } from '../../types'
2 | 
3 | export type SelectSettings<U = unknown> = { options: Record<string, U> | U[] }
4 | export type InternalSelectSettings = { keys: string[]; values: any[] }
5 | 
6 | export type SelectInput<P = unknown, U = unknown> = { value?: P } & SelectSettings<U>
7 | 
8 | export type SelectProps = LevaInputProps<any, InternalSelectSettings, number>
9 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/String/String.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { ValueInput, ValueInputProps } from '../ValueInput'
 3 | import { Label, Row } from '../UI'
 4 | import { useInputContext } from '../../context'
 5 | import type { StringProps } from './string-types'
 6 | import { styled } from '../../styles'
 7 | 
 8 | type BaseStringProps = Pick<StringProps, 'displayValue' | 'onUpdate' | 'onChange'> &
 9 |   Omit<ValueInputProps, 'value'> & { editable?: boolean }
10 | 
11 | const NonEditableString = styled('div', {
12 |   whiteSpace: 'pre-wrap',
13 | })
14 | 
15 | export function String({ displayValue, onUpdate, onChange, editable = true, ...props }: BaseStringProps) {
16 |   if (editable) return <ValueInput value={displayValue} onUpdate={onUpdate} onChange={onChange} {...props} />
17 |   return <NonEditableString>{displayValue}</NonEditableString>
18 | }
19 | 
20 | export function StringComponent() {
21 |   const { label, settings, displayValue, onUpdate, onChange } = useInputContext<StringProps>()
22 |   return (
23 |     <Row input>
24 |       <Label>{label}</Label>
25 |       <String displayValue={displayValue} onUpdate={onUpdate} onChange={onChange} {...settings} />
26 |     </Row>
27 |   )
28 | }
29 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/String/index.ts:
--------------------------------------------------------------------------------
 1 | import * as props from './string-plugin'
 2 | import { StringComponent } from './String'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | export * from './String'
 6 | 
 7 | export default createInternalPlugin({
 8 |   component: StringComponent,
 9 |   ...props,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/String/string-plugin.ts:
--------------------------------------------------------------------------------
 1 | import v8n from 'v8n'
 2 | import { StringInput } from './string-types'
 3 | 
 4 | export const schema = (o: any) => v8n().string().test(o)
 5 | 
 6 | export const sanitize = (v: any) => {
 7 |   if (typeof v !== 'string') throw Error(`Invalid string`)
 8 |   return v
 9 | }
10 | 
11 | export const normalize = ({ value, editable = true, rows = false }: StringInput) => {
12 |   return {
13 |     value,
14 |     settings: { editable, rows: typeof rows === 'number' ? rows : rows ? 5 : 0 },
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/String/string-types.ts:
--------------------------------------------------------------------------------
1 | import type { InputWithSettings, LevaInputProps } from '../../types'
2 | 
3 | export type StringSettings = { editable?: boolean; rows?: boolean | number }
4 | export type InternalStringSettings = { editable: boolean; rows: number }
5 | export type StringInput = InputWithSettings<string, StringSettings>
6 | export type StringProps = LevaInputProps<string, InternalStringSettings>
7 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/UI/Chevron.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { styled } from '../../styles'
 3 | 
 4 | // TODO remove as any when this is corrected by stitches
 5 | const Svg = styled('svg', {
 6 |   fill: 'currentColor',
 7 |   transition: 'transform 350ms ease, fill 250ms ease',
 8 | }) as any
 9 | 
10 | export function Chevron({ toggled, ...props }: React.SVGProps<SVGSVGElement> & { toggled?: boolean }) {
11 |   return (
12 |     <Svg
13 |       width="9"
14 |       height="5"
15 |       viewBox="0 0 9 5"
16 |       xmlns="http://www.w3.org/2000/svg"
17 |       style={{ transform: `rotate(${toggled ? 0 : -90}deg)` }}
18 |       {...props}>
19 |       <path d="M3.8 4.4c.4.3 1 .3 1.4 0L8 1.7A1 1 0 007.4 0H1.6a1 1 0 00-.7 1.7l3 2.7z" />
20 |     </Svg>
21 |   )
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/UI/Misc.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useContext } from 'react'
 2 | import * as P from '@radix-ui/react-portal'
 3 | import { ThemeContext } from '../../context'
 4 | export { Overlay } from './StyledUI'
 5 | 
 6 | // @ts-ignore
 7 | export function Portal({ children, container = globalThis?.document?.body }) {
 8 |   const { className } = useContext(ThemeContext)!
 9 |   return (
10 |     <P.Root className={className} container={container}>
11 |       {children}
12 |     </P.Root>
13 |   )
14 | }
15 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/UI/Row.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { StyledRow, StyledInputRow } from './StyledUI'
 3 | 
 4 | type RowProps = React.ComponentProps<typeof StyledRow> & { input?: boolean }
 5 | 
 6 | export function Row({ input, ...props }: RowProps) {
 7 |   if (input) return <StyledInputRow {...props} />
 8 |   return <StyledRow {...props} />
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/UI/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Misc'
2 | export * from './Label'
3 | export * from './Chevron'
4 | export * from './Row'
5 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/ValueInput/StyledInput.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const StyledInput = styled('input', {
 4 |   /* input reset */
 5 |   $reset: '',
 6 |   padding: '0 $sm',
 7 |   width: 0,
 8 |   minWidth: 0,
 9 |   flex: 1,
10 |   height: '100%',
11 |   variants: {
12 |     levaType: { number: { textAlign: 'right' } },
13 |     as: { textarea: { padding: '$sm' } },
14 |   },
15 | })
16 | 
17 | export const InnerLabel = styled('div', {
18 |   $draggable: '',
19 |   height: '100%',
20 |   $flexCenter: '',
21 |   position: 'relative',
22 |   padding: '0 $xs',
23 |   fontSize: '0.8em',
24 |   opacity: 0.8,
25 |   cursor: 'default',
26 |   touchAction: 'none',
27 |   [`& + ${StyledInput}`]: { paddingLeft: 0 },
28 | })
29 | 
30 | export const InnerNumberLabel = styled(InnerLabel, {
31 |   cursor: 'ew-resize',
32 |   marginRight: '-$xs',
33 |   textTransform: 'uppercase',
34 |   opacity: 0.3,
35 |   '&:hover': { opacity: 1 },
36 |   variants: {
37 |     dragging: { true: { backgroundColor: '$accent2', opacity: 1 } },
38 |   },
39 | })
40 | 
41 | export const InputContainer = styled('div', {
42 |   $flex: '',
43 |   position: 'relative',
44 |   borderRadius: '$sm',
45 |   overflow: 'hidden',
46 |   color: 'inherit',
47 |   height: '$rowHeight',
48 |   backgroundColor: '$elevation3',
49 |   $inputStyle: '$elevation1',
50 |   $hover: '',
51 |   $focusWithin: '',
52 |   variants: {
53 |     textArea: { true: { height: 'auto' } },
54 |   },
55 | })
56 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/ValueInput/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ValueInput'
2 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Vector'
2 | export * from './vector-plugin'
3 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector/vector-utils.ts:
--------------------------------------------------------------------------------
 1 | import { normalize } from '../Number/number-plugin'
 2 | import type { NumberSettings } from '../../types'
 3 | import type { InternalNumberSettings } from '../Number/number-types'
 4 | 
 5 | export const normalizeKeyedNumberSettings = <V extends Record<string, number>>(
 6 |   value: V,
 7 |   settings: { [key in keyof V]?: NumberSettings }
 8 | ) => {
 9 |   const _settings = {} as { [key in keyof V]: InternalNumberSettings }
10 | 
11 |   let maxStep = 0
12 |   let minPad = Infinity
13 |   Object.entries(value).forEach(([key, v]: [keyof V, any]) => {
14 |     _settings[key] = normalize({ value: v, ...settings[key] }).settings
15 |     maxStep = Math.max(maxStep, _settings[key].step)
16 |     minPad = Math.min(minPad, _settings[key].pad)
17 |   })
18 | 
19 |   // makes sure we get a consistent step and pad on all vector components when
20 |   // step is not specified in settings.
21 |   for (let key in _settings) {
22 |     const { step, min, max } = (settings[key] as any) || {}
23 |     if (!isFinite(step) && (!isFinite(min) || !isFinite(max))) {
24 |       _settings[key].step = maxStep
25 |       _settings[key].pad = minPad
26 |     }
27 |   }
28 | 
29 |   return _settings
30 | }
31 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector2d/StyledJoystick.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from '../../styles'
 2 | 
 3 | export const JoystickTrigger = styled('div', {
 4 |   $flexCenter: '',
 5 |   position: 'relative',
 6 |   backgroundColor: '$elevation3',
 7 |   borderRadius: '$sm',
 8 |   cursor: 'pointer',
 9 |   height: '$rowHeight',
10 |   width: '$rowHeight',
11 |   touchAction: 'none',
12 |   $draggable: '',
13 |   $hover: '',
14 | 
15 |   '&:active': { cursor: 'none' },
16 | 
17 |   '&::after': {
18 |     content: '""',
19 |     backgroundColor: '$accent2',
20 |     height: 4,
21 |     width: 4,
22 |     borderRadius: 2,
23 |   },
24 | })
25 | 
26 | export const JoystickPlayground = styled('div', {
27 |   $flexCenter: '',
28 |   width: '$joystickWidth',
29 |   height: '$joystickHeight',
30 |   borderRadius: '$sm',
31 |   boxShadow: '$level2',
32 |   position: 'fixed',
33 |   zIndex: 10000,
34 |   overflow: 'hidden',
35 |   $draggable: '',
36 |   transform: 'translate(-50%, -50%)',
37 | 
38 |   variants: {
39 |     isOutOfBounds: {
40 |       true: { backgroundColor: '$elevation1' },
41 |       false: { backgroundColor: '$elevation3' },
42 |     },
43 |   },
44 |   '> div': {
45 |     position: 'absolute',
46 |     $flexCenter: '',
47 |     borderStyle: 'solid',
48 |     borderWidth: 1,
49 |     borderColor: '$highlight1',
50 |     backgroundColor: '$elevation3',
51 |     width: '80%',
52 |     height: '80%',
53 | 
54 |     '&::after,&::before': {
55 |       content: '""',
56 |       position: 'absolute',
57 |       zindex: 10,
58 |       backgroundColor: '$highlight1',
59 |     },
60 | 
61 |     '&::before': {
62 |       width: '100%',
63 |       height: 1,
64 |     },
65 | 
66 |     '&::after': {
67 |       height: '100%',
68 |       width: 1,
69 |     },
70 |   },
71 | 
72 |   '> span': {
73 |     position: 'relative',
74 |     zindex: 100,
75 |     width: 10,
76 |     height: 10,
77 |     backgroundColor: '$accent2',
78 |     borderRadius: '50%',
79 |   },
80 | })
81 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector2d/Vector2d.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { styled } from '../../styles'
 3 | import { Vector } from '../Vector'
 4 | import { Label, Row } from '../UI'
 5 | import { Joystick } from './Joystick'
 6 | import { useInputContext } from '../../context'
 7 | import type { Vector2dProps } from './vector2d-types'
 8 | 
 9 | export const Container = styled('div', {
10 |   display: 'grid',
11 |   columnGap: '$colGap',
12 |   variants: {
13 |     withJoystick: {
14 |       true: { gridTemplateColumns: '$sizes$rowHeight auto' },
15 |       false: { gridTemplateColumns: 'auto' },
16 |     },
17 |   },
18 | })
19 | 
20 | export function Vector2dComponent() {
21 |   const { label, displayValue, onUpdate, settings } = useInputContext<Vector2dProps>()
22 |   return (
23 |     <Row input>
24 |       <Label>{label}</Label>
25 |       <Container withJoystick={!!settings.joystick}>
26 |         {settings.joystick && <Joystick value={displayValue} settings={settings} onUpdate={onUpdate} />}
27 |         <Vector value={displayValue} settings={settings} onUpdate={onUpdate} />
28 |       </Container>
29 |     </Row>
30 |   )
31 | }
32 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector2d/index.ts:
--------------------------------------------------------------------------------
 1 | import { Vector2dComponent } from './Vector2d'
 2 | import { getVectorPlugin } from '../Vector'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | import type { InternalVector2dSettings } from './vector2d-types'
 5 | 
 6 | export * from './Vector2d'
 7 | 
 8 | const plugin = getVectorPlugin(['x', 'y'])
 9 | const normalize = ({ joystick = true, ...input }: any) => {
10 |   const { value, settings } = plugin.normalize(input)
11 |   return { value, settings: { ...settings, joystick } as InternalVector2dSettings }
12 | }
13 | 
14 | export default createInternalPlugin({
15 |   component: Vector2dComponent,
16 |   ...plugin,
17 |   normalize,
18 | })
19 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector2d/vector2d-types.ts:
--------------------------------------------------------------------------------
1 | import type { LevaInputProps, Vector2d, VectorObj } from '../../types'
2 | import type { InternalVectorSettings } from '../Vector/vector-types'
3 | 
4 | export type InternalVector2dSettings = InternalVectorSettings<string, [string, string]> & {
5 |   joystick: boolean | 'invertY'
6 | }
7 | export type Vector2dProps = LevaInputProps<Vector2d, InternalVector2dSettings, VectorObj>
8 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector3d/Vector3d.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Vector } from '../Vector'
 3 | import { Label, Row } from '../UI'
 4 | import { useInputContext } from '../../context'
 5 | import type { Vector3dProps } from './vector3d-types'
 6 | 
 7 | export function Vector3dComponent() {
 8 |   const { label, displayValue, onUpdate, settings } = useInputContext<Vector3dProps>()
 9 |   return (
10 |     <Row input>
11 |       <Label>{label}</Label>
12 |       <Vector value={displayValue} settings={settings} onUpdate={onUpdate} />
13 |     </Row>
14 |   )
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector3d/index.ts:
--------------------------------------------------------------------------------
 1 | import { Vector3dComponent } from './Vector3d'
 2 | import { getVectorPlugin } from '../Vector'
 3 | import { createInternalPlugin } from '../../plugin'
 4 | 
 5 | export * from './Vector3d'
 6 | 
 7 | export default createInternalPlugin({
 8 |   component: Vector3dComponent,
 9 |   ...getVectorPlugin(['x', 'y', 'z']),
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/leva/src/components/Vector3d/vector3d-types.ts:
--------------------------------------------------------------------------------
1 | import type { LevaInputProps, Vector3d, VectorObj } from '../../types'
2 | import type { InternalVectorSettings } from '../Vector/vector-types'
3 | 
4 | export type InternalVector3dSettings = InternalVectorSettings<string, [string, string, string]>
5 | export type Vector3dProps = LevaInputProps<Vector3d, InternalVector3dSettings, VectorObj>
6 | 


--------------------------------------------------------------------------------
/packages/leva/src/context.tsx:
--------------------------------------------------------------------------------
 1 | import React, { createContext, useContext } from 'react'
 2 | import type { FullTheme } from './styles'
 3 | import type { StoreType, PanelSettingsType, InputContextProps } from './types'
 4 | 
 5 | export const InputContext = createContext({})
 6 | 
 7 | export function useInputContext<T = {}>() {
 8 |   return useContext(InputContext) as InputContextProps & T
 9 | }
10 | 
11 | type ThemeContextProps = { theme: FullTheme; className: string }
12 | 
13 | export const ThemeContext = createContext<ThemeContextProps | null>(null)
14 | 
15 | export const StoreContext = createContext<StoreType | null>(null)
16 | 
17 | export const PanelSettingsContext = createContext<PanelSettingsType | null>(null)
18 | 
19 | export function useStoreContext() {
20 |   return useContext(StoreContext)!
21 | }
22 | 
23 | export function usePanelSettingsContext() {
24 |   return useContext(PanelSettingsContext)!
25 | }
26 | 
27 | type ReactChild = React.ReactElement | string | number
28 | 
29 | type LevaStoreProviderProps = {
30 |   children: ReactChild | ReactChild[] | typeof React.Children
31 |   store: StoreType
32 | }
33 | 
34 | export function LevaStoreProvider({ children, store }: LevaStoreProviderProps) {
35 |   // @ts-expect-error portal JSX types are broken upstream
36 |   return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/leva/src/eventEmitter.ts:
--------------------------------------------------------------------------------
 1 | type Listener = (...args: Array<any>) => void
 2 | 
 3 | type EventEmitter = {
 4 |   on: (topic: string, listener: Listener) => void
 5 |   off: (topic: string, listener: Listener) => void
 6 |   emit: (event: string, ...args: Array<any>) => void
 7 | }
 8 | 
 9 | /**
10 |  * Super simple event emitter.
11 |  */
12 | export const createEventEmitter = (): EventEmitter => {
13 |   const listenerMapping = new Map<string, Set<Listener>>()
14 |   return {
15 |     on: (topic, listener) => {
16 |       let listeners = listenerMapping.get(topic)
17 |       if (listeners === undefined) {
18 |         listeners = new Set()
19 |         listenerMapping.set(topic, listeners)
20 |       }
21 |       listeners.add(listener)
22 |     },
23 |     off: (topic, listener) => {
24 |       const listeners = listenerMapping.get(topic)
25 |       if (listeners === undefined) {
26 |         return
27 |       }
28 |       listeners.delete(listener)
29 |       if (listeners.size === 0) {
30 |         listenerMapping.delete(topic)
31 |       }
32 |     },
33 |     emit: (topic, ...args) => {
34 |       const listeners = listenerMapping.get(topic)
35 |       if (listeners === undefined) {
36 |         return
37 |       }
38 |       for (const listener of listeners) {
39 |         listener(...args)
40 |       }
41 |     },
42 |   }
43 | }
44 | 


--------------------------------------------------------------------------------
/packages/leva/src/helpers/button.ts:
--------------------------------------------------------------------------------
 1 | import { SpecialInputs } from '../types'
 2 | import type { ButtonInput, ButtonSettings } from '../types'
 3 | 
 4 | const defaultSettings = { disabled: false }
 5 | 
 6 | /**
 7 |  *
 8 |  * @param name button name
 9 |  * @param onClick function that executes when the button is clicked
10 |  */
11 | export function button(onClick: ButtonInput['onClick'], settings?: ButtonSettings): ButtonInput {
12 |   return { type: SpecialInputs.BUTTON, onClick, settings: { ...defaultSettings, ...settings } }
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/leva/src/helpers/buttonGroup.ts:
--------------------------------------------------------------------------------
 1 | import { SpecialInputs } from '../types'
 2 | import type { ButtonGroupInput, ButtonGroupInputOpts } from '../types'
 3 | 
 4 | /**
 5 |  *
 6 |  * @param name button name
 7 |  * @param onClick function that executes when the button is clicked
 8 |  */
 9 | export function buttonGroup(opts: ButtonGroupInputOpts): ButtonGroupInput {
10 |   return { type: SpecialInputs.BUTTON_GROUP, opts }
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/leva/src/helpers/folder.ts:
--------------------------------------------------------------------------------
 1 | import { SpecialInputs } from '../types'
 2 | import type { FolderInput, Schema, SchemaToValues, FolderSettings } from '../types'
 3 | 
 4 | const defaultSettings = { collapsed: false }
 5 | 
 6 | export function folder<S extends Schema>(schema: S, settings?: FolderSettings): FolderInput<SchemaToValues<S>> {
 7 |   return {
 8 |     type: SpecialInputs.FOLDER,
 9 |     schema,
10 |     settings: { ...defaultSettings, ...settings },
11 |   } as any
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/leva/src/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './folder'
2 | export * from './button'
3 | export * from './buttonGroup'
4 | export * from './monitor'
5 | 


--------------------------------------------------------------------------------
/packages/leva/src/helpers/monitor.ts:
--------------------------------------------------------------------------------
1 | import { SpecialInputs } from '../types'
2 | import type { MonitorInput, MonitorSettings } from '../types'
3 | 
4 | const defaultSettings = { graph: false, interval: 100 }
5 | 
6 | export function monitor(objectOrFn: React.MutableRefObject<any> | Function, settings?: MonitorSettings): MonitorInput {
7 |   return { type: SpecialInputs.MONITOR, objectOrFn, settings: { ...defaultSettings, ...settings } }
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './useInputSetters'
 2 | export * from './useDeepMemo'
 3 | export * from './useShallowMemo'
 4 | export * from './useCompareMemoize'
 5 | export * from './useDrag'
 6 | export * from './useCanvas'
 7 | export * from './useTransform'
 8 | export * from './useToggle'
 9 | export * from './useVisiblePaths'
10 | export * from './useValuesForPath'
11 | export * from './useInput'
12 | export * from './usePopin'
13 | export * from './useValue'
14 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useCanvas.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect, useRef } from 'react'
 2 | import { debounce } from '../utils'
 3 | 
 4 | export function useCanvas2d(
 5 |   fn: Function
 6 | ): [React.RefObject<HTMLCanvasElement>, React.RefObject<CanvasRenderingContext2D>] {
 7 |   const canvas = useRef<HTMLCanvasElement>(null)
 8 |   const ctx = useRef<CanvasRenderingContext2D | null>(null)
 9 |   const hasFired = useRef(false)
10 | 
11 |   // TODO this is pretty much useless in 90% of cases since panels
12 |   // have a fixed width
13 |   useEffect(() => {
14 |     const handleCanvas = debounce(() => {
15 |       canvas.current!.width = canvas.current!.offsetWidth * window.devicePixelRatio
16 |       canvas.current!.height = canvas.current!.offsetHeight * window.devicePixelRatio
17 |       fn(canvas.current, ctx.current)
18 |     }, 250)
19 |     window.addEventListener('resize', handleCanvas)
20 |     if (!hasFired.current) {
21 |       handleCanvas()
22 |       hasFired.current = true
23 |     }
24 |     return () => window.removeEventListener('resize', handleCanvas)
25 |   }, [fn])
26 | 
27 |   useEffect(() => {
28 |     ctx.current = canvas.current!.getContext('2d')
29 |   }, [])
30 | 
31 |   return [canvas, ctx]
32 | }
33 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useCompareMemoize.ts:
--------------------------------------------------------------------------------
 1 | import { useRef } from 'react'
 2 | import { dequal } from 'dequal/lite'
 3 | import shallow from 'zustand/shallow'
 4 | 
 5 | export function useCompareMemoize(value: any, deep: boolean) {
 6 |   const ref = useRef()
 7 |   const compare = deep ? dequal : shallow
 8 | 
 9 |   if (!compare(value, ref.current)) {
10 |     ref.current = value
11 |   }
12 | 
13 |   return ref.current
14 | }
15 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useDeepMemo.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useCompareMemoize } from './useCompareMemoize'
3 | 
4 | export function useDeepMemo<T>(fn: () => T, deps: React.DependencyList | undefined) {
5 |   // NOTE: useMemo implementation allows undefined, but types do not
6 |   // eslint-disable-next-line react-hooks/exhaustive-deps
7 |   return useMemo(fn, useCompareMemoize(deps, true)!)
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useDrag.ts:
--------------------------------------------------------------------------------
 1 | import { useInputContext } from '../context'
 2 | import { FullGestureState, useDrag as useDragHook, UserDragConfig } from '@use-gesture/react'
 3 | 
 4 | export function useDrag(handler: (state: FullGestureState<'drag'>) => any, config?: UserDragConfig) {
 5 |   const { emitOnEditStart, emitOnEditEnd } = useInputContext()
 6 |   return useDragHook((state) => {
 7 |     if (state.first) {
 8 |       document.body.classList.add('leva__panel__dragged')
 9 |       emitOnEditStart?.()
10 |     }
11 |     const result = handler(state)
12 |     if (state.last) {
13 |       document.body.classList.remove('leva__panel__dragged')
14 |       emitOnEditEnd?.()
15 |     }
16 |     return result
17 |   }, config)
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useInput.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback, useState, useEffect } from 'react'
 2 | import shallow from 'zustand/shallow'
 3 | import { useStoreContext } from '../context'
 4 | import type { Data, DataItem } from '../types'
 5 | 
 6 | const getInputAtPath = (data: Data, path: string) => {
 7 |   if (!data[path]) return null
 8 |   const { __refCount, ...input } = data[path]
 9 |   return input
10 | }
11 | 
12 | type Input = Omit<DataItem, '__refCount'>
13 | 
14 | /**
15 |  * Return all input (value and settings) properties at a given path.
16 |  *
17 |  * @param path
18 |  */
19 | export function useInput(path: string): [
20 |   Input | null,
21 |   {
22 |     set: (value: any, onValueChanged?: (value: any) => void) => void
23 |     setSettings: (value: any) => void
24 |     disable: (flag: boolean) => void
25 |     storeId: string
26 |     emitOnEditStart: () => void
27 |     emitOnEditEnd: () => void
28 |   }
29 | ] {
30 |   const store = useStoreContext()
31 |   const [state, setState] = useState<Input | null>(getInputAtPath(store.getData(), path))
32 | 
33 |   const set = useCallback((value: any) => store.setValueAtPath(path, value, true), [path, store])
34 |   const setSettings = useCallback((settings: any) => store.setSettingsAtPath(path, settings), [path, store])
35 |   const disable = useCallback((flag: boolean) => store.disableInputAtPath(path, flag), [path, store])
36 |   const emitOnEditStart = useCallback(() => store.emitOnEditStart(path), [path, store])
37 |   const emitOnEditEnd = useCallback(() => store.emitOnEditEnd(path), [path, store])
38 | 
39 |   useEffect(() => {
40 |     setState(getInputAtPath(store.getData(), path))
41 |     const unsub = store.useStore.subscribe((s) => getInputAtPath(s.data, path), setState, { equalityFn: shallow })
42 |     return () => unsub()
43 |   }, [store, path])
44 | 
45 |   return [state, { set, setSettings, disable, storeId: store.storeId, emitOnEditStart, emitOnEditEnd }]
46 | }
47 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useInputSetters.ts:
--------------------------------------------------------------------------------
 1 | import { dequal } from 'dequal/lite'
 2 | import { useState, useCallback, useEffect, useRef } from 'react'
 3 | import { format } from '../plugin'
 4 | 
 5 | type Props<V, Settings> = {
 6 |   type: string
 7 |   value: V
 8 |   settings?: Settings
 9 |   setValue: (v: V) => void
10 | }
11 | 
12 | export function useInputSetters<V, Settings extends object>({ value, type, settings, setValue }: Props<V, Settings>) {
13 |   // the value used by the panel vs the value
14 |   const [displayValue, setDisplayValue] = useState(format(type, value, settings))
15 |   const previousValueRef = useRef(value)
16 |   const settingsRef = useRef(settings)
17 |   settingsRef.current = settings
18 |   const setFormat = useCallback((v: V) => setDisplayValue(format(type, v, settingsRef.current)), [type])
19 | 
20 |   const onUpdate = useCallback(
21 |     (updatedValue: any) => {
22 |       try {
23 |         setValue(updatedValue)
24 |       } catch (error: any) {
25 |         const { type, previousValue } = error
26 |         // make sure we throw an error if it's not a sanitization error
27 |         if (type !== 'LEVA_ERROR') throw error
28 |         setFormat(previousValue)
29 |       }
30 |     },
31 |     [setFormat, setValue]
32 |   )
33 | 
34 |   useEffect(() => {
35 |     if (!dequal(value, previousValueRef.current)) {
36 |       setFormat(value)
37 |     }
38 |     previousValueRef.current = value
39 |   }, [value, setFormat])
40 | 
41 |   return { displayValue, onChange: setDisplayValue, onUpdate }
42 | }
43 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/usePopin.ts:
--------------------------------------------------------------------------------
 1 | import { useState, useRef, useLayoutEffect, useCallback } from 'react'
 2 | 
 3 | export function usePopin(margin = 3) {
 4 |   const popinRef = useRef<HTMLDivElement>(null)
 5 |   const wrapperRef = useRef<HTMLDivElement>(null)
 6 | 
 7 |   const [shown, setShow] = useState(false)
 8 | 
 9 |   const show = useCallback(() => setShow(true), [])
10 |   const hide = useCallback(() => setShow(false), [])
11 | 
12 |   useLayoutEffect(() => {
13 |     if (shown) {
14 |       const { bottom, top, left } = popinRef.current!.getBoundingClientRect()
15 |       const { height } = wrapperRef.current!.getBoundingClientRect()
16 |       const direction = bottom + height > window.innerHeight - 40 ? 'up' : 'down'
17 | 
18 |       wrapperRef.current!.style.position = 'fixed'
19 |       wrapperRef.current!.style.zIndex = '10000'
20 |       wrapperRef.current!.style.left = left + 'px'
21 | 
22 |       if (direction === 'down') wrapperRef.current!.style.top = bottom + margin + 'px'
23 |       else wrapperRef.current!.style.bottom = window.innerHeight - top + margin + 'px'
24 |     }
25 |   }, [margin, shown])
26 | 
27 |   return { popinRef, wrapperRef, shown, show, hide }
28 | }
29 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useShallowMemo.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useCompareMemoize } from './useCompareMemoize'
3 | 
4 | export function useShallowMemo<T>(fn: () => T, deps: React.DependencyList | undefined) {
5 |   // NOTE: useMemo implementation allows undefined, but types do not
6 |   // eslint-disable-next-line react-hooks/exhaustive-deps
7 |   return useMemo(fn, useCompareMemoize(deps, false)!)
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useToggle.ts:
--------------------------------------------------------------------------------
 1 | import { useRef, useEffect, useLayoutEffect } from 'react'
 2 | 
 3 | export function useToggle(toggled: boolean) {
 4 |   const wrapperRef = useRef<HTMLDivElement>(null)
 5 |   const contentRef = useRef<HTMLDivElement>(null)
 6 |   const firstRender = useRef(true)
 7 | 
 8 |   // this should be fine for SSR since the store is set in useEffect and
 9 |   // therefore the pane doesn't show on first render.
10 |   useLayoutEffect(() => {
11 |     if (!toggled) {
12 |       wrapperRef.current!.style.height = '0px'
13 |       wrapperRef.current!.style.overflow = 'hidden'
14 |     }
15 |     // we only want to do this once so that's ok to break the rules of hooks.
16 |     // eslint-disable-next-line react-hooks/exhaustive-deps
17 |   }, [])
18 | 
19 |   useEffect(() => {
20 |     // prevents first animation
21 |     if (firstRender.current) {
22 |       firstRender.current = false
23 |       return
24 |     }
25 | 
26 |     let timeout: number
27 |     const ref = wrapperRef.current!
28 | 
29 |     const fixHeight = () => {
30 |       if (toggled) {
31 |         ref.style.removeProperty('height')
32 |         ref.style.removeProperty('overflow')
33 |         contentRef.current!.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
34 |       }
35 |     }
36 | 
37 |     ref.addEventListener('transitionend', fixHeight, { once: true })
38 | 
39 |     const { height } = contentRef.current!.getBoundingClientRect()
40 |     ref.style.height = height + 'px'
41 |     if (!toggled) {
42 |       ref.style.overflow = 'hidden'
43 |       timeout = window.setTimeout(() => (ref.style.height = '0px'), 50)
44 |     }
45 | 
46 |     return () => {
47 |       ref.removeEventListener('transitionend', fixHeight)
48 |       clearTimeout(timeout)
49 |     }
50 |   }, [toggled])
51 | 
52 |   return { wrapperRef, contentRef }
53 | }
54 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useTransform.ts:
--------------------------------------------------------------------------------
 1 | import { useRef, useCallback } from 'react'
 2 | 
 3 | export function useTransform<T extends HTMLElement>(): [
 4 |   React.RefObject<T>,
 5 |   (point: { x?: number; y?: number }) => void
 6 | ] {
 7 |   const ref = useRef<T>(null)
 8 |   const local = useRef({ x: 0, y: 0 })
 9 | 
10 |   const set = useCallback((point: { x?: number; y?: number }) => {
11 |     Object.assign(local.current, point)
12 |     if (ref.current) ref.current.style.transform = `translate3d(${local.current.x}px, ${local.current.y}px, 0)`
13 |   }, [])
14 | 
15 |   return [ref, set]
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useValue.ts:
--------------------------------------------------------------------------------
 1 | import shallow from 'zustand/shallow'
 2 | import { useStoreContext } from '../context'
 3 | 
 4 | export const useValue = (path: string) => {
 5 |   return useValues([path])[path]
 6 | }
 7 | 
 8 | export const useValues = <T extends string>(paths: T[]) => {
 9 |   const store = useStoreContext()
10 |   const value = store.useStore(
11 |     ({ data }) =>
12 |       paths.reduce((acc, path) => {
13 |         // @ts-expect-error
14 |         if (data[path] && 'value' in data[path]) return Object.assign(acc, { [path]: data[path].value })
15 |         return acc
16 |       }, {} as { [key in T]: any }),
17 |     shallow
18 |   )
19 |   return value
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useValuesForPath.ts:
--------------------------------------------------------------------------------
 1 | import shallow from 'zustand/shallow'
 2 | import { getValuesForPaths } from '../utils/data'
 3 | import type { Data, StoreType } from '../types'
 4 | 
 5 | /**
 6 |  * Hook that returns the values from the zustand store for the given paths.
 7 |  * @param paths paths for which to return values
 8 |  * @param initialData
 9 |  */
10 | export function useValuesForPath(store: StoreType, paths: string[], initialData: Data) {
11 |   const valuesForPath = store.useStore((s) => {
12 |     const data = { ...initialData, ...s.data }
13 |     return getValuesForPaths(data, paths)
14 |   }, shallow)
15 | 
16 |   return valuesForPath
17 | }
18 | 


--------------------------------------------------------------------------------
/packages/leva/src/hooks/useVisiblePaths.ts:
--------------------------------------------------------------------------------
 1 | import { useState, useEffect } from 'react'
 2 | import shallow from 'zustand/shallow'
 3 | import type { StoreType } from '../types'
 4 | 
 5 | /**
 6 |  * Hook used by the root component to get all visible inputs.
 7 |  */
 8 | export const useVisiblePaths = (store: StoreType) => {
 9 |   const [paths, setPaths] = useState(store.getVisiblePaths())
10 | 
11 |   useEffect(() => {
12 |     setPaths(store.getVisiblePaths())
13 |     const unsub = store.useStore.subscribe(store.getVisiblePaths, setPaths, { equalityFn: shallow })
14 |     return () => unsub()
15 |   }, [store])
16 | 
17 |   return paths
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/leva/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { register } from './plugin'
 2 | import number from './components/Number'
 3 | import select from './components/Select'
 4 | import color from './components/Color'
 5 | import string from './components/String'
 6 | import boolean from './components/Boolean'
 7 | import vector3d from './components/Vector3d'
 8 | import vector2d from './components/Vector2d'
 9 | import image from './components/Image'
10 | import interval from './components/Interval'
11 | import { LevaInputs } from './types'
12 | 
13 | /**
14 |  * Register all the primitive inputs.
15 |  * @note could potentially be done elsewhere.
16 |  */
17 | 
18 | register(LevaInputs.SELECT, select)
19 | register(LevaInputs.IMAGE, image)
20 | register(LevaInputs.NUMBER, number)
21 | register(LevaInputs.COLOR, color)
22 | register(LevaInputs.STRING, string)
23 | register(LevaInputs.BOOLEAN, boolean)
24 | register(LevaInputs.INTERVAL, interval)
25 | register(LevaInputs.VECTOR3D, vector3d)
26 | register(LevaInputs.VECTOR2D, vector2d)
27 | 
28 | // main hook
29 | export { useControls } from './useControls'
30 | 
31 | // panel components
32 | export { Leva, LevaPanel } from './components/Leva'
33 | 
34 | // simplifies passing store as context
35 | export { useStoreContext, LevaStoreProvider } from './context'
36 | 
37 | // export the levaStore (default store)
38 | // hook to create custom store
39 | export { levaStore, useCreateStore } from './store'
40 | 
41 | // export folder, monitor, button
42 | export * from './helpers'
43 | 
44 | export { LevaInputs }
45 | 


--------------------------------------------------------------------------------
/packages/leva/src/plugin/index.ts:
--------------------------------------------------------------------------------
 1 | // used as entrypoint
 2 | 
 3 | // export all components
 4 | import { Row, Label, Portal, Overlay } from '../components/UI'
 5 | import { String } from '../components/String'
 6 | import { Number } from '../components/Number'
 7 | import { Boolean } from '../components/Boolean'
 8 | import { Select } from '../components/Select'
 9 | import { Vector } from '../components/Vector'
10 | import { InnerLabel } from '../components/ValueInput/StyledInput'
11 | 
12 | export const Components = {
13 |   Row,
14 |   Label,
15 |   Portal,
16 |   Overlay,
17 |   String,
18 |   Number,
19 |   Boolean,
20 |   Select,
21 |   Vector,
22 |   InnerLabel,
23 | }
24 | 
25 | export { colord } from 'colord'
26 | export { dequal } from 'dequal/lite'
27 | 
28 | export { debounce, clamp, pad, evaluate, range, invertedRange, mergeRefs } from '../utils'
29 | export { normalizeKeyedNumberSettings } from '../components/Vector/vector-utils'
30 | 
31 | export { createPlugin } from '../plugin'
32 | 
33 | // export vector utilities
34 | export * from '../components/Vector/vector-plugin'
35 | // export useful hooks
36 | export { useDrag, useCanvas2d, useTransform, useInput, useValue, useValues, useInputSetters } from '../hooks'
37 | export { useInputContext, useStoreContext } from '../context'
38 | 
39 | // export styling utilities
40 | export { styled, keyframes, useTh } from '../styles'
41 | 
42 | // export types
43 | export * from '../types/public'
44 | export type { InternalVectorSettings } from '../components/Vector/vector-types'
45 | export type { InternalNumberSettings } from '../components/Number/number-types'
46 | 


--------------------------------------------------------------------------------
/packages/leva/src/styles/index.ts:
--------------------------------------------------------------------------------
 1 | import { useContext } from 'react'
 2 | import { getDefaultTheme, FullTheme, LevaCustomTheme, createTheme } from './stitches.config'
 3 | import { ThemeContext } from '../context'
 4 | import { warn, LevaErrors } from '../utils'
 5 | 
 6 | export function mergeTheme(newTheme?: LevaCustomTheme): { theme: FullTheme; className: string } {
 7 |   const defaultTheme = getDefaultTheme()
 8 |   if (!newTheme) return { theme: defaultTheme, className: '' }
 9 |   Object.keys(newTheme!).forEach((key) => {
10 |     // @ts-ignore
11 |     Object.assign(defaultTheme![key], newTheme![key])
12 |   })
13 |   const customTheme = createTheme(defaultTheme)
14 |   return { theme: defaultTheme, className: customTheme.className }
15 | }
16 | 
17 | export function useTh<C extends keyof FullTheme>(category: C, key: keyof FullTheme[C]) {
18 |   const { theme } = useContext(ThemeContext)!
19 |   if (!(category in theme!) || !(key in theme![category]!)) {
20 |     warn(LevaErrors.THEME_ERROR, category, key)
21 |     return ''
22 |   }
23 | 
24 |   let _key = key
25 |   while (true) {
26 |     // @ts-ignore
27 |     let value = theme[category][_key]
28 |     if (typeof value === 'string' && value.charAt(0) === '
#39;) _key = value.substr(1) as any
29 |     else return value
30 |   }
31 | }
32 | 
33 | export * from './stitches.config'
34 | 


--------------------------------------------------------------------------------
/packages/leva/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './internal'
2 | export * from './public'
3 | export * from './utils'
4 | 


--------------------------------------------------------------------------------
/packages/leva/src/types/utils.ts:
--------------------------------------------------------------------------------
 1 | // Utils from https://github.com/pmndrs/use-tweaks/blob/92561618abbf43c581fc5950fd35c0f8b21047cd/src/types.ts#L70
 2 | /**
 3 |  * It does nothing but beautify union type
 4 |  *
 5 |  * ```
 6 |  * type A = { a: 'a' } & { b: 'b' } // { a: 'a' } & { b: 'b' }
 7 |  * type B = Id<{ a: 'a' } & { b: 'b' }> // { a: 'a', b: 'b' }
 8 |  * ```
 9 |  */
10 | export type BeautifyUnionType<T> = T extends object
11 |   ? T extends Function // if T is a function return it as is
12 |     ? T
13 |     : any[] extends T // if T is a plain array return it as is
14 |     ? T
15 |     : T extends infer TT // if T is an object beautify it
16 |     ? { [k in keyof TT]: TT[k] } & GetIterator<TT>
17 |     : never
18 |   : T
19 | 
20 | // adds Iterator to the return type in case it has any
21 | type GetIterator<T> = T extends { [Symbol.iterator]: infer U } ? { [Symbol.iterator]: U } : {}
22 | 
23 | export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never
24 | 
25 | /**
26 |  * Gets keys from Record
27 |  */
28 | export type GetKeys<V> = V extends Record<infer K, number> ? K : never
29 | 


--------------------------------------------------------------------------------
/packages/leva/src/types/v8n.d.ts:
--------------------------------------------------------------------------------
 1 | // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/27449/commits/3afbb600c58f0739410b5f882d35eb323976fe80
 2 | 
 3 | declare module 'v8n' {
 4 |   const v8n: {
 5 |     (): Validation
 6 |     extend(item: Record<string, (item: any) => (value: any) => boolean>)
 7 |   }
 8 | 
 9 |   export default v8n
10 | 
11 |   interface Validation {
12 |     chain: Rule[]
13 |     every: Validation
14 |     invert?: boolean
15 |     extend(newRules: { [key: string]: () => boolean }): void
16 |     test(value: any): boolean
17 |     check(value: any): never
18 |     pattern(pattern: RegExp): Validation
19 |     equal(expected: any): Validation
20 |     exact(expected: any): Validation
21 |     string(): Validation
22 |     number(): Validation
23 |     boolean(): Validation
24 |     undefined(): Validation
25 |     null(): Validation
26 |     array(): Validation
27 |     lowercase(): Validation
28 |     vowel(): Validation
29 |     object(): Validation
30 |     consonant(): Validation
31 |     first(item: any): Validation
32 |     last(item: any): Validation
33 |     empty(): Validation
34 |     length(min: number, max?: number): Validation
35 |     minLength(min: number): Validation
36 |     maxLength(max: number): Validation
37 |     negative(): Validation
38 |     positive(): Validation
39 |     between(min: number, max: number): Validation
40 |     range(min: number, max: number): Validation
41 |     lessThan(bound: number): Validation
42 |     lessThanOrEqual(bound: number): Validation
43 |     greaterThan(bound: number): Validation
44 |     greaterThanOrEqual(bound: number): Validation
45 |     even(): Validation
46 |     odd(): Validation
47 |     includes(expected: any): Validation
48 |     integer(): Validation
49 |     schema(item: any): Validation
50 |     passesAnyOf(...args: Validation[]): Validation
51 |     optional(item: Validation): Validation
52 |   }
53 |   class Rule {
54 |     constructor(name: string, fn: () => boolean, args?: any, invert?: boolean)
55 |     name: string
56 |     fn: () => boolean
57 |     args?: any
58 |     invert?: boolean
59 |   }
60 | }
61 | 


--------------------------------------------------------------------------------
/packages/leva/src/utils/data.ts:
--------------------------------------------------------------------------------
 1 | import { pick } from '.'
 2 | import { Data } from '../types'
 3 | 
 4 | /**
 5 |  * Takes a data object with { [path.key]: value } and returns { [key]: value }.
 6 |  * Also warns when two similar keys are being used by the user.
 7 |  *
 8 |  * @param data
 9 |  * @param paths
10 |  * @param shouldWarn
11 |  */
12 | export function getValuesForPaths(data: Data, paths: string[]) {
13 |   return Object.entries(pick(data, paths)).reduce(
14 |     // Typescript complains that SpecialInput type doesn't have a value key.
15 |     // But getValuesForPath is only called from paths that are inputs,
16 |     // so they always have a value key.
17 | 
18 |     // @ts-expect-error
19 |     (acc, [, { value, disabled, key }]) => {
20 |       acc[key] = disabled ? undefined : value
21 |       return acc
22 |     },
23 |     {} as { [path: string]: any }
24 |   )
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/leva/src/utils/event.ts:
--------------------------------------------------------------------------------
1 | export const multiplyStep = (event: any) => (event.shiftKey ? 5 : event.altKey ? 1 / 5 : 1)
2 | 


--------------------------------------------------------------------------------
/packages/leva/src/utils/fn.ts:
--------------------------------------------------------------------------------
 1 | export const debounce = <F extends Function>(callback: F, wait: number, immediate = false) => {
 2 |   let timeout: number = 0
 3 | 
 4 |   return function () {
 5 |     const args = arguments as any
 6 |     const callNow = immediate && !timeout
 7 |     // @ts-expect-error
 8 |     const next = () => callback.apply(this, args)
 9 | 
10 |     window.clearTimeout(timeout)
11 |     timeout = window.setTimeout(next, wait)
12 | 
13 |     if (callNow) next()
14 |   } as F extends (...args: infer A) => infer B ? (...args: A) => B : never
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/leva/src/utils/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './math'
 2 | export * from './path'
 3 | export * from './object'
 4 | export * from './input'
 5 | export * from './fn'
 6 | export * from './log'
 7 | export * from './data'
 8 | export * from './event'
 9 | export * from './react'
10 | 


--------------------------------------------------------------------------------
/packages/leva/src/utils/object.ts:
--------------------------------------------------------------------------------
 1 | export function pick<K extends string, T extends { [k in K]: unknown }>(object: T, keys: K[]) {
 2 |   return keys.reduce((obj, key) => {
 3 |     if (!!object && object.hasOwnProperty(key)) {
 4 |       obj[key] = object[key]
 5 |     }
 6 |     return obj
 7 |   }, {} as { [k in K]: T[k] })
 8 | }
 9 | 
10 | export function omit<K extends string, T extends { [k in K]: unknown }>(object: T, keys: K[]) {
11 |   const obj = { ...object }
12 |   keys.forEach((k) => k in object && delete obj[k])
13 |   return obj
14 | }
15 | export function mapArrayToKeys<U extends any, K extends string>(value: U[], keys: K[]): Record<K, U> {
16 |   return value.reduce((acc, v, i) => Object.assign(acc, { [keys[i]]: v }), {} as any)
17 | }
18 | 
19 | export function isObject(variable: any) {
20 |   return Object.prototype.toString.call(variable) === '[object Object]'
21 | }
22 | 
23 | export const isEmptyObject = (obj: Object) => isObject(obj) && Object.keys(obj).length === 0
24 | 


--------------------------------------------------------------------------------
/packages/leva/src/utils/path.ts:
--------------------------------------------------------------------------------
 1 | export const join = (...args: (string | undefined)[]) => args.filter(Boolean).join('.')
 2 | 
 3 | export const prefix = (obj: object, p: string) =>
 4 |   Object.entries(obj).reduce((acc, [key, v]) => ({ ...acc, [join(p, key)]: v }), {})
 5 | 
 6 | export function getKeyPath(path: string): [string, string | undefined] {
 7 |   const dir = path.split('.')
 8 |   return [dir.pop()!, dir.join('.') || undefined]
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/leva/src/utils/react.ts:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | 
 3 | /*
 4 |  * https://github.com/gregberge/react-merge-refs
 5 |  * MIT License
 6 |  * Copyright (c) 2020 Greg Bergé
 7 |  *
 8 |  * Permission is hereby granted, free of charge, to any person obtaining a copy
 9 |  * of this software and associated documentation files (the "Software"), to deal
10 |  * in the Software without restriction, including without limitation the rights
11 |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 |  * copies of the Software, and to permit persons to whom the Software is
13 |  * furnished to do so, subject to the following conditions:
14 |  *
15 |  * The above copyright notice and this permission notice shall be included in all
16 |  * copies or substantial portions of the Software.
17 |  *
18 |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 |  * SOFTWARE.
25 |  */
26 | 
27 | export function mergeRefs<T>(
28 |   refs: Array<React.RefCallback<T> | React.RefObject<T> | null | undefined>
29 | ): React.RefCallback<T> {
30 |   return (value) => {
31 |     refs.forEach((ref) => {
32 |       if (typeof ref === 'function') ref(value)
33 |       else if (ref != null) {
34 |         ;(ref as React.MutableRefObject<T | null>).current = value
35 |       }
36 |     })
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/packages/leva/stories/caching.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import Reset from './components/decorator-reset'
 3 | import { Story, Meta } from '@storybook/react'
 4 | 
 5 | import { useControls } from '../src'
 6 | 
 7 | export default {
 8 |   title: 'Hook/Caching',
 9 |   decorators: [Reset],
10 | } as Meta
11 | 
12 | const Controls = () => {
13 |   const values = useControls({ num: 10, color: '#f00' })
14 | 
15 |   return (
16 |     <div>
17 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
18 |     </div>
19 |   )
20 | }
21 | 
22 | const Template: Story<any> = () => {
23 |   const [mounted, toggle] = React.useState(true)
24 |   return (
25 |     <div>
26 |       <button onClick={() => toggle((t) => !t)}>{mounted ? 'Unmount' : 'Mount'}</button>
27 |       {mounted && <Controls />}
28 |     </div>
29 |   )
30 | }
31 | 
32 | export const Caching = Template.bind({})
33 | 


--------------------------------------------------------------------------------
/packages/leva/stories/components/decorator-reset.tsx:
--------------------------------------------------------------------------------
 1 | import { StoryFnReactReturnType } from '@storybook/react/dist/ts3.9/client/preview/types'
 2 | import * as React from 'react'
 3 | import { levaStore } from '../../src'
 4 | 
 5 | const DefaultStory = (Story: () => StoryFnReactReturnType) => {
 6 |   const [_, set] = React.useState(false)
 7 |   React.useEffect(() => {
 8 |     levaStore.dispose()
 9 |     set(true)
10 |   }, [])
11 |   return _ ? <Story /> : <></>
12 | }
13 | 
14 | export default DefaultStory
15 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/Boolean.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/Boolean',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | const Template: Story<any> = (args) => {
14 |   const values = useControls({
15 |     foo: args,
16 |   })
17 | 
18 |   return (
19 |     <div>
20 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
21 |     </div>
22 |   )
23 | }
24 | 
25 | export const Default = Template.bind({})
26 | Default.args = {
27 |   value: false,
28 | }
29 | 
30 | export const Checked = Template.bind({})
31 | Checked.args = {
32 |   value: true,
33 | }
34 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/Button.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls, button } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/Button',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | export const Button = () => {
14 |   const values = useControls({
15 |     number: 3,
16 |     foo: button((get) => alert(`Number value is ${get('number').toFixed(2)}`)),
17 |   })
18 | 
19 |   return (
20 |     <div>
21 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
22 |     </div>
23 |   )
24 | }
25 | 
26 | export const DisabledButton = () => {
27 |   const values = useControls({
28 |     number: 3,
29 |     foo: button((get) => alert(`Number value is ${get('number')}`), { disabled: true }),
30 |   })
31 | 
32 |   return (
33 |     <div>
34 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
35 |     </div>
36 |   )
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/Image.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/Image',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | const Template: Story<any> = (args = undefined) => {
14 |   const values = useControls({ foo: args }) as any
15 | 
16 |   return (
17 |     <div>
18 |       <img src={values.foo} alt="" width="200" />
19 |     </div>
20 |   )
21 | }
22 | 
23 | export const Image = Template.bind({})
24 | Image.args = { image: undefined }
25 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/Interval.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/Interval',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | const Template: Story<any> = (args) => {
14 |   const values = useControls({
15 |     foo: args,
16 |   })
17 | 
18 |   return (
19 |     <div>
20 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
21 |     </div>
22 |   )
23 | }
24 | 
25 | export const Simple = Template.bind({})
26 | Simple.args = {
27 |   value: [10, 15],
28 |   min: 1,
29 |   max: 20,
30 | }
31 | 
32 | export const OverflowingValue = Template.bind({})
33 | OverflowingValue.args = {
34 |   value: [-10, 150],
35 |   min: 1,
36 |   max: 20,
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/Number.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/Number',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | const Template: Story<any> = (args) => {
14 |   const values = useControls({
15 |     foo: args,
16 |   })
17 | 
18 |   return (
19 |     <div>
20 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
21 |     </div>
22 |   )
23 | }
24 | 
25 | export const Simple = Template.bind({})
26 | Simple.args = {
27 |   value: 1,
28 | }
29 | 
30 | export const MinMax = Template.bind({})
31 | MinMax.args = {
32 |   value: 1,
33 |   min: 0,
34 |   max: 10,
35 | }
36 | 
37 | export const WithValueOverflow = Template.bind({})
38 | WithValueOverflow.args = {
39 |   value: 100,
40 |   min: 0,
41 |   max: 10,
42 | }
43 | 
44 | export const Step = Template.bind({})
45 | Step.args = {
46 |   value: 10,
47 |   step: 0.25,
48 | }
49 | 
50 | export const Suffix = Template.bind({})
51 | Suffix.args = { value: '10px' }
52 | 
53 | export const Complete = Template.bind({})
54 | Complete.args = {
55 |   value: 5,
56 |   min: 0,
57 |   max: 10,
58 |   step: 1,
59 |   suffix: 'px',
60 | }
61 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/Select.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/Select',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | const Template: Story<any> = (args) => {
14 |   const values = useControls({
15 |     foo: args,
16 |   })
17 | 
18 |   return (
19 |     <div>
20 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
21 |     </div>
22 |   )
23 | }
24 | 
25 | export const Simple = Template.bind({})
26 | Simple.args = {
27 |   value: 'x',
28 |   options: ['x', 'y'],
29 | }
30 | 
31 | export const CustomLabels = Template.bind({})
32 | CustomLabels.args = {
33 |   value: 'helloWorld',
34 |   options: {
35 |     'Hello World': 'helloWorld',
36 |     'Leva is awesome!': 'leva',
37 |   },
38 | }
39 | 
40 | export const InferredValueAsOption = Template.bind({})
41 | InferredValueAsOption.args = {
42 |   value: true,
43 |   options: [false],
44 | }
45 | 
46 | export const DifferentOptionTypes = Template.bind({})
47 | DifferentOptionTypes.args = {
48 |   value: undefined,
49 |   options: ['x', 'y', ['x', 'y']],
50 | }
51 | 
52 | const IconA = () => <span>IconA</span>
53 | const IconB = () => <span>IconB</span>
54 | 
55 | export const FunctionAsOptions = () => {
56 |   const values = useControls({
57 |     foo: { options: { none: '', IconA, IconB } },
58 |   })
59 | 
60 |   return (
61 |     <div>
62 |       <pre>{values.foo.toString()}</pre>
63 |     </div>
64 |   )
65 | }
66 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/String.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/String',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | const Template: Story<any> = (args) => {
14 |   const values = useControls({
15 |     foo: args,
16 |   })
17 | 
18 |   return (
19 |     <div>
20 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
21 |     </div>
22 |   )
23 | }
24 | 
25 | export const Simple = Template.bind({})
26 | Simple.args = {
27 |   value: 'Leva is awesome',
28 | }
29 | 
30 | export const DefaultRows = Template.bind({})
31 | DefaultRows.args = {
32 |   value: 'Leva also supports <textarea/>\nAllowing for\nmultiple lines',
33 |   rows: true,
34 | }
35 | 
36 | export const CustomRows = Template.bind({})
37 | CustomRows.args = {
38 |   value: 'You can specify the number of rows you need',
39 |   rows: 3,
40 | }
41 | 
42 | export const NonEditable = Template.bind({})
43 | NonEditable.args = {
44 |   value: 'This text is not editable but still supports\nline\nbreaks.',
45 |   editable: false,
46 | }
47 | 


--------------------------------------------------------------------------------
/packages/leva/stories/inputs/Vector.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../components/decorator-reset'
 5 | 
 6 | import { useControls } from '../../src'
 7 | 
 8 | export default {
 9 |   title: 'Inputs/Vector',
10 |   decorators: [Reset],
11 | } as Meta
12 | 
13 | const Template: Story<any> = (args) => {
14 |   const values = useControls({
15 |     foo: args,
16 |   })
17 | 
18 |   return (
19 |     <div>
20 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
21 |     </div>
22 |   )
23 | }
24 | 
25 | export const Vector2 = Template.bind({})
26 | Vector2.args = {
27 |   value: { x: 0, y: 0 },
28 | }
29 | 
30 | export const Vector2FromArray = Template.bind({})
31 | Vector2FromArray.args = {
32 |   value: [1, 10],
33 | }
34 | 
35 | export const Vector2WithLock = Template.bind({})
36 | Vector2WithLock.args = {
37 |   value: [1, 10],
38 |   lock: true,
39 | }
40 | 
41 | export const Vector2WithoutJoystick = Template.bind({})
42 | Vector2WithoutJoystick.args = {
43 |   value: { x: 0, y: 0 },
44 |   joystick: false,
45 | }
46 | 
47 | export const Vector2WithInvertedJoystickY = ({ value, invertY }) => (
48 |   <Template value={value} joystick={invertY ? 'invertY' : undefined} />
49 | )
50 | Vector2WithInvertedJoystickY.args = {
51 |   value: [0, 0],
52 |   invertY: true,
53 | }
54 | 
55 | export const Vector3 = Template.bind({})
56 | Vector3.args = {
57 |   value: { x: 0, y: 0, z: 0 },
58 | }
59 | 
60 | export const Vector3FromArray = Template.bind({})
61 | Vector3FromArray.args = {
62 |   value: [1, 10, 0],
63 | }
64 | 
65 | export const Vector3WithLock = Template.bind({})
66 | Vector3WithLock.args = {
67 |   value: [1, 10, 0],
68 |   lock: true,
69 | }
70 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/CHANGELOG.md:
--------------------------------------------------------------------------------
 1 | # @leva-ui/plugin-bezier
 2 | 
 3 | ## 0.10.0
 4 | 
 5 | ### Minor Changes
 6 | 
 7 | - 3d4a620: feat!: React 18 and 19 support
 8 | 
 9 | ### Patch Changes
10 | 
11 | - Updated dependencies [b9c6376]
12 | - Updated dependencies [3d4a620]
13 |   - leva@0.10.0
14 | 
15 | ## 0.9.19
16 | 
17 | ### Patch Changes
18 | 
19 | - 3177e59: style: label alignment
20 | - Updated dependencies [3177e59]
21 |   - leva@0.9.23
22 | 
23 | ## 0.9.18
24 | 
25 | ### Patch Changes
26 | 
27 | - e45e9de: Feat: pass `get` function to Button and ButtonGroup
28 | - Updated dependencies [e45e9de]
29 |   - leva@0.9.18
30 | 
31 | ## 0.9.14
32 | 
33 | ### Minor Changes
34 | 
35 | - 1001f25: Fix version for stitches before moving to 1.x
36 | 
37 | ## 0.9.12
38 | 
39 | ### Patch Changes
40 | 
41 | - Updated dependencies
42 |   - leva@0.9.12
43 | 
44 | ## 0.9.10
45 | 
46 | ### Patch Changes
47 | 
48 | - 16e3c14: feat: add `preview` flag to disable dot preview.
49 | - Updated dependencies [16e3c14]
50 |   - leva@0.9.10
51 | 
52 | ## 0.9.8
53 | 
54 | ### Patch Changes
55 | 
56 | - f8f7b57: fix: double render issue when using nested components.
57 | - Updated dependencies [f8f7b57]
58 |   - leva@0.9.9
59 | 
60 | ## 0.0.4
61 | 
62 | ### Patch Changes
63 | 
64 | - 0511799: styles: remove manual 'leva\_\_' prefix from stitches styles.
65 | - Updated dependencies [0511799]
66 |   - leva@0.9.6
67 | 
68 | ## 0.0.3
69 | 
70 | ### Patch Changes
71 | 
72 | - 26ead12: Feat: add cssEasing to returned prop
73 | 
74 | ## 0.0.2
75 | 
76 | ### Patch Changes
77 | 
78 | - c997410: Plugin: add the Bezier plugin
79 | 
80 |   ```js
81 |   import { bezier } from '@leva-ui/plugin-bezier'
82 |   useControls({ curve: bezier([0.25, 0.1, 0.25, 1]) })
83 |   ```
84 | 
85 | - Updated dependencies [c997410]
86 |   - leva@0.8.1
87 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/README.md:
--------------------------------------------------------------------------------
 1 | ## Leva Bezier
 2 | 
 3 | ### Installation
 4 | 
 5 | ```bash
 6 | npm i @leva-ui/plugin-bezier
 7 | ```
 8 | 
 9 | ### Quick start
10 | 
11 | ```jsx
12 | import { useControls } from 'leva'
13 | import { bezier } from '@leva-ui/plugin-bezier'
14 | 
15 | function MyComponent() {
16 |   const { curve } = useControls({ curve: bezier() })
17 |   // or
18 |   const { curve } = useControls({ curve: bezier([0.54, 0.05, 0.6, 0.98]) })
19 |   // or
20 |   const { curve } = useControls({ curve: bezier('in-out-quadratic') })
21 |   // or
22 |   const { curve } = useControls({ curve: bezier({ handles: [0.54, 0.05, 0.6, 0.98], graph: false }) })
23 | 
24 |   // built-in function evaluation
25 |   console.log(curve.evaluate(0.3))
26 | 
27 |   // inside a css like animation-timing-function
28 |   return <div style={{ animationTimingFunction: value.cssEasing }} />
29 | }
30 | ```
31 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@leva-ui/plugin-bezier",
 3 |   "version": "0.10.0",
 4 |   "main": "dist/leva-ui-plugin-bezier.cjs.js",
 5 |   "module": "dist/leva-ui-plugin-bezier.esm.js",
 6 |   "types": "dist/leva-ui-plugin-bezier.cjs.d.ts",
 7 |   "license": "MIT",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "https://github.com/pmndrs/leva.git",
11 |     "directory": "packages/plugin-beziers"
12 |   },
13 |   "bugs": "https://github.com/pmndrs/leva/issues",
14 |   "peerDependencies": {
15 |     "leva": ">=0.10.0",
16 |     "react": "^18.0.0 || ^19.0.0",
17 |     "react-dom": "^18.0.0 || ^19.0.0"
18 |   },
19 |   "dependencies": {
20 |     "react-use-measure": "^2.1.1"
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/src/Bezier.stories.css:
--------------------------------------------------------------------------------
 1 | @keyframes bezierStoryScale {
 2 |   0% {
 3 |     transform: scaleX(0);
 4 |   }
 5 | 
 6 |   100% {
 7 |     transform: scaleX(1);
 8 |   }
 9 | }
10 | 
11 | .bezier-animated {
12 |   height: 10px;
13 |   width: 200px;
14 |   background: indianred;
15 |   transform-origin: left;
16 |   animation: bezierStoryScale 1000ms infinite alternate both;
17 | }
18 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/src/Bezier.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from 'leva/stories/components/decorator-reset'
 5 | import { useControls } from 'leva/src'
 6 | 
 7 | import { bezier } from './index'
 8 | import './Bezier.stories.css'
 9 | 
10 | export default {
11 |   title: 'Plugins/Bezier',
12 |   decorators: [Reset],
13 | } as Meta
14 | 
15 | const Template: Story<any> = (args) => {
16 |   const data = useControls({ curve: args })
17 |   return (
18 |     <div>
19 |       <div className="bezier-animated" style={{ animationTimingFunction: data.curve.cssEasing }} />
20 |       <pre>{JSON.stringify(data, null, '  ')}</pre>
21 |     </div>
22 |   )
23 | }
24 | 
25 | export const DefaultBezier = Template.bind({})
26 | DefaultBezier.args = bezier(undefined)
27 | 
28 | export const WithArguments = Template.bind({})
29 | WithArguments.args = bezier([0.54, 0.05, 0.6, 0.98])
30 | 
31 | export const WithPreset = Template.bind({})
32 | WithPreset.args = bezier('in-out-quadratic')
33 | 
34 | export const WithOptions = Template.bind({})
35 | WithOptions.args = bezier({ handles: [0.54, 0.05, 0.6, 0.98], graph: false, preview: false, label: 'no graph' })
36 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/src/BezierPreview.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useEffect, useState, useMemo, useReducer } from 'react'
 2 | import { debounce } from 'leva/plugin'
 3 | import { PreviewSvg } from './StyledBezier'
 4 | import type { BezierProps } from './bezier-types'
 5 | 
 6 | const DebouncedBezierPreview = React.memo(({ value }: Pick<BezierProps, 'value'>) => {
 7 |   // use to forceUpdate on click
 8 |   const [, forceUpdate] = useReducer((x) => x + 1, 0)
 9 | 
10 |   const plotPoints = Array(21)
11 |     .fill(0)
12 |     .map((_, i) => 5 + value.evaluate(i / 20) * 90)
13 |   return (
14 |     <PreviewSvg onClick={forceUpdate}>
15 |       {plotPoints.map((p, i) => (
16 |         <circle key={i + Date.now()} r={3} cx={`${p}%`} style={{ animationDelay: `${i * 50}ms` }} />
17 |       ))}
18 |       <circle
19 |         key={Date.now() - 1}
20 |         r={3}
21 |         style={{
22 |           animationTimingFunction: `cubic-bezier(${value.join(',')})`,
23 |           animationDuration: `${plotPoints.length * 50}ms`,
24 |         }}
25 |       />
26 |     </PreviewSvg>
27 |   )
28 | })
29 | 
30 | export function BezierPreview({ value }: Pick<BezierProps, 'value'>) {
31 |   const [debouncedValue, set] = useState(value)
32 |   const debounceValue = useMemo(() => debounce((v: typeof value) => set(v), 250), [])
33 |   useEffect(() => void debounceValue(value), [value, debounceValue])
34 | 
35 |   return <DebouncedBezierPreview value={debouncedValue} />
36 | }
37 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/src/StyledBezier.ts:
--------------------------------------------------------------------------------
 1 | import { styled, keyframes } from 'leva/plugin'
 2 | 
 3 | export const Svg = styled('svg', {
 4 |   width: '100%',
 5 |   height: '$controlWidth',
 6 |   marginTop: '$rowGap',
 7 |   overflow: 'visible',
 8 |   zIndex: 100,
 9 |   '> path': {
10 |     stroke: '$highlight3',
11 |     strokeWidth: 2,
12 |   },
13 |   g: {
14 |     color: '$accent1',
15 |     '&:hover': { color: '$accent3' },
16 |     '&:active': { color: '$vivid1' },
17 |   },
18 |   circle: {
19 |     fill: 'currentColor',
20 |     strokeWidth: 10,
21 |     stroke: 'transparent',
22 |     cursor: 'pointer',
23 |   },
24 |   '> line': {
25 |     stroke: '$highlight1',
26 |     strokeWidth: 2,
27 |   },
28 |   '> g > line': {
29 |     stroke: 'currentColor',
30 |   },
31 |   variants: {
32 |     withPreview: { true: { marginBottom: 0 }, false: { marginBottom: '$rowGap' } },
33 |   },
34 | })
35 | 
36 | const fadeIn = (o: number) =>
37 |   keyframes({
38 |     '0%': { opacity: 0 },
39 |     '10%': { opacity: 0.8 },
40 |     '100%': { opacity: o },
41 |   })
42 | 
43 | const move = keyframes({
44 |   '0%': { transform: 'translateX(5%)' },
45 |   '100%': { transform: 'translateX(95%)' },
46 | })
47 | 
48 | export const PreviewSvg = styled('svg', {
49 |   width: '100%',
50 |   overflow: 'visible',
51 |   height: 6,
52 |   '> circle': {
53 |     fill: '$vivid1',
54 |     cy: '50%',
55 |     animation: `${fadeIn(0.3)} 1000ms both`,
56 |     '&:first-of-type': { animationName: fadeIn(0.7) },
57 |     '&:last-of-type': { animationName: move },
58 |   },
59 | })
60 | 
61 | export const SyledInnerLabel = styled('div', {
62 |   userSelect: 'none',
63 |   $flexCenter: '',
64 |   height: 14,
65 |   width: 14,
66 |   borderRadius: 7,
67 |   marginRight: '$sm',
68 |   cursor: 'pointer',
69 |   fontSize: '0.8em',
70 |   variants: {
71 |     graph: { true: { backgroundColor: '$elevation1' } },
72 |   },
73 | })
74 | 
75 | export const Container = styled('div', {
76 |   display: 'grid',
77 |   gridTemplateColumns: 'auto 1fr',
78 |   alignItems: 'center',
79 | })
80 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/src/bezier-plugin.ts:
--------------------------------------------------------------------------------
 1 | import { normalizeVector, sanitizeVector } from 'leva/plugin'
 2 | import { bezier } from './bezier-utils'
 3 | import type { BezierArray, BezierInput, InternalBezierSettings, InternalBezier, BuiltInKeys } from './bezier-types'
 4 | 
 5 | const abscissasSettings = { min: 0, max: 1, step: 0.01 }
 6 | const ordinatesSettings = { step: 0.01 }
 7 | const defaultSettings = { graph: true, preview: true }
 8 | 
 9 | export const BuiltIn: Record<BuiltInKeys, BezierArray> = {
10 |   ease: [0.25, 0.1, 0.25, 1],
11 |   linear: [0, 0, 1, 1],
12 |   'ease-in': [0.42, 0, 1, 1],
13 |   'ease-out': [0, 0, 0.58, 1],
14 |   'ease-in-out': [0.42, 0, 0.58, 1],
15 |   'in-out-sine': [0.45, 0.05, 0.55, 0.95],
16 |   'in-out-quadratic': [0.46, 0.03, 0.52, 0.96],
17 |   'in-out-cubic': [0.65, 0.05, 0.36, 1],
18 |   'fast-out-slow-in': [0.4, 0, 0.2, 1],
19 |   'in-out-back': [0.68, -0.55, 0.27, 1.55],
20 | }
21 | 
22 | export const normalize = (input: BezierInput = [0.25, 0.1, 0.25, 1]) => {
23 |   let { handles, ..._settings } = typeof input === 'object' && 'handles' in input ? input : { handles: input }
24 |   handles = typeof handles === 'string' ? BuiltIn[handles] : handles
25 | 
26 |   const mergedSettings = { x1: abscissasSettings, y1: ordinatesSettings, x2: abscissasSettings, y2: ordinatesSettings }
27 | 
28 |   const { value: _value, settings } = normalizeVector(handles, mergedSettings, ['x1', 'y1', 'x2', 'y2'])
29 |   const value = _value as InternalBezier
30 |   value.evaluate = bezier(..._value)
31 |   value.cssEasing = `cubic-bezier(${_value.join(',')})`
32 |   return { value, settings: { ...settings, ...defaultSettings, ..._settings } as InternalBezierSettings }
33 | }
34 | 
35 | export const sanitize = (value: any, settings: InternalBezierSettings, prevValue?: any) => {
36 |   const _value = sanitizeVector(value, settings, prevValue) as BezierArray
37 |   const newValue = _value as InternalBezier
38 |   newValue.evaluate = bezier(..._value)
39 |   newValue.cssEasing = `cubic-bezier(${_value.join(',')})`
40 |   return newValue
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/src/bezier-types.ts:
--------------------------------------------------------------------------------
 1 | import type { LevaInputProps, InternalVectorSettings, MergedInputWithSettings } from 'leva/plugin'
 2 | 
 3 | export type BuiltInKeys =
 4 |   | 'ease'
 5 |   | 'linear'
 6 |   | 'ease-in'
 7 |   | 'ease-out'
 8 |   | 'ease-in-out'
 9 |   | 'in-out-sine'
10 |   | 'in-out-quadratic'
11 |   | 'in-out-cubic'
12 |   | 'fast-out-slow-in'
13 |   | 'in-out-back'
14 | 
15 | export type BezierArray = [number, number, number, number]
16 | 
17 | export type Bezier = BezierArray | BuiltInKeys
18 | 
19 | export type BezierSettings = { graph?: boolean; preview?: boolean }
20 | export type BezierInput = MergedInputWithSettings<Bezier, BezierSettings, 'handles'>
21 | 
22 | export type InternalBezier = [number, number, number, number] & { evaluate(value: number): number; cssEasing: string }
23 | 
24 | export type DisplayValueBezier = { x1: number; y1: number; x2: number; y2: number }
25 | 
26 | export type InternalBezierSettings = InternalVectorSettings<
27 |   keyof DisplayValueBezier,
28 |   (keyof DisplayValueBezier)[],
29 |   'array'
30 | > & { graph: boolean; preview: boolean }
31 | 
32 | export type BezierProps = LevaInputProps<InternalBezier, InternalBezierSettings, DisplayValueBezier>
33 | 


--------------------------------------------------------------------------------
/packages/plugin-bezier/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { createPlugin, formatVector } from 'leva/plugin'
 2 | import { Bezier } from './Bezier'
 3 | import { normalize, sanitize } from './bezier-plugin'
 4 | import { InternalBezierSettings } from './bezier-types'
 5 | 
 6 | export const bezier = createPlugin({
 7 |   normalize,
 8 |   sanitize,
 9 |   format: (value: any, settings: InternalBezierSettings) => formatVector(value, settings),
10 |   component: Bezier,
11 | })
12 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/CHANGELOG.md:
--------------------------------------------------------------------------------
 1 | # @leva-ui/plugin-dates
 2 | 
 3 | ## 0.10.1
 4 | 
 5 | ### Patch Changes
 6 | 
 7 | - 89764b0: fix(plugin-dates): update React Calendar
 8 | 
 9 | ## 0.10.0
10 | 
11 | ### Minor Changes
12 | 
13 | - 3d4a620: feat!: React 18 and 19 support
14 | 
15 | ### Patch Changes
16 | 
17 | - Updated dependencies [b9c6376]
18 | - Updated dependencies [3d4a620]
19 |   - leva@0.10.0
20 | 
21 | ## 0.9.32
22 | 
23 | ### Patch Changes
24 | 
25 | - 8b21a5c: fix: scrolling long panels
26 | - Updated dependencies [8b21a5c]
27 |   - leva@0.9.34
28 | 
29 | ## 0.9.31
30 | 
31 | ### Major Changes
32 | 
33 | - 14a5605: feat: new date picker plugin
34 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/README.md:
--------------------------------------------------------------------------------
 1 | ## Leva Plot
 2 | 
 3 | ### Installation
 4 | 
 5 | ```bash
 6 | npm i @leva-ui/plugin-plot
 7 | ```
 8 | 
 9 | ### Quick start
10 | 
11 | ```jsx
12 | import { useControls } from 'leva'
13 | import { plot } from '@leva-ui/plugin-plot'
14 | 
15 | function MyComponent() {
16 |   const { y } = useControls({ y: plot({ expression: 'cos(x)', graph: true, boundsX: [-10, 10], boundsY: [0, 100] }) })
17 |   return y(Math.PI)
18 | }
19 | ```
20 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@leva-ui/plugin-dates",
 3 |   "version": "0.10.1",
 4 |   "main": "dist/leva-ui-plugin-dates.cjs.js",
 5 |   "module": "dist/leva-ui-plugin-dates.esm.js",
 6 |   "types": "dist/leva-ui-plugin-dates.cjs.d.ts",
 7 |   "license": "MIT",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "https://github.com/pmndrs/leva.git",
11 |     "directory": "packages/plugin-dates"
12 |   },
13 |   "bugs": "https://github.com/pmndrs/leva/issues",
14 |   "peerDependencies": {
15 |     "@use-gesture/react": "^10.0.0",
16 |     "leva": ">=0.10.0",
17 |     "react": "^18.0.0 || ^19.0.0",
18 |     "react-dom": "^18.0.0 || ^19.0.0"
19 |   },
20 |   "dependencies": {
21 |     "react-datepicker": "^7.6.0"
22 |   },
23 |   "devDependencies": {
24 |     "@types/react-datepicker": "^7.0.0"
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/src/Date.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../../leva/stories/components/decorator-reset'
 5 | import { useControls } from '../../leva/src'
 6 | 
 7 | import { date } from './index'
 8 | import { DateInput } from './date-types'
 9 | 
10 | export default {
11 |   title: 'Plugins/Dates',
12 |   decorators: [Reset],
13 | } as Meta
14 | 
15 | const Template: Story<DateInput> = (args) => {
16 |   const { birthday } = useControls({ birthday: date(args) })
17 |   return <div>{birthday.formattedDate}</div>
18 | }
19 | 
20 | export const DefaultDate = Template.bind({})
21 | DefaultDate.args = { date: new Date() }
22 | 
23 | export const CustomLocale = Template.bind({})
24 | CustomLocale.args = { date: new Date(), locale: 'en-US' }
25 | 
26 | export const CustomInputFormat = Template.bind({})
27 | CustomInputFormat.args = { date: new Date(), inputFormat: 'yyyy-MM-dd' }
28 | 
29 | export const WithOtherFields = () => {
30 |   const { birthday, ...values } = useControls({
31 |     text: 'text',
32 |     birthday: date({ date: new Date() }),
33 |     number: 0,
34 |   })
35 |   return (
36 |     <div>
37 |       {birthday.formattedDate}
38 |       <br />
39 |       {JSON.stringify(values)}
40 |     </div>
41 |   )
42 | }
43 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/src/Date.tsx:
--------------------------------------------------------------------------------
 1 | import { Components, useInputContext } from 'leva/plugin'
 2 | import React, { forwardRef, useState } from 'react'
 3 | import DatePicker, { CalendarContainer } from 'react-datepicker'
 4 | import 'react-datepicker/dist/react-datepicker.css'
 5 | import { DateCalendarContainerProps, DateInputProps, DateProps } from './date-types'
 6 | import { InputContainer, StyledInput, StyledWrapper } from './StyledDate'
 7 | 
 8 | const { Label, Row } = Components
 9 | 
10 | const DateCalendarContainer = ({ children }: DateCalendarContainerProps) => {
11 |   return (
12 |     <CalendarContainer>
13 |       {/* @ts-expect-error portal JSX types are broken upstream */}
14 |       <StyledWrapper>{children}</StyledWrapper>
15 |     </CalendarContainer>
16 |   )
17 | }
18 | 
19 | const DateInput = forwardRef<HTMLInputElement, Partial<DateInputProps>>(({ value, onClick, onChange }, ref) => {
20 |   return <StyledInput ref={ref} value={value} onClick={onClick} onChange={onChange} />
21 | })
22 | 
23 | export function Date() {
24 |   const { label, value, onUpdate, settings } = useInputContext<DateProps>()
25 | 
26 |   const [isOpen, setIsOpen] = useState(false)
27 | 
28 |   return (
29 |     <Row input style={{ height: isOpen ? 300 : 'auto' }}>
30 |       <Label>{label}</Label>
31 |       <InputContainer>
32 |         <DatePicker
33 |           selected={value.date}
34 |           onChange={onUpdate}
35 |           dateFormat={settings.inputFormat}
36 |           calendarContainer={DateCalendarContainer}
37 |           customInput={<DateInput />}
38 |           onCalendarOpen={() => setIsOpen(true)}
39 |           onCalendarClose={() => setIsOpen(false)}
40 |         />
41 |       </InputContainer>
42 |     </Row>
43 |   )
44 | }
45 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/src/StyledDate.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from 'leva/plugin'
 2 | 
 3 | export const StyledInput = styled('input', {
 4 |   $reset: '',
 5 |   padding: '0 $sm',
 6 |   width: '100%',
 7 |   minWidth: 0,
 8 |   flex: 1,
 9 |   height: '100%',
10 | })
11 | 
12 | export const InputContainer = styled('div', {
13 |   $flex: '',
14 |   position: 'relative',
15 |   borderRadius: '$sm',
16 |   color: 'inherit',
17 |   height: '$rowHeight',
18 |   backgroundColor: '$elevation3',
19 |   $inputStyle: '$elevation1',
20 |   $hover: '',
21 |   $focusWithin: '',
22 |   variants: {
23 |     textArea: { true: { height: 'auto' } },
24 |   },
25 | })
26 | 
27 | export const StyledWrapper = styled('div', {
28 |   position: 'relative',
29 | 
30 |   '& .react-datepicker__header': {
31 |     backgroundColor: '$elevation3',
32 |     border: 'none',
33 |   },
34 | 
35 |   '& .react-datepicker__current-month, .react-datepicker__day, .react-datepicker__day-name': {
36 |     color: 'inherit',
37 |   },
38 | 
39 |   '& .react-datepicker__day': {
40 |     transition: 'all 0.2s ease',
41 |   },
42 | 
43 |   '& .react-datepicker__day--selected': {
44 |     backgroundColor: '$accent1',
45 |     color: '$highlight3',
46 |   },
47 | 
48 |   '& .react-datepicker__day--keyboard-selected': {
49 |     backgroundColor: 'transparent',
50 |     color: 'inherit',
51 |   },
52 | 
53 |   '& .react-datepicker__day--today': {
54 |     backgroundColor: '$accent3',
55 |     color: '$highlight3',
56 |   },
57 | 
58 |   '& .react-datepicker__month-container': {
59 |     backgroundColor: '$elevation2',
60 |     borderRadius: '$lg',
61 |   },
62 | 
63 |   '& .react-datepicker__day:hover': {
64 |     backgroundColor: '$highlight1',
65 |   },
66 | })
67 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/src/date-plugin.ts:
--------------------------------------------------------------------------------
 1 | import type { DateInput, DateSettings, InternalDate, InternalDateSettings } from './date-types'
 2 | import { formatDate } from './date-utils'
 3 | 
 4 | const defaultSettings = {
 5 |   inputFormat: 'MM/dd/yyyy',
 6 | }
 7 | 
 8 | export const sanitize = (value: Date, settings: DateSettings) => {
 9 |   return {
10 |     date: value,
11 |     formattedDate: formatDate(value, settings.locale),
12 |   }
13 | }
14 | 
15 | export const format = (value: InternalDate, settings: DateSettings) => {
16 |   return {
17 |     date: value.date,
18 |     formattedDate: formatDate(value.date, settings.locale),
19 |   }
20 | }
21 | 
22 | export const normalize = ({ date, ..._settings }: DateInput) => {
23 |   const settings = { ...defaultSettings, ..._settings }
24 |   const defaultDate = date ?? new Date()
25 |   return {
26 |     value: { date: defaultDate, formattedDate: formatDate(defaultDate, settings.locale) },
27 |     settings: settings as InternalDateSettings,
28 |   }
29 | }
30 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/src/date-types.ts:
--------------------------------------------------------------------------------
 1 | import type { LevaInputProps } from 'leva/plugin'
 2 | import { ChangeEventHandler, MouseEventHandler } from 'react'
 3 | import { CalendarContainer } from 'react-datepicker'
 4 | 
 5 | export type DateSettings = { locale: string; inputFormat: string }
 6 | export type DateInput = { date: Date } & Partial<DateSettings>
 7 | 
 8 | // TODO: export this upstream
 9 | export type DateCalendarContainerProps = React.ComponentProps<typeof CalendarContainer>
10 | export type DateInputProps = { value: string; onClick: MouseEventHandler; onChange: ChangeEventHandler }
11 | 
12 | export type InternalDate = { date: Date; formattedDate: string }
13 | 
14 | export type InternalDateSettings = Required<DateSettings>
15 | 
16 | export type DateProps = LevaInputProps<InternalDate, InternalDateSettings, string>
17 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/src/date-utils.ts:
--------------------------------------------------------------------------------
1 | export function parseDate(date: string, locale: string) {
2 |   return new Date(date)
3 | }
4 | 
5 | export function formatDate(date: Date, locale?: string) {
6 |   return date.toLocaleDateString(locale)
7 | }
8 | 


--------------------------------------------------------------------------------
/packages/plugin-dates/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { createPlugin } from 'leva/plugin'
 2 | import { Date } from './Date'
 3 | import { sanitize, normalize, format } from './date-plugin'
 4 | 
 5 | export const date = createPlugin({
 6 |   sanitize,
 7 |   format,
 8 |   normalize,
 9 |   component: Date,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/README.md:
--------------------------------------------------------------------------------
 1 | ## Leva Plot
 2 | 
 3 | ### Installation
 4 | 
 5 | ```bash
 6 | npm i @leva-ui/plugin-plot
 7 | ```
 8 | 
 9 | ### Quick start
10 | 
11 | ```jsx
12 | import { useControls } from 'leva'
13 | import { plot } from '@leva-ui/plugin-plot'
14 | 
15 | function MyComponent() {
16 |   const { y } = useControls({ y: plot({ expression: 'cos(x)', graph: true, boundsX: [-10, 10], boundsY: [0, 100] }) })
17 |   return y(Math.PI)
18 | }
19 | ```
20 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@leva-ui/plugin-plot",
 3 |   "version": "0.10.0",
 4 |   "main": "dist/leva-ui-plugin-plot.cjs.js",
 5 |   "module": "dist/leva-ui-plugin-plot.esm.js",
 6 |   "types": "dist/leva-ui-plugin-plot.cjs.d.ts",
 7 |   "license": "MIT",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "https://github.com/pmndrs/leva.git",
11 |     "directory": "packages/plugin-plot"
12 |   },
13 |   "bugs": "https://github.com/pmndrs/leva/issues",
14 |   "peerDependencies": {
15 |     "@use-gesture/react": "^10.0.0",
16 |     "leva": ">=0.10.0",
17 |     "react": "^18.0.0 || ^19.0.0",
18 |     "react-dom": "^18.0.0 || ^19.0.0"
19 |   },
20 |   "dependencies": {
21 |     "mathjs": "^10.1.1"
22 |   }
23 | }
24 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/src/Plot.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../../leva/stories/components/decorator-reset'
 5 | import { useControls } from '../../leva/src'
 6 | 
 7 | import { plot } from './index'
 8 | 
 9 | export default {
10 |   title: 'Plugins/Plot',
11 |   decorators: [Reset],
12 | } as Meta
13 | 
14 | const Template: Story<any> = (args) => {
15 |   const { y } = useControls({ y: plot(args) })
16 |   return (
17 |     <div>
18 |       {[0, 0.5, -1].map((x) => (
19 |         <pre key={x}>
20 |           y({x}) = {y(x).toFixed(2)}
21 |         </pre>
22 |       ))}
23 |     </div>
24 |   )
25 | }
26 | 
27 | export const DefaultBounds = Template.bind({})
28 | DefaultBounds.args = { expression: 'x' }
29 | 
30 | export const HideGraph = Template.bind({})
31 | HideGraph.args = { expression: 'x', graph: false }
32 | 
33 | export const BoundsX = Template.bind({})
34 | BoundsX.args = { expression: 'cos(x)', boundsX: [-10, 10] }
35 | 
36 | export const BoundsY = Template.bind({})
37 | BoundsY.args = { expression: 'sin(x) * tan(x)', boundsX: [-10, 10], boundsY: [-1, 1] }
38 | 
39 | export const InputAsVariable = () => {
40 |   const { y } = useControls({ var: 10, y: plot({ expression: 'cos(x * var)' }) })
41 |   return (
42 |     <div>
43 |       {[0, 0.5, -1].map((x) => (
44 |         <pre key={x}>
45 |           y({x}) = {y(x).toFixed(2)}
46 |         </pre>
47 |       ))}
48 |     </div>
49 |   )
50 | }
51 | 
52 | export const CurveAsVariable = () => {
53 |   const { y2 } = useControls({
54 |     var: 10,
55 |     y1: plot({ expression: 'cos(x * var)' }),
56 |     y2: plot({ expression: 'x * y1' }),
57 |   })
58 |   return (
59 |     <div>
60 |       {[0, 0.5, -1].map((x) => (
61 |         <pre key={x}>
62 |           y2({x}) = {y2(x).toFixed(2)}
63 |         </pre>
64 |       ))}
65 |     </div>
66 |   )
67 | }
68 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/src/Plot.tsx:
--------------------------------------------------------------------------------
 1 | import React, { useEffect, useRef } from 'react'
 2 | import { useInputContext, useValues, Components } from 'leva/plugin'
 3 | import { PlotCanvas } from './PlotCanvas'
 4 | import type { PlotProps } from './plot-types'
 5 | import { SyledInnerLabel, Container } from './StyledPlot'
 6 | 
 7 | const { Label, Row, String } = Components
 8 | 
 9 | export function Plot() {
10 |   const { label, value, displayValue, settings, onUpdate, onChange, setSettings } = useInputContext<PlotProps>()
11 | 
12 |   const { graph } = settings
13 | 
14 |   const scope = useValues(value.__symbols)
15 |   const displayRef = useRef(displayValue)
16 |   displayRef.current = displayValue
17 | 
18 |   useEffect(() => {
19 |     // recomputes when scope which holds the values of the symbols change
20 |     onUpdate(displayRef.current)
21 |   }, [scope, onUpdate])
22 | 
23 |   return (
24 |     <>
25 |       {graph && (
26 |         <Row>
27 |           <PlotCanvas value={value} settings={settings} />
28 |         </Row>
29 |       )}
30 |       <Row input>
31 |         <Label>{label}</Label>
32 |         <Container>
33 |           <SyledInnerLabel graph={graph} onClick={() => setSettings({ graph: !graph })}>
34 |             𝑓
35 |           </SyledInnerLabel>
36 |           <String displayValue={displayValue} onUpdate={onUpdate} onChange={onChange} />
37 |         </Container>
38 |       </Row>
39 |     </>
40 |   )
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/src/StyledPlot.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from 'leva/plugin'
 2 | 
 3 | export const Wrapper = styled('div', {
 4 |   position: 'relative',
 5 |   height: 80,
 6 |   width: '100%',
 7 |   marginBottom: '$sm',
 8 | })
 9 | 
10 | export const ToolTip = styled('div', {
11 |   position: 'absolute',
12 |   top: -4,
13 |   pointerEvents: 'none',
14 |   fontFamily: '$mono',
15 |   fontSize: 'calc($fontSizes$root * 0.9)',
16 |   padding: '$xs $sm',
17 |   color: '$toolTipBackground',
18 |   backgroundColor: '$toolTipText',
19 |   borderRadius: '$xs',
20 |   whiteSpace: 'nowrap',
21 |   transform: 'translate(-50%, -100%)',
22 |   boxShadow: '$level2',
23 | })
24 | 
25 | export const Canvas = styled('canvas', {
26 |   height: '100%',
27 |   width: '100%',
28 | })
29 | 
30 | export const Dot = styled('div', {
31 |   position: 'absolute',
32 |   height: 6,
33 |   width: 6,
34 |   borderRadius: 6,
35 |   backgroundColor: '$highlight3',
36 |   pointerEvents: 'none',
37 | })
38 | 
39 | export const SyledInnerLabel = styled('div', {
40 |   userSelect: 'none',
41 |   $flexCenter: '',
42 |   height: 14,
43 |   width: 14,
44 |   borderRadius: 7,
45 |   marginRight: '$sm',
46 |   cursor: 'pointer',
47 |   fontSize: '0.8em',
48 |   variants: {
49 |     graph: { true: { backgroundColor: '$elevation1' } },
50 |   },
51 | })
52 | 
53 | export const Container = styled('div', {
54 |   display: 'grid',
55 |   gridTemplateColumns: 'auto 1fr',
56 |   alignItems: 'center',
57 | })
58 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { createPlugin } from 'leva/plugin'
 2 | import { Plot } from './Plot'
 3 | import { normalize, sanitize, format } from './plot-plugin'
 4 | 
 5 | export const plot = createPlugin({
 6 |   normalize,
 7 |   sanitize,
 8 |   format,
 9 |   component: Plot,
10 | })
11 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/src/plot-plugin.ts:
--------------------------------------------------------------------------------
 1 | import { Data, StoreType } from 'packages/leva/src/types'
 2 | import * as math from 'mathjs'
 3 | import { parseExpression } from './plot-utils'
 4 | import type { PlotInput, InternalPlot, InternalPlotSettings } from './plot-types'
 5 | 
 6 | export const sanitize = (
 7 |   expression: string,
 8 |   _settings: InternalPlotSettings,
 9 |   _prevValue: math.MathNode,
10 |   _path: string,
11 |   store: StoreType
12 | ) => {
13 |   if (expression === '') throw Error('Empty mathjs expression')
14 |   try {
15 |     return parseExpression(expression, store.get)
16 |   } catch (e) {
17 |     throw Error('Invalid mathjs expression string')
18 |   }
19 | }
20 | 
21 | export const format = (value: InternalPlot) => {
22 |   return value.__parsed.toString()
23 | }
24 | 
25 | const defaultSettings = { boundsX: [-1, 1], boundsY: [-Infinity, Infinity], graph: true }
26 | 
27 | export const normalize = ({ expression, ..._settings }: PlotInput, _path: string, data: Data) => {
28 |   const get = (path: string) => {
29 |     // @ts-expect-error
30 |     if ('value' in data[path]) return data[path].value
31 |     return undefined // TODO should throw
32 |   }
33 |   const value = parseExpression(expression, get) as (v: number) => any
34 |   const settings = { ...defaultSettings, ..._settings }
35 |   return { value, settings: settings as InternalPlotSettings }
36 | }
37 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/src/plot-types.ts:
--------------------------------------------------------------------------------
 1 | import type { LevaInputProps } from 'leva/plugin'
 2 | 
 3 | export type Plot = { expression: string }
 4 | export type PlotSettings = { boundsX?: [number, number]; boundsY?: [number, number]; graph?: boolean }
 5 | export type PlotInput = Plot & PlotSettings
 6 | 
 7 | export type InternalPlot = {
 8 |   (v: number): any
 9 |   __parsedScoped: math.MathNode
10 |   __parsed: math.MathNode
11 |   __symbols: string[]
12 | }
13 | 
14 | export type InternalPlotSettings = Required<PlotSettings>
15 | 
16 | export type PlotProps = LevaInputProps<InternalPlot, InternalPlotSettings, string>
17 | 


--------------------------------------------------------------------------------
/packages/plugin-plot/src/plot-utils.ts:
--------------------------------------------------------------------------------
 1 | import * as math from 'mathjs'
 2 | 
 3 | export function getSymbols(expr: math.MathNode) {
 4 |   return expr
 5 |     .filter((node) => {
 6 |       if (node instanceof math.SymbolNode && node.isSymbolNode) {
 7 |         try {
 8 |           const e = node.evaluate()
 9 |           return !!e.units
10 |         } catch {
11 |           return node.name !== 'x'
12 |         }
13 |       }
14 |       return false
15 |     })
16 |     .map((node: unknown) => (node as math.SymbolNode).name)
17 | }
18 | 
19 | export function parseExpression(expression: string, get: (path: string) => any) {
20 |   const parsed = math.parse(expression)
21 |   const symbols = getSymbols(parsed)
22 |   const scope = symbols.reduce((acc, path) => {
23 |     const symbol = get(path)
24 |     if (!symbol) throw Error(`Invalid symbol at path \`${path}\``)
25 |     return Object.assign(acc, { [path]: symbol })
26 |   }, {} as { [key in keyof typeof symbols]: any })
27 | 
28 |   let _formattedString = parsed.toString()
29 | 
30 |   for (let key in scope) {
31 |     const re = new RegExp(`\\b${key}\\b`, 'g')
32 |     // TODO check type better than this
33 |     const s = typeof scope[key] === 'function' ? scope[key].__parsedScoped.toString() : scope[key]
34 |     _formattedString = _formattedString.replace(re, s)
35 |   }
36 | 
37 |   const parsedScoped = math.parse(_formattedString)
38 |   const compiled = parsedScoped.compile()
39 | 
40 |   function expr(v: number) {
41 |     return compiled.evaluate({ x: v })
42 |   }
43 | 
44 |   Object.assign(expr, {
45 |     __parsedScoped: parsedScoped,
46 |     __parsed: parsed,
47 |     __symbols: symbols,
48 |   })
49 | 
50 |   return expr
51 | }
52 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/README.md:
--------------------------------------------------------------------------------
 1 | ## Leva Spring
 2 | 
 3 | ### Installation
 4 | 
 5 | ```bash
 6 | npm i @leva-ui/plugin-spring
 7 | ```
 8 | 
 9 | ### Quick start
10 | 
11 | ```jsx
12 | import { useControls } from 'leva'
13 | import { spring } from '@leva-ui/plugin-spring'
14 | 
15 | function MyComponent() {
16 |   const { mySpring } = useControls({ mySpring: spring({ tension: 100, friction: 30, mass: 1 }) })
17 |   return mySpring.toString()
18 | }
19 | ```
20 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@leva-ui/plugin-spring",
 3 |   "version": "0.10.0",
 4 |   "main": "dist/leva-ui-plugin-spring.cjs.js",
 5 |   "module": "dist/leva-ui-plugin-spring.esm.js",
 6 |   "types": "dist/leva-ui-plugin-spring.cjs.d.ts",
 7 |   "license": "MIT",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "https://github.com/pmndrs/leva.git",
11 |     "directory": "packages/plugin-spring"
12 |   },
13 |   "bugs": "https://github.com/pmndrs/leva/issues",
14 |   "peerDependencies": {
15 |     "leva": ">=0.10.0",
16 |     "react": "^18.0.0 || ^19.0.0",
17 |     "react-dom": "^18.0.0 || ^19.0.0"
18 |   },
19 |   "dependencies": {
20 |     "@react-spring/web": "9.4.2"
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/src/Spring.stories.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { Story, Meta } from '@storybook/react'
 3 | 
 4 | import Reset from '../../leva/stories/components/decorator-reset'
 5 | import { useControls } from '../../leva/src'
 6 | 
 7 | import { spring } from './index'
 8 | 
 9 | export default {
10 |   title: 'Plugins/Spring',
11 |   decorators: [Reset],
12 | } as Meta
13 | 
14 | const Template: Story<any> = (args) => {
15 |   const values = useControls(
16 |     {
17 |       bar: spring({ tension: 100, friction: 30 }),
18 |     },
19 |     args
20 |   )
21 | 
22 |   return (
23 |     <div>
24 |       <pre>{JSON.stringify(values, null, '  ')}</pre>
25 |     </div>
26 |   )
27 | }
28 | 
29 | export const Spring = Template.bind({})
30 | Spring.args = {}
31 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/src/Spring.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import { useInputContext, Components } from 'leva/plugin'
 3 | import { SpringCanvas } from './SpringCanvas'
 4 | import type { SpringProps } from './spring-types'
 5 | 
 6 | const { Row, Label, Vector } = Components
 7 | 
 8 | export function Spring() {
 9 |   const { label, displayValue, onUpdate, settings } = useInputContext<SpringProps>()
10 | 
11 |   return (
12 |     <>
13 |       <Row>
14 |         <SpringCanvas />
15 |       </Row>
16 |       <Row input>
17 |         <Label>{label}</Label>
18 |         <Vector value={displayValue} settings={settings} onUpdate={onUpdate} />
19 |       </Row>
20 |     </>
21 |   )
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/src/StyledSpring.ts:
--------------------------------------------------------------------------------
 1 | import { styled } from 'leva/plugin'
 2 | 
 3 | export const Canvas = styled('canvas', {
 4 |   height: 80,
 5 |   width: '100%',
 6 |   cursor: 'crosshair',
 7 |   display: 'block',
 8 |   $draggable: '',
 9 | })
10 | 
11 | export const SpringPreview = styled('div', {
12 |   position: 'relative',
13 |   top: -2,
14 |   backgroundColor: '$accent2',
15 |   width: '100%',
16 |   height: 2,
17 |   opacity: 0.2,
18 |   borderRadius: 1,
19 |   transition: 'opacity 350ms ease',
20 |   transformOrigin: 'left',
21 | })
22 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/src/index.ts:
--------------------------------------------------------------------------------
 1 | import { createPlugin } from 'leva/plugin'
 2 | import { Spring } from './Spring'
 3 | import { normalize, sanitize } from './spring-plugin'
 4 | 
 5 | export const spring = createPlugin({
 6 |   normalize,
 7 |   sanitize,
 8 |   component: Spring,
 9 | })
10 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/src/math.ts:
--------------------------------------------------------------------------------
 1 | export function springFn(tension: number, friction: number, mass = 1) {
 2 |   const w0 = Math.sqrt(tension / mass) / 1000 // angular frequency in rad/ms
 3 |   const zeta = friction / (2 * Math.sqrt(tension * mass)) // damping ratio
 4 | 
 5 |   const w1 = w0 * Math.sqrt(1.0 - zeta * zeta) // exponential decay
 6 |   const w2 = w0 * Math.sqrt(zeta * zeta - 1.0) // frequency of damped oscillation
 7 | 
 8 |   const v_0 = 0
 9 | 
10 |   const to = 1
11 |   const from = 0
12 |   const x_0 = to - from
13 | 
14 |   if (zeta < 1) {
15 |     // Under damped
16 |     return (t: number) =>
17 |       to - Math.exp(-zeta * w0 * t) * (((-v_0 + zeta * w0 * x_0) / w1) * Math.sin(w1 * t) + x_0 * Math.cos(w1 * t))
18 |   } else if (zeta === 1) {
19 |     // Critically damped
20 |     return (t: number) => to - Math.exp(-w0 * t) * (x_0 + (-v_0 + w0 * x_0) * t)
21 |   } else {
22 |     // Overdamped
23 |     return (t: number) =>
24 |       to -
25 |       (Math.exp(-zeta * w0 * t) * ((-v_0 + zeta * w0 * x_0) * Math.sinh(w2 * t) + w2 * x_0 * Math.cosh(w2 * t))) / w2
26 |   }
27 | }
28 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/src/spring-plugin.ts:
--------------------------------------------------------------------------------
 1 | import { normalizeVector, sanitizeVector } from 'leva/plugin'
 2 | import type { InternalSpring, InternalSpringSettings, SpringInput } from './spring-types'
 3 | 
 4 | const defaultTensionSettings = { min: 1, step: 1 }
 5 | const defaultFrictionSettings = { min: 1, step: 0.5 }
 6 | const defaultMassSettings = { min: 0.1, step: 0.1 }
 7 | const defaultValue = { tension: 100, friction: 30, mass: 1 }
 8 | 
 9 | export const normalize = (input: SpringInput = {}) => {
10 |   const { value: _value, ..._settings } = 'value' in input ? input : { value: input }
11 |   const mergedSettings = {
12 |     tension: { ...defaultTensionSettings, ..._settings.tension },
13 |     friction: { ...defaultFrictionSettings, ..._settings.friction },
14 |     mass: { ...defaultMassSettings, ..._settings.mass },
15 |   }
16 | 
17 |   const { value, settings } = normalizeVector({ ...defaultValue, ..._value }, mergedSettings)
18 |   return { value, settings: settings as InternalSpringSettings }
19 | }
20 | 
21 | export const sanitize = (value: InternalSpring, settings: InternalSpringSettings, prevValue?: any) =>
22 |   sanitizeVector(value, settings, prevValue)
23 | 


--------------------------------------------------------------------------------
/packages/plugin-spring/src/spring-types.ts:
--------------------------------------------------------------------------------
 1 | import type { InputWithSettings, NumberSettings, LevaInputProps, InternalVectorSettings } from 'leva/plugin'
 2 | 
 3 | export type Spring = { tension?: number; friction?: number; mass?: number }
 4 | export type InternalSpring = { tension: number; friction: number; mass: number }
 5 | export type SpringSettings = { [key in keyof Spring]?: NumberSettings }
 6 | 
 7 | export type SpringInput = Spring | InputWithSettings<Spring, SpringSettings>
 8 | 
 9 | export type InternalSpringSettings = InternalVectorSettings<keyof InternalSpring, (keyof InternalSpring)[], 'object'>
10 | 
11 | export type SpringProps = LevaInputProps<InternalSpring, InternalSpringSettings, InternalSpring>
12 | 


--------------------------------------------------------------------------------