Fix type errors found by tsgo and vue-tsc

- Add interaction constants to core barrel export (were in constants.ts
  but not reachable via '@open-pencil/core')
- Fix variable shadowing in use-text-edit.ts: rename inner 'editor'
  (textEditor) to 'textEd' to avoid shadowing the Editor parameter
- Fix unused params: prefix with _ in scene.ts, describe-issues.ts, xpath.ts
- Remove unused getCoords param from setupPanZoom
- Move cursorCanvasX/Y assignment to app callback (not on EditorState)
- Simplify EditorStore return: spread editor instead of listing 80+ methods
  individually — new Editor methods auto-forward, no more manual sync
- Add tsgo to check script, add check:vue script for vue-tsc
- Add vue-tsc as dev dependency
- Reduces vue-tsc errors from 172 (pre-existing) to 135 (37 fixed)
This commit is contained in:
Danila Poyarkov 2026-03-16 21:54:08 +03:00
parent 59b66e68ca
commit 4a2c5ac0ce
11 changed files with 73 additions and 127 deletions

View file

@ -74,6 +74,7 @@
"unplugin-vue-components": "^31.0.0", "unplugin-vue-components": "^31.0.0",
"vite": "^8.0.0", "vite": "^8.0.0",
"vite-plugin-pwa": "^1.2.0", "vite-plugin-pwa": "^1.2.0",
"vue-tsc": "^3.2.5",
"workbox-window": "^7.4.0", "workbox-window": "^7.4.0",
"ws": "^8.19.0", "ws": "^8.19.0",
}, },
@ -1085,6 +1086,12 @@
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg=="],
"@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
"@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="],
"@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="],
"@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="], "@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="],
"@vue/compiler-core": ["@vue/compiler-core@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.29", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw=="], "@vue/compiler-core": ["@vue/compiler-core@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.29", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw=="],
@ -1101,6 +1108,8 @@
"@vue/devtools-shared": ["@vue/devtools-shared@8.0.7", "", {}, "sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA=="], "@vue/devtools-shared": ["@vue/devtools-shared@8.0.7", "", {}, "sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA=="],
"@vue/language-core": ["@vue/language-core@3.2.5", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g=="],
"@vue/reactivity": ["@vue/reactivity@3.5.29", "", { "dependencies": { "@vue/shared": "3.5.29" } }, "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA=="], "@vue/reactivity": ["@vue/reactivity@3.5.29", "", { "dependencies": { "@vue/shared": "3.5.29" } }, "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA=="],
"@vue/runtime-core": ["@vue/runtime-core@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg=="], "@vue/runtime-core": ["@vue/runtime-core@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg=="],
@ -1155,6 +1164,8 @@
"algoliasearch": ["algoliasearch@5.49.1", "", { "dependencies": { "@algolia/abtesting": "1.15.1", "@algolia/client-abtesting": "5.49.1", "@algolia/client-analytics": "5.49.1", "@algolia/client-common": "5.49.1", "@algolia/client-insights": "5.49.1", "@algolia/client-personalization": "5.49.1", "@algolia/client-query-suggestions": "5.49.1", "@algolia/client-search": "5.49.1", "@algolia/ingestion": "1.49.1", "@algolia/monitoring": "1.49.1", "@algolia/recommend": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ=="], "algoliasearch": ["algoliasearch@5.49.1", "", { "dependencies": { "@algolia/abtesting": "1.15.1", "@algolia/client-abtesting": "5.49.1", "@algolia/client-analytics": "5.49.1", "@algolia/client-common": "5.49.1", "@algolia/client-insights": "5.49.1", "@algolia/client-personalization": "5.49.1", "@algolia/client-query-suggestions": "5.49.1", "@algolia/client-search": "5.49.1", "@algolia/ingestion": "1.49.1", "@algolia/monitoring": "1.49.1", "@algolia/recommend": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ=="],
"alien-signals": ["alien-signals@3.1.2", "", {}, "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@ -2009,6 +2020,8 @@
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
@ -2409,6 +2422,8 @@
"void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="],
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
"vue": ["vue@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", "@vue/runtime-dom": "3.5.29", "@vue/server-renderer": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA=="], "vue": ["vue@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", "@vue/runtime-dom": "3.5.29", "@vue/server-renderer": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA=="],
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
@ -2417,6 +2432,8 @@
"vue-stream-markdown": ["vue-stream-markdown@0.6.5", "", { "dependencies": { "@floating-ui/dom": "^1.7.5", "@vueuse/core": "^14.2.1", "marked": "^17.0.3", "mdast-util-from-markdown": "^2.0.3", "mdast-util-frontmatter": "^2.0.1", "mdast-util-gfm": "^3.1.0", "mdast-util-math": "^3.0.0", "mdast-util-to-markdown": "^2.1.2", "micromark-extension-cjk-friendly": "^2.0.1", "micromark-extension-cjk-friendly-gfm-strikethrough": "^2.0.1", "micromark-extension-frontmatter": "^2.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-extension-math": "^3.1.0", "micromark-util-types": "^2.0.2" }, "peerDependencies": { "beautiful-mermaid": ">=0.1.0", "katex": ">=0.16.20", "mermaid": ">=11", "shiki": ">=3.15.0", "vue": ">=3.0.0" }, "optionalPeers": ["beautiful-mermaid", "katex", "mermaid", "shiki"] }, "sha512-wXj1Kjul+7tP1GcuEZ1fqrfT+UokBuTJXM8rGMzo8qH7pTmcUp//sGPIbjIPSfhhIrSHxuBdMLSBEbccwFp07g=="], "vue-stream-markdown": ["vue-stream-markdown@0.6.5", "", { "dependencies": { "@floating-ui/dom": "^1.7.5", "@vueuse/core": "^14.2.1", "marked": "^17.0.3", "mdast-util-from-markdown": "^2.0.3", "mdast-util-frontmatter": "^2.0.1", "mdast-util-gfm": "^3.1.0", "mdast-util-math": "^3.0.0", "mdast-util-to-markdown": "^2.1.2", "micromark-extension-cjk-friendly": "^2.0.1", "micromark-extension-cjk-friendly-gfm-strikethrough": "^2.0.1", "micromark-extension-frontmatter": "^2.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-extension-math": "^3.1.0", "micromark-util-types": "^2.0.2" }, "peerDependencies": { "beautiful-mermaid": ">=0.1.0", "katex": ">=0.16.20", "mermaid": ">=11", "shiki": ">=3.15.0", "vue": ">=3.0.0" }, "optionalPeers": ["beautiful-mermaid", "katex", "mermaid", "shiki"] }, "sha512-wXj1Kjul+7tP1GcuEZ1fqrfT+UokBuTJXM8rGMzo8qH7pTmcUp//sGPIbjIPSfhhIrSHxuBdMLSBEbccwFp07g=="],
"vue-tsc": ["vue-tsc@3.2.5", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.5" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA=="],
"weald": ["weald@1.1.1", "", { "dependencies": { "ms": "^3.0.0-canary.1", "supports-color": "^10.0.0" } }, "sha512-PaEQShzMCz8J/AD2N3dJMc1hTZWkJeLKS2NMeiVkV5KDHwgZe7qXLEzyodsT/SODxWDdXJJqocuwf3kHzcXhSQ=="], "weald": ["weald@1.1.1", "", { "dependencies": { "ms": "^3.0.0-canary.1", "supports-color": "^10.0.0" } }, "sha512-PaEQShzMCz8J/AD2N3dJMc1hTZWkJeLKS2NMeiVkV5KDHwgZe7qXLEzyodsT/SODxWDdXJJqocuwf3kHzcXhSQ=="],
"web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="],

View file

@ -14,7 +14,8 @@
"tauri": "tauri", "tauri": "tauri",
"lint": "oxlint -c oxlint.json --type-aware --type-check src/ packages/core/src/ packages/cli/src/ packages/mcp/src/", "lint": "oxlint -c oxlint.json --type-aware --type-check src/ packages/core/src/ packages/cli/src/ packages/mcp/src/",
"format": "oxfmt --write src/", "format": "oxfmt --write src/",
"check": "bun run lint", "check": "bun run lint && tsgo --noEmit",
"check:vue": "vue-tsc --noEmit",
"test": "playwright test --project=openpencil", "test": "playwright test --project=openpencil",
"test:update": "playwright test --project=openpencil --update-snapshots", "test:update": "playwright test --project=openpencil --update-snapshots",
"test:figma": "playwright test --project=figma", "test:figma": "playwright test --project=figma",
@ -76,6 +77,7 @@
"@hono/node-server": "^1.19.11", "@hono/node-server": "^1.19.11",
"@iconify-json/lucide": "^1.2.94", "@iconify-json/lucide": "^1.2.94",
"@modelcontextprotocol/sdk": "^1.27.1", "@modelcontextprotocol/sdk": "^1.27.1",
"@open-pencil/mcp": "workspace:*",
"@playwright/test": "^1.58.2", "@playwright/test": "^1.58.2",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
"@types/culori": "^4.0.1", "@types/culori": "^4.0.1",
@ -97,8 +99,8 @@
"unplugin-vue-components": "^31.0.0", "unplugin-vue-components": "^31.0.0",
"vite": "^8.0.0", "vite": "^8.0.0",
"vite-plugin-pwa": "^1.2.0", "vite-plugin-pwa": "^1.2.0",
"vue-tsc": "^3.2.5",
"workbox-window": "^7.4.0", "workbox-window": "^7.4.0",
"ws": "^8.19.0", "ws": "^8.19.0"
"@open-pencil/mcp": "workspace:*"
} }
} }

View file

@ -316,3 +316,11 @@ export const SECTION_DEFAULT_STROKE: Stroke = {
export const ZOOM_DIVISOR = 50 export const ZOOM_DIVISOR = 50
export const ZOOM_SCALE_MIN = 0.75 export const ZOOM_SCALE_MIN = 0.75
export const ZOOM_SCALE_MAX = 1.25 export const ZOOM_SCALE_MAX = 1.25
export const PEN_CLOSE_THRESHOLD = 8
export const ROTATION_SNAP_DEGREES = 15
export const CORNER_ROTATE_ZONE = 16
export const HANDLE_HIT_RADIUS = 6
export const DEFAULT_TEXT_WIDTH = 200
export const DEFAULT_TEXT_HEIGHT = 24
export const AUTO_LAYOUT_BREAK_THRESHOLD = 8

View file

@ -34,7 +34,7 @@ function isCulled(
} }
function applyNodeTransforms( function applyNodeTransforms(
r: SkiaRenderer, _r: SkiaRenderer,
canvas: Canvas, canvas: Canvas,
node: SceneNode, node: SceneNode,
nodeId: string, nodeId: string,

View file

@ -293,7 +293,7 @@ function detectTypographyIssues(node: SceneNode, graph: SceneGraph, issues: Desc
const SPACING_GRID = 4 const SPACING_GRID = 4
function detectSpacingIssues(node: SceneNode, graph: SceneGraph, gridSize: number, issues: DescribeIssue[]): void { function detectSpacingIssues(node: SceneNode, graph: SceneGraph, _gridSize: number, issues: DescribeIssue[]): void {
if (node.layoutMode === 'NONE') return if (node.layoutMode === 'NONE') return
const children = node.childIds const children = node.childIds
.map((id) => graph.getNode(id)) .map((id) => graph.getNode(id))

View file

@ -66,7 +66,7 @@ interface XPathNode {
} }
function wrapNode( function wrapNode(
graph: SceneGraph, _graph: SceneGraph,
node: SceneNode, node: SceneNode,
parent?: XPathNode | XPathDocument | null parent?: XPathNode | XPathDocument | null
): XPathNode { ): XPathNode {

View file

@ -603,11 +603,9 @@ export function useCanvasInput(
} }
function onMouseMove(e: MouseEvent) { function onMouseMove(e: MouseEvent) {
{ if (onCursorMove) {
const { cx, cy } = getCoords(e) const { cx, cy } = getCoords(e)
editor.state.cursorCanvasX = cx onCursorMove(cx, cy)
editor.state.cursorCanvasY = cy
if (onCursorMove) onCursorMove(cx, cy)
} }
if (editor.state.activeTool === 'PEN' && editor.state.penState && !drag.value) { if (editor.state.activeTool === 'PEN' && editor.state.penState && !drag.value) {
@ -808,7 +806,7 @@ export function useCanvasInput(
if (drag.value) onMouseUp() if (drag.value) onMouseUp()
}) })
setupPanZoom(canvasRef, editor, drag, getCoords, onMouseDown, onMouseMove, onMouseUp) setupPanZoom(canvasRef, editor, drag, onMouseDown, onMouseMove, onMouseUp)
return { return {
drag, drag,

View file

@ -14,37 +14,37 @@ import type { SceneNode } from '@open-pencil/core'
const CARET_BLINK_MS = 530 const CARET_BLINK_MS = 530
export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Editor) { export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, store: Editor) {
const textareaRef = shallowRef<HTMLTextAreaElement | null>(null) const textareaRef = shallowRef<HTMLTextAreaElement | null>(null)
let isComposing = false let isComposing = false
let blinkTimer = 0 let blinkTimer = 0
function getEditingNode() { function getEditingNode() {
const id = editor.state.editingTextId const id = store.state.editingTextId
if (!id) return null if (!id) return null
return editor.graph.getNode(id) ?? null return store.graph.getNode(id) ?? null
} }
function resetBlink() { function resetBlink() {
if (editor.textEditor) editor.textEditor.caretVisible = true if (store.textEditor) store.textEditor.caretVisible = true
clearInterval(blinkTimer) clearInterval(blinkTimer)
blinkTimer = window.setInterval(() => { blinkTimer = window.setInterval(() => {
if (!editor.textEditor) return if (!store.textEditor) return
editor.textEditor.caretVisible = !editor.textEditor.caretVisible store.textEditor.caretVisible = !store.textEditor.caretVisible
editor.requestRepaint() store.requestRepaint()
}, CARET_BLINK_MS) }, CARET_BLINK_MS)
editor.requestRepaint() store.requestRepaint()
} }
function syncText(nodeId: string, text: string, runs?: SceneNode['styleRuns']) { function syncText(nodeId: string, text: string, runs?: SceneNode['styleRuns']) {
const changes: Partial<SceneNode> = { text } const changes: Partial<SceneNode> = { text }
if (runs !== undefined) changes.styleRuns = runs if (runs !== undefined) changes.styleRuns = runs
editor.graph.updateNode(nodeId, changes) store.graph.updateNode(nodeId, changes)
editor.requestRender() store.requestRender()
} }
function insertText(text: string, node: SceneNode) { function insertText(text: string, node: SceneNode) {
const editor = editor.textEditor const editor = store.textEditor
if (!editor) return if (!editor) return
const range = editor.getSelectionRange() const range = editor.getSelectionRange()
let runs = node.styleRuns let runs = node.styleRuns
@ -59,7 +59,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
} }
function deleteText(node: SceneNode, forward: boolean) { function deleteText(node: SceneNode, forward: boolean) {
const editor = editor.textEditor const editor = store.textEditor
if (!editor) return if (!editor) return
const range = editor.getSelectionRange() const range = editor.getSelectionRange()
let runs = node.styleRuns let runs = node.styleRuns
@ -105,7 +105,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
resetBlink() resetBlink()
} }
function handleHorizontalArrow(e: KeyboardEvent, editor: NonNullable<typeof editor.textEditor>) { function handleHorizontalArrow(e: KeyboardEvent, editor: NonNullable<typeof store.textEditor>) {
const select = e.shiftKey const select = e.shiftKey
const isMeta = e.metaKey || e.ctrlKey const isMeta = e.metaKey || e.ctrlKey
if (e.key === 'ArrowLeft') { if (e.key === 'ArrowLeft') {
@ -121,7 +121,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
function handleDeletion( function handleDeletion(
e: KeyboardEvent, e: KeyboardEvent,
editor: NonNullable<typeof editor.textEditor>, editor: NonNullable<typeof store.textEditor>,
node: SceneNode node: SceneNode
) { ) {
const isMeta = e.metaKey || e.ctrlKey const isMeta = e.metaKey || e.ctrlKey
@ -138,7 +138,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
type MetaAction = (node: SceneNode) => void type MetaAction = (node: SceneNode) => void
const metaKeyActions: Partial<Record<string, MetaAction>> = { const metaKeyActions: Partial<Record<string, MetaAction>> = {
a: () => editor.textEditor?.selectAll(), a: () => store.textEditor?.selectAll(),
c: () => handleCopy(), c: () => handleCopy(),
x: (node) => handleCut(node), x: (node) => handleCut(node),
v: (node) => void handlePaste(node), v: (node) => void handlePaste(node),
@ -149,7 +149,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
function onKeyDown(e: KeyboardEvent) { function onKeyDown(e: KeyboardEvent) {
if (isComposing) return if (isComposing) return
const editor = editor.textEditor const editor = store.textEditor
const node = getEditingNode() const node = getEditingNode()
if (!editor || !node) return if (!editor || !node) return
@ -158,7 +158,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
switch (e.key) { switch (e.key) {
case 'Escape': case 'Escape':
editor.commitTextEdit() store.commitTextEdit()
canvasRef.value?.focus() canvasRef.value?.focus()
e.preventDefault() e.preventDefault()
return return
@ -198,21 +198,21 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
} }
if (!textChanged) { if (!textChanged) {
editor.requestRender() store.requestRender()
} }
resetBlink() resetBlink()
e.preventDefault() e.preventDefault()
} }
function applyFormatting(nodeId: string, changes: Partial<SceneNode>, label: string) { function applyFormatting(nodeId: string, changes: Partial<SceneNode>, label: string) {
editor.updateNodeWithUndo(nodeId, changes, label) store.updateNodeWithUndo(nodeId, changes, label)
const updated = editor.graph.getNode(nodeId) const updated = store.graph.getNode(nodeId)
if (updated) editor.textEditor?.rebuildParagraph(updated) if (updated) store.textEditor?.rebuildParagraph(updated)
editor.requestRender() store.requestRender()
} }
function toggleBold(node: SceneNode) { function toggleBold(node: SceneNode) {
const editor = editor.textEditor const editor = store.textEditor
const range = editor?.getSelectionRange() const range = editor?.getSelectionRange()
if (range) { if (range) {
const { runs } = toggleBoldInRange( const { runs } = toggleBoldInRange(
@ -229,7 +229,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
} }
function toggleItalic(node: SceneNode) { function toggleItalic(node: SceneNode) {
const editor = editor.textEditor const editor = store.textEditor
const range = editor?.getSelectionRange() const range = editor?.getSelectionRange()
if (range) { if (range) {
const { runs } = toggleItalicInRange( const { runs } = toggleItalicInRange(
@ -246,7 +246,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
} }
function toggleUnderline(node: SceneNode) { function toggleUnderline(node: SceneNode) {
const editor = editor.textEditor const editor = store.textEditor
const range = editor?.getSelectionRange() const range = editor?.getSelectionRange()
if (range) { if (range) {
const { runs } = toggleDecorationInRange( const { runs } = toggleDecorationInRange(
@ -268,14 +268,14 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
} }
function handleCopy() { function handleCopy() {
const editor = editor.textEditor const editor = store.textEditor
if (!editor) return if (!editor) return
const text = editor.getSelectedText() const text = editor.getSelectedText()
if (text) void navigator.clipboard.writeText(text) if (text) void navigator.clipboard.writeText(text)
} }
function handleCut(node: ReturnType<typeof getEditingNode>) { function handleCut(node: ReturnType<typeof getEditingNode>) {
const editor = editor.textEditor const editor = store.textEditor
if (!editor || !node) return if (!editor || !node) return
const text = editor.getSelectedText() const text = editor.getSelectedText()
if (text) { if (text) {
@ -286,7 +286,7 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
} }
async function handlePaste(node: ReturnType<typeof getEditingNode>) { async function handlePaste(node: ReturnType<typeof getEditingNode>) {
const editor = editor.textEditor const editor = store.textEditor
if (!editor || !node) return if (!editor || !node) return
try { try {
const text = await navigator.clipboard.readText() const text = await navigator.clipboard.readText()
@ -305,13 +305,13 @@ export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, editor: Ed
useEventListener(textareaRef, 'keydown', onKeyDown) useEventListener(textareaRef, 'keydown', onKeyDown)
useEventListener(canvasRef, 'mousedown', () => { useEventListener(canvasRef, 'mousedown', () => {
if (editor.state.editingTextId && textareaRef.value) { if (store.state.editingTextId && textareaRef.value) {
requestAnimationFrame(() => textareaRef.value?.focus()) requestAnimationFrame(() => textareaRef.value?.focus())
} }
}) })
watch( watch(
() => editor.state.editingTextId, () => store.state.editingTextId,
(id, _, onCleanup) => { (id, _, onCleanup) => {
if (id) { if (id) {
const el = document.createElement('textarea') const el = document.createElement('textarea')

View file

@ -9,7 +9,6 @@ export function setupPanZoom(
canvasRef: Ref<HTMLCanvasElement | null>, canvasRef: Ref<HTMLCanvasElement | null>,
editor: Editor, editor: Editor,
drag: Ref<DragState | null>, drag: Ref<DragState | null>,
getCoords: (e: MouseEvent) => { sx: number; sy: number; cx: number; cy: number },
onMouseDown: (e: MouseEvent) => void, onMouseDown: (e: MouseEvent) => void,
onMouseMove: (e: MouseEvent) => void, onMouseMove: (e: MouseEvent) => void,
onMouseUp: () => void onMouseUp: () => void

View file

@ -24,7 +24,11 @@ const { cursorOverride } = useCanvasInput(
hitTestSectionTitle, hitTestSectionTitle,
hitTestComponentLabel, hitTestComponentLabel,
hitTestFrameTitle, hitTestFrameTitle,
(cx, cy) => collab?.updateCursor(cx, cy, store.state.currentPageId) (cx, cy) => {
store.state.cursorCanvasX = cx
store.state.cursorCanvasY = cy
collab?.updateCursor(cx, cy, store.state.currentPageId)
}
) )
useTextEdit(canvasRef, store) useTextEdit(canvasRef, store)

View file

@ -519,97 +519,15 @@ export function createEditorStore() {
} }
// ─── Public API ─────────────────────────────────────────────── // ─── Public API ───────────────────────────────────────────────
// Spread all core Editor methods, then override getters and add app-specific.
return { return {
get graph() { ...editor,
return editor.graph
},
get renderer() {
return editor.renderer
},
get textEditor() {
return editor.textEditor
},
undo: editor.undo,
state, state,
selectedNodes, selectedNodes,
selectedNode, selectedNode,
layerTree, layerTree,
// Core editor methods
requestRender: editor.requestRender,
requestRepaint: editor.requestRepaint,
setCanvasKit: editor.setCanvasKit,
isTopLevel: editor.isTopLevel,
switchPage: editor.switchPage,
addPage: editor.addPage,
deletePage: editor.deletePage,
renamePage: editor.renamePage,
setTool: editor.setTool,
select: editor.select,
clearSelection: editor.clearSelection,
enterContainer: editor.enterContainer,
exitContainer: editor.exitContainer,
validateEnteredContainer: editor.validateEnteredContainer,
selectAll: editor.selectAll,
setMarquee: editor.setMarquee,
setSnapGuides: editor.setSnapGuides,
setRotationPreview: editor.setRotationPreview,
setHoveredNode: editor.setHoveredNode,
setDropTarget: editor.setDropTarget,
setLayoutInsertIndicator: editor.setLayoutInsertIndicator,
reorderInAutoLayout: editor.reorderInAutoLayout,
reorderChildWithUndo: editor.reorderChildWithUndo,
reparentNodes: editor.reparentNodes,
penAddVertex: editor.penAddVertex,
penSetDragTangent: editor.penSetDragTangent,
penSetClosingToFirst: editor.penSetClosingToFirst,
penCommit: editor.penCommit,
penCancel: editor.penCancel,
startTextEditing: editor.startTextEditing,
commitTextEdit: editor.commitTextEdit,
updateNode: editor.updateNode,
updateNodeWithUndo: editor.updateNodeWithUndo,
setLayoutMode: editor.setLayoutMode,
wrapInAutoLayout: editor.wrapInAutoLayout,
groupSelected: editor.groupSelected,
ungroupSelected: editor.ungroupSelected,
createComponentFromSelection: editor.createComponentFromSelection,
createComponentSetFromComponents: editor.createComponentSetFromComponents,
createInstanceFromComponent: editor.createInstanceFromComponent,
detachInstance: editor.detachInstance,
goToMainComponent: editor.goToMainComponent,
bringToFront: editor.bringToFront,
sendToBack: editor.sendToBack,
toggleVisibility: editor.toggleVisibility,
toggleLock: editor.toggleLock,
moveToPage: editor.moveToPage,
renameNode: editor.renameNode,
createShape: editor.createShape,
adoptNodesIntoSection: editor.adoptNodesIntoSection,
duplicateSelected: editor.duplicateSelected,
writeCopyData: editor.writeCopyData,
pasteFromHTML: editor.pasteFromHTML,
deleteSelected: editor.deleteSelected,
commitMove: editor.commitMove,
commitMoveWithReparent: editor.commitMoveWithReparent,
commitResize: editor.commitResize,
commitRotation: editor.commitRotation,
commitNodeUpdate: editor.commitNodeUpdate,
undoAction: editor.undoAction,
redoAction: editor.redoAction,
snapshotPage: editor.snapshotPage,
restorePageFromSnapshot: editor.restorePageFromSnapshot,
pushUndoEntry: editor.pushUndoEntry,
screenToCanvas: editor.screenToCanvas,
applyZoom: editor.applyZoom,
pan: editor.pan,
zoomToFit: editor.zoomToFit,
zoomTo100: editor.zoomTo100,
zoomToSelection: editor.zoomToSelection,
storeImage: editor.storeImage,
placeImageFiles: editor.placeImageFiles,
// App-specific methods // App-specific methods
flashNodes, flashNodes,
aiMarkActive, aiMarkActive,