Initialize OpenPencil project with essential files and configurations
- Added .cta.json for project settings including TypeScript, Tailwind, and Bun as package manager. - Created .gitignore to exclude common build and environment files. - Introduced bun.lock for dependency management. - Added CLAUDE.md for guidance on using Claude Code with the project. - Set up package.json with scripts for development, build, and testing. - Created README.md outlining project features, tech stack, and getting started instructions. - Configured TypeScript with tsconfig.json for strict type checking. - Established Vite configuration in vite.config.ts for project bundling. - Added VSCode settings to exclude generated files from linting. - Included public assets such as favicon and logos. - Implemented initial routing structure in src/router.tsx and generated route tree in src/routeTree.gen.ts. - Developed canvas functionality with multiple files for object management and event handling in src/canvas. - Styled the application using Tailwind CSS in src/styles.css.
This commit is contained in:
commit
c009ab7925
15
.cta.json
Normal file
15
.cta.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"projectName": "openpencil",
|
||||
"mode": "file-router",
|
||||
"typescript": true,
|
||||
"tailwind": true,
|
||||
"packageManager": "bun",
|
||||
"git": true,
|
||||
"install": true,
|
||||
"addOnOptions": {},
|
||||
"version": 1,
|
||||
"framework": "react-cra",
|
||||
"chosenAddOns": [
|
||||
"nitro"
|
||||
]
|
||||
}
|
||||
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
count.txt
|
||||
.env
|
||||
.nitro
|
||||
.tanstack
|
||||
.wrangler
|
||||
.output
|
||||
.vinxi
|
||||
__unconfig*
|
||||
todos.json
|
||||
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"files.watcherExclude": {
|
||||
"**/routeTree.gen.ts": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/routeTree.gen.ts": true
|
||||
},
|
||||
"files.readonlyInclude": {
|
||||
"**/routeTree.gen.ts": true
|
||||
}
|
||||
}
|
||||
78
CLAUDE.md
Normal file
78
CLAUDE.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Commands
|
||||
|
||||
- **Dev server:** `bun --bun run dev` (runs on port 3000)
|
||||
- **Build:** `bun --bun run build`
|
||||
- **Preview production build:** `bun --bun run preview`
|
||||
- **Run all tests:** `bun --bun run test` (Vitest)
|
||||
- **Run a single test:** `bun --bun vitest run path/to/test.ts`
|
||||
- **Type check:** `npx tsc --noEmit`
|
||||
- **Install dependencies:** `bun install`
|
||||
|
||||
## Architecture
|
||||
|
||||
OpenPencil is an open-source vector design tool (alternative to Pencil.dev) with a Design-as-Code philosophy. Built as a **TanStack Start** full-stack React application with Bun runtime.
|
||||
|
||||
**Key technologies:** React 19, Fabric.js v7 (canvas engine), Zustand v5 (state management), TanStack Router (file-based routing), Tailwind CSS v4, Vite 7, TypeScript (strict mode).
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
React Components (Toolbar, LayerPanel, PropertyPanel)
|
||||
│ Zustand hooks
|
||||
▼
|
||||
┌─────────────────┐ ┌───────────────────┐
|
||||
│ canvas-store │ │ document-store │ ← single source of truth
|
||||
│ (UI state: │ │ (PenDocument) │
|
||||
│ tool/selection │ │ CRUD / tree ops │
|
||||
│ /viewport) │ │ │
|
||||
└────────┬────────┘ └────────┬──────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
Fabric.js Canvas canvas-sync-lock
|
||||
(imperative render) (prevents circular sync)
|
||||
```
|
||||
|
||||
- **document-store** is the single source of truth. Fabric.js only renders.
|
||||
- User edits on canvas → Fabric events → update document-store (with sync lock)
|
||||
- User edits in panels → update document-store → `use-canvas-sync` updates Fabric
|
||||
- `canvas-sync-lock.ts` prevents circular updates when Fabric events write to the store
|
||||
|
||||
### Key Modules
|
||||
|
||||
- **`src/canvas/`** — Fabric.js integration: canvas init, drawing events, viewport (pan/zoom), selection sync, bidirectional document↔canvas sync, object factory
|
||||
- **`src/stores/`** — Zustand stores: `canvas-store` (UI state), `document-store` (PenDocument tree CRUD)
|
||||
- **`src/types/`** — Type system: `pen.ts` (PenDocument/PenNode), `canvas.ts` (ToolType), `styles.ts` (Fill/Stroke/Effect)
|
||||
- **`src/components/editor/`** — Editor layout, toolbar, tool buttons
|
||||
- **`src/components/panels/`** — Layer panel, property panel with section components
|
||||
- **`src/components/shared/`** — Reusable UI: ColorPicker, NumberInput, SliderInput
|
||||
|
||||
### Fabric.js v7 Gotchas
|
||||
|
||||
- **Default origin is `center`/`center`** — always set `originX: 'left'`, `originY: 'top'` on objects so `left`/`top` means top-left corner
|
||||
- **Pointer capture** — Fabric captures pointers on `upperCanvasEl`; attach pointer listeners there, not on `document`
|
||||
- **Coordinate conversion** — use `canvas.getScenePoint(e)` with `canvas.calcOffset()` for accurate pointer-to-scene mapping
|
||||
- **Default strokeWidth is 1** — explicitly set `strokeWidth: 0` when no stroke is desired
|
||||
|
||||
### Routing
|
||||
|
||||
File-based routing via TanStack Router. Routes in `src/routes/`, auto-generated tree in `src/routeTree.gen.ts` (do not edit).
|
||||
|
||||
- `/` — Landing page
|
||||
- `/editor` — Main design editor
|
||||
|
||||
### Path Aliases
|
||||
|
||||
`@/*` maps to `./src/*` (configured in `tsconfig.json` and `vite.config.ts`).
|
||||
|
||||
### Styling
|
||||
|
||||
Tailwind CSS v4 imported via `src/styles.css`. Icons from `lucide-react`.
|
||||
|
||||
## Code Style
|
||||
|
||||
- 单个文件不要超过 800 行。超出时应拆分为更小的模块。
|
||||
- 每个文件只导出一个组件,每个组件只承担单一职责。
|
||||
66
README.md
Normal file
66
README.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# OpenPencil
|
||||
|
||||
Open-source vector design tool with a Design-as-Code philosophy. An alternative to [Pencil.dev](https://pencil.dev).
|
||||
|
||||
## Features
|
||||
|
||||
- Infinite canvas with pan (Space+drag / middle mouse / Hand tool) and zoom (scroll wheel)
|
||||
- Drawing tools: Rectangle, Ellipse, Line, Frame, Text
|
||||
- Real-time property editing: position, size, rotation, fill, stroke, corner radius, opacity
|
||||
- Layer panel with tree view, selection sync, rename
|
||||
- Keyboard shortcuts (V/R/O/L/T/F/H, Delete, arrow keys, bracket keys for z-order)
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework:** [TanStack Start](https://tanstack.com/start) (React 19, SSR, file-based routing)
|
||||
- **Canvas:** [Fabric.js](http://fabricjs.com/) v7
|
||||
- **State:** [Zustand](https://zustand-demo.pmnd.rs/) v5
|
||||
- **Styling:** [Tailwind CSS](https://tailwindcss.com/) v4
|
||||
- **Icons:** [Lucide React](https://lucide.dev/)
|
||||
- **Runtime:** [Bun](https://bun.sh/)
|
||||
- **Build:** [Vite](https://vite.dev/) 7
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun --bun run dev
|
||||
```
|
||||
|
||||
Open http://localhost:3000 and click "New Design" to enter the editor.
|
||||
|
||||
## Scripts
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `bun --bun run dev` | Start dev server on port 3000 |
|
||||
| `bun --bun run build` | Production build |
|
||||
| `bun --bun run preview` | Preview production build |
|
||||
| `bun --bun run test` | Run tests (Vitest) |
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
canvas/ # Fabric.js canvas engine, drawing, sync
|
||||
components/
|
||||
editor/ # Editor layout, toolbar
|
||||
panels/ # Layer panel, property panel sections
|
||||
shared/ # Reusable UI (ColorPicker, NumberInput, etc.)
|
||||
hooks/ # Keyboard shortcuts
|
||||
stores/ # Zustand stores (canvas-store, document-store)
|
||||
types/ # PenDocument/PenNode types, style types
|
||||
routes/ # TanStack Router pages (/, /editor)
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] .pen file save/load (JSON-based, Git-friendly)
|
||||
- [ ] Undo/redo
|
||||
- [ ] Component system (reusable components with instances & overrides)
|
||||
- [ ] Design variables/tokens with CSS sync
|
||||
- [ ] Code generation (React/HTML/Tailwind)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
970
bun.lock
Normal file
970
bun.lock
Normal file
|
|
@ -0,0 +1,970 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "openpencil",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-devtools": "^0.7.0",
|
||||
"@tanstack/react-router": "^1.132.0",
|
||||
"@tanstack/react-router-devtools": "^1.132.0",
|
||||
"@tanstack/react-router-ssr-query": "^1.131.7",
|
||||
"@tanstack/react-start": "^1.132.0",
|
||||
"@tanstack/router-plugin": "^1.132.0",
|
||||
"fabric": "^7.1.0",
|
||||
"lucide-react": "^0.545.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nitro": "npm:nitro-nightly@latest",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"zustand": "^5.0.11",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/devtools-vite": "^0.3.11",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"jsdom": "^27.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.1.7",
|
||||
"vitest": "^3.0.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@acemir/cssom": ["@acemir/cssom@0.9.31", "https://registry.npmmirror.com/@acemir/cssom/-/cssom-0.9.31.tgz", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="],
|
||||
|
||||
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@4.1.2", "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", { "dependencies": { "@csstools/css-calc": "^3.0.0", "@csstools/css-color-parser": "^4.0.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "lru-cache": "^11.2.5" } }, "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg=="],
|
||||
|
||||
"@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@6.8.1", "https://registry.npmmirror.com/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.6" } }, "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ=="],
|
||||
|
||||
"@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "https://registry.npmmirror.com/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.0", "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.6", "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.6.tgz", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.6", "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@csstools/color-helpers": ["@csstools/color-helpers@6.0.1", "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-6.0.1.tgz", {}, "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ=="],
|
||||
|
||||
"@csstools/css-calc": ["@csstools/css-calc@3.1.1", "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-3.1.1.tgz", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ=="],
|
||||
|
||||
"@csstools/css-color-parser": ["@csstools/css-color-parser@4.0.1", "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz", { "dependencies": { "@csstools/color-helpers": "^6.0.1", "@csstools/css-calc": "^3.0.0" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw=="],
|
||||
|
||||
"@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="],
|
||||
|
||||
"@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.0.27", "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz", {}, "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow=="],
|
||||
|
||||
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.8.1", "https://registry.npmmirror.com/@emnapi/core/-/core-1.8.1.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
||||
|
||||
"@exodus/bytes": ["@exodus/bytes@1.14.1", "https://registry.npmmirror.com/@exodus/bytes/-/bytes-1.14.1.tgz", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@oozcitak/dom": ["@oozcitak/dom@2.0.2", "https://registry.npmmirror.com/@oozcitak/dom/-/dom-2.0.2.tgz", { "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/url": "^3.0.0", "@oozcitak/util": "^10.0.0" } }, "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w=="],
|
||||
|
||||
"@oozcitak/infra": ["@oozcitak/infra@2.0.2", "https://registry.npmmirror.com/@oozcitak/infra/-/infra-2.0.2.tgz", { "dependencies": { "@oozcitak/util": "^10.0.0" } }, "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA=="],
|
||||
|
||||
"@oozcitak/url": ["@oozcitak/url@3.0.0", "https://registry.npmmirror.com/@oozcitak/url/-/url-3.0.0.tgz", { "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0" } }, "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ=="],
|
||||
|
||||
"@oozcitak/util": ["@oozcitak/util@10.0.0", "https://registry.npmmirror.com/@oozcitak/util/-/util-10.0.0.tgz", {}, "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.113.0", "https://registry.npmmirror.com/@oxc-project/types/-/types-0.113.0.tgz", {}, "sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.4.tgz", { "os": "android", "cpu": "arm64" }, "sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.4.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.4.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.4.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.4.tgz", { "os": "linux", "cpu": "arm" }, "sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.4.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.4.tgz", { "os": "linux", "cpu": "x64" }, "sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.4.tgz", { "os": "none", "cpu": "arm64" }, "sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.4.tgz", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.4.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.4.tgz", { "os": "win32", "cpu": "x64" }, "sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
|
||||
|
||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
|
||||
|
||||
"@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "https://registry.npmmirror.com/@solid-primitives/event-listener/-/event-listener-2.4.3.tgz", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="],
|
||||
|
||||
"@solid-primitives/keyboard": ["@solid-primitives/keyboard@1.3.3", "https://registry.npmmirror.com/@solid-primitives/keyboard/-/keyboard-1.3.3.tgz", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA=="],
|
||||
|
||||
"@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "https://registry.npmmirror.com/@solid-primitives/resize-observer/-/resize-observer-2.1.3.tgz", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
||||
|
||||
"@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.2", "https://registry.npmmirror.com/@solid-primitives/rootless/-/rootless-1.5.2.tgz", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ=="],
|
||||
|
||||
"@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.2", "https://registry.npmmirror.com/@solid-primitives/static-store/-/static-store-0.1.2.tgz", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw=="],
|
||||
|
||||
"@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "https://registry.npmmirror.com/@solid-primitives/utils/-/utils-6.3.2.tgz", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "https://registry.npmmirror.com/@tailwindcss/node/-/node-4.1.18.tgz", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide/-/oxide-4.1.18.tgz", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "https://registry.npmmirror.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "https://registry.npmmirror.com/@tailwindcss/vite/-/vite-4.1.18.tgz", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
||||
|
||||
"@tanstack/devtools": ["@tanstack/devtools@0.7.0", "https://registry.npmmirror.com/@tanstack/devtools/-/devtools-0.7.0.tgz", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/keyboard": "^1.3.3", "@solid-primitives/resize-observer": "^2.1.3", "@tanstack/devtools-client": "0.0.3", "@tanstack/devtools-event-bus": "0.3.3", "@tanstack/devtools-ui": "0.4.4", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" } }, "sha512-AlAoCqJhWLg9GBEaoV1g/j+X/WA1aJSWOsekxeuZpYeS2hdVuKAjj04KQLUMJhtLfNl2s2E+TCj7ZRtWyY3U4w=="],
|
||||
|
||||
"@tanstack/devtools-client": ["@tanstack/devtools-client@0.0.5", "https://registry.npmmirror.com/@tanstack/devtools-client/-/devtools-client-0.0.5.tgz", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.0" } }, "sha512-hsNDE3iu4frt9cC2ppn1mNRnLKo2uc1/1hXAyY9z4UYb+o40M2clFAhiFoo4HngjfGJDV3x18KVVIq7W4Un+zA=="],
|
||||
|
||||
"@tanstack/devtools-event-bus": ["@tanstack/devtools-event-bus@0.3.3", "https://registry.npmmirror.com/@tanstack/devtools-event-bus/-/devtools-event-bus-0.3.3.tgz", { "dependencies": { "ws": "^8.18.3" } }, "sha512-lWl88uLAz7ZhwNdLH6A3tBOSEuBCrvnY9Fzr5JPdzJRFdM5ZFdyNWz1Bf5l/F3GU57VodrN0KCFi9OA26H5Kpg=="],
|
||||
|
||||
"@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.0", "https://registry.npmmirror.com/@tanstack/devtools-event-client/-/devtools-event-client-0.4.0.tgz", {}, "sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw=="],
|
||||
|
||||
"@tanstack/devtools-ui": ["@tanstack/devtools-ui@0.4.4", "https://registry.npmmirror.com/@tanstack/devtools-ui/-/devtools-ui-0.4.4.tgz", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" } }, "sha512-5xHXFyX3nom0UaNfiOM92o6ziaHjGo3mcSGe2HD5Xs8dWRZNpdZ0Smd0B9ddEhy0oB+gXyMzZgUJb9DmrZV0Mg=="],
|
||||
|
||||
"@tanstack/devtools-vite": ["@tanstack/devtools-vite@0.3.12", "https://registry.npmmirror.com/@tanstack/devtools-vite/-/devtools-vite-0.3.12.tgz", { "dependencies": { "@babel/core": "^7.28.4", "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.4", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@tanstack/devtools-client": "0.0.5", "@tanstack/devtools-event-bus": "0.3.3", "chalk": "^5.6.2", "launch-editor": "^2.11.1", "picomatch": "^4.0.3" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0" } }, "sha512-fGJgu4xUhKmGk+a+/aHD8l5HKVk6+ObA+6D3YC3xCXbai/YmaGhztqcZf1tKUqjZyYyQLHsjqmKzvJgVpQP1jw=="],
|
||||
|
||||
"@tanstack/history": ["@tanstack/history@1.154.14", "https://registry.npmmirror.com/@tanstack/history/-/history-1.154.14.tgz", {}, "sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.90.20", "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.90.20.tgz", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="],
|
||||
|
||||
"@tanstack/react-devtools": ["@tanstack/react-devtools@0.7.11", "https://registry.npmmirror.com/@tanstack/react-devtools/-/react-devtools-0.7.11.tgz", { "dependencies": { "@tanstack/devtools": "0.7.0" }, "peerDependencies": { "@types/react": ">=16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-a2Lmz8x+JoDrsU6f7uKRcyyY+k8mA/n5mb9h7XJ3Fz/y3+sPV9t7vAW1s5lyNkQyyDt6V1Oim99faLthoJSxMw=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.90.21", "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.90.21.tgz", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg=="],
|
||||
|
||||
"@tanstack/react-router": ["@tanstack/react-router@1.160.2", "https://registry.npmmirror.com/@tanstack/react-router/-/react-router-1.160.2.tgz", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.160.0", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-EJWAMS4qCfWKNCzzYGy6ZuWTdBATYEEWieaQdmM7zUesyOQ01j7o6aKXdmCp9rWuSKjPHXagWubEnEo+Puhi3w=="],
|
||||
|
||||
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.160.2", "https://registry.npmmirror.com/@tanstack/react-router-devtools/-/react-router-devtools-1.160.2.tgz", { "dependencies": { "@tanstack/router-devtools-core": "1.160.0" }, "peerDependencies": { "@tanstack/react-router": "^1.160.2", "@tanstack/router-core": "^1.160.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-uSdQD77S+LcANCiWcLsrqSxyEqTXdZhBETbciKcYJrcgd8rfkxg6AIewYi7148QPU6gb3VKQbeRlqXmBeEs5dg=="],
|
||||
|
||||
"@tanstack/react-router-ssr-query": ["@tanstack/react-router-ssr-query@1.160.2", "https://registry.npmmirror.com/@tanstack/react-router-ssr-query/-/react-router-ssr-query-1.160.2.tgz", { "dependencies": { "@tanstack/router-ssr-query-core": "1.160.0" }, "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/react-query": ">=5.90.0", "@tanstack/react-router": ">=1.127.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-/9eDT69AtsFdSY2lSz2BjW2c8SwJMXB/VGW/HxVKGavTPYD6J4xN6Ew7iSwJqfbjB7yRer5Dp0lvKFSQbcJGtw=="],
|
||||
|
||||
"@tanstack/react-start": ["@tanstack/react-start@1.160.2", "https://registry.npmmirror.com/@tanstack/react-start/-/react-start-1.160.2.tgz", { "dependencies": { "@tanstack/react-router": "1.160.2", "@tanstack/react-start-client": "1.160.2", "@tanstack/react-start-server": "1.160.2", "@tanstack/router-utils": "^1.158.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-plugin-core": "1.160.2", "@tanstack/start-server-core": "1.160.0", "pathe": "^2.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" } }, "sha512-+v+m33wTOlcjp0L66YcjS6YCi9tz9QZBC1ZC4L6o3eV/6S2gZqNZ32Tz6LLfsDuipr7iyyeAiXcFTwtZrfhSdQ=="],
|
||||
|
||||
"@tanstack/react-start-client": ["@tanstack/react-start-client@1.160.2", "https://registry.npmmirror.com/@tanstack/react-start-client/-/react-start-client-1.160.2.tgz", { "dependencies": { "@tanstack/react-router": "1.160.2", "@tanstack/router-core": "1.160.0", "@tanstack/start-client-core": "1.160.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-LZRM5hKb5nj0vKhgdydm7S0kXZG7yrv11Pyt0itzUOomXg2JuDM9ckvAw8PDl/O+sp3RJWBqfGj90Cpl5PQOuQ=="],
|
||||
|
||||
"@tanstack/react-start-server": ["@tanstack/react-start-server@1.160.2", "https://registry.npmmirror.com/@tanstack/react-start-server/-/react-start-server-1.160.2.tgz", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-router": "1.160.2", "@tanstack/router-core": "1.160.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-server-core": "1.160.0" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-y1PsLVfgD8UWNA3mFIg4mQNJkbjz6nZJTMBne5L/dndhk/SGQGBM/AA2El5n/4tKl82TgEum59bVjvKjudX6RQ=="],
|
||||
|
||||
"@tanstack/react-store": ["@tanstack/react-store@0.8.1", "https://registry.npmmirror.com/@tanstack/react-store/-/react-store-0.8.1.tgz", { "dependencies": { "@tanstack/store": "0.8.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XItJt+rG8c5Wn/2L/bnxys85rBpm0BfMbhb4zmPVLXAKY9POrp1xd6IbU4PKoOI+jSEGc3vntPRfLGSgXfE2Ig=="],
|
||||
|
||||
"@tanstack/router-core": ["@tanstack/router-core@1.160.0", "https://registry.npmmirror.com/@tanstack/router-core/-/router-core-1.160.0.tgz", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-vbh6OsE0MG+0c+SKh2uk5yEEZlWsxT96Ub2JaTs7ixOvZp3Wu9PTEIe2BA3cShNZhEsDI0Le4NqgY4XIaHLLvA=="],
|
||||
|
||||
"@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.160.0", "https://registry.npmmirror.com/@tanstack/router-devtools-core/-/router-devtools-core-1.160.0.tgz", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.160.0", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-P/l0GVd0qmDbskg8/UbkOrCxuFz0t69BCxv2j4+8Xfy8AcqnFtoR1LChKgYyxGPy9sWOxktAneFdy1xA3X/Q6A=="],
|
||||
|
||||
"@tanstack/router-generator": ["@tanstack/router-generator@1.160.1", "https://registry.npmmirror.com/@tanstack/router-generator/-/router-generator-1.160.1.tgz", { "dependencies": { "@tanstack/router-core": "1.160.0", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-De6TicInwy3/9rQ++RZAyFOvB2oi5UV5T0iiIlxe3jgiOLFxMA4EKKVlT+alDxKnq6udTLam9xqhvGOVZ6a2hw=="],
|
||||
|
||||
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.160.2", "https://registry.npmmirror.com/@tanstack/router-plugin/-/router-plugin-1.160.2.tgz", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.160.0", "@tanstack/router-generator": "1.160.1", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.160.2", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-+uyjLK4hImFyENb08PwDfvnKpwfdjCA29sKrHRk/ff9H23RUm10MW0UMjIrVU+4Dql/xsbzsqrTwIyRS6zna9Q=="],
|
||||
|
||||
"@tanstack/router-ssr-query-core": ["@tanstack/router-ssr-query-core@1.160.0", "https://registry.npmmirror.com/@tanstack/router-ssr-query-core/-/router-ssr-query-core-1.160.0.tgz", { "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/router-core": ">=1.127.0" } }, "sha512-RS2L7NchJ2/YadTWJjOA1czGiTlshMLd5CBPUZURKAlkVymGsupOHG9ds63egpyHgKI/zMQOUHgtOY74ishoLA=="],
|
||||
|
||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.158.0", "https://registry.npmmirror.com/@tanstack/router-utils/-/router-utils-1.158.0.tgz", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-qZ76eaLKU6Ae9iI/mc5zizBX149DXXZkBVVO3/QRIll79uKLJZHQlMKR++2ba7JsciBWz1pgpIBcCJPE9S0LVg=="],
|
||||
|
||||
"@tanstack/start-client-core": ["@tanstack/start-client-core@1.160.0", "https://registry.npmmirror.com/@tanstack/start-client-core/-/start-client-core-1.160.0.tgz", { "dependencies": { "@tanstack/router-core": "1.160.0", "@tanstack/start-fn-stubs": "1.154.7", "@tanstack/start-storage-context": "1.160.0", "seroval": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-qVzbDT6tTED3+3rPJsyAOskszEjCW0/DkzEMoG4an8Gh3cNnEXqrCDyKWjvqYpj9zrquNTzgBh3GFZzzfgr4ZQ=="],
|
||||
|
||||
"@tanstack/start-fn-stubs": ["@tanstack/start-fn-stubs@1.154.7", "https://registry.npmmirror.com/@tanstack/start-fn-stubs/-/start-fn-stubs-1.154.7.tgz", {}, "sha512-D69B78L6pcFN5X5PHaydv7CScQcKLzJeEYqs7jpuyyqGQHSUIZUjS955j+Sir8cHhuDIovCe2LmsYHeZfWf3dQ=="],
|
||||
|
||||
"@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.160.2", "https://registry.npmmirror.com/@tanstack/start-plugin-core/-/start-plugin-core-1.160.2.tgz", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@rolldown/pluginutils": "1.0.0-beta.40", "@tanstack/router-core": "1.160.0", "@tanstack/router-generator": "1.160.1", "@tanstack/router-plugin": "1.160.2", "@tanstack/router-utils": "1.158.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-server-core": "1.160.0", "cheerio": "^1.0.0", "exsolve": "^1.0.7", "pathe": "^2.0.3", "srvx": "^0.11.2", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^3.24.2" }, "peerDependencies": { "vite": ">=7.0.0" } }, "sha512-scFSvcoHHLhiz0RhH4tetBXkjZLgUR+xfAA5aeqWcC0dcMS4Cj5Irb+AxAkvbdGQPc0Afyrs77OmOUi3v4YjcA=="],
|
||||
|
||||
"@tanstack/start-server-core": ["@tanstack/start-server-core@1.160.0", "https://registry.npmmirror.com/@tanstack/start-server-core/-/start-server-core-1.160.0.tgz", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/router-core": "1.160.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-storage-context": "1.160.0", "h3-v2": "npm:h3@2.0.1-rc.14", "seroval": "^1.4.2", "tiny-invariant": "^1.3.3" } }, "sha512-zr30GxMV5VBAFSPQ9le/VW9Ql8p6ctrv4a94avgR3hEzjnXOxOBpOO29EdzVtrTcbTPO6rxC9B0f3yOwbEcucg=="],
|
||||
|
||||
"@tanstack/start-storage-context": ["@tanstack/start-storage-context@1.160.0", "https://registry.npmmirror.com/@tanstack/start-storage-context/-/start-storage-context-1.160.0.tgz", { "dependencies": { "@tanstack/router-core": "1.160.0" } }, "sha512-Y4mvNu0/R1poZsBoz9JCXeanwj9BVaiv4XBt33bQk12kLhdNwyTXzZ3cuuNyfiHL+tNK0/b6oQyQZFGMLfB1Hw=="],
|
||||
|
||||
"@tanstack/store": ["@tanstack/store@0.8.1", "https://registry.npmmirror.com/@tanstack/store/-/store-0.8.1.tgz", {}, "sha512-PtOisLjUZPz5VyPRSCGjNOlwTvabdTBQ2K80DpVL1chGVr35WRxfeavAPdNq6pm/t7F8GhoR2qtmkkqtCEtHYw=="],
|
||||
|
||||
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.154.7", "https://registry.npmmirror.com/@tanstack/virtual-file-routes/-/virtual-file-routes-1.154.7.tgz", {}, "sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg=="],
|
||||
|
||||
"@testing-library/dom": ["@testing-library/dom@10.4.1", "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.1.tgz", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
|
||||
|
||||
"@testing-library/react": ["@testing-library/react@16.3.2", "https://registry.npmmirror.com/@testing-library/react/-/react-16.3.2.tgz", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/aria-query": ["@types/aria-query@5.0.4", "https://registry.npmmirror.com/@types/aria-query/-/aria-query-5.0.4.tgz", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||
|
||||
"@types/babel__template": ["@types/babel__template@7.4.4", "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/node": ["@types/node@22.19.11", "https://registry.npmmirror.com/@types/node/-/node-22.19.11.tgz", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.14", "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@3.2.4", "https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@3.2.4", "https://registry.npmmirror.com/@vitest/mocker/-/mocker-3.2.4.tgz", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@3.2.4", "https://registry.npmmirror.com/@vitest/runner/-/runner-3.2.4.tgz", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@3.2.4", "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@3.2.4", "https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@3.2.4", "https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@5.2.0", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"ansis": ["ansis@4.2.0", "https://registry.npmmirror.com/ansis/-/ansis-4.2.0.tgz", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.0", "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.0.tgz", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"ast-types": ["ast-types@0.16.1", "https://registry.npmmirror.com/ast-types/-/ast-types-0.16.1.tgz", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||
|
||||
"babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "https://registry.npmmirror.com/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||
|
||||
"bidi-js": ["bidi-js@1.0.3", "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"bl": ["bl@4.1.0", "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.1", "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||
|
||||
"buffer": ["buffer@5.7.1", "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001770", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
|
||||
|
||||
"canvas": ["canvas@3.2.1", "https://registry.npmmirror.com/canvas/-/canvas-3.2.1.tgz", { "dependencies": { "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.3" } }, "sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg=="],
|
||||
|
||||
"chai": ["chai@5.3.3", "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"check-error": ["check-error@2.1.3", "https://registry.npmmirror.com/check-error/-/check-error-2.1.3.tgz", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
|
||||
|
||||
"cheerio": ["cheerio@1.2.0", "https://registry.npmmirror.com/cheerio/-/cheerio-1.2.0.tgz", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg=="],
|
||||
|
||||
"cheerio-select": ["cheerio-select@2.1.0", "https://registry.npmmirror.com/cheerio-select/-/cheerio-select-2.1.0.tgz", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
|
||||
|
||||
"chokidar": ["chokidar@3.6.0", "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"chownr": ["chownr@1.1.4", "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie-es": ["cookie-es@2.0.0", "https://registry.npmmirror.com/cookie-es/-/cookie-es-2.0.0.tgz", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
|
||||
|
||||
"crossws": ["crossws@0.4.4", "https://registry.npmmirror.com/crossws/-/crossws-0.4.4.tgz", { "peerDependencies": { "srvx": ">=0.7.1" }, "optionalPeers": ["srvx"] }, "sha512-w6c4OdpRNnudVmcgr7brb/+/HmYjMQvYToO/oTrprTwxRUiom3LYWU1PMWuD006okbUWpII1Ea9/+kwpUfmyRg=="],
|
||||
|
||||
"css-select": ["css-select@5.2.2", "https://registry.npmmirror.com/css-select/-/css-select-5.2.2.tgz", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||
|
||||
"css-tree": ["css-tree@3.1.0", "https://registry.npmmirror.com/css-tree/-/css-tree-3.1.0.tgz", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
|
||||
|
||||
"css-what": ["css-what@6.2.2", "https://registry.npmmirror.com/css-what/-/css-what-6.2.2.tgz", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
||||
|
||||
"cssstyle": ["cssstyle@5.3.7", "https://registry.npmmirror.com/cssstyle/-/cssstyle-5.3.7.tgz", { "dependencies": { "@asamuzakjp/css-color": "^4.1.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.21", "css-tree": "^3.1.0", "lru-cache": "^11.2.4" } }, "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"data-urls": ["data-urls@6.0.1", "https://registry.npmmirror.com/data-urls/-/data-urls-6.0.1.tgz", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^15.1.0" } }, "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ=="],
|
||||
|
||||
"db0": ["db0@0.3.4", "https://registry.npmmirror.com/db0/-/db0-0.3.4.tgz", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decimal.js": ["decimal.js@10.6.0", "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
|
||||
|
||||
"decompress-response": ["decompress-response@6.0.0", "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
||||
|
||||
"deep-eql": ["deep-eql@5.0.2", "https://registry.npmmirror.com/deep-eql/-/deep-eql-5.0.2.tgz", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||
|
||||
"deep-extend": ["deep-extend@0.6.0", "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"diff": ["diff@8.0.3", "https://registry.npmmirror.com/diff/-/diff-8.0.3.tgz", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||
|
||||
"dom-accessibility-api": ["dom-accessibility-api@0.5.16", "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.286", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
|
||||
|
||||
"encoding-sniffer": ["encoding-sniffer@0.2.1", "https://registry.npmmirror.com/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
|
||||
|
||||
"end-of-stream": ["end-of-stream@1.4.5", "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.19.0", "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
||||
|
||||
"entities": ["entities@6.0.1", "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.3", "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"expand-template": ["expand-template@2.0.3", "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.8", "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
|
||||
"fabric": ["fabric@7.1.0", "https://registry.npmmirror.com/fabric/-/fabric-7.1.0.tgz", { "optionalDependencies": { "canvas": "^3.2.0", "jsdom": "^26.1.0" } }, "sha512-061QsoSw6xn7UoRXYq816qMyvObP4gRNVph0jAFWtG5E2kBlfdjrYBiLPRuaAHSmVQUz9RjbPpePB/hljiYJIw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"fs-constants": ["fs-constants@1.0.0", "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.6", "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.13.6.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
||||
|
||||
"github-from-package": ["github-from-package@0.0.0", "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"globrex": ["globrex@0.1.2", "https://registry.npmmirror.com/globrex/-/globrex-0.1.2.tgz", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||
|
||||
"goober": ["goober@2.1.18", "https://registry.npmmirror.com/goober/-/goober-2.1.18.tgz", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"h3": ["h3@2.0.1-rc.14", "https://registry.npmmirror.com/h3/-/h3-2.0.1-rc.14.tgz", { "dependencies": { "rou3": "^0.7.12", "srvx": "^0.11.2" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-163qbGmTr/9rqQRNuqMqtgXnOUAkE4KTdauiC9y0E5iG1I65kte9NyfWvZw5RTDMt6eY+DtyoNzrQ9wA2BfvGQ=="],
|
||||
|
||||
"h3-v2": ["h3@2.0.1-rc.14", "https://registry.npmmirror.com/h3/-/h3-2.0.1-rc.14.tgz", { "dependencies": { "rou3": "^0.7.12", "srvx": "^0.11.2" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-163qbGmTr/9rqQRNuqMqtgXnOUAkE4KTdauiC9y0E5iG1I65kte9NyfWvZw5RTDMt6eY+DtyoNzrQ9wA2BfvGQ=="],
|
||||
|
||||
"hookable": ["hookable@6.0.1", "https://registry.npmmirror.com/hookable/-/hookable-6.0.1.tgz", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
||||
|
||||
"html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
|
||||
|
||||
"htmlparser2": ["htmlparser2@10.1.0", "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-10.1.0.tgz", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],
|
||||
|
||||
"http-proxy-agent": ["http-proxy-agent@7.0.2", "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ini": ["ini@1.3.8", "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||
|
||||
"is-binary-path": ["is-binary-path@2.1.0", "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
|
||||
|
||||
"isbot": ["isbot@5.1.35", "https://registry.npmmirror.com/isbot/-/isbot-5.1.35.tgz", {}, "sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"jsdom": ["jsdom@27.4.0", "https://registry.npmmirror.com/jsdom/-/jsdom-27.4.0.tgz", { "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", "@exodus/bytes": "^1.6.0", "cssstyle": "^5.3.4", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.0", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.1.0", "ws": "^8.18.3", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"launch-editor": ["launch-editor@2.12.0", "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.12.0.tgz", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.2", "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.2.tgz", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||
|
||||
"loupe": ["loupe@3.2.1", "https://registry.npmmirror.com/loupe/-/loupe-3.2.1.tgz", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
|
||||
|
||||
"lru-cache": ["lru-cache@11.2.6", "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.6.tgz", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.545.0", "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.545.0.tgz", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw=="],
|
||||
|
||||
"lz-string": ["lz-string@1.5.0", "https://registry.npmmirror.com/lz-string/-/lz-string-1.5.0.tgz", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"mdn-data": ["mdn-data@2.12.2", "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.12.2.tgz", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
|
||||
|
||||
"mimic-response": ["mimic-response@3.1.0", "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"mkdirp-classic": ["mkdirp-classic@0.5.3", "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@5.1.6", "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.6.tgz", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
|
||||
|
||||
"napi-build-utils": ["napi-build-utils@2.0.0", "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
|
||||
|
||||
"nf3": ["nf3@0.3.10", "https://registry.npmmirror.com/nf3/-/nf3-0.3.10.tgz", {}, "sha512-UlqmHkZiHGgSkRj17yrOXEsSu5ECvtlJ3Xm1W5WsWrTKgu9m7OjrMZh9H/ME2LcWrTlMD0/vmmNVpyBG4yRdGg=="],
|
||||
|
||||
"nitro": ["nitro-nightly@3.0.1-20260217-092337-b28fa21a", "https://registry.npmmirror.com/nitro-nightly/-/nitro-nightly-3.0.1-20260217-092337-b28fa21a.tgz", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.4", "db0": "^0.3.4", "h3": "^2.0.1-rc.14", "hookable": "^6.0.1", "nf3": "^0.3.10", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "rolldown": "^1.0.0-rc.4", "srvx": "^0.11.5", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.5" }, "peerDependencies": { "dotenv": "*", "giget": "*", "jiti": "^2.6.1", "rollup": "^4.57.1", "vite": "^7 || ^8 || >=8.0.0-0", "xml2js": "^0.6.2" }, "optionalPeers": ["dotenv", "giget", "jiti", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-TlJ2aISs/U7ZzDL9rC09NB8KCNtx0vE1SxQy8tRelzMWkObM3uOUf3joX+YXADrynMc4xVF1b7GpO0iX1VSyrQ=="],
|
||||
|
||||
"node-abi": ["node-abi@3.87.0", "https://registry.npmmirror.com/node-abi/-/node-abi-3.87.0.tgz", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
|
||||
|
||||
"node-addon-api": ["node-addon-api@7.1.1", "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"nwsapi": ["nwsapi@2.2.23", "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.23.tgz", {}, "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ=="],
|
||||
|
||||
"ofetch": ["ofetch@2.0.0-alpha.3", "https://registry.npmmirror.com/ofetch/-/ofetch-2.0.0-alpha.3.tgz", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="],
|
||||
|
||||
"ohash": ["ohash@2.0.11", "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
|
||||
"once": ["once@1.4.0", "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"parse5": ["parse5@8.0.0", "https://registry.npmmirror.com/parse5/-/parse5-8.0.0.tgz", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
|
||||
|
||||
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
|
||||
|
||||
"parse5-parser-stream": ["parse5-parser-stream@7.1.2", "https://registry.npmmirror.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"pathval": ["pathval@2.0.1", "https://registry.npmmirror.com/pathval/-/pathval-2.0.1.tgz", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"prebuild-install": ["prebuild-install@7.1.3", "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
||||
|
||||
"prettier": ["prettier@3.8.1", "https://registry.npmmirror.com/prettier/-/prettier-3.8.1.tgz", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
||||
|
||||
"pretty-format": ["pretty-format@27.5.1", "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||
|
||||
"pump": ["pump@3.0.3", "https://registry.npmmirror.com/pump/-/pump-3.0.3.tgz", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"rc": ["rc@1.2.8", "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
||||
|
||||
"react": ["react@19.2.4", "https://registry.npmmirror.com/react/-/react-19.2.4.tgz", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.4", "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.4.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
||||
|
||||
"react-is": ["react-is@17.0.2", "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.18.0", "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.18.0.tgz", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"readdirp": ["readdirp@3.6.0", "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"recast": ["recast@0.23.11", "https://registry.npmmirror.com/recast/-/recast-0.23.11.tgz", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.4", "https://registry.npmmirror.com/rolldown/-/rolldown-1.0.0-rc.4.tgz", { "dependencies": { "@oxc-project/types": "=0.113.0", "@rolldown/pluginutils": "1.0.0-rc.4" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.4", "@rolldown/binding-darwin-arm64": "1.0.0-rc.4", "@rolldown/binding-darwin-x64": "1.0.0-rc.4", "@rolldown/binding-freebsd-x64": "1.0.0-rc.4", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.4", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.4", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.4", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.4", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.4", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.4", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.4", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.4", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.4" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA=="],
|
||||
|
||||
"rollup": ["rollup@4.57.1", "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
|
||||
|
||||
"rou3": ["rou3@0.7.12", "https://registry.npmmirror.com/rou3/-/rou3-0.7.12.tgz", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
||||
|
||||
"rrweb-cssom": ["rrweb-cssom@0.8.0", "https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"saxes": ["saxes@6.0.0", "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"seroval": ["seroval@1.5.0", "https://registry.npmmirror.com/seroval/-/seroval-1.5.0.tgz", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="],
|
||||
|
||||
"seroval-plugins": ["seroval-plugins@1.5.0", "https://registry.npmmirror.com/seroval-plugins/-/seroval-plugins-1.5.0.tgz", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.3", "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"simple-concat": ["simple-concat@1.0.1", "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
||||
|
||||
"simple-get": ["simple-get@4.0.1", "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
|
||||
|
||||
"solid-js": ["solid-js@1.9.11", "https://registry.npmmirror.com/solid-js/-/solid-js-1.9.11.tgz", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="],
|
||||
|
||||
"source-map": ["source-map@0.7.6", "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"srvx": ["srvx@0.11.5", "https://registry.npmmirror.com/srvx/-/srvx-0.11.5.tgz", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-MbQgu/gbLcXjg1bhUhPXXOpeMfmDMTGSKPWeht5acXnlQNldD925eS4+bIH/qESecSkP71dU3Fmvunlai1+yzw=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"std-env": ["std-env@3.10.0", "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.3.0", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@2.0.1", "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
||||
|
||||
"strip-literal": ["strip-literal@3.1.0", "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
|
||||
|
||||
"symbol-tree": ["symbol-tree@3.2.4", "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.18", "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.1.18.tgz", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "https://registry.npmmirror.com/tapable/-/tapable-2.3.0.tgz", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
"tar-fs": ["tar-fs@2.1.4", "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
|
||||
|
||||
"tar-stream": ["tar-stream@2.2.0", "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
|
||||
"tiny-warning": ["tiny-warning@1.0.3", "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="],
|
||||
|
||||
"tinybench": ["tinybench@2.9.0", "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@0.3.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tinypool": ["tinypool@1.1.1", "https://registry.npmmirror.com/tinypool/-/tinypool-1.1.1.tgz", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@2.0.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||
|
||||
"tinyspy": ["tinyspy@4.0.4", "https://registry.npmmirror.com/tinyspy/-/tinyspy-4.0.4.tgz", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
|
||||
|
||||
"tldts": ["tldts@7.0.23", "https://registry.npmmirror.com/tldts/-/tldts-7.0.23.tgz", { "dependencies": { "tldts-core": "^7.0.23" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw=="],
|
||||
|
||||
"tldts-core": ["tldts-core@7.0.23", "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.0.23.tgz", {}, "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"tough-cookie": ["tough-cookie@6.0.0", "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.0.tgz", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="],
|
||||
|
||||
"tr46": ["tr46@6.0.0", "https://registry.npmmirror.com/tr46/-/tr46-6.0.0.tgz", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
|
||||
|
||||
"tsconfck": ["tsconfck@3.1.6", "https://registry.npmmirror.com/tsconfck/-/tsconfck-3.1.6.tgz", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tsx": ["tsx@4.21.0", "https://registry.npmmirror.com/tsx/-/tsx-4.21.0.tgz", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
|
||||
|
||||
"tunnel-agent": ["tunnel-agent@0.6.0", "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"ufo": ["ufo@1.6.3", "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||
|
||||
"undici": ["undici@7.22.0", "https://registry.npmmirror.com/undici/-/undici-7.22.0.tgz", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unenv": ["unenv@2.0.0-rc.24", "https://registry.npmmirror.com/unenv/-/unenv-2.0.0-rc.24.tgz", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
||||
|
||||
"unplugin": ["unplugin@2.3.11", "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
|
||||
"unstorage": ["unstorage@2.0.0-alpha.5", "https://registry.npmmirror.com/unstorage/-/unstorage-2.0.0-alpha.5.tgz", { "peerDependencies": { "@azure/app-configuration": "^1.9.0", "@azure/cosmos": "^4.7.0", "@azure/data-tables": "^13.3.1", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.29.1", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.12.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.35.6", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.8.2", "lru-cache": "^11.2.2", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"vite": ["vite@7.3.1", "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "https://registry.npmmirror.com/vite-node/-/vite-node-3.2.4.tgz", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "https://registry.npmmirror.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.1", "https://registry.npmmirror.com/vitefu/-/vitefu-1.1.1.tgz", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||
|
||||
"vitest": ["vitest@3.2.4", "https://registry.npmmirror.com/vitest/-/vitest-3.2.4.tgz", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
|
||||
|
||||
"w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@8.0.1", "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-8.0.1.tgz", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
"whatwg-encoding": ["whatwg-encoding@3.1.1", "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@15.1.0", "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-15.1.0.tgz", { "dependencies": { "tr46": "^6.0.0", "webidl-conversions": "^8.0.0" } }, "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"ws": ["ws@8.19.0", "https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
||||
|
||||
"xml-name-validator": ["xml-name-validator@5.0.0", "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
|
||||
|
||||
"xmlbuilder2": ["xmlbuilder2@4.0.3", "https://registry.npmmirror.com/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz", { "dependencies": { "@oozcitak/dom": "^2.0.2", "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0", "js-yaml": "^4.1.1" } }, "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA=="],
|
||||
|
||||
"xmlchars": ["xmlchars@2.2.0", "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"zustand": ["zustand@5.0.11", "https://registry.npmmirror.com/zustand/-/zustand-5.0.11.tgz", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "https://registry.npmmirror.com/@emnapi/core/-/core-1.8.1.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tanstack/devtools/@tanstack/devtools-client": ["@tanstack/devtools-client@0.0.3", "https://registry.npmmirror.com/@tanstack/devtools-client/-/devtools-client-0.0.3.tgz", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.3" } }, "sha512-kl0r6N5iIL3t9gGDRAv55VRM3UIyMKVH83esRGq7xBjYsRLe/BeCIN2HqrlJkObUXQMKhy7i8ejuGOn+bDqDBw=="],
|
||||
|
||||
"@tanstack/start-plugin-core/@babel/code-frame": ["@babel/code-frame@7.27.1", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@tanstack/start-plugin-core/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.40", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.40.tgz", {}, "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"cheerio/parse5": ["parse5@7.3.0", "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"data-urls/whatwg-mimetype": ["whatwg-mimetype@5.0.0", "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"fabric/jsdom": ["jsdom@26.1.0", "https://registry.npmmirror.com/jsdom/-/jsdom-26.1.0.tgz", { "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", "decimal.js": "^10.5.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.16", "parse5": "^7.2.1", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^5.1.1", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.1.1", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@7.0.1", "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
|
||||
"node-abi/semver": ["semver@7.7.4", "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"parse5-htmlparser2-tree-adapter/parse5": ["parse5@7.3.0", "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"parse5-parser-stream/parse5": ["parse5@7.3.0", "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"postcss/nanoid": ["nanoid@3.3.11", "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"readdirp/picomatch": ["picomatch@2.3.1", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"recast/source-map": ["source-map@0.6.1", "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.4", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.4.tgz", {}, "sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ=="],
|
||||
|
||||
"strip-literal/js-tokens": ["js-tokens@9.0.1", "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||
|
||||
"@tanstack/devtools/@tanstack/devtools-client/@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.3.5", "https://registry.npmmirror.com/@tanstack/devtools-event-client/-/devtools-event-client-0.3.5.tgz", {}, "sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw=="],
|
||||
|
||||
"fabric/jsdom/cssstyle": ["cssstyle@4.6.0", "https://registry.npmmirror.com/cssstyle/-/cssstyle-4.6.0.tgz", { "dependencies": { "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" } }, "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg=="],
|
||||
|
||||
"fabric/jsdom/data-urls": ["data-urls@5.0.0", "https://registry.npmmirror.com/data-urls/-/data-urls-5.0.0.tgz", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="],
|
||||
|
||||
"fabric/jsdom/html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="],
|
||||
|
||||
"fabric/jsdom/parse5": ["parse5@7.3.0", "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"fabric/jsdom/tough-cookie": ["tough-cookie@5.1.2", "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-5.1.2.tgz", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
|
||||
|
||||
"fabric/jsdom/webidl-conversions": ["webidl-conversions@7.0.0", "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
|
||||
|
||||
"fabric/jsdom/whatwg-url": ["whatwg-url@14.2.0", "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-14.2.0.tgz", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
|
||||
|
||||
"fabric/jsdom/cssstyle/@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
|
||||
|
||||
"fabric/jsdom/tough-cookie/tldts": ["tldts@6.1.86", "https://registry.npmmirror.com/tldts/-/tldts-6.1.86.tgz", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="],
|
||||
|
||||
"fabric/jsdom/whatwg-url/tr46": ["tr46@5.1.1", "https://registry.npmmirror.com/tr46/-/tr46-5.1.1.tgz", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
|
||||
|
||||
"fabric/jsdom/cssstyle/@asamuzakjp/css-color/@csstools/css-calc": ["@csstools/css-calc@2.1.4", "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.4.tgz", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="],
|
||||
|
||||
"fabric/jsdom/cssstyle/@asamuzakjp/css-color/@csstools/css-color-parser": ["@csstools/css-color-parser@3.1.0", "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", { "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA=="],
|
||||
|
||||
"fabric/jsdom/cssstyle/@asamuzakjp/css-color/@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.5", "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="],
|
||||
|
||||
"fabric/jsdom/cssstyle/@asamuzakjp/css-color/@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
|
||||
|
||||
"fabric/jsdom/cssstyle/@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"fabric/jsdom/tough-cookie/tldts/tldts-core": ["tldts-core@6.1.86", "https://registry.npmmirror.com/tldts-core/-/tldts-core-6.1.86.tgz", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
|
||||
|
||||
"fabric/jsdom/cssstyle/@asamuzakjp/css-color/@csstools/css-color-parser/@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="],
|
||||
}
|
||||
}
|
||||
42
package.json
Normal file
42
package.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "openpencil",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun --bun vite dev --port 3000",
|
||||
"build": "bun --bun vite build",
|
||||
"preview": "bun --bun vite preview",
|
||||
"test": "bun --bun vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-devtools": "^0.7.0",
|
||||
"@tanstack/react-router": "^1.132.0",
|
||||
"@tanstack/react-router-devtools": "^1.132.0",
|
||||
"@tanstack/react-router-ssr-query": "^1.131.7",
|
||||
"@tanstack/react-start": "^1.132.0",
|
||||
"@tanstack/router-plugin": "^1.132.0",
|
||||
"fabric": "^7.1.0",
|
||||
"lucide-react": "^0.545.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nitro": "npm:nitro-nightly@latest",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/devtools-vite": "^0.3.11",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"jsdom": "^27.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.1.7",
|
||||
"vitest": "^3.0.5"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/logo192.png
Normal file
BIN
public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/logo512.png
Normal file
BIN
public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
public/manifest.json
Normal file
25
public/manifest.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "TanStack App",
|
||||
"name": "Create TanStack App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
BIN
public/tanstack-circle-logo.png
Normal file
BIN
public/tanstack-circle-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
1
public/tanstack-word-logo-white.svg
Normal file
1
public/tanstack-word-logo-white.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
26
src/canvas/FabricCanvas.tsx
Normal file
26
src/canvas/FabricCanvas.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useRef } from 'react'
|
||||
import { useFabricCanvas } from './use-fabric-canvas'
|
||||
import { useCanvasEvents } from './use-canvas-events'
|
||||
import { useCanvasViewport } from './use-canvas-viewport'
|
||||
import { useCanvasSelection } from './use-canvas-selection'
|
||||
import { useCanvasSync } from './use-canvas-sync'
|
||||
|
||||
export default function FabricCanvas() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useFabricCanvas(canvasRef, containerRef)
|
||||
useCanvasEvents()
|
||||
useCanvasViewport()
|
||||
useCanvasSelection()
|
||||
useCanvasSync()
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex-1 relative overflow-hidden bg-neutral-100"
|
||||
>
|
||||
<canvas ref={canvasRef} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
8
src/canvas/canvas-constants.ts
Normal file
8
src/canvas/canvas-constants.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export const MIN_ZOOM = 0.02
|
||||
export const MAX_ZOOM = 256
|
||||
export const ZOOM_STEP = 0.1
|
||||
export const SNAP_THRESHOLD = 5
|
||||
export const DEFAULT_FILL = '#d1d5db'
|
||||
export const DEFAULT_STROKE = '#374151'
|
||||
export const DEFAULT_STROKE_WIDTH = 1
|
||||
export const CANVAS_BACKGROUND = '#f5f5f5'
|
||||
191
src/canvas/canvas-object-factory.ts
Normal file
191
src/canvas/canvas-object-factory.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import * as fabric from 'fabric'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import type { PenFill, PenStroke } from '@/types/styles'
|
||||
import {
|
||||
DEFAULT_FILL,
|
||||
DEFAULT_STROKE,
|
||||
DEFAULT_STROKE_WIDTH,
|
||||
} from './canvas-constants'
|
||||
|
||||
export function resolveFillColor(fills?: PenFill[]): string {
|
||||
if (!fills || fills.length === 0) return DEFAULT_FILL
|
||||
const first = fills[0]
|
||||
if (first.type === 'solid') return first.color
|
||||
// Gradients/images: fallback to first color or default
|
||||
if (
|
||||
first.type === 'linear_gradient' ||
|
||||
first.type === 'radial_gradient'
|
||||
) {
|
||||
return first.stops[0]?.color ?? DEFAULT_FILL
|
||||
}
|
||||
return DEFAULT_FILL
|
||||
}
|
||||
|
||||
export function resolveStrokeColor(stroke?: PenStroke): string | undefined {
|
||||
if (!stroke) return undefined
|
||||
if (stroke.fill && stroke.fill.length > 0) {
|
||||
return resolveFillColor(stroke.fill)
|
||||
}
|
||||
return DEFAULT_STROKE
|
||||
}
|
||||
|
||||
export function resolveStrokeWidth(stroke?: PenStroke): number {
|
||||
if (!stroke) return 0
|
||||
if (typeof stroke.thickness === 'number') return stroke.thickness
|
||||
return stroke.thickness[0] ?? DEFAULT_STROKE_WIDTH
|
||||
}
|
||||
|
||||
function resolveTextContent(
|
||||
content: string | { text: string }[],
|
||||
): string {
|
||||
if (typeof content === 'string') return content
|
||||
return content.map((s) => s.text).join('')
|
||||
}
|
||||
|
||||
function sizeToNumber(
|
||||
val: number | string | undefined,
|
||||
fallback: number,
|
||||
): number {
|
||||
if (typeof val === 'number') return val
|
||||
return fallback
|
||||
}
|
||||
|
||||
function cornerRadiusValue(
|
||||
cr: number | [number, number, number, number] | undefined,
|
||||
): number {
|
||||
if (cr === undefined) return 0
|
||||
if (typeof cr === 'number') return cr
|
||||
return cr[0]
|
||||
}
|
||||
|
||||
export interface FabricObjectWithPenId extends fabric.FabricObject {
|
||||
penNodeId?: string
|
||||
}
|
||||
|
||||
export function createFabricObject(
|
||||
node: PenNode,
|
||||
): FabricObjectWithPenId | null {
|
||||
let obj: FabricObjectWithPenId | null = null
|
||||
|
||||
const baseProps = {
|
||||
left: node.x ?? 0,
|
||||
top: node.y ?? 0,
|
||||
originX: 'left' as const,
|
||||
originY: 'top' as const,
|
||||
angle: node.rotation ?? 0,
|
||||
opacity: typeof node.opacity === 'number' ? node.opacity : 1,
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'rectangle':
|
||||
case 'frame': {
|
||||
const r = cornerRadiusValue(node.cornerRadius)
|
||||
obj = new fabric.Rect({
|
||||
...baseProps,
|
||||
width: sizeToNumber(node.width, 100),
|
||||
height: sizeToNumber(node.height, 100),
|
||||
rx: r,
|
||||
ry: r,
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
}) as FabricObjectWithPenId
|
||||
break
|
||||
}
|
||||
case 'ellipse': {
|
||||
const w = sizeToNumber(node.width, 100)
|
||||
const h = sizeToNumber(node.height, 100)
|
||||
obj = new fabric.Ellipse({
|
||||
...baseProps,
|
||||
rx: w / 2,
|
||||
ry: h / 2,
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
}) as FabricObjectWithPenId
|
||||
break
|
||||
}
|
||||
case 'line': {
|
||||
obj = new fabric.Line(
|
||||
[
|
||||
node.x ?? 0,
|
||||
node.y ?? 0,
|
||||
node.x2 ?? (node.x ?? 0) + 100,
|
||||
node.y2 ?? (node.y ?? 0),
|
||||
],
|
||||
{
|
||||
...baseProps,
|
||||
stroke: resolveStrokeColor(node.stroke) ?? DEFAULT_STROKE,
|
||||
strokeWidth: resolveStrokeWidth(node.stroke) || DEFAULT_STROKE_WIDTH,
|
||||
fill: '',
|
||||
},
|
||||
) as FabricObjectWithPenId
|
||||
break
|
||||
}
|
||||
case 'polygon': {
|
||||
const w = sizeToNumber(node.width, 100)
|
||||
const h = sizeToNumber(node.height, 100)
|
||||
const count = node.polygonCount || 6
|
||||
const points = Array.from({ length: count }, (_, i) => {
|
||||
const angle = (i * 2 * Math.PI) / count - Math.PI / 2
|
||||
return {
|
||||
x: (w / 2) * Math.cos(angle) + w / 2,
|
||||
y: (h / 2) * Math.sin(angle) + h / 2,
|
||||
}
|
||||
})
|
||||
obj = new fabric.Polygon(points, {
|
||||
...baseProps,
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
}) as FabricObjectWithPenId
|
||||
break
|
||||
}
|
||||
case 'path': {
|
||||
obj = new fabric.Path(node.d, {
|
||||
...baseProps,
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
}) as FabricObjectWithPenId
|
||||
break
|
||||
}
|
||||
case 'text': {
|
||||
obj = new fabric.IText(resolveTextContent(node.content), {
|
||||
...baseProps,
|
||||
width: sizeToNumber(node.width, undefined!),
|
||||
fontFamily: node.fontFamily ?? 'Inter, sans-serif',
|
||||
fontSize: node.fontSize ?? 16,
|
||||
fontWeight: (node.fontWeight as string) ?? 'normal',
|
||||
fontStyle: node.fontStyle ?? 'normal',
|
||||
fill: resolveFillColor(node.fill),
|
||||
textAlign: node.textAlign ?? 'left',
|
||||
underline: node.underline ?? false,
|
||||
linethrough: node.strikethrough ?? false,
|
||||
}) as FabricObjectWithPenId
|
||||
break
|
||||
}
|
||||
case 'group': {
|
||||
// Groups are rendered as their children; the group itself is a container
|
||||
obj = new fabric.Rect({
|
||||
...baseProps,
|
||||
width: sizeToNumber(node.width, 100),
|
||||
height: sizeToNumber(node.height, 100),
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
selectable: true,
|
||||
}) as FabricObjectWithPenId
|
||||
break
|
||||
}
|
||||
case 'ref': {
|
||||
// RefNodes need to be resolved before rendering
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (obj) {
|
||||
obj.penNodeId = node.id
|
||||
}
|
||||
return obj
|
||||
}
|
||||
104
src/canvas/canvas-object-sync.ts
Normal file
104
src/canvas/canvas-object-sync.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import type { PenNode } from '@/types/pen'
|
||||
import type { FabricObjectWithPenId } from './canvas-object-factory'
|
||||
import {
|
||||
resolveFillColor,
|
||||
resolveStrokeColor,
|
||||
resolveStrokeWidth,
|
||||
} from './canvas-object-factory'
|
||||
|
||||
function sizeToNumber(
|
||||
val: number | string | undefined,
|
||||
fallback: number,
|
||||
): number {
|
||||
if (typeof val === 'number') return val
|
||||
return fallback
|
||||
}
|
||||
|
||||
function cornerRadiusValue(
|
||||
cr: number | [number, number, number, number] | undefined,
|
||||
): number {
|
||||
if (cr === undefined) return 0
|
||||
if (typeof cr === 'number') return cr
|
||||
return cr[0]
|
||||
}
|
||||
|
||||
export function syncFabricObject(
|
||||
obj: FabricObjectWithPenId,
|
||||
node: PenNode,
|
||||
) {
|
||||
obj.set({
|
||||
left: node.x ?? obj.left,
|
||||
top: node.y ?? obj.top,
|
||||
angle: node.rotation ?? 0,
|
||||
opacity: typeof node.opacity === 'number' ? node.opacity : 1,
|
||||
})
|
||||
|
||||
switch (node.type) {
|
||||
case 'rectangle':
|
||||
case 'frame':
|
||||
case 'group': {
|
||||
obj.set({
|
||||
width: sizeToNumber(node.width, 100),
|
||||
height: sizeToNumber(node.height, 100),
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
})
|
||||
if ('rx' in obj) {
|
||||
const r = cornerRadiusValue(node.cornerRadius)
|
||||
obj.set({ rx: r, ry: r })
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'ellipse': {
|
||||
const w = sizeToNumber(node.width, 100)
|
||||
const h = sizeToNumber(node.height, 100)
|
||||
obj.set({
|
||||
rx: w / 2,
|
||||
ry: h / 2,
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'line': {
|
||||
obj.set({
|
||||
x1: node.x ?? 0,
|
||||
y1: node.y ?? 0,
|
||||
x2: node.x2 ?? 100,
|
||||
y2: node.y2 ?? 0,
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'text': {
|
||||
const content =
|
||||
typeof node.content === 'string'
|
||||
? node.content
|
||||
: node.content.map((s) => s.text).join('')
|
||||
obj.set({
|
||||
text: content,
|
||||
fontFamily: node.fontFamily ?? 'Inter, sans-serif',
|
||||
fontSize: node.fontSize ?? 16,
|
||||
fontWeight: (node.fontWeight as string) ?? 'normal',
|
||||
fontStyle: node.fontStyle ?? 'normal',
|
||||
fill: resolveFillColor(node.fill),
|
||||
textAlign: node.textAlign ?? 'left',
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'polygon':
|
||||
case 'path': {
|
||||
obj.set({
|
||||
fill: resolveFillColor(node.fill),
|
||||
stroke: resolveStrokeColor(node.stroke),
|
||||
strokeWidth: resolveStrokeWidth(node.stroke),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
obj.setCoords()
|
||||
}
|
||||
6
src/canvas/canvas-sync-lock.ts
Normal file
6
src/canvas/canvas-sync-lock.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/** When true, document-store → Fabric sync is skipped (Fabric is the source). */
|
||||
export let fabricSyncLock = false
|
||||
|
||||
export function setFabricSyncLock(v: boolean) {
|
||||
fabricSyncLock = v
|
||||
}
|
||||
366
src/canvas/use-canvas-events.ts
Normal file
366
src/canvas/use-canvas-events.ts
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
import { useEffect } from 'react'
|
||||
import * as fabric from 'fabric'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import { useDocumentStore, generateId } from '@/stores/document-store'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import type { ToolType } from '@/types/canvas'
|
||||
import {
|
||||
DEFAULT_FILL,
|
||||
DEFAULT_STROKE,
|
||||
DEFAULT_STROKE_WIDTH,
|
||||
} from './canvas-constants'
|
||||
import type { FabricObjectWithPenId } from './canvas-object-factory'
|
||||
import { setFabricSyncLock } from './canvas-sync-lock'
|
||||
|
||||
function createNodeForTool(
|
||||
tool: ToolType,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
): PenNode | null {
|
||||
const id = generateId()
|
||||
|
||||
switch (tool) {
|
||||
case 'rectangle':
|
||||
return {
|
||||
id,
|
||||
type: 'rectangle',
|
||||
name: 'Rectangle',
|
||||
x,
|
||||
y,
|
||||
width: Math.abs(width),
|
||||
height: Math.abs(height),
|
||||
fill: [{ type: 'solid', color: DEFAULT_FILL }],
|
||||
stroke: {
|
||||
thickness: DEFAULT_STROKE_WIDTH,
|
||||
fill: [{ type: 'solid', color: DEFAULT_STROKE }],
|
||||
},
|
||||
}
|
||||
case 'frame':
|
||||
return {
|
||||
id,
|
||||
type: 'frame',
|
||||
name: 'Frame',
|
||||
x,
|
||||
y,
|
||||
width: Math.abs(width),
|
||||
height: Math.abs(height),
|
||||
fill: [{ type: 'solid', color: '#ffffff' }],
|
||||
children: [],
|
||||
}
|
||||
case 'ellipse':
|
||||
return {
|
||||
id,
|
||||
type: 'ellipse',
|
||||
name: 'Ellipse',
|
||||
x,
|
||||
y,
|
||||
width: Math.abs(width),
|
||||
height: Math.abs(height),
|
||||
fill: [{ type: 'solid', color: DEFAULT_FILL }],
|
||||
stroke: {
|
||||
thickness: DEFAULT_STROKE_WIDTH,
|
||||
fill: [{ type: 'solid', color: DEFAULT_STROKE }],
|
||||
},
|
||||
}
|
||||
case 'line':
|
||||
return {
|
||||
id,
|
||||
type: 'line',
|
||||
name: 'Line',
|
||||
x,
|
||||
y,
|
||||
x2: x + width,
|
||||
y2: y + height,
|
||||
stroke: {
|
||||
thickness: DEFAULT_STROKE_WIDTH,
|
||||
fill: [{ type: 'solid', color: DEFAULT_STROKE }],
|
||||
},
|
||||
}
|
||||
case 'text':
|
||||
return {
|
||||
id,
|
||||
type: 'text',
|
||||
name: 'Text',
|
||||
x,
|
||||
y,
|
||||
content: 'Type here',
|
||||
fontSize: 16,
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fill: [{ type: 'solid', color: '#000000' }],
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function isDrawingTool(tool: ToolType): boolean {
|
||||
return tool !== 'select' && tool !== 'hand'
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a pointer event to scene coordinates using Fabric's own method
|
||||
* which correctly handles DPR / retina scaling and viewport transform.
|
||||
*/
|
||||
function toScene(
|
||||
canvas: fabric.Canvas,
|
||||
e: PointerEvent,
|
||||
): { x: number; y: number } {
|
||||
canvas.calcOffset()
|
||||
const point = canvas.getScenePoint(e)
|
||||
return { x: point.x, y: point.y }
|
||||
}
|
||||
|
||||
export function useCanvasEvents() {
|
||||
useEffect(() => {
|
||||
let tempObj: fabric.FabricObject | null = null
|
||||
let startPoint: { x: number; y: number } | null = null
|
||||
let drawing = false
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (!canvas) return
|
||||
clearInterval(interval)
|
||||
|
||||
const upperEl = canvas.upperCanvasEl
|
||||
if (!upperEl) return
|
||||
|
||||
// --- Tool change: toggle selection ---
|
||||
let prevTool = useCanvasStore.getState().activeTool
|
||||
const unsubTool = useCanvasStore.subscribe((state) => {
|
||||
if (state.activeTool === prevTool) return
|
||||
prevTool = state.activeTool
|
||||
if (!state.fabricCanvas) return
|
||||
if (isDrawingTool(state.activeTool)) {
|
||||
state.fabricCanvas.selection = false
|
||||
state.fabricCanvas.discardActiveObject()
|
||||
state.fabricCanvas.requestRenderAll()
|
||||
} else if (state.activeTool === 'select') {
|
||||
state.fabricCanvas.selection = true
|
||||
}
|
||||
})
|
||||
|
||||
// --- Drawing via native pointer events on the upper canvas ---
|
||||
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
const tool = useCanvasStore.getState().activeTool
|
||||
if (!isDrawingTool(tool)) return
|
||||
const { isPanning } = useCanvasStore.getState().interaction
|
||||
if (isPanning) return
|
||||
|
||||
const pointer = toScene(canvas, e)
|
||||
startPoint = { x: pointer.x, y: pointer.y }
|
||||
drawing = true
|
||||
|
||||
// Text: create immediately
|
||||
if (tool === 'text') {
|
||||
const node = createNodeForTool(tool, pointer.x, pointer.y, 0, 0)
|
||||
if (node) {
|
||||
useDocumentStore.getState().addNode(null, node)
|
||||
}
|
||||
drawing = false
|
||||
startPoint = null
|
||||
useCanvasStore.getState().setActiveTool('select')
|
||||
return
|
||||
}
|
||||
|
||||
const baseProps = {
|
||||
left: pointer.x,
|
||||
top: pointer.y,
|
||||
originX: 'left' as const,
|
||||
originY: 'top' as const,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
objectCaching: false,
|
||||
}
|
||||
|
||||
switch (tool) {
|
||||
case 'rectangle':
|
||||
case 'frame':
|
||||
tempObj = new fabric.Rect({
|
||||
...baseProps,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: 'rgba(59, 130, 246, 0.1)',
|
||||
strokeWidth: 0,
|
||||
})
|
||||
break
|
||||
case 'ellipse':
|
||||
tempObj = new fabric.Ellipse({
|
||||
...baseProps,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
fill: 'rgba(59, 130, 246, 0.1)',
|
||||
strokeWidth: 0,
|
||||
})
|
||||
break
|
||||
case 'line':
|
||||
tempObj = new fabric.Line(
|
||||
[pointer.x, pointer.y, pointer.x, pointer.y],
|
||||
{
|
||||
...baseProps,
|
||||
fill: '',
|
||||
stroke: '#3b82f6',
|
||||
strokeWidth: 1,
|
||||
strokeUniform: true,
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
if (tempObj) {
|
||||
canvas.add(tempObj)
|
||||
canvas.renderAll()
|
||||
}
|
||||
}
|
||||
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
if (!drawing || !tempObj || !startPoint) return
|
||||
|
||||
const tool = useCanvasStore.getState().activeTool
|
||||
const pointer = toScene(canvas, e)
|
||||
const dx = pointer.x - startPoint.x
|
||||
const dy = pointer.y - startPoint.y
|
||||
|
||||
switch (tool) {
|
||||
case 'rectangle':
|
||||
case 'frame': {
|
||||
tempObj.set({
|
||||
left: dx < 0 ? pointer.x : startPoint.x,
|
||||
top: dy < 0 ? pointer.y : startPoint.y,
|
||||
width: Math.abs(dx),
|
||||
height: Math.abs(dy),
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'ellipse': {
|
||||
tempObj.set({
|
||||
left: dx < 0 ? pointer.x : startPoint.x,
|
||||
top: dy < 0 ? pointer.y : startPoint.y,
|
||||
rx: Math.abs(dx) / 2,
|
||||
ry: Math.abs(dy) / 2,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'line': {
|
||||
tempObj.set({ x2: pointer.x, y2: pointer.y })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tempObj.setCoords()
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
const onPointerUp = (_e: PointerEvent) => {
|
||||
if (!drawing || !tempObj || !startPoint) {
|
||||
drawing = false
|
||||
startPoint = null
|
||||
return
|
||||
}
|
||||
|
||||
const tool = useCanvasStore.getState().activeTool
|
||||
const finalX = tempObj.left ?? 0
|
||||
const finalY = tempObj.top ?? 0
|
||||
|
||||
let width = 0
|
||||
let height = 0
|
||||
|
||||
if (tool === 'line') {
|
||||
width = ((tempObj as fabric.Line).x2 ?? 0) - startPoint.x
|
||||
height = ((tempObj as fabric.Line).y2 ?? 0) - startPoint.y
|
||||
} else {
|
||||
width = tempObj.width ?? 0
|
||||
height = tempObj.height ?? 0
|
||||
}
|
||||
|
||||
canvas.remove(tempObj)
|
||||
tempObj = null
|
||||
drawing = false
|
||||
|
||||
if (
|
||||
Math.abs(width) > 2 ||
|
||||
Math.abs(height) > 2 ||
|
||||
tool === 'line'
|
||||
) {
|
||||
const node = createNodeForTool(tool, finalX, finalY, width, height)
|
||||
if (node) {
|
||||
useDocumentStore.getState().addNode(null, node)
|
||||
}
|
||||
}
|
||||
|
||||
startPoint = null
|
||||
useCanvasStore.getState().setActiveTool('select')
|
||||
}
|
||||
|
||||
// All listeners on upperEl because Fabric.js captures the pointer
|
||||
// to this element, so pointermove/pointerup won't reach document.
|
||||
upperEl.addEventListener('pointerdown', onPointerDown)
|
||||
upperEl.addEventListener('pointermove', onPointerMove)
|
||||
upperEl.addEventListener('pointerup', onPointerUp)
|
||||
|
||||
// --- Object modifications (drag, resize, rotate) via Fabric events ---
|
||||
|
||||
const syncObjToStore = (obj: FabricObjectWithPenId) => {
|
||||
if (!obj?.penNodeId) return
|
||||
|
||||
const scaleX = obj.scaleX ?? 1
|
||||
const scaleY = obj.scaleY ?? 1
|
||||
const updates: Partial<PenNode> = {
|
||||
x: obj.left,
|
||||
y: obj.top,
|
||||
rotation: obj.angle,
|
||||
}
|
||||
if (obj.width !== undefined) {
|
||||
;(updates as Record<string, unknown>).width = obj.width * scaleX
|
||||
}
|
||||
if (obj.height !== undefined) {
|
||||
;(updates as Record<string, unknown>).height = obj.height * scaleY
|
||||
}
|
||||
|
||||
setFabricSyncLock(true)
|
||||
useDocumentStore.getState().updateNode(obj.penNodeId, updates)
|
||||
setFabricSyncLock(false)
|
||||
}
|
||||
|
||||
// Real-time sync during drag / resize / rotate
|
||||
canvas.on('object:moving', (opt) => {
|
||||
syncObjToStore(opt.target as FabricObjectWithPenId)
|
||||
})
|
||||
canvas.on('object:scaling', (opt) => {
|
||||
syncObjToStore(opt.target as FabricObjectWithPenId)
|
||||
})
|
||||
canvas.on('object:rotating', (opt) => {
|
||||
syncObjToStore(opt.target as FabricObjectWithPenId)
|
||||
})
|
||||
|
||||
// Final sync: reset scale to 1 and bake into width/height
|
||||
canvas.on('object:modified', (opt) => {
|
||||
const obj = opt.target as FabricObjectWithPenId
|
||||
if (!obj?.penNodeId) return
|
||||
|
||||
const scaleX = obj.scaleX ?? 1
|
||||
const scaleY = obj.scaleY ?? 1
|
||||
if (obj.width !== undefined) {
|
||||
obj.set({ width: obj.width * scaleX, scaleX: 1 })
|
||||
}
|
||||
if (obj.height !== undefined) {
|
||||
obj.set({ height: obj.height * scaleY, scaleY: 1 })
|
||||
}
|
||||
obj.setCoords()
|
||||
|
||||
syncObjToStore(obj)
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubTool()
|
||||
upperEl.removeEventListener('pointerdown', onPointerDown)
|
||||
upperEl.removeEventListener('pointermove', onPointerMove)
|
||||
upperEl.removeEventListener('pointerup', onPointerUp)
|
||||
}
|
||||
}, 100)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
}
|
||||
35
src/canvas/use-canvas-selection.ts
Normal file
35
src/canvas/use-canvas-selection.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import type { FabricObjectWithPenId } from './canvas-object-factory'
|
||||
|
||||
export function useCanvasSelection() {
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (!canvas) return
|
||||
clearInterval(interval)
|
||||
|
||||
canvas.on('selection:created', (e) => {
|
||||
const selected = e.selected ?? []
|
||||
const ids = selected
|
||||
.map((obj) => (obj as FabricObjectWithPenId).penNodeId)
|
||||
.filter(Boolean) as string[]
|
||||
useCanvasStore.getState().setSelection(ids, ids[0] ?? null)
|
||||
})
|
||||
|
||||
canvas.on('selection:updated', (e) => {
|
||||
const selected = e.selected ?? []
|
||||
const ids = selected
|
||||
.map((obj) => (obj as FabricObjectWithPenId).penNodeId)
|
||||
.filter(Boolean) as string[]
|
||||
useCanvasStore.getState().setSelection(ids, ids[0] ?? null)
|
||||
})
|
||||
|
||||
canvas.on('selection:cleared', () => {
|
||||
useCanvasStore.getState().clearSelection()
|
||||
})
|
||||
}, 100)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
}
|
||||
67
src/canvas/use-canvas-sync.ts
Normal file
67
src/canvas/use-canvas-sync.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import { useDocumentStore } from '@/stores/document-store'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import {
|
||||
createFabricObject,
|
||||
type FabricObjectWithPenId,
|
||||
} from './canvas-object-factory'
|
||||
import { syncFabricObject } from './canvas-object-sync'
|
||||
import { fabricSyncLock } from './canvas-sync-lock'
|
||||
|
||||
function flattenNodes(nodes: PenNode[]): PenNode[] {
|
||||
const result: PenNode[] = []
|
||||
for (const node of nodes) {
|
||||
result.push(node)
|
||||
if ('children' in node && node.children) {
|
||||
result.push(...flattenNodes(node.children))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function useCanvasSync() {
|
||||
useEffect(() => {
|
||||
const unsub = useDocumentStore.subscribe((state) => {
|
||||
if (fabricSyncLock) return
|
||||
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (!canvas) return
|
||||
|
||||
const flatNodes = flattenNodes(state.document.children)
|
||||
const nodeMap = new Map(flatNodes.map((n) => [n.id, n]))
|
||||
const objects = canvas.getObjects() as FabricObjectWithPenId[]
|
||||
const objMap = new Map(
|
||||
objects
|
||||
.filter((o) => o.penNodeId)
|
||||
.map((o) => [o.penNodeId!, o]),
|
||||
)
|
||||
|
||||
// Remove objects that no longer exist in the document
|
||||
for (const obj of objects) {
|
||||
if (obj.penNodeId && !nodeMap.has(obj.penNodeId)) {
|
||||
canvas.remove(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// Add or update objects
|
||||
for (const node of flatNodes) {
|
||||
if (node.type === 'ref') continue // Skip unresolved refs
|
||||
|
||||
const existingObj = objMap.get(node.id)
|
||||
if (existingObj) {
|
||||
syncFabricObject(existingObj, node)
|
||||
} else {
|
||||
const newObj = createFabricObject(node)
|
||||
if (newObj) {
|
||||
canvas.add(newObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canvas.requestRenderAll()
|
||||
})
|
||||
|
||||
return () => unsub()
|
||||
}, [])
|
||||
}
|
||||
149
src/canvas/use-canvas-viewport.ts
Normal file
149
src/canvas/use-canvas-viewport.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import { MIN_ZOOM, MAX_ZOOM } from './canvas-constants'
|
||||
|
||||
export function useCanvasViewport() {
|
||||
useEffect(() => {
|
||||
// Set up wheel zoom
|
||||
const handleWheel = () => {
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (!canvas) return
|
||||
|
||||
canvas.on('mouse:wheel', (opt) => {
|
||||
const e = opt.e as WheelEvent
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const delta = -e.deltaY
|
||||
const zoom = canvas.getZoom()
|
||||
const factor = delta > 0 ? 1.05 : 0.95
|
||||
let newZoom = zoom * factor
|
||||
|
||||
newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom))
|
||||
|
||||
const point = canvas.getScenePoint(e)
|
||||
canvas.zoomToPoint(point, newZoom)
|
||||
|
||||
const vpt = canvas.viewportTransform
|
||||
if (vpt) {
|
||||
useCanvasStore.getState().setZoom(newZoom)
|
||||
useCanvasStore.getState().setPan(vpt[4], vpt[5])
|
||||
}
|
||||
|
||||
canvas.requestRenderAll()
|
||||
})
|
||||
}
|
||||
|
||||
// Set up space + drag panning
|
||||
const handlePan = () => {
|
||||
let isPanning = false
|
||||
let lastX = 0
|
||||
let lastY = 0
|
||||
let spacePressed = false
|
||||
|
||||
const isHandTool = () =>
|
||||
useCanvasStore.getState().activeTool === 'hand'
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.code === 'Space' && !e.repeat) {
|
||||
spacePressed = true
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (canvas) {
|
||||
canvas.defaultCursor = 'grab'
|
||||
canvas.selection = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.code === 'Space') {
|
||||
spacePressed = false
|
||||
isPanning = false
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (canvas && !isHandTool()) {
|
||||
canvas.defaultCursor = 'default'
|
||||
canvas.selection = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep cursor in sync when switching to/from hand tool
|
||||
let prevTool = useCanvasStore.getState().activeTool
|
||||
const unsubTool = useCanvasStore.subscribe((state) => {
|
||||
if (state.activeTool === prevTool) return
|
||||
prevTool = state.activeTool
|
||||
const canvas = state.fabricCanvas
|
||||
if (!canvas) return
|
||||
if (state.activeTool === 'hand') {
|
||||
canvas.defaultCursor = 'grab'
|
||||
canvas.selection = false
|
||||
} else if (!spacePressed) {
|
||||
canvas.defaultCursor = 'default'
|
||||
canvas.selection = true
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('keydown', onKeyDown)
|
||||
document.addEventListener('keyup', onKeyUp)
|
||||
|
||||
// Check for canvas periodically since it may not exist yet
|
||||
const interval = setInterval(() => {
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (!canvas) return
|
||||
|
||||
clearInterval(interval)
|
||||
|
||||
canvas.on('mouse:down', (opt) => {
|
||||
const e = opt.e as MouseEvent
|
||||
if (spacePressed || isHandTool() || e.button === 1) {
|
||||
isPanning = true
|
||||
lastX = e.clientX
|
||||
lastY = e.clientY
|
||||
canvas.defaultCursor = 'grabbing'
|
||||
useCanvasStore.getState().setInteraction({ isPanning: true })
|
||||
}
|
||||
})
|
||||
|
||||
canvas.on('mouse:move', (opt) => {
|
||||
if (!isPanning) return
|
||||
const e = opt.e as MouseEvent
|
||||
const dx = e.clientX - lastX
|
||||
const dy = e.clientY - lastY
|
||||
lastX = e.clientX
|
||||
lastY = e.clientY
|
||||
|
||||
const vpt = canvas.viewportTransform
|
||||
if (vpt) {
|
||||
vpt[4] += dx
|
||||
vpt[5] += dy
|
||||
canvas.setViewportTransform(vpt)
|
||||
useCanvasStore.getState().setPan(vpt[4], vpt[5])
|
||||
}
|
||||
})
|
||||
|
||||
canvas.on('mouse:up', () => {
|
||||
if (isPanning) {
|
||||
isPanning = false
|
||||
canvas.defaultCursor =
|
||||
spacePressed || isHandTool() ? 'grab' : 'default'
|
||||
useCanvasStore.getState().setInteraction({ isPanning: false })
|
||||
}
|
||||
})
|
||||
}, 100)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKeyDown)
|
||||
document.removeEventListener('keyup', onKeyUp)
|
||||
clearInterval(interval)
|
||||
unsubTool()
|
||||
}
|
||||
}
|
||||
|
||||
handleWheel()
|
||||
const cleanupPan = handlePan()
|
||||
|
||||
return () => {
|
||||
cleanupPan?.()
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
54
src/canvas/use-fabric-canvas.ts
Normal file
54
src/canvas/use-fabric-canvas.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { useEffect, useRef, type RefObject } from 'react'
|
||||
import * as fabric from 'fabric'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import { CANVAS_BACKGROUND } from './canvas-constants'
|
||||
|
||||
export function useFabricCanvas(
|
||||
canvasRef: RefObject<HTMLCanvasElement | null>,
|
||||
containerRef: RefObject<HTMLDivElement | null>,
|
||||
) {
|
||||
const initialized = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const el = canvasRef.current
|
||||
const container = containerRef.current
|
||||
if (!el || !container || initialized.current) return
|
||||
|
||||
initialized.current = true
|
||||
|
||||
const canvas = new fabric.Canvas(el, {
|
||||
width: container.clientWidth,
|
||||
height: container.clientHeight,
|
||||
backgroundColor: CANVAS_BACKGROUND,
|
||||
selection: true,
|
||||
preserveObjectStacking: true,
|
||||
stopContextMenu: true,
|
||||
fireRightClick: true,
|
||||
})
|
||||
|
||||
// Selection styling
|
||||
canvas.selectionColor = 'rgba(59, 130, 246, 0.1)'
|
||||
canvas.selectionBorderColor = '#3b82f6'
|
||||
canvas.selectionLineWidth = 1
|
||||
|
||||
useCanvasStore.getState().setFabricCanvas(canvas)
|
||||
canvas.requestRenderAll()
|
||||
|
||||
// Resize observer
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect
|
||||
canvas.setDimensions({ width, height })
|
||||
canvas.requestRenderAll()
|
||||
}
|
||||
})
|
||||
resizeObserver.observe(container)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
useCanvasStore.getState().setFabricCanvas(null)
|
||||
canvas.dispose()
|
||||
initialized.current = false
|
||||
}
|
||||
}, [canvasRef, containerRef])
|
||||
}
|
||||
67
src/components/Header.tsx
Normal file
67
src/components/Header.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { Link } from '@tanstack/react-router'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Home, Menu, X } from 'lucide-react'
|
||||
|
||||
export default function Header() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="p-4 flex items-center bg-gray-800 text-white shadow-lg">
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<Menu size={24} />
|
||||
</button>
|
||||
<h1 className="ml-4 text-xl font-semibold">
|
||||
<Link to="/">
|
||||
<img
|
||||
src="/tanstack-word-logo-white.svg"
|
||||
alt="TanStack Logo"
|
||||
className="h-10"
|
||||
/>
|
||||
</Link>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<aside
|
||||
className={`fixed top-0 left-0 h-full w-80 bg-gray-900 text-white shadow-2xl z-50 transform transition-transform duration-300 ease-in-out flex flex-col ${
|
||||
isOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
||||
<h2 className="text-xl font-bold">Navigation</h2>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="p-2 hover:bg-gray-800 rounded-lg transition-colors"
|
||||
aria-label="Close menu"
|
||||
>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 p-4 overflow-y-auto">
|
||||
<Link
|
||||
to="/"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
|
||||
activeProps={{
|
||||
className:
|
||||
'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',
|
||||
}}
|
||||
>
|
||||
<Home size={20} />
|
||||
<span className="font-medium">Home</span>
|
||||
</Link>
|
||||
|
||||
{/* Demo Links Start */}
|
||||
|
||||
{/* Demo Links End */}
|
||||
</nav>
|
||||
</aside>
|
||||
</>
|
||||
)
|
||||
}
|
||||
27
src/components/editor/EditorLayout.tsx
Normal file
27
src/components/editor/EditorLayout.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { lazy, Suspense } from 'react'
|
||||
import Toolbar from './Toolbar'
|
||||
import LayerPanel from '@/components/panels/LayerPanel'
|
||||
import PropertyPanel from '@/components/panels/PropertyPanel'
|
||||
|
||||
const FabricCanvas = lazy(() => import('@/canvas/FabricCanvas'))
|
||||
|
||||
export default function EditorLayout() {
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-gray-900">
|
||||
<Toolbar />
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
<LayerPanel />
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex-1 flex items-center justify-center bg-neutral-100 text-gray-400">
|
||||
Loading canvas...
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<FabricCanvas />
|
||||
</Suspense>
|
||||
<PropertyPanel />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
37
src/components/editor/ToolButton.tsx
Normal file
37
src/components/editor/ToolButton.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import type { ToolType } from '@/types/canvas'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
|
||||
interface ToolButtonProps {
|
||||
tool: ToolType
|
||||
icon: ReactNode
|
||||
label: string
|
||||
shortcut?: string
|
||||
}
|
||||
|
||||
export default function ToolButton({
|
||||
tool,
|
||||
icon,
|
||||
label,
|
||||
shortcut,
|
||||
}: ToolButtonProps) {
|
||||
const activeTool = useCanvasStore((s) => s.activeTool)
|
||||
const setActiveTool = useCanvasStore((s) => s.setActiveTool)
|
||||
const isActive = activeTool === tool
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTool(tool)}
|
||||
title={shortcut ? `${label} (${shortcut})` : label}
|
||||
aria-label={label}
|
||||
className={`p-1.5 rounded transition-colors ${
|
||||
isActive
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'text-gray-400 hover:bg-gray-700 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
126
src/components/editor/Toolbar.tsx
Normal file
126
src/components/editor/Toolbar.tsx
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import {
|
||||
MousePointer2,
|
||||
Square,
|
||||
Circle,
|
||||
Minus,
|
||||
Type,
|
||||
Frame,
|
||||
Hand,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
} from 'lucide-react'
|
||||
import ToolButton from './ToolButton'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import { MIN_ZOOM, MAX_ZOOM } from '@/canvas/canvas-constants'
|
||||
|
||||
export default function Toolbar() {
|
||||
const zoom = useCanvasStore((s) => s.viewport.zoom)
|
||||
const fabricCanvas = useCanvasStore((s) => s.fabricCanvas)
|
||||
|
||||
const handleZoomIn = () => {
|
||||
if (!fabricCanvas) return
|
||||
const newZoom = Math.min(MAX_ZOOM, zoom * 1.25)
|
||||
const center = fabricCanvas.getCenterPoint()
|
||||
fabricCanvas.zoomToPoint(center, newZoom)
|
||||
useCanvasStore.getState().setZoom(newZoom)
|
||||
fabricCanvas.requestRenderAll()
|
||||
}
|
||||
|
||||
const handleZoomOut = () => {
|
||||
if (!fabricCanvas) return
|
||||
const newZoom = Math.max(MIN_ZOOM, zoom / 1.25)
|
||||
const center = fabricCanvas.getCenterPoint()
|
||||
fabricCanvas.zoomToPoint(center, newZoom)
|
||||
useCanvasStore.getState().setZoom(newZoom)
|
||||
fabricCanvas.requestRenderAll()
|
||||
}
|
||||
|
||||
const handleZoomReset = () => {
|
||||
if (!fabricCanvas) return
|
||||
const center = fabricCanvas.getCenterPoint()
|
||||
fabricCanvas.zoomToPoint(center, 1)
|
||||
useCanvasStore.getState().setZoom(1)
|
||||
fabricCanvas.requestRenderAll()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-10 bg-gray-800 border-b border-gray-700 flex items-center px-2 gap-1 shrink-0">
|
||||
{/* Drawing Tools */}
|
||||
<div className="flex items-center gap-0.5 border-r border-gray-700 pr-2 mr-1">
|
||||
<ToolButton
|
||||
tool="select"
|
||||
icon={<MousePointer2 size={16} />}
|
||||
label="Select"
|
||||
shortcut="V"
|
||||
/>
|
||||
<ToolButton
|
||||
tool="frame"
|
||||
icon={<Frame size={16} />}
|
||||
label="Frame"
|
||||
shortcut="F"
|
||||
/>
|
||||
<ToolButton
|
||||
tool="rectangle"
|
||||
icon={<Square size={16} />}
|
||||
label="Rectangle"
|
||||
shortcut="R"
|
||||
/>
|
||||
<ToolButton
|
||||
tool="ellipse"
|
||||
icon={<Circle size={16} />}
|
||||
label="Ellipse"
|
||||
shortcut="O"
|
||||
/>
|
||||
<ToolButton
|
||||
tool="line"
|
||||
icon={<Minus size={16} />}
|
||||
label="Line"
|
||||
shortcut="L"
|
||||
/>
|
||||
<ToolButton
|
||||
tool="text"
|
||||
icon={<Type size={16} />}
|
||||
label="Text"
|
||||
shortcut="T"
|
||||
/>
|
||||
<ToolButton
|
||||
tool="hand"
|
||||
icon={<Hand size={16} />}
|
||||
label="Hand"
|
||||
shortcut="H"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Zoom Controls */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleZoomOut}
|
||||
className="p-1 text-gray-400 hover:text-white rounded hover:bg-gray-700 transition-colors"
|
||||
title="Zoom Out"
|
||||
>
|
||||
<ZoomOut size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleZoomReset}
|
||||
className="text-xs text-gray-300 px-1.5 py-0.5 rounded hover:bg-gray-700 transition-colors tabular-nums min-w-[3.5rem] text-center"
|
||||
title="Reset Zoom"
|
||||
>
|
||||
{Math.round(zoom * 100)}%
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleZoomIn}
|
||||
className="p-1 text-gray-400 hover:text-white rounded hover:bg-gray-700 transition-colors"
|
||||
title="Zoom In"
|
||||
>
|
||||
<ZoomIn size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
30
src/components/panels/AppearanceSection.tsx
Normal file
30
src/components/panels/AppearanceSection.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import SliderInput from '@/components/shared/SliderInput'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
|
||||
interface AppearanceSectionProps {
|
||||
node: PenNode
|
||||
onUpdate: (updates: Partial<PenNode>) => void
|
||||
}
|
||||
|
||||
export default function AppearanceSection({
|
||||
node,
|
||||
onUpdate,
|
||||
}: AppearanceSectionProps) {
|
||||
const opacity =
|
||||
typeof node.opacity === 'number' ? node.opacity * 100 : 100
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Appearance
|
||||
</h4>
|
||||
<SliderInput
|
||||
label="Opacity"
|
||||
value={opacity}
|
||||
onChange={(v) => onUpdate({ opacity: v / 100 })}
|
||||
min={0}
|
||||
max={100}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
35
src/components/panels/CornerRadiusSection.tsx
Normal file
35
src/components/panels/CornerRadiusSection.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import NumberInput from '@/components/shared/NumberInput'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
|
||||
interface CornerRadiusSectionProps {
|
||||
cornerRadius?: number | [number, number, number, number]
|
||||
onUpdate: (updates: Partial<PenNode>) => void
|
||||
}
|
||||
|
||||
export default function CornerRadiusSection({
|
||||
cornerRadius,
|
||||
onUpdate,
|
||||
}: CornerRadiusSectionProps) {
|
||||
const value =
|
||||
typeof cornerRadius === 'number'
|
||||
? cornerRadius
|
||||
: Array.isArray(cornerRadius)
|
||||
? cornerRadius[0]
|
||||
: 0
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Corner Radius
|
||||
</h4>
|
||||
<NumberInput
|
||||
value={value}
|
||||
onChange={(v) =>
|
||||
onUpdate({ cornerRadius: v } as Partial<PenNode>)
|
||||
}
|
||||
min={0}
|
||||
max={999}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
32
src/components/panels/FillSection.tsx
Normal file
32
src/components/panels/FillSection.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import ColorPicker from '@/components/shared/ColorPicker'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import type { PenFill } from '@/types/styles'
|
||||
|
||||
interface FillSectionProps {
|
||||
fills?: PenFill[]
|
||||
onUpdate: (updates: Partial<PenNode>) => void
|
||||
}
|
||||
|
||||
export default function FillSection({
|
||||
fills,
|
||||
onUpdate,
|
||||
}: FillSectionProps) {
|
||||
const currentColor =
|
||||
fills && fills.length > 0 && fills[0].type === 'solid'
|
||||
? fills[0].color
|
||||
: '#d1d5db'
|
||||
|
||||
const handleColorChange = (color: string) => {
|
||||
const newFills: PenFill[] = [{ type: 'solid', color }]
|
||||
onUpdate({ fill: newFills } as Partial<PenNode>)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Fill
|
||||
</h4>
|
||||
<ColorPicker value={currentColor} onChange={handleColorChange} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
128
src/components/panels/LayerItem.tsx
Normal file
128
src/components/panels/LayerItem.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { useState } from 'react'
|
||||
import {
|
||||
Square,
|
||||
Circle,
|
||||
Type,
|
||||
Minus,
|
||||
Frame,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Lock,
|
||||
Unlock,
|
||||
FolderOpen,
|
||||
Hexagon,
|
||||
Spline,
|
||||
Link,
|
||||
} from 'lucide-react'
|
||||
import type { PenNodeType } from '@/types/pen'
|
||||
|
||||
const TYPE_ICONS: Record<PenNodeType, typeof Square> = {
|
||||
rectangle: Square,
|
||||
ellipse: Circle,
|
||||
text: Type,
|
||||
line: Minus,
|
||||
frame: Frame,
|
||||
group: FolderOpen,
|
||||
polygon: Hexagon,
|
||||
path: Spline,
|
||||
ref: Link,
|
||||
}
|
||||
|
||||
interface LayerItemProps {
|
||||
id: string
|
||||
name: string
|
||||
type: PenNodeType
|
||||
depth: number
|
||||
selected: boolean
|
||||
onSelect: (id: string) => void
|
||||
onRename: (id: string, name: string) => void
|
||||
}
|
||||
|
||||
export default function LayerItem({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
depth,
|
||||
selected,
|
||||
onSelect,
|
||||
onRename,
|
||||
}: LayerItemProps) {
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editName, setEditName] = useState(name)
|
||||
const [visible, setVisible] = useState(true)
|
||||
const [locked, setLocked] = useState(false)
|
||||
|
||||
const Icon = TYPE_ICONS[type] ?? Square
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
setEditName(name)
|
||||
setIsEditing(true)
|
||||
}
|
||||
|
||||
const handleRenameBlur = () => {
|
||||
setIsEditing(false)
|
||||
if (editName.trim() && editName !== name) {
|
||||
onRename(id, editName.trim())
|
||||
}
|
||||
}
|
||||
|
||||
const handleRenameKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') handleRenameBlur()
|
||||
if (e.key === 'Escape') {
|
||||
setIsEditing(false)
|
||||
setEditName(name)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center h-7 px-1 gap-1 cursor-pointer rounded text-xs transition-colors ${
|
||||
selected
|
||||
? 'bg-blue-500/20 text-blue-300'
|
||||
: 'text-gray-400 hover:bg-gray-700/50'
|
||||
}`}
|
||||
style={{ paddingLeft: `${depth * 12 + 4}px` }}
|
||||
onClick={() => onSelect(id)}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
>
|
||||
<Icon size={12} className="shrink-0 opacity-60" />
|
||||
|
||||
{isEditing ? (
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
onBlur={handleRenameBlur}
|
||||
onKeyDown={handleRenameKeyDown}
|
||||
className="flex-1 bg-gray-700 text-white text-xs px-1 py-0.5 rounded border border-blue-500 focus:outline-none"
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span className="flex-1 truncate">{name}</span>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setVisible(!visible)
|
||||
}}
|
||||
className="p-0.5 opacity-0 group-hover:opacity-100 hover:opacity-100 transition-opacity"
|
||||
title={visible ? 'Hide' : 'Show'}
|
||||
>
|
||||
{visible ? <Eye size={10} /> : <EyeOff size={10} />}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setLocked(!locked)
|
||||
}}
|
||||
className="p-0.5 opacity-0 group-hover:opacity-100 hover:opacity-100 transition-opacity"
|
||||
title={locked ? 'Unlock' : 'Lock'}
|
||||
>
|
||||
{locked ? <Lock size={10} /> : <Unlock size={10} />}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
91
src/components/panels/LayerPanel.tsx
Normal file
91
src/components/panels/LayerPanel.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { useDocumentStore } from '@/stores/document-store'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import type { FabricObjectWithPenId } from '@/canvas/canvas-object-factory'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import LayerItem from './LayerItem'
|
||||
|
||||
function renderLayerTree(
|
||||
nodes: PenNode[],
|
||||
depth: number,
|
||||
selectedIds: string[],
|
||||
onSelect: (id: string) => void,
|
||||
onRename: (id: string, name: string) => void,
|
||||
) {
|
||||
// Render in reverse order so top items appear at top of panel
|
||||
return [...nodes].reverse().map((node) => (
|
||||
<div key={node.id} className="group">
|
||||
<LayerItem
|
||||
id={node.id}
|
||||
name={node.name ?? node.type}
|
||||
type={node.type}
|
||||
depth={depth}
|
||||
selected={selectedIds.includes(node.id)}
|
||||
onSelect={onSelect}
|
||||
onRename={onRename}
|
||||
/>
|
||||
{'children' in node &&
|
||||
node.children &&
|
||||
node.children.length > 0 &&
|
||||
renderLayerTree(
|
||||
node.children,
|
||||
depth + 1,
|
||||
selectedIds,
|
||||
onSelect,
|
||||
onRename,
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
export default function LayerPanel() {
|
||||
const children = useDocumentStore((s) => s.document.children)
|
||||
const updateNode = useDocumentStore((s) => s.updateNode)
|
||||
const selectedIds = useCanvasStore((s) => s.selection.selectedIds)
|
||||
const setSelection = useCanvasStore((s) => s.setSelection)
|
||||
const fabricCanvas = useCanvasStore((s) => s.fabricCanvas)
|
||||
|
||||
const handleSelect = (id: string) => {
|
||||
setSelection([id], id)
|
||||
|
||||
// Also select the corresponding fabric object
|
||||
if (fabricCanvas) {
|
||||
const objects = fabricCanvas.getObjects()
|
||||
const target = objects.find(
|
||||
(o) => (o as FabricObjectWithPenId).penNodeId === id,
|
||||
)
|
||||
if (target) {
|
||||
fabricCanvas.setActiveObject(target)
|
||||
fabricCanvas.requestRenderAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleRename = (id: string, name: string) => {
|
||||
updateNode(id, { name })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-56 bg-gray-800 border-r border-gray-700 flex flex-col shrink-0">
|
||||
<div className="h-8 flex items-center px-3 border-b border-gray-700">
|
||||
<span className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Layers
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto py-1 px-1">
|
||||
{children.length === 0 ? (
|
||||
<p className="text-xs text-gray-500 text-center mt-4 px-2">
|
||||
No layers yet. Use the toolbar to draw shapes.
|
||||
</p>
|
||||
) : (
|
||||
renderLayerTree(
|
||||
children,
|
||||
0,
|
||||
selectedIds,
|
||||
handleSelect,
|
||||
handleRename,
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
92
src/components/panels/PropertyPanel.tsx
Normal file
92
src/components/panels/PropertyPanel.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import { useDocumentStore } from '@/stores/document-store'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import SizeSection from './SizeSection'
|
||||
import FillSection from './FillSection'
|
||||
import StrokeSection from './StrokeSection'
|
||||
import AppearanceSection from './AppearanceSection'
|
||||
import CornerRadiusSection from './CornerRadiusSection'
|
||||
import TextSection from './TextSection'
|
||||
|
||||
export default function PropertyPanel() {
|
||||
const activeId = useCanvasStore((s) => s.selection.activeId)
|
||||
const children = useDocumentStore((s) => s.document.children)
|
||||
const getNodeById = useDocumentStore((s) => s.getNodeById)
|
||||
const updateNode = useDocumentStore((s) => s.updateNode)
|
||||
|
||||
// Subscribe to `children` so we re-render when nodes change
|
||||
void children
|
||||
const node = activeId ? getNodeById(activeId) : undefined
|
||||
|
||||
const handleUpdate = (updates: Partial<PenNode>) => {
|
||||
if (activeId) {
|
||||
updateNode(activeId, updates)
|
||||
}
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
return (
|
||||
<div className="w-64 bg-gray-800 border-l border-gray-700 flex flex-col shrink-0">
|
||||
<div className="h-8 flex items-center px-3 border-b border-gray-700">
|
||||
<span className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Properties
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<p className="text-xs text-gray-500 px-4 text-center">
|
||||
Select an element to view its properties.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const hasFill =
|
||||
node.type !== 'line' && node.type !== 'ref'
|
||||
const hasStroke = node.type !== 'ref'
|
||||
const hasCornerRadius =
|
||||
node.type === 'rectangle' || node.type === 'frame'
|
||||
const isText = node.type === 'text'
|
||||
|
||||
return (
|
||||
<div className="w-64 bg-gray-800 border-l border-gray-700 flex flex-col shrink-0">
|
||||
<div className="h-8 flex items-center px-3 border-b border-gray-700">
|
||||
<span className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
{node.name ?? node.type}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-3 space-y-4">
|
||||
<SizeSection node={node} onUpdate={handleUpdate} />
|
||||
|
||||
{hasFill && (
|
||||
<FillSection
|
||||
fills={'fill' in node ? node.fill : undefined}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasStroke && (
|
||||
<StrokeSection
|
||||
stroke={'stroke' in node ? node.stroke : undefined}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasCornerRadius && (
|
||||
<CornerRadiusSection
|
||||
cornerRadius={
|
||||
'cornerRadius' in node ? node.cornerRadius : undefined
|
||||
}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AppearanceSection node={node} onUpdate={handleUpdate} />
|
||||
|
||||
{isText && node.type === 'text' && (
|
||||
<TextSection node={node} onUpdate={handleUpdate} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
68
src/components/panels/SizeSection.tsx
Normal file
68
src/components/panels/SizeSection.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import NumberInput from '@/components/shared/NumberInput'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
|
||||
interface SizeSectionProps {
|
||||
node: PenNode
|
||||
onUpdate: (updates: Partial<PenNode>) => void
|
||||
}
|
||||
|
||||
export default function SizeSection({ node, onUpdate }: SizeSectionProps) {
|
||||
const x = node.x ?? 0
|
||||
const y = node.y ?? 0
|
||||
const rotation = node.rotation ?? 0
|
||||
|
||||
const width =
|
||||
'width' in node && typeof node.width === 'number'
|
||||
? node.width
|
||||
: undefined
|
||||
const height =
|
||||
'height' in node && typeof node.height === 'number'
|
||||
? node.height
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Transform
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
<NumberInput
|
||||
label="X"
|
||||
value={Math.round(x)}
|
||||
onChange={(v) => onUpdate({ x: v })}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Y"
|
||||
value={Math.round(y)}
|
||||
onChange={(v) => onUpdate({ y: v })}
|
||||
/>
|
||||
{width !== undefined && (
|
||||
<NumberInput
|
||||
label="W"
|
||||
value={Math.round(width)}
|
||||
onChange={(v) =>
|
||||
onUpdate({ width: v } as Partial<PenNode>)
|
||||
}
|
||||
min={1}
|
||||
/>
|
||||
)}
|
||||
{height !== undefined && (
|
||||
<NumberInput
|
||||
label="H"
|
||||
value={Math.round(height)}
|
||||
onChange={(v) =>
|
||||
onUpdate({ height: v } as Partial<PenNode>)
|
||||
}
|
||||
min={1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<NumberInput
|
||||
label="R"
|
||||
value={Math.round(rotation)}
|
||||
onChange={(v) => onUpdate({ rotation: v })}
|
||||
suffix="°"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
src/components/panels/StrokeSection.tsx
Normal file
61
src/components/panels/StrokeSection.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import ColorPicker from '@/components/shared/ColorPicker'
|
||||
import NumberInput from '@/components/shared/NumberInput'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import type { PenStroke, PenFill } from '@/types/styles'
|
||||
|
||||
interface StrokeSectionProps {
|
||||
stroke?: PenStroke
|
||||
onUpdate: (updates: Partial<PenNode>) => void
|
||||
}
|
||||
|
||||
export default function StrokeSection({
|
||||
stroke,
|
||||
onUpdate,
|
||||
}: StrokeSectionProps) {
|
||||
const strokeColor =
|
||||
stroke?.fill && stroke.fill.length > 0 && stroke.fill[0].type === 'solid'
|
||||
? stroke.fill[0].color
|
||||
: '#374151'
|
||||
|
||||
const strokeWidth =
|
||||
stroke && typeof stroke.thickness === 'number'
|
||||
? stroke.thickness
|
||||
: 0
|
||||
|
||||
const handleColorChange = (color: string) => {
|
||||
const newFill: PenFill[] = [{ type: 'solid', color }]
|
||||
const newStroke: PenStroke = {
|
||||
...(stroke ?? { thickness: 1 }),
|
||||
fill: newFill,
|
||||
}
|
||||
onUpdate({ stroke: newStroke } as Partial<PenNode>)
|
||||
}
|
||||
|
||||
const handleWidthChange = (width: number) => {
|
||||
const newStroke: PenStroke = {
|
||||
...(stroke ?? {
|
||||
thickness: 1,
|
||||
fill: [{ type: 'solid', color: strokeColor }],
|
||||
}),
|
||||
thickness: width,
|
||||
}
|
||||
onUpdate({ stroke: newStroke } as Partial<PenNode>)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Stroke
|
||||
</h4>
|
||||
<ColorPicker value={strokeColor} onChange={handleColorChange} />
|
||||
<NumberInput
|
||||
label="W"
|
||||
value={strokeWidth}
|
||||
onChange={handleWidthChange}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
86
src/components/panels/TextSection.tsx
Normal file
86
src/components/panels/TextSection.tsx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import NumberInput from '@/components/shared/NumberInput'
|
||||
import DropdownSelect from '@/components/shared/DropdownSelect'
|
||||
import type { PenNode, TextNode } from '@/types/pen'
|
||||
|
||||
interface TextSectionProps {
|
||||
node: TextNode
|
||||
onUpdate: (updates: Partial<PenNode>) => void
|
||||
}
|
||||
|
||||
const FONT_OPTIONS = [
|
||||
{ value: 'Inter, sans-serif', label: 'Inter' },
|
||||
{ value: 'Arial, sans-serif', label: 'Arial' },
|
||||
{ value: 'Helvetica, sans-serif', label: 'Helvetica' },
|
||||
{ value: 'Georgia, serif', label: 'Georgia' },
|
||||
{ value: 'Times New Roman, serif', label: 'Times' },
|
||||
{ value: 'Courier New, monospace', label: 'Courier' },
|
||||
]
|
||||
|
||||
const WEIGHT_OPTIONS = [
|
||||
{ value: '100', label: 'Thin' },
|
||||
{ value: '300', label: 'Light' },
|
||||
{ value: '400', label: 'Regular' },
|
||||
{ value: '500', label: 'Medium' },
|
||||
{ value: '600', label: 'Semibold' },
|
||||
{ value: '700', label: 'Bold' },
|
||||
{ value: '900', label: 'Black' },
|
||||
]
|
||||
|
||||
const ALIGN_OPTIONS = [
|
||||
{ value: 'left', label: 'Left' },
|
||||
{ value: 'center', label: 'Center' },
|
||||
{ value: 'right', label: 'Right' },
|
||||
{ value: 'justify', label: 'Justify' },
|
||||
]
|
||||
|
||||
export default function TextSection({
|
||||
node,
|
||||
onUpdate,
|
||||
}: TextSectionProps) {
|
||||
const fontFamily = node.fontFamily ?? 'Inter, sans-serif'
|
||||
const fontSize = node.fontSize ?? 16
|
||||
const fontWeight = String(node.fontWeight ?? '400')
|
||||
const textAlign = node.textAlign ?? 'left'
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xs font-medium text-gray-300 uppercase tracking-wider">
|
||||
Typography
|
||||
</h4>
|
||||
<DropdownSelect
|
||||
label="Font"
|
||||
value={fontFamily}
|
||||
options={FONT_OPTIONS}
|
||||
onChange={(v) => onUpdate({ fontFamily: v } as Partial<PenNode>)}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
<NumberInput
|
||||
label="Sz"
|
||||
value={fontSize}
|
||||
onChange={(v) =>
|
||||
onUpdate({ fontSize: v } as Partial<PenNode>)
|
||||
}
|
||||
min={1}
|
||||
max={999}
|
||||
/>
|
||||
<DropdownSelect
|
||||
value={fontWeight}
|
||||
options={WEIGHT_OPTIONS}
|
||||
onChange={(v) =>
|
||||
onUpdate({ fontWeight: Number(v) } as Partial<PenNode>)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<DropdownSelect
|
||||
label="Align"
|
||||
value={textAlign}
|
||||
options={ALIGN_OPTIONS}
|
||||
onChange={(v) =>
|
||||
onUpdate({
|
||||
textAlign: v as TextNode['textAlign'],
|
||||
} as Partial<PenNode>)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
66
src/components/shared/ColorPicker.tsx
Normal file
66
src/components/shared/ColorPicker.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { useState, useRef, useEffect } from 'react'
|
||||
|
||||
interface ColorPickerProps {
|
||||
value: string
|
||||
onChange: (color: string) => void
|
||||
label?: string
|
||||
}
|
||||
|
||||
export default function ColorPicker({
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
}: ColorPickerProps) {
|
||||
const [hexInput, setHexInput] = useState(value)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setHexInput(value)
|
||||
}, [value])
|
||||
|
||||
const handleHexChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const v = e.target.value
|
||||
setHexInput(v)
|
||||
if (/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(v)) {
|
||||
onChange(v)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNativeChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
onChange(e.target.value)
|
||||
setHexInput(e.target.value)
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
if (!/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(hexInput)) {
|
||||
setHexInput(value)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{label && (
|
||||
<span className="text-xs text-gray-400">{label}</span>
|
||||
)}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="color"
|
||||
value={value.slice(0, 7)}
|
||||
onChange={handleNativeChange}
|
||||
className="w-6 h-6 rounded border border-gray-600 cursor-pointer bg-transparent p-0"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={hexInput}
|
||||
onChange={handleHexChange}
|
||||
onBlur={handleBlur}
|
||||
className="flex-1 bg-gray-700 text-white text-xs px-1.5 py-1 rounded border border-gray-600 focus:border-blue-500 focus:outline-none font-mono"
|
||||
placeholder="#000000"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
src/components/shared/DropdownSelect.tsx
Normal file
34
src/components/shared/DropdownSelect.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
interface DropdownSelectProps {
|
||||
value: string
|
||||
options: { value: string; label: string }[]
|
||||
onChange: (value: string) => void
|
||||
label?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function DropdownSelect({
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
label,
|
||||
className = '',
|
||||
}: DropdownSelectProps) {
|
||||
return (
|
||||
<div className={`flex items-center gap-2 ${className}`}>
|
||||
{label && (
|
||||
<span className="text-xs text-gray-400">{label}</span>
|
||||
)}
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="flex-1 bg-gray-700 text-white text-xs px-1.5 py-1 rounded border border-gray-600 focus:border-blue-500 focus:outline-none cursor-pointer"
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
src/components/shared/IconButton.tsx
Normal file
34
src/components/shared/IconButton.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import type { ReactNode, ButtonHTMLAttributes } from 'react'
|
||||
|
||||
interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
icon: ReactNode
|
||||
label: string
|
||||
active?: boolean
|
||||
size?: 'sm' | 'md'
|
||||
}
|
||||
|
||||
export default function IconButton({
|
||||
icon,
|
||||
label,
|
||||
active = false,
|
||||
size = 'md',
|
||||
className = '',
|
||||
...props
|
||||
}: IconButtonProps) {
|
||||
const sizeClass = size === 'sm' ? 'p-1' : 'p-1.5'
|
||||
const activeClass = active
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'text-gray-400 hover:bg-gray-700 hover:text-white'
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
title={label}
|
||||
aria-label={label}
|
||||
className={`${sizeClass} rounded transition-colors ${activeClass} ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
111
src/components/shared/NumberInput.tsx
Normal file
111
src/components/shared/NumberInput.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { useState, useRef, useCallback, useEffect } from 'react'
|
||||
|
||||
interface NumberInputProps {
|
||||
value: number
|
||||
onChange: (value: number) => void
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
label?: string
|
||||
suffix?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function NumberInput({
|
||||
value,
|
||||
onChange,
|
||||
min,
|
||||
max,
|
||||
step = 1,
|
||||
label,
|
||||
suffix,
|
||||
className = '',
|
||||
}: NumberInputProps) {
|
||||
const [localValue, setLocalValue] = useState(String(value))
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const dragStartY = useRef(0)
|
||||
const dragStartValue = useRef(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDragging) {
|
||||
setLocalValue(String(Math.round(value * 100) / 100))
|
||||
}
|
||||
}, [value, isDragging])
|
||||
|
||||
const clamp = useCallback(
|
||||
(v: number) => {
|
||||
let result = v
|
||||
if (min !== undefined) result = Math.max(min, result)
|
||||
if (max !== undefined) result = Math.min(max, result)
|
||||
return result
|
||||
},
|
||||
[min, max],
|
||||
)
|
||||
|
||||
const handleBlur = () => {
|
||||
const parsed = parseFloat(localValue)
|
||||
if (!isNaN(parsed)) {
|
||||
onChange(clamp(parsed))
|
||||
} else {
|
||||
setLocalValue(String(value))
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleBlur()
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault()
|
||||
onChange(clamp(value + step))
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
onChange(clamp(value - step))
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
if (e.target instanceof HTMLInputElement) return
|
||||
setIsDragging(true)
|
||||
dragStartY.current = e.clientY
|
||||
dragStartValue.current = value
|
||||
|
||||
const handleMouseMove = (ev: MouseEvent) => {
|
||||
const delta = dragStartY.current - ev.clientY
|
||||
const newValue = clamp(dragStartValue.current + delta * step)
|
||||
onChange(newValue)
|
||||
}
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(false)
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-1 ${className}`}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
{label && (
|
||||
<span className="text-xs text-gray-400 w-5 cursor-ew-resize select-none">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
type="text"
|
||||
value={localValue}
|
||||
onChange={(e) => setLocalValue(e.target.value)}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full bg-gray-700 text-white text-xs px-1.5 py-1 rounded border border-gray-600 focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
{suffix && (
|
||||
<span className="text-xs text-gray-500">{suffix}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
41
src/components/shared/SliderInput.tsx
Normal file
41
src/components/shared/SliderInput.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
interface SliderInputProps {
|
||||
value: number
|
||||
onChange: (value: number) => void
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
label?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function SliderInput({
|
||||
value,
|
||||
onChange,
|
||||
min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
label,
|
||||
className = '',
|
||||
}: SliderInputProps) {
|
||||
return (
|
||||
<div className={`flex items-center gap-2 ${className}`}>
|
||||
{label && (
|
||||
<span className="text-xs text-gray-400 w-12 shrink-0">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={(e) => onChange(Number(e.target.value))}
|
||||
className="flex-1 h-1 accent-blue-500 cursor-pointer"
|
||||
/>
|
||||
<span className="text-xs text-gray-300 w-8 text-right tabular-nums">
|
||||
{Math.round(value)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
134
src/hooks/use-keyboard-shortcuts.ts
Normal file
134
src/hooks/use-keyboard-shortcuts.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import { useEffect } from 'react'
|
||||
import { ActiveSelection } from 'fabric'
|
||||
import { useCanvasStore } from '@/stores/canvas-store'
|
||||
import { useDocumentStore } from '@/stores/document-store'
|
||||
import type { ToolType } from '@/types/canvas'
|
||||
|
||||
const TOOL_KEYS: Record<string, ToolType> = {
|
||||
v: 'select',
|
||||
f: 'frame',
|
||||
r: 'rectangle',
|
||||
o: 'ellipse',
|
||||
l: 'line',
|
||||
t: 'text',
|
||||
p: 'path',
|
||||
h: 'hand',
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts() {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Skip if user is typing in an input
|
||||
const target = e.target as HTMLElement
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const isMod = e.metaKey || e.ctrlKey
|
||||
|
||||
// Tool shortcuts (single key, no modifier)
|
||||
if (!isMod && !e.shiftKey && !e.altKey) {
|
||||
const tool = TOOL_KEYS[e.key.toLowerCase()]
|
||||
if (tool) {
|
||||
e.preventDefault()
|
||||
useCanvasStore.getState().setActiveTool(tool)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Escape: deselect all
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
useCanvasStore.getState().clearSelection()
|
||||
useCanvasStore.getState().setActiveTool('select')
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (canvas) {
|
||||
canvas.discardActiveObject()
|
||||
canvas.requestRenderAll()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete / Backspace: remove selected elements
|
||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
const { selectedIds } = useCanvasStore.getState().selection
|
||||
if (selectedIds.length > 0) {
|
||||
e.preventDefault()
|
||||
for (const id of selectedIds) {
|
||||
useDocumentStore.getState().removeNode(id)
|
||||
}
|
||||
useCanvasStore.getState().clearSelection()
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (canvas) {
|
||||
canvas.discardActiveObject()
|
||||
canvas.requestRenderAll()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Cmd+A: select all
|
||||
if (isMod && e.key === 'a') {
|
||||
e.preventDefault()
|
||||
const allNodes = useDocumentStore.getState().getFlatNodes()
|
||||
const ids = allNodes.map((n) => n.id)
|
||||
useCanvasStore.getState().setSelection(ids, ids[0] ?? null)
|
||||
const canvas = useCanvasStore.getState().fabricCanvas
|
||||
if (canvas) {
|
||||
const objects = canvas.getObjects()
|
||||
if (objects.length > 0) {
|
||||
const sel = new ActiveSelection(objects, { canvas })
|
||||
canvas.setActiveObject(sel)
|
||||
canvas.requestRenderAll()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// [ ] : reorder layers
|
||||
if (e.key === '[') {
|
||||
e.preventDefault()
|
||||
const { selectedIds } = useCanvasStore.getState().selection
|
||||
for (const id of selectedIds) {
|
||||
useDocumentStore.getState().reorderNode(id, 'down')
|
||||
}
|
||||
return
|
||||
}
|
||||
if (e.key === ']') {
|
||||
e.preventDefault()
|
||||
const { selectedIds } = useCanvasStore.getState().selection
|
||||
for (const id of selectedIds) {
|
||||
useDocumentStore.getState().reorderNode(id, 'up')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Arrow keys: nudge
|
||||
const nudgeKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
|
||||
if (nudgeKeys.includes(e.key) && !isMod) {
|
||||
const { selectedIds } = useCanvasStore.getState().selection
|
||||
if (selectedIds.length === 0) return
|
||||
e.preventDefault()
|
||||
const amount = e.shiftKey ? 10 : 1
|
||||
for (const id of selectedIds) {
|
||||
const node = useDocumentStore.getState().getNodeById(id)
|
||||
if (!node) continue
|
||||
const updates: Record<string, number> = {}
|
||||
if (e.key === 'ArrowLeft') updates.x = (node.x ?? 0) - amount
|
||||
if (e.key === 'ArrowRight') updates.x = (node.x ?? 0) + amount
|
||||
if (e.key === 'ArrowUp') updates.y = (node.y ?? 0) - amount
|
||||
if (e.key === 'ArrowDown') updates.y = (node.y ?? 0) + amount
|
||||
useDocumentStore.getState().updateNode(id, updates)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [])
|
||||
}
|
||||
86
src/routeTree.gen.ts
Normal file
86
src/routeTree.gen.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as EditorRouteImport } from './routes/editor'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
|
||||
const EditorRoute = EditorRouteImport.update({
|
||||
id: '/editor',
|
||||
path: '/editor',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/editor': typeof EditorRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/editor': typeof EditorRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/editor': typeof EditorRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/editor'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/editor'
|
||||
id: '__root__' | '/' | '/editor'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
EditorRoute: typeof EditorRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/editor': {
|
||||
id: '/editor'
|
||||
path: '/editor'
|
||||
fullPath: '/editor'
|
||||
preLoaderRoute: typeof EditorRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
EditorRoute: EditorRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
}
|
||||
}
|
||||
20
src/router.tsx
Normal file
20
src/router.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
|
||||
import { routeTree } from './routeTree.gen'
|
||||
|
||||
export function getRouter() {
|
||||
const router = createTanStackRouter({
|
||||
routeTree,
|
||||
|
||||
scrollRestoration: true,
|
||||
defaultPreload: 'intent',
|
||||
defaultPreloadStaleTime: 0,
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: ReturnType<typeof getRouter>
|
||||
}
|
||||
}
|
||||
45
src/routes/__root.tsx
Normal file
45
src/routes/__root.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import {
|
||||
HeadContent,
|
||||
Scripts,
|
||||
createRootRoute,
|
||||
} from '@tanstack/react-router'
|
||||
|
||||
import appCss from '../styles.css?url'
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{
|
||||
charSet: 'utf-8',
|
||||
},
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1',
|
||||
},
|
||||
{
|
||||
title: 'OpenPencil',
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: appCss,
|
||||
},
|
||||
],
|
||||
}),
|
||||
shellComponent: RootDocument,
|
||||
})
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
16
src/routes/editor.tsx
Normal file
16
src/routes/editor.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import EditorLayout from '@/components/editor/EditorLayout'
|
||||
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
|
||||
|
||||
export const Route = createFileRoute('/editor')({
|
||||
component: EditorPage,
|
||||
head: () => ({
|
||||
meta: [{ title: 'OpenPencil Editor' }],
|
||||
}),
|
||||
})
|
||||
|
||||
function EditorPage() {
|
||||
useKeyboardShortcuts()
|
||||
|
||||
return <EditorLayout />
|
||||
}
|
||||
50
src/routes/index.tsx
Normal file
50
src/routes/index.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||
import { PenTool, Plus } from 'lucide-react'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: LandingPage,
|
||||
head: () => ({
|
||||
meta: [{ title: 'OpenPencil - Design as Code' }],
|
||||
}),
|
||||
})
|
||||
|
||||
function LandingPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 flex flex-col items-center justify-center text-white">
|
||||
<div className="text-center mb-12">
|
||||
<div className="flex items-center justify-center gap-3 mb-4">
|
||||
<PenTool size={40} className="text-blue-400" />
|
||||
<h1 className="text-5xl font-bold tracking-tight">
|
||||
Open
|
||||
<span className="text-blue-400">Pencil</span>
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-xl text-gray-400">
|
||||
Open-source vector design tool. Design as Code.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Link
|
||||
to="/editor"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors shadow-lg shadow-blue-500/25"
|
||||
>
|
||||
<Plus size={18} />
|
||||
New Design
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<p className="mt-8 text-sm text-gray-500">
|
||||
Press{' '}
|
||||
<kbd className="px-1.5 py-0.5 bg-gray-800 rounded text-gray-400 text-xs">
|
||||
Ctrl
|
||||
</kbd>
|
||||
{' + '}
|
||||
<kbd className="px-1.5 py-0.5 bg-gray-800 rounded text-gray-400 text-xs">
|
||||
N
|
||||
</kbd>
|
||||
{' '}to create a new design
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
56
src/stores/canvas-store.ts
Normal file
56
src/stores/canvas-store.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { create } from 'zustand'
|
||||
import type { Canvas } from 'fabric'
|
||||
import type {
|
||||
ToolType,
|
||||
ViewportState,
|
||||
SelectionState,
|
||||
CanvasInteraction,
|
||||
} from '@/types/canvas'
|
||||
|
||||
interface CanvasStoreState {
|
||||
activeTool: ToolType
|
||||
viewport: ViewportState
|
||||
selection: SelectionState
|
||||
interaction: CanvasInteraction
|
||||
fabricCanvas: Canvas | null
|
||||
|
||||
setActiveTool: (tool: ToolType) => void
|
||||
setZoom: (zoom: number) => void
|
||||
setPan: (x: number, y: number) => void
|
||||
setSelection: (ids: string[], activeId: string | null) => void
|
||||
clearSelection: () => void
|
||||
setInteraction: (partial: Partial<CanvasInteraction>) => void
|
||||
setFabricCanvas: (canvas: Canvas | null) => void
|
||||
}
|
||||
|
||||
export const useCanvasStore = create<CanvasStoreState>((set) => ({
|
||||
activeTool: 'select',
|
||||
viewport: { zoom: 1, panX: 0, panY: 0 },
|
||||
selection: { selectedIds: [], activeId: null },
|
||||
interaction: {
|
||||
isDrawing: false,
|
||||
isPanning: false,
|
||||
isDragging: false,
|
||||
drawStartPoint: null,
|
||||
},
|
||||
fabricCanvas: null,
|
||||
|
||||
setActiveTool: (tool) => set({ activeTool: tool }),
|
||||
|
||||
setZoom: (zoom) =>
|
||||
set((s) => ({ viewport: { ...s.viewport, zoom } })),
|
||||
|
||||
setPan: (panX, panY) =>
|
||||
set((s) => ({ viewport: { ...s.viewport, panX, panY } })),
|
||||
|
||||
setSelection: (selectedIds, activeId) =>
|
||||
set({ selection: { selectedIds, activeId } }),
|
||||
|
||||
clearSelection: () =>
|
||||
set({ selection: { selectedIds: [], activeId: null } }),
|
||||
|
||||
setInteraction: (partial) =>
|
||||
set((s) => ({ interaction: { ...s.interaction, ...partial } })),
|
||||
|
||||
setFabricCanvas: (fabricCanvas) => set({ fabricCanvas }),
|
||||
}))
|
||||
267
src/stores/document-store.ts
Normal file
267
src/stores/document-store.ts
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
import { create } from 'zustand'
|
||||
import type { PenDocument, PenNode } from '@/types/pen'
|
||||
|
||||
function createEmptyDocument(): PenDocument {
|
||||
return {
|
||||
version: '1.0.0',
|
||||
children: [],
|
||||
}
|
||||
}
|
||||
|
||||
function findNodeInTree(
|
||||
nodes: PenNode[],
|
||||
id: string,
|
||||
): PenNode | undefined {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node
|
||||
if ('children' in node && node.children) {
|
||||
const found = findNodeInTree(node.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function findParentInTree(
|
||||
nodes: PenNode[],
|
||||
id: string,
|
||||
): PenNode | undefined {
|
||||
for (const node of nodes) {
|
||||
if ('children' in node && node.children) {
|
||||
for (const child of node.children) {
|
||||
if (child.id === id) return node
|
||||
}
|
||||
const found = findParentInTree(node.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function removeNodeFromTree(nodes: PenNode[], id: string): PenNode[] {
|
||||
return nodes
|
||||
.filter((n) => n.id !== id)
|
||||
.map((n) => {
|
||||
if ('children' in n && n.children) {
|
||||
return { ...n, children: removeNodeFromTree(n.children, id) }
|
||||
}
|
||||
return n
|
||||
})
|
||||
}
|
||||
|
||||
function updateNodeInTree(
|
||||
nodes: PenNode[],
|
||||
id: string,
|
||||
updates: Partial<PenNode>,
|
||||
): PenNode[] {
|
||||
return nodes.map((n) => {
|
||||
if (n.id === id) {
|
||||
return { ...n, ...updates } as PenNode
|
||||
}
|
||||
if ('children' in n && n.children) {
|
||||
return {
|
||||
...n,
|
||||
children: updateNodeInTree(n.children, id, updates),
|
||||
} as PenNode
|
||||
}
|
||||
return n
|
||||
})
|
||||
}
|
||||
|
||||
function flattenNodes(nodes: PenNode[]): PenNode[] {
|
||||
const result: PenNode[] = []
|
||||
for (const node of nodes) {
|
||||
result.push(node)
|
||||
if ('children' in node && node.children) {
|
||||
result.push(...flattenNodes(node.children))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function insertNodeInTree(
|
||||
nodes: PenNode[],
|
||||
parentId: string | null,
|
||||
node: PenNode,
|
||||
index?: number,
|
||||
): PenNode[] {
|
||||
if (parentId === null) {
|
||||
const arr = [...nodes]
|
||||
if (index !== undefined) {
|
||||
arr.splice(index, 0, node)
|
||||
} else {
|
||||
arr.push(node)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
return nodes.map((n) => {
|
||||
if (n.id === parentId && 'children' in n) {
|
||||
const children = [...(n.children ?? [])]
|
||||
if (index !== undefined) {
|
||||
children.splice(index, 0, node)
|
||||
} else {
|
||||
children.push(node)
|
||||
}
|
||||
return { ...n, children } as PenNode
|
||||
}
|
||||
if ('children' in n && n.children) {
|
||||
return {
|
||||
...n,
|
||||
children: insertNodeInTree(n.children, parentId, node, index),
|
||||
} as PenNode
|
||||
}
|
||||
return n
|
||||
})
|
||||
}
|
||||
|
||||
interface DocumentStoreState {
|
||||
document: PenDocument
|
||||
fileName: string | null
|
||||
isDirty: boolean
|
||||
|
||||
addNode: (
|
||||
parentId: string | null,
|
||||
node: PenNode,
|
||||
index?: number,
|
||||
) => void
|
||||
updateNode: (id: string, updates: Partial<PenNode>) => void
|
||||
removeNode: (id: string) => void
|
||||
moveNode: (
|
||||
id: string,
|
||||
newParentId: string | null,
|
||||
index: number,
|
||||
) => void
|
||||
reorderNode: (id: string, direction: 'up' | 'down') => void
|
||||
getNodeById: (id: string) => PenNode | undefined
|
||||
getParentOf: (id: string) => PenNode | undefined
|
||||
getFlatNodes: () => PenNode[]
|
||||
|
||||
loadDocument: (doc: PenDocument, fileName?: string) => void
|
||||
newDocument: () => void
|
||||
markClean: () => void
|
||||
}
|
||||
|
||||
export const useDocumentStore = create<DocumentStoreState>(
|
||||
(set, get) => ({
|
||||
document: createEmptyDocument(),
|
||||
fileName: null,
|
||||
isDirty: false,
|
||||
|
||||
addNode: (parentId, node, index) =>
|
||||
set((s) => ({
|
||||
document: {
|
||||
...s.document,
|
||||
children: insertNodeInTree(
|
||||
s.document.children,
|
||||
parentId,
|
||||
node,
|
||||
index,
|
||||
),
|
||||
},
|
||||
isDirty: true,
|
||||
})),
|
||||
|
||||
updateNode: (id, updates) =>
|
||||
set((s) => ({
|
||||
document: {
|
||||
...s.document,
|
||||
children: updateNodeInTree(
|
||||
s.document.children,
|
||||
id,
|
||||
updates,
|
||||
),
|
||||
},
|
||||
isDirty: true,
|
||||
})),
|
||||
|
||||
removeNode: (id) =>
|
||||
set((s) => ({
|
||||
document: {
|
||||
...s.document,
|
||||
children: removeNodeFromTree(s.document.children, id),
|
||||
},
|
||||
isDirty: true,
|
||||
})),
|
||||
|
||||
moveNode: (id, newParentId, index) => {
|
||||
const state = get()
|
||||
const node = findNodeInTree(state.document.children, id)
|
||||
if (!node) return
|
||||
const withoutNode = removeNodeFromTree(
|
||||
state.document.children,
|
||||
id,
|
||||
)
|
||||
const withNode = insertNodeInTree(
|
||||
withoutNode,
|
||||
newParentId,
|
||||
node,
|
||||
index,
|
||||
)
|
||||
set({
|
||||
document: { ...state.document, children: withNode },
|
||||
isDirty: true,
|
||||
})
|
||||
},
|
||||
|
||||
reorderNode: (id, direction) => {
|
||||
const state = get()
|
||||
const parent = findParentInTree(state.document.children, id)
|
||||
const siblings = parent
|
||||
? ('children' in parent ? parent.children ?? [] : [])
|
||||
: state.document.children
|
||||
const idx = siblings.findIndex((n) => n.id === id)
|
||||
if (idx === -1) return
|
||||
const newIdx =
|
||||
direction === 'up'
|
||||
? Math.max(0, idx - 1)
|
||||
: Math.min(siblings.length - 1, idx + 1)
|
||||
if (newIdx === idx) return
|
||||
const newSiblings = [...siblings]
|
||||
const [removed] = newSiblings.splice(idx, 1)
|
||||
newSiblings.splice(newIdx, 0, removed)
|
||||
|
||||
if (parent && 'children' in parent) {
|
||||
set((s) => ({
|
||||
document: {
|
||||
...s.document,
|
||||
children: updateNodeInTree(
|
||||
s.document.children,
|
||||
parent.id,
|
||||
{ children: newSiblings } as Partial<PenNode>,
|
||||
),
|
||||
},
|
||||
isDirty: true,
|
||||
}))
|
||||
} else {
|
||||
set((s) => ({
|
||||
document: { ...s.document, children: newSiblings },
|
||||
isDirty: true,
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
getNodeById: (id) =>
|
||||
findNodeInTree(get().document.children, id),
|
||||
|
||||
getParentOf: (id) =>
|
||||
findParentInTree(get().document.children, id),
|
||||
|
||||
getFlatNodes: () => flattenNodes(get().document.children),
|
||||
|
||||
loadDocument: (doc, fileName) =>
|
||||
set({ document: doc, fileName: fileName ?? null, isDirty: false }),
|
||||
|
||||
newDocument: () =>
|
||||
set({
|
||||
document: createEmptyDocument(),
|
||||
fileName: null,
|
||||
isDirty: false,
|
||||
}),
|
||||
|
||||
markClean: () => set({ isDirty: false }),
|
||||
}),
|
||||
)
|
||||
|
||||
export { createEmptyDocument }
|
||||
export { nanoid as generateId } from 'nanoid'
|
||||
15
src/styles.css
Normal file
15
src/styles.css
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
body {
|
||||
@apply m-0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
28
src/types/canvas.ts
Normal file
28
src/types/canvas.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
export type ToolType =
|
||||
| 'select'
|
||||
| 'frame'
|
||||
| 'rectangle'
|
||||
| 'ellipse'
|
||||
| 'line'
|
||||
| 'polygon'
|
||||
| 'path'
|
||||
| 'text'
|
||||
| 'hand'
|
||||
|
||||
export interface ViewportState {
|
||||
zoom: number
|
||||
panX: number
|
||||
panY: number
|
||||
}
|
||||
|
||||
export interface SelectionState {
|
||||
selectedIds: string[]
|
||||
activeId: string | null
|
||||
}
|
||||
|
||||
export interface CanvasInteraction {
|
||||
isDrawing: boolean
|
||||
isPanning: boolean
|
||||
isDragging: boolean
|
||||
drawStartPoint: { x: number; y: number } | null
|
||||
}
|
||||
170
src/types/pen.ts
Normal file
170
src/types/pen.ts
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import type {
|
||||
PenFill,
|
||||
PenStroke,
|
||||
PenEffect,
|
||||
StyledTextSegment,
|
||||
} from './styles'
|
||||
import type { VariableDefinition } from './variables'
|
||||
|
||||
// --- Document Root ---
|
||||
|
||||
export interface PenDocument {
|
||||
version: string
|
||||
name?: string
|
||||
themes?: Record<string, string[]>
|
||||
variables?: Record<string, VariableDefinition>
|
||||
children: PenNode[]
|
||||
}
|
||||
|
||||
// --- Node Types ---
|
||||
|
||||
export type PenNodeType =
|
||||
| 'frame'
|
||||
| 'group'
|
||||
| 'rectangle'
|
||||
| 'ellipse'
|
||||
| 'line'
|
||||
| 'polygon'
|
||||
| 'path'
|
||||
| 'text'
|
||||
| 'ref'
|
||||
|
||||
export type SizingBehavior = number | 'fit_content' | 'fill_container' | string
|
||||
|
||||
// --- Base ---
|
||||
|
||||
export interface PenNodeBase {
|
||||
id: string
|
||||
type: PenNodeType
|
||||
name?: string
|
||||
x?: number
|
||||
y?: number
|
||||
rotation?: number
|
||||
opacity?: number | string // number or $variable
|
||||
enabled?: boolean | string
|
||||
flipX?: boolean
|
||||
flipY?: boolean
|
||||
theme?: Record<string, string>
|
||||
}
|
||||
|
||||
// --- Container (shared layout props) ---
|
||||
|
||||
export interface ContainerProps {
|
||||
width?: SizingBehavior
|
||||
height?: SizingBehavior
|
||||
layout?: 'none' | 'vertical' | 'horizontal'
|
||||
gap?: number | string
|
||||
padding?:
|
||||
| number
|
||||
| [number, number]
|
||||
| [number, number, number, number]
|
||||
| string
|
||||
justifyContent?:
|
||||
| 'start'
|
||||
| 'center'
|
||||
| 'end'
|
||||
| 'space_between'
|
||||
| 'space_around'
|
||||
alignItems?: 'start' | 'center' | 'end'
|
||||
children?: PenNode[]
|
||||
cornerRadius?: number | [number, number, number, number]
|
||||
fill?: PenFill[]
|
||||
stroke?: PenStroke
|
||||
effects?: PenEffect[]
|
||||
}
|
||||
|
||||
// --- Concrete Nodes ---
|
||||
|
||||
export interface FrameNode extends PenNodeBase, ContainerProps {
|
||||
type: 'frame'
|
||||
reusable?: boolean
|
||||
slot?: string[]
|
||||
}
|
||||
|
||||
export interface GroupNode extends PenNodeBase, ContainerProps {
|
||||
type: 'group'
|
||||
}
|
||||
|
||||
export interface RectangleNode extends PenNodeBase, ContainerProps {
|
||||
type: 'rectangle'
|
||||
}
|
||||
|
||||
export interface EllipseNode extends PenNodeBase {
|
||||
type: 'ellipse'
|
||||
width?: SizingBehavior
|
||||
height?: SizingBehavior
|
||||
innerRadius?: number
|
||||
startAngle?: number
|
||||
sweepAngle?: number
|
||||
fill?: PenFill[]
|
||||
stroke?: PenStroke
|
||||
effects?: PenEffect[]
|
||||
}
|
||||
|
||||
export interface LineNode extends PenNodeBase {
|
||||
type: 'line'
|
||||
x2?: number
|
||||
y2?: number
|
||||
stroke?: PenStroke
|
||||
effects?: PenEffect[]
|
||||
}
|
||||
|
||||
export interface PolygonNode extends PenNodeBase {
|
||||
type: 'polygon'
|
||||
polygonCount: number
|
||||
width?: SizingBehavior
|
||||
height?: SizingBehavior
|
||||
fill?: PenFill[]
|
||||
stroke?: PenStroke
|
||||
effects?: PenEffect[]
|
||||
}
|
||||
|
||||
export interface PathNode extends PenNodeBase {
|
||||
type: 'path'
|
||||
d: string
|
||||
width?: SizingBehavior
|
||||
height?: SizingBehavior
|
||||
fill?: PenFill[]
|
||||
stroke?: PenStroke
|
||||
effects?: PenEffect[]
|
||||
}
|
||||
|
||||
export interface TextNode extends PenNodeBase {
|
||||
type: 'text'
|
||||
width?: SizingBehavior
|
||||
height?: SizingBehavior
|
||||
content: string | StyledTextSegment[]
|
||||
fontFamily?: string
|
||||
fontSize?: number
|
||||
fontWeight?: number | string
|
||||
fontStyle?: 'normal' | 'italic'
|
||||
letterSpacing?: number
|
||||
lineHeight?: number
|
||||
textAlign?: 'left' | 'center' | 'right' | 'justify'
|
||||
textAlignVertical?: 'top' | 'middle' | 'bottom'
|
||||
textGrowth?: 'auto' | 'fixed-width' | 'fixed-width-height'
|
||||
underline?: boolean
|
||||
strikethrough?: boolean
|
||||
fill?: PenFill[]
|
||||
effects?: PenEffect[]
|
||||
}
|
||||
|
||||
export interface RefNode extends PenNodeBase {
|
||||
type: 'ref'
|
||||
ref: string
|
||||
descendants?: Record<string, Partial<PenNode>>
|
||||
children?: PenNode[]
|
||||
}
|
||||
|
||||
// --- Union ---
|
||||
|
||||
export type PenNode =
|
||||
| FrameNode
|
||||
| GroupNode
|
||||
| RectangleNode
|
||||
| EllipseNode
|
||||
| LineNode
|
||||
| PolygonNode
|
||||
| PathNode
|
||||
| TextNode
|
||||
| RefNode
|
||||
101
src/types/styles.ts
Normal file
101
src/types/styles.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
export type BlendMode =
|
||||
| 'normal'
|
||||
| 'darken'
|
||||
| 'multiply'
|
||||
| 'screen'
|
||||
| 'overlay'
|
||||
| 'lighten'
|
||||
| 'difference'
|
||||
| 'hue'
|
||||
| 'saturation'
|
||||
| 'color'
|
||||
| 'luminosity'
|
||||
|
||||
// --- Fill Types ---
|
||||
|
||||
export interface SolidFill {
|
||||
type: 'solid'
|
||||
color: string // #RRGGBB or #RRGGBBAA
|
||||
opacity?: number
|
||||
blendMode?: BlendMode
|
||||
}
|
||||
|
||||
export interface GradientStop {
|
||||
offset: number // 0 to 1
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface LinearGradientFill {
|
||||
type: 'linear_gradient'
|
||||
angle?: number
|
||||
stops: GradientStop[]
|
||||
opacity?: number
|
||||
blendMode?: BlendMode
|
||||
}
|
||||
|
||||
export interface RadialGradientFill {
|
||||
type: 'radial_gradient'
|
||||
cx?: number
|
||||
cy?: number
|
||||
radius?: number
|
||||
stops: GradientStop[]
|
||||
opacity?: number
|
||||
blendMode?: BlendMode
|
||||
}
|
||||
|
||||
export interface ImageFill {
|
||||
type: 'image'
|
||||
url: string
|
||||
mode?: 'stretch' | 'fill' | 'fit'
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
export type PenFill =
|
||||
| SolidFill
|
||||
| LinearGradientFill
|
||||
| RadialGradientFill
|
||||
| ImageFill
|
||||
|
||||
// --- Stroke ---
|
||||
|
||||
export interface PenStroke {
|
||||
thickness: number | [number, number, number, number]
|
||||
align?: 'inside' | 'center' | 'outside'
|
||||
join?: 'miter' | 'bevel' | 'round'
|
||||
cap?: 'none' | 'round' | 'square'
|
||||
dashPattern?: number[]
|
||||
fill?: PenFill[]
|
||||
}
|
||||
|
||||
// --- Effects ---
|
||||
|
||||
export interface BlurEffect {
|
||||
type: 'blur' | 'background_blur'
|
||||
radius: number
|
||||
}
|
||||
|
||||
export interface ShadowEffect {
|
||||
type: 'shadow'
|
||||
inner?: boolean
|
||||
offsetX: number
|
||||
offsetY: number
|
||||
blur: number
|
||||
spread: number
|
||||
color: string
|
||||
}
|
||||
|
||||
export type PenEffect = BlurEffect | ShadowEffect
|
||||
|
||||
// --- Text ---
|
||||
|
||||
export interface StyledTextSegment {
|
||||
text: string
|
||||
fontFamily?: string
|
||||
fontSize?: number
|
||||
fontWeight?: number
|
||||
fontStyle?: 'normal' | 'italic'
|
||||
fill?: string
|
||||
underline?: boolean
|
||||
strikethrough?: boolean
|
||||
href?: string
|
||||
}
|
||||
11
src/types/variables.ts
Normal file
11
src/types/variables.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export interface VariableDefinition {
|
||||
type: 'color' | 'number' | 'boolean' | 'string'
|
||||
value: VariableValue
|
||||
}
|
||||
|
||||
export type VariableValue = string | number | boolean | ThemedValue[]
|
||||
|
||||
export interface ThemedValue {
|
||||
value: string | number | boolean
|
||||
theme?: Record<string, string>
|
||||
}
|
||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"jsx": "react-jsx",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"types": ["vite/client"],
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
29
vite.config.ts
Normal file
29
vite.config.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { devtools } from '@tanstack/devtools-vite'
|
||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
||||
import viteReact from '@vitejs/plugin-react'
|
||||
import viteTsConfigPaths from 'vite-tsconfig-paths'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { nitro } from 'nitro/vite'
|
||||
|
||||
const config = defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
devtools(),
|
||||
nitro({ rollupConfig: { external: [/^@sentry\//] } }),
|
||||
// this is the plugin that enables path aliases
|
||||
viteTsConfigPaths({
|
||||
projects: ['./tsconfig.json'],
|
||||
}),
|
||||
tailwindcss(),
|
||||
tanstackStart(),
|
||||
viteReact(),
|
||||
],
|
||||
})
|
||||
|
||||
export default config
|
||||
Loading…
Reference in a new issue