This commit is contained in:
2026-04-05 15:10:29 +03:00
parent 7684a2a871
commit c346c604fe
37 changed files with 2460 additions and 625 deletions

View File

@@ -5,6 +5,14 @@
"": {
"name": "frontend",
"dependencies": {
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/language": "^6.12.3",
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.41.0",
"@lezer/highlight": "^1.2.3",
"@lezer/lr": "^1.4.8",
"codemirror": "^6.0.2",
"codemirror-lang-dexpr": "file:../../rust-expr/editor",
"pinia": "^3.0.4",
"vue": "^3.5.31",
},
@@ -31,16 +39,98 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A=="],
"@codemirror/commands": ["@codemirror/commands@6.10.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q=="],
"@codemirror/language": ["@codemirror/language@6.12.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA=="],
"@codemirror/lint": ["@codemirror/lint@6.9.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA=="],
"@codemirror/search": ["@codemirror/search@6.6.0", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw=="],
"@codemirror/state": ["@codemirror/state@6.6.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ=="],
"@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
"@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@lezer/common": ["@lezer/common@1.5.1", "", {}, "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw=="],
"@lezer/generator": ["@lezer/generator@1.8.0", "", { "dependencies": { "@lezer/common": "^1.1.0", "@lezer/lr": "^1.3.0" }, "bin": { "lezer-generator": "src/lezer-generator.cjs" } }, "sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg=="],
"@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
"@lezer/lr": ["@lezer/lr@1.4.8", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA=="],
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
"@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="],
@@ -83,6 +173,56 @@
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
@@ -153,12 +293,16 @@
"abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"alien-signals": ["alien-signals@3.1.2", "", {}, "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
@@ -167,24 +311,42 @@
"brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="],
"bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
"codemirror-lang-dexpr": ["codemirror-lang-dexpr@file:../../rust-expr/editor", { "devDependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/generator": "^1.8.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.4.8", "codemirror": "^6.0.2", "tsup": "^8.0.0", "typescript": "^5.0.0" }, "peerDependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="],
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
@@ -197,12 +359,16 @@
"es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
"esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -223,6 +389,8 @@
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
"js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js", "js-beautify": "js/bin/js-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="],
"js-cookie": ["js-cookie@3.0.5", "", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="],
@@ -251,6 +419,12 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="],
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
@@ -261,12 +435,20 @@
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
@@ -287,18 +469,30 @@
"pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="],
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
"playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
"postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
"proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
"rolldown": ["rolldown@1.0.0-rc.12", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.12" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-x64": "1.0.0-rc.12", "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A=="],
"rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -309,6 +503,8 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
@@ -325,8 +521,16 @@
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
"sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
"superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
@@ -335,10 +539,18 @@
"tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="],
"typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.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", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="],
@@ -353,6 +565,8 @@
"vue-tsc": ["vue-tsc@3.2.6", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.6" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q=="],
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
@@ -371,6 +585,8 @@
"@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"codemirror-lang-dexpr/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"happy-dom/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
@@ -383,6 +599,10 @@
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"tsup/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],

View File

@@ -12,6 +12,14 @@
"test:visual": "playwright test"
},
"dependencies": {
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/language": "^6.12.3",
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.41.0",
"@lezer/highlight": "^1.2.3",
"@lezer/lr": "^1.4.8",
"codemirror": "^6.0.2",
"codemirror-lang-dexpr": "file:../../rust-expr/editor",
"pinia": "^3.0.4",
"vue": "^3.5.31"
},

View File

@@ -0,0 +1,237 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, computed } from 'vue'
import { EditorView, lineNumbers } from '@codemirror/view'
import { EditorState } from '@codemirror/state'
import { dexpr } from 'codemirror-lang-dexpr'
import type { DexprLanguageInfo } from 'codemirror-lang-dexpr'
import { useSchemaStore } from '../../stores/schema'
import type { SchemaNode } from '../../core/schema-parser'
const props = defineProps<{
modelValue: string
placeholder?: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const editorEl = ref<HTMLDivElement>()
let view: EditorView | null = null
let debounceTimer: ReturnType<typeof setTimeout> | null = null
function emitDebounced(val: string) {
if (debounceTimer) clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
emit('update:modelValue', val)
}, 300)
}
const schemaStore = useSchemaStore()
/** Schema tree'den dexpr LanguageInfo formatina donustur */
function schemaToLanguageInfo(): DexprLanguageInfo {
const info: DexprLanguageInfo = {
functions: [
{ name: 'log', signature: '(...args) -> Null', doc: 'Deger yazdir' },
{ name: 'rand', signature: '(min: Number, max: Number) -> Number', doc: 'Rastgele sayi' },
],
methods: {
String: [
{ name: 'upper', signature: '() -> String' },
{ name: 'lower', signature: '() -> String' },
{ name: 'trim', signature: '() -> String' },
{ name: 'length', signature: '() -> Number' },
{ name: 'contains', signature: '(substr: String) -> Boolean' },
{ name: 'replace', signature: '(old: String, new: String) -> String' },
{ name: 'split', signature: '(delim: String) -> StringList' },
{ name: 'substring', signature: '(start: Number, end?: Number) -> String' },
{ name: 'startsWith', signature: '(prefix: String) -> Boolean' },
{ name: 'endsWith', signature: '(suffix: String) -> Boolean' },
{ name: 'charAt', signature: '(index: Number) -> String' },
{ name: 'trimStart', signature: '() -> String' },
{ name: 'trimEnd', signature: '() -> String' },
],
Number: [],
Boolean: [],
NumberList: [
{ name: 'length', signature: '() -> Number' },
{ name: 'sum', signature: '() -> Number' },
{ name: 'avg', signature: '() -> Number' },
{ name: 'min', signature: '() -> Number' },
{ name: 'max', signature: '() -> Number' },
{ name: 'first', signature: '() -> Number' },
{ name: 'last', signature: '() -> Number' },
{ name: 'sort', signature: '() -> NumberList' },
{ name: 'reverse', signature: '() -> NumberList' },
{ name: 'contains', signature: '(value: Number) -> Boolean' },
],
StringList: [
{ name: 'length', signature: '() -> Number' },
{ name: 'join', signature: '(delim?: String) -> String' },
{ name: 'first', signature: '() -> String' },
{ name: 'last', signature: '() -> String' },
{ name: 'sort', signature: '() -> StringList' },
{ name: 'reverse', signature: '() -> StringList' },
{ name: 'contains', signature: '(value: String) -> Boolean' },
],
Object: [
{ name: 'keys', signature: '() -> StringList' },
{ name: 'values', signature: '() -> StringList | NumberList' },
{ name: 'length', signature: '() -> Number' },
{ name: 'contains', signature: '(key: String) -> Boolean' },
{ name: 'get', signature: '(key: String) -> any' },
],
},
variables: [],
}
// Schema tree'deki top-level object property'lerinden dexpr degiskenleri olustur
const tree = schemaStore.schemaTree
for (const child of tree.children) {
if (child.type === 'object') {
const fields = child.children.map(f => ({
name: f.key,
type: schemaToDexprType(f),
}))
info.variables!.push({
name: child.key,
type: 'Object',
doc: child.title,
fields,
})
} else {
info.variables!.push({
name: child.key,
type: schemaToDexprType(child),
doc: child.title,
})
}
}
return info
}
function schemaToDexprType(node: SchemaNode): 'String' | 'Number' | 'Boolean' | 'Object' | 'NumberList' | 'StringList' {
switch (node.type) {
case 'number':
case 'integer':
return 'Number'
case 'boolean':
return 'Boolean'
case 'object':
return 'Object'
case 'array':
return 'StringList'
default:
return 'String'
}
}
const langInfo = computed(() => schemaToLanguageInfo())
function createState(doc: string): EditorState {
return EditorState.create({
doc,
extensions: [
EditorView.updateListener.of(update => {
if (update.docChanged) {
const val = update.state.doc.toString()
if (val !== props.modelValue) {
emitDebounced(val)
}
}
}),
lineNumbers(),
dexpr(langInfo.value),
EditorView.lineWrapping,
EditorView.theme({
'&': {
fontSize: '11px',
border: '1px solid #e2e8f0',
borderRadius: '4px',
backgroundColor: '#fff',
maxHeight: '120px',
},
'&.cm-focused': {
outline: '2px solid #93c5fd',
outlineOffset: '-1px',
},
'.cm-scroller': {
overflow: 'auto',
},
'.cm-content': {
padding: '4px 6px',
fontFamily: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
minHeight: '20px',
},
'.cm-line': {
padding: '0',
},
'.cm-gutters': {
backgroundColor: '#f8fafc',
borderRight: '1px solid #e2e8f0',
color: '#94a3b8',
fontSize: '10px',
minWidth: '20px',
paddingLeft: '2px',
paddingRight: '4px',
},
'.cm-activeLine': {
backgroundColor: 'transparent',
},
'.cm-tooltip.cm-tooltip-autocomplete': {
fontSize: '11px',
zIndex: '9999',
},
}),
EditorState.tabSize.of(2),
EditorView.contentAttributes.of({
'aria-label': 'dexpr expression editor',
}),
],
})
}
onMounted(() => {
if (!editorEl.value) return
view = new EditorView({
state: createState(props.modelValue ?? ''),
parent: editorEl.value,
})
})
onBeforeUnmount(() => {
view?.destroy()
view = null
})
// Disaridan gelen deger degisikligi (undo/redo vs.)
watch(() => props.modelValue, (newVal) => {
if (!view) return
const current = view.state.doc.toString()
if (current !== newVal) {
view.dispatch({
changes: { from: 0, to: current.length, insert: newVal ?? '' },
})
}
})
// Schema degisince editor'u yeniden olustur (autocomplete guncellenmeli)
watch(langInfo, () => {
if (!view) return
const doc = view.state.doc.toString()
view.setState(createState(doc))
}, { deep: true })
</script>
<template>
<div ref="editorEl" class="dexpr-editor" />
</template>
<style scoped>
.dexpr-editor {
width: 100%;
min-width: 0;
}
</style>

View File

@@ -168,7 +168,7 @@ function applyZoom(delta: number, clientX: number, clientY: number) {
}
function onKeyDown(e: KeyboardEvent) {
if (e.code === 'Space' && !e.repeat && !(e.target instanceof HTMLInputElement || e.target instanceof HTMLSelectElement || e.target instanceof HTMLTextAreaElement)) {
if (e.code === 'Space' && !e.repeat && !(e.target instanceof HTMLInputElement || e.target instanceof HTMLSelectElement || e.target instanceof HTMLTextAreaElement || (e.target as HTMLElement)?.isContentEditable)) {
e.preventDefault()
spaceHeld.value = true
}

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue'
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import { isContainer } from '../../core/types'
import type { ContainerElement, TextStyle } from '../../core/types'
import type { ContainerElement, TextStyle, RepeatingTableElement, TableStyle } from '../../core/types'
import type { ElementLayout } from '../../core/layout-types'
const props = defineProps<{
@@ -32,6 +32,10 @@ const isText = computed(() => {
const isLine = computed(() => selected.value?.type === 'line')
const isTable = computed(() => selected.value?.type === 'repeating_table')
const tableEl = computed(() => isTable.value ? selected.value as RepeatingTableElement : null)
const tableStyle = computed(() => tableEl.value?.style as TableStyle | undefined)
const toolbarStyle = computed(() => {
const el = selected.value
if (!el) return { display: 'none' }
@@ -66,6 +70,12 @@ function setGap(e: Event) { update({ gap: parseFloat((e.target as HTMLInputEleme
// Text
function setFontWeight(w: string) { updateStyle('fontWeight', w) }
function setTextAlign(a: string) { updateStyle('align', a) }
// Table
function updateTableStyle(key: string, value: unknown) {
if (!selected.value) return
update({ style: { ...selected.value.style, [key]: value } })
}
</script>
<template>
@@ -230,6 +240,66 @@ function setTextAlign(a: string) { updateStyle('align', a) }
</div>
</template>
<!-- ===== Repeating Table ===== -->
<template v-if="isTable && tableStyle">
<!-- Font size -->
<div class="et__group et__group--gap" data-tip="Yazi Boyutu">
<svg class="et__gap-icon" width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2 10L6 2l4 8" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<line x1="3.5" y1="7" x2="8.5" y2="7" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
</svg>
<input type="number" class="et__num" step="1" min="6" :value="tableStyle.fontSize ?? 10" @input="(e) => updateTableStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</div>
<div class="et__sep" />
<!-- Header bg color -->
<div class="et__group">
<label class="et__color-wrap" data-tip="Header Rengi">
<input type="color" class="et__color" :value="tableStyle.headerBg ?? '#f0f0f0'" @input="(e) => updateTableStyle('headerBg', (e.target as HTMLInputElement).value)" />
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect x="2" y="2" width="10" height="4" rx="1" :fill="tableStyle.headerBg ?? '#f0f0f0'" stroke="#94a3b8" stroke-width="0.5"/>
<rect x="2" y="7" width="10" height="2" rx="0.5" fill="none" stroke="#94a3b8" stroke-width="0.5"/>
<rect x="2" y="10" width="10" height="2" rx="0.5" fill="none" stroke="#94a3b8" stroke-width="0.5"/>
</svg>
</label>
</div>
<!-- Zebra color -->
<div class="et__group">
<label class="et__color-wrap" data-tip="Zebra Rengi">
<input type="color" class="et__color" :value="tableStyle.zebraOdd ?? '#fafafa'" @input="(e) => updateTableStyle('zebraOdd', (e.target as HTMLInputElement).value)" />
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect x="2" y="2" width="10" height="2.5" rx="0.5" fill="none" stroke="#94a3b8" stroke-width="0.5"/>
<rect x="2" y="5.5" width="10" height="2.5" rx="0.5" :fill="tableStyle.zebraOdd ?? '#fafafa'" stroke="#94a3b8" stroke-width="0.5"/>
<rect x="2" y="9" width="10" height="2.5" rx="0.5" fill="none" stroke="#94a3b8" stroke-width="0.5"/>
</svg>
</label>
</div>
<div class="et__sep" />
<!-- Border color -->
<div class="et__group">
<label class="et__color-wrap" data-tip="Kenarlik Rengi">
<input type="color" class="et__color" :value="tableStyle.borderColor ?? '#cccccc'" @input="(e) => updateTableStyle('borderColor', (e.target as HTMLInputElement).value)" />
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect x="2" y="2" width="10" height="10" rx="1" fill="none" :stroke="tableStyle.borderColor ?? '#cccccc'" stroke-width="1.5"/>
<line x1="2" y1="6" x2="12" y2="6" :stroke="tableStyle.borderColor ?? '#cccccc'" stroke-width="0.8"/>
<line x1="7" y1="2" x2="7" y2="12" :stroke="tableStyle.borderColor ?? '#cccccc'" stroke-width="0.8"/>
</svg>
</label>
</div>
<!-- Border width -->
<div class="et__group et__group--gap" data-tip="Kenarlik (mm)">
<svg class="et__gap-icon" width="12" height="12" viewBox="0 0 12 12" fill="none">
<rect x="1" y="1" width="10" height="10" rx="1" fill="none" stroke="currentColor" stroke-width="1.5"/>
</svg>
<input type="number" class="et__num" step="0.1" min="0" :value="tableStyle.borderWidth ?? 0.5" @input="(e) => updateTableStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</div>
</template>
<!-- ===== Line ===== -->
<template v-if="isLine">
<!-- Stroke width -->
@@ -282,34 +352,6 @@ function setTextAlign(a: string) { updateStyle('align', a) }
flex-shrink: 0;
}
/* Tooltip */
[data-tip] {
position: relative;
}
[data-tip]::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
background: #0f172a;
color: #e2e8f0;
font-size: 10px;
padding: 3px 6px;
border-radius: 4px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
z-index: 10;
}
[data-tip]:hover::after,
[data-tip]:focus-within::after {
opacity: 1;
}
/* Button */
.et__btn {
display: flex;

View File

@@ -124,7 +124,7 @@ function findDeepestContainer(mouseX: number, mouseY: number, excludeId?: string
if (!l) continue
const cx = l.x_mm * s
const cy = l.y_mm * s
const cy = l.y_mm * s + pageYOffset(l.pageIndex)
const cw = l.width_mm * s
const ch = l.height_mm * s
@@ -154,7 +154,7 @@ function computeDropIndex(container: ContainerElement, mouseX: number, mouseY: n
const centerX = l.x_mm * s + (l.width_mm * s) / 2
if (mouseX < centerX) { visualIdx = i; break }
} else {
const centerY = l.y_mm * s + (l.height_mm * s) / 2
const centerY = l.y_mm * s + pageYOffset(l.pageIndex) + (l.height_mm * s) / 2
if (mouseY < centerY) { visualIdx = i; break }
}
}
@@ -246,7 +246,8 @@ const dropIndicatorStyle = computed(() => {
}
}
const top = cl.y_mm * s
const clPageOff = pageYOffset(cl.pageIndex)
const top = cl.y_mm * s + clPageOff
const height = cl.height_mm * s
return {
@@ -263,30 +264,31 @@ const dropIndicatorStyle = computed(() => {
}
// Column container: yatay gösterge çizgisi
const colPageOff = pageYOffset(cl.pageIndex)
let y = 0
if (idx === 0 && flowChildren.length > 0) {
const l = props.layoutMap[flowChildren[0].id]
if (l) {
y = (cl.y_mm * s + l.y_mm * s) / 2
y = (cl.y_mm * s + colPageOff + l.y_mm * s + pageYOffset(l.pageIndex)) / 2
} else {
y = cl.y_mm * s - 4
y = cl.y_mm * s + colPageOff - 4
}
} else if (idx < flowChildren.length && idx > 0) {
const above = props.layoutMap[flowChildren[idx - 1].id]
const below = props.layoutMap[flowChildren[idx].id]
if (above && below) {
const aboveBottom = (above.y_mm + above.height_mm) * s
const belowTop = below.y_mm * s
const aboveBottom = (above.y_mm + above.height_mm) * s + pageYOffset(above.pageIndex)
const belowTop = below.y_mm * s + pageYOffset(below.pageIndex)
y = (aboveBottom + belowTop) / 2
}
} else if (idx === 0 && flowChildren.length === 0) {
y = cl.y_mm * s + 8
y = cl.y_mm * s + colPageOff + 8
} else if (flowChildren.length > 0) {
const last = flowChildren[flowChildren.length - 1]
const l = props.layoutMap[last.id]
if (l) {
const gapPx = container.gap * props.scale
y = (l.y_mm + l.height_mm) * s + gapPx / 2
y = (l.y_mm + l.height_mm) * s + pageYOffset(l.pageIndex) + gapPx / 2
}
}

View File

@@ -205,6 +205,9 @@ const tools: ToolItem[] = [
create: (): PageBreakElement => ({
id: nextId('pb'),
type: 'page_break',
position: { type: 'flow' },
size: { width: sz.fr(1), height: sz.auto() },
style: {},
}),
},
]

View File

@@ -94,7 +94,7 @@ function onBarcodeFormatChange(newFormat: BarcodeFormat) {
<template>
<div class="prop-section">
<div class="prop-section__title">Barkod Ayarlari</div>
<div class="prop-row">
<div class="prop-row" data-tip="Barkod formati">
<label class="prop-label">Format</label>
<select class="prop-input prop-select"
:value="element.format"
@@ -106,14 +106,14 @@ function onBarcodeFormatChange(newFormat: BarcodeFormat) {
<option value="code39">Code 39</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Barkod icerigi formata uygun olmali">
<label class="prop-label">Deger</label>
<input class="prop-input" type="text"
:class="{ 'prop-input--invalid': barcodeInputInvalid }"
:value="barcodeInputValue"
@input="onBarcodeValueInput" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Barkod cizgi/modül rengi">
<label class="prop-label">Renk</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
@@ -122,13 +122,13 @@ function onBarcodeFormatChange(newFormat: BarcodeFormat) {
<button v-if="element.style.color" class="prop-clear" @click="updateStyle('color', undefined)">x</button>
</div>
</div>
<div v-if="element.format !== 'qr'" class="prop-row">
<div v-if="element.format !== 'qr'" class="prop-row" data-tip="Barkod altinda degeri metin olarak goster">
<label class="prop-label">Metin Goster</label>
<input type="checkbox"
:checked="element.style.includeText ?? (element.format === 'ean13' || element.format === 'ean8')"
@change="(e) => updateStyle('includeText', (e.target as HTMLInputElement).checked)" />
</div>
<div v-if="schemaStore.scalarFields.length > 0" class="prop-row">
<div v-if="schemaStore.scalarFields.length > 0" class="prop-row" data-tip="Schema'dan dinamik veri baglama">
<label class="prop-label">Veri Baglama</label>
<select class="prop-input prop-select"
:value="element.binding?.path ?? ''"

View File

@@ -2,6 +2,7 @@
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import type { CalculatedTextElement, TextStyle, TemplateElement } from '../../core/types'
import DexprEditor from '../common/DexprEditor.vue'
import '../../styles/properties.css'
const props = defineProps<{ element: CalculatedTextElement }>()
@@ -17,19 +18,23 @@ function update(updates: Partial<TemplateElement>) {
function updateStyle(key: string, value: unknown) {
update({ style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
}
function onExpressionChange(value: string) {
update({ expression: value } as any)
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Hesaplanan Metin</div>
<div class="prop-row">
<div class="prop-row-stack" data-tip="Hesaplama ifadesi (orn: toplamlar.kdv + toplamlar.araToplam)">
<label class="prop-label">Ifade</label>
<input class="prop-input" type="text"
:value="element.expression"
@change="(e) => update({ expression: (e.target as HTMLInputElement).value } as any)"
<DexprEditor
:model-value="element.expression"
@update:model-value="onExpressionChange"
placeholder="toplamlar.kdv + toplamlar.araToplam" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Sonucun gosterim formati">
<label class="prop-label">Format</label>
<select class="prop-input prop-select"
:value="element.format ?? ''"
@@ -40,19 +45,19 @@ function updateStyle(key: string, value: unknown) {
<option value="percentage">Yuzde</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
<label class="prop-label">Boyut (pt)</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.style as TextStyle).fontSize ?? 11"
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metin rengi">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="(element.style as TextStyle).color ?? '#000000'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Yazi tipi kalinligi">
<label class="prop-label">Kalinlik</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).fontWeight ?? 'normal'"
@@ -61,7 +66,7 @@ function updateStyle(key: string, value: unknown) {
<option value="bold">Kalin</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).align ?? 'left'"

View File

@@ -22,25 +22,25 @@ function updateStyle(key: string, value: unknown) {
<template>
<div class="prop-section">
<div class="prop-section__title">Onay Kutusu</div>
<div v-if="!element.binding" class="prop-row">
<div v-if="!element.binding" class="prop-row" data-tip="Onay kutusunun varsayilan durumu">
<label class="prop-label">Isaretli</label>
<input type="checkbox"
:checked="element.checked ?? false"
@change="(e) => update({ checked: (e.target as HTMLInputElement).checked } as any)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Onay kutusu boyutu (mm)">
<label class="prop-label">Boyut (mm)</label>
<input class="prop-input" type="number" step="0.5" min="1"
:value="element.style.size ?? 4"
@input="(e) => updateStyle('size', parseFloat((e.target as HTMLInputElement).value) || 4)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Isaret (tik) rengi">
<label class="prop-label">Isaret Rengi</label>
<input class="prop-input prop-color" type="color"
:value="element.style.checkColor ?? '#000000'"
@input="(e) => updateStyle('checkColor', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Kutu kenarlik rengi">
<label class="prop-label">Kenar Rengi</label>
<input class="prop-input prop-color" type="color"
:value="element.style.borderColor ?? '#333333'"

View File

@@ -23,7 +23,7 @@ function updateStyle(key: string, value: unknown) {
<template>
<div class="prop-section">
<div class="prop-section__title">Container Ayarlari</div>
<div class="prop-row">
<div class="prop-row" data-tip="Cocuk elemanlarin dizilim yonu">
<label class="prop-label">Yon</label>
<select class="prop-input prop-select"
:value="element.direction"
@@ -32,13 +32,13 @@ function updateStyle(key: string, value: unknown) {
<option value="row">Yatay</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Cocuk elemanlar arasi bosluk (mm)">
<label class="prop-label">Bosluk (mm)</label>
<input class="prop-input" type="number" step="1" min="0"
:value="element.gap"
@input="(e) => update({ gap: parseFloat((e.target as HTMLInputElement).value) || 0 } as any)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Cocuklarin cross-axis hizalamasi">
<label class="prop-label">{{ element.direction === 'column' ? 'Yatay Hizalama' : 'Dikey Hizalama' }}</label>
<select class="prop-input prop-select"
:value="element.align"
@@ -49,7 +49,7 @@ function updateStyle(key: string, value: unknown) {
<option value="stretch">Esnet</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Cocuklarin main-axis dagilimi">
<label class="prop-label">{{ element.direction === 'column' ? 'Dikey Dagilim' : 'Yatay Dagilim' }}</label>
<select class="prop-input prop-select"
:value="element.justify"
@@ -70,7 +70,7 @@ function updateStyle(key: string, value: unknown) {
@update="(side, value) => update({ padding: { ...element.padding, [side]: value } } as any)"
/>
<div class="prop-row">
<div class="prop-row" data-tip="Sayfa sonunda bolunmeyi kontrol eder">
<label class="prop-label">Sayfa Bolme</label>
<select class="prop-input prop-select"
:value="element.breakInside ?? 'auto'"
@@ -81,7 +81,7 @@ function updateStyle(key: string, value: unknown) {
</div>
<div class="prop-section__subtitle">Stil</div>
<div class="prop-row">
<div class="prop-row" data-tip="Container arka plan rengi">
<label class="prop-label">Arka plan</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
@@ -90,13 +90,13 @@ function updateStyle(key: string, value: unknown) {
<button v-if="element.style.backgroundColor" class="prop-clear" @click="updateStyle('backgroundColor', undefined)">x</button>
</div>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Kenarlik kalinligi (mm)">
<label class="prop-label">Kenarlik (mm)</label>
<input class="prop-input" type="number" step="0.1" min="0"
:value="element.style.borderWidth ?? 0"
@input="(e) => updateStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Kenarlik cizgisi rengi">
<label class="prop-label">Kenarlik rengi</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
@@ -105,7 +105,7 @@ function updateStyle(key: string, value: unknown) {
<button v-if="element.style.borderColor" class="prop-clear" @click="updateStyle('borderColor', undefined)">x</button>
</div>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Kenarlik cizgi stili">
<label class="prop-label">Kenarlik stili</label>
<select class="prop-input prop-select"
:value="element.style.borderStyle ?? 'solid'"
@@ -115,7 +115,7 @@ function updateStyle(key: string, value: unknown) {
<option value="dotted">Noktali</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Kose yuvarlakligi (mm)">
<label class="prop-label">Radius (mm)</label>
<input class="prop-input" type="number" step="0.5" min="0"
:value="element.style.borderRadius ?? 0"

View File

@@ -22,7 +22,7 @@ function updateStyle(key: string, value: unknown) {
<template>
<div class="prop-section">
<div class="prop-section__title">Tarih</div>
<div class="prop-row">
<div class="prop-row" data-tip="Tarih gosterim formati">
<label class="prop-label">Format</label>
<select class="prop-input prop-select"
:value="element.format ?? 'DD.MM.YYYY'"
@@ -33,19 +33,19 @@ function updateStyle(key: string, value: unknown) {
<option value="DD.MM.YYYY HH:mm">30.03.2026 14:30</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
<label class="prop-label">Boyut (pt)</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.style as TextStyle).fontSize ?? 10"
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metin rengi">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="(element.style as TextStyle).color ?? '#666666'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).align ?? 'left'"

View File

@@ -33,22 +33,22 @@ function onImageFileSelect(e: Event) {
<template>
<div class="prop-section">
<div class="prop-section__title">Gorsel</div>
<div class="prop-row">
<div class="prop-row" data-tip="Gorsel dosyasi secin (PNG, JPG, SVG)">
<label class="prop-label">Kaynak</label>
<label class="prop-file-btn">
Dosya Sec
<input type="file" accept="image/*" style="display: none" @change="onImageFileSelect" />
</label>
</div>
<div v-if="element.src" class="prop-row">
<div v-if="element.src" class="prop-row" data-tip="Yuklenen gorsel onizlemesi">
<label class="prop-label">Onizleme</label>
<img :src="element.src" class="prop-image-preview" />
</div>
<div v-if="element.src" class="prop-row">
<div v-if="element.src" class="prop-row" data-tip="Gorseli kaldirmak icin tiklayin">
<label class="prop-label"></label>
<button class="prop-clear" @click="update({ src: undefined } as any)">Gorseli kaldir</button>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Gorselin alana sigdirma modu">
<label class="prop-label">Sigdirma</label>
<select class="prop-input prop-select"
:value="element.style.objectFit ?? 'contain'"

View File

@@ -18,13 +18,13 @@ function updateStyle(key: string, value: unknown) {
<template>
<div class="prop-section">
<div class="prop-section__title">Cizgi Stili</div>
<div class="prop-row">
<div class="prop-row" data-tip="Cizgi kalinligi (mm)">
<label class="prop-label">Kalinlik (mm)</label>
<input class="prop-input" type="number" step="0.1" min="0.1"
:value="element.style.strokeWidth ?? 0.5"
@input="(e) => updateStyle('strokeWidth', parseFloat((e.target as HTMLInputElement).value) || 0.5)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Cizgi rengi">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="element.style.strokeColor ?? '#000000'"

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import '../../styles/properties.css'
const props = defineProps<{
top: number
right: number
@@ -20,10 +22,10 @@ function onInput(side: 'top' | 'right' | 'bottom' | 'left', e: Event) {
<div class="pb">
<span class="pb__label">Padding</span>
<div class="pb__box">
<input class="pb__in pb__in--t" type="number" step="1" min="0" :value="props.top" @input="(e) => onInput('top', e)" />
<input class="pb__in pb__in--r" type="number" step="1" min="0" :value="props.right" @input="(e) => onInput('right', e)" />
<input class="pb__in pb__in--b" type="number" step="1" min="0" :value="props.bottom" @input="(e) => onInput('bottom', e)" />
<input class="pb__in pb__in--l" type="number" step="1" min="0" :value="props.left" @input="(e) => onInput('left', e)" />
<input class="pb__in pb__in--t" type="number" step="1" min="0" :value="props.top" @input="(e) => onInput('top', e)" data-tip="Ust bosluk (mm)" />
<input class="pb__in pb__in--r" type="number" step="1" min="0" :value="props.right" @input="(e) => onInput('right', e)" data-tip="Sag bosluk (mm)" />
<input class="pb__in pb__in--b" type="number" step="1" min="0" :value="props.bottom" @input="(e) => onInput('bottom', e)" data-tip="Alt bosluk (mm)" />
<input class="pb__in pb__in--l" type="number" step="1" min="0" :value="props.left" @input="(e) => onInput('left', e)" data-tip="Sol bosluk (mm)" />
<div class="pb__center" />
</div>
</div>

View File

@@ -22,7 +22,7 @@ function updateStyle(key: string, value: unknown) {
<template>
<div class="prop-section">
<div class="prop-section__title">Sayfa Numarasi</div>
<div class="prop-row">
<div class="prop-row" data-tip="Sayfa numarasi gosterim formati">
<label class="prop-label">Format</label>
<select class="prop-input prop-select"
:value="element.format ?? '{current} / {total}'"
@@ -33,19 +33,19 @@ function updateStyle(key: string, value: unknown) {
<option value="Sayfa {current} / {total}">Sayfa 1 / 5</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
<label class="prop-label">Boyut (pt)</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.style as TextStyle).fontSize ?? 10"
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metin rengi">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="(element.style as TextStyle).color ?? '#666666'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).align ?? 'center'"

View File

@@ -18,7 +18,7 @@ function togglePositioning() {
<template>
<div class="prop-section">
<div class="prop-section__title">Pozisyon</div>
<div class="prop-row">
<div class="prop-row" data-tip="Flow: otomatik dizilim, Absolute: sabit konum">
<label class="prop-label">Mod</label>
<select class="prop-input prop-select" :value="element.position.type" @change="togglePositioning">
<option value="flow">Flow</option>
@@ -26,13 +26,13 @@ function togglePositioning() {
</select>
</div>
<template v-if="element.position.type === 'absolute'">
<div class="prop-row">
<div class="prop-row" data-tip="Yatay pozisyon parent sol kenardan uzaklik (mm)">
<label class="prop-label">X (mm)</label>
<input class="prop-input" type="number" step="0.5"
:value="element.position.x"
@input="(e) => templateStore.updateElementPosition(element.id, { type: 'absolute', x: parseFloat((e.target as HTMLInputElement).value) || 0, y: (element.position as any).y ?? 0 })" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Dikey pozisyon parent ust kenardan uzaklik (mm)">
<label class="prop-label">Y (mm)</label>
<input class="prop-input" type="number" step="0.5"
:value="element.position.y"

View File

@@ -88,7 +88,7 @@ const tableItemFields = computed(() => {
<!-- Data source -->
<div class="prop-section">
<div class="prop-section__title">Veri Kaynagi</div>
<div class="prop-row">
<div class="prop-row" data-tip="Tablonun baglanacagi array veri kaynagi">
<label class="prop-label">Kaynak</label>
<select class="prop-input prop-select"
:value="element.dataSource.path"
@@ -112,24 +112,31 @@ const tableItemFields = computed(() => {
<div
v-for="col in element.columns"
:key="col.id"
class="prop-column-card"
class="tbl-col"
>
<div class="prop-column-header">
<span class="prop-column-title">{{ col.title || col.field }}</span>
<div class="prop-column-actions">
<button class="prop-icon-btn" @click="moveColumn(col.id, -1)" title="Yukari">&#8593;</button>
<button class="prop-icon-btn" @click="moveColumn(col.id, 1)" title="Asagi">&#8595;</button>
<button class="prop-icon-btn prop-icon-btn--danger" @click="removeColumn(col.id)" title="Sil">x</button>
<!-- Row 1: title + actions -->
<div class="tbl-col__head">
<input class="tbl-col__title" type="text" :value="col.title"
@change="(e) => updateColumn(col.id, { title: (e.target as HTMLInputElement).value })"
:placeholder="col.field"
data-tip="Sutun basligi" />
<div class="tbl-col__actions">
<button class="tbl-col__act" @click="moveColumn(col.id, -1)" data-tip="Yukari tasi">
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M5 2L2 6h6L5 2z" fill="currentColor"/></svg>
</button>
<button class="tbl-col__act" @click="moveColumn(col.id, 1)" data-tip="Asagi tasi">
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M5 8L2 4h6L5 8z" fill="currentColor"/></svg>
</button>
<button class="tbl-col__act tbl-col__act--del" @click="removeColumn(col.id)" data-tip="Sutunu sil">
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M2 2l6 6M8 2l-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</button>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Baslik</label>
<input class="prop-input" type="text" :value="col.title"
@change="(e) => updateColumn(col.id, { title: (e.target as HTMLInputElement).value })" />
</div>
<div class="prop-row">
<label class="prop-label">Alan</label>
<select v-if="tableItemFields.length > 0" class="prop-input prop-select" :value="col.field"
<!-- Row 2: field + align + format + width compact -->
<div class="tbl-col__controls">
<!-- Field -->
<select v-if="tableItemFields.length > 0" class="tbl-col__field" :value="col.field" data-tip="Veri alani"
@change="(e) => {
const field = (e.target as HTMLSelectElement).value
const node = tableItemFields.find(f => f.key === field)
@@ -144,23 +151,30 @@ const tableItemFields = computed(() => {
updateColumn(col.id, { field })
}
}">
<option v-for="f in tableItemFields" :key="f.key" :value="f.key">{{ f.title }} ({{ f.key }})</option>
<option v-for="f in tableItemFields" :key="f.key" :value="f.key">{{ f.key }}</option>
</select>
<input v-else class="prop-input" type="text" :value="col.field"
@change="(e) => updateColumn(col.id, { field: (e.target as HTMLInputElement).value })" />
<input v-else class="tbl-col__field" type="text" :value="col.field"
@change="(e) => updateColumn(col.id, { field: (e.target as HTMLInputElement).value })"
data-tip="Veri alani" />
<!-- Alignment icons -->
<div class="tbl-col__align">
<button class="tbl-col__align-btn" :class="{ 'tbl-col__align-btn--on': col.align === 'left' }" @click="updateColumn(col.id, { align: 'left' })" data-tip="Sola hizala">
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="1" y1="3" x2="11" y2="3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="1" y1="6" x2="8" y2="6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="1" y1="9" x2="10" y2="9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</button>
<button class="tbl-col__align-btn" :class="{ 'tbl-col__align-btn--on': col.align === 'center' }" @click="updateColumn(col.id, { align: 'center' })" data-tip="Ortala">
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="1" y1="3" x2="11" y2="3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="2.5" y1="6" x2="9.5" y2="6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="1.5" y1="9" x2="10.5" y2="9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</button>
<button class="tbl-col__align-btn" :class="{ 'tbl-col__align-btn--on': col.align === 'right' }" @click="updateColumn(col.id, { align: 'right' })" data-tip="Saga hizala">
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="1" y1="3" x2="11" y2="3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="4" y1="6" x2="11" y2="6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="2" y1="9" x2="11" y2="9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</button>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select" :value="col.align"
@change="(e) => updateColumn(col.id, { align: (e.target as HTMLSelectElement).value as 'left'|'center'|'right' })">
<option value="left">Sol</option>
<option value="center">Orta</option>
<option value="right">Sag</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Format</label>
<select class="prop-input prop-select" :value="col.format ?? ''"
<!-- Row 3: format + width -->
<div class="tbl-col__extra" data-tip="Veri gosterim formati">
<label class="tbl-col__elabel">Format</label>
<select class="tbl-col__fmt" :value="col.format ?? ''"
@change="(e) => updateColumn(col.id, { format: ((e.target as HTMLSelectElement).value || undefined) as FormatType | undefined })">
<option value="">Yok</option>
<option value="currency">Para birimi</option>
@@ -169,10 +183,9 @@ const tableItemFields = computed(() => {
<option value="percentage">Yuzde</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Genislik</label>
<select class="prop-input prop-select"
:value="col.width.type"
<div class="tbl-col__extra" data-tip="Sutun genislik modu">
<label class="tbl-col__elabel">Genislik</label>
<select class="tbl-col__wtype" :value="col.width.type"
@change="(e) => {
const t = (e.target as HTMLSelectElement).value
if (t === 'auto') updateColumn(col.id, { width: { type: 'auto' } })
@@ -183,71 +196,484 @@ const tableItemFields = computed(() => {
<option value="fixed">Sabit (mm)</option>
<option value="fr">Oran (fr)</option>
</select>
<span v-if="col.width.type === 'fixed' || col.width.type === 'fr'" class="ts-tip-wrap" :data-tip="col.width.type === 'fixed' ? 'Sabit genislik (mm)' : 'Oran degeri (fr)'">
<input class="tbl-col__wval" type="number" step="1"
:min="col.width.type === 'fixed' ? 5 : 1"
:value="(col.width as any).value"
@change="(e) => updateColumn(col.id, { width: { type: col.width.type, value: parseFloat((e.target as HTMLInputElement).value) || (col.width.type === 'fixed' ? 30 : 1) } as any })" />
</span>
</div>
<div v-if="col.width.type === 'fixed'" class="prop-row">
<label class="prop-label">mm</label>
<input class="prop-input" type="number" step="1" min="5"
:value="(col.width as any).value"
@change="(e) => updateColumn(col.id, { width: { type: 'fixed', value: parseFloat((e.target as HTMLInputElement).value) || 30 } })" />
</div>
</div>
</div>
<!-- Sayfa bölme ayarları -->
<div class="prop-section">
<div class="prop-section__title">Sayfa Bolme</div>
<div class="prop-row">
<label class="prop-label">Header tekrarla</label>
<input type="checkbox"
:checked="element.repeatHeader !== false"
@change="(e) => update({ repeatHeader: (e.target as HTMLInputElement).checked } as any)" />
</div>
</div>
<!-- Table style -->
<div class="prop-section">
<div class="prop-section__title">Tablo Stili</div>
<div class="prop-row">
<label class="prop-label">Yazi boyutu</label>
<input class="prop-input" type="number" step="1" min="6"
:value="element.style.fontSize ?? 10"
@input="(e) => updateTableStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</div>
<div class="prop-row">
<label class="prop-label">Header bg</label>
<input class="prop-input prop-color" type="color"
:value="element.style.headerBg ?? '#f0f0f0'"
@input="(e) => updateTableStyle('headerBg', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<label class="prop-label">Header renk</label>
<input class="prop-input prop-color" type="color"
:value="element.style.headerColor ?? '#000000'"
@input="(e) => updateTableStyle('headerColor', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<label class="prop-label">Zebra tek</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
:value="element.style.zebraOdd ?? '#fafafa'"
@input="(e) => updateTableStyle('zebraOdd', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.zebraOdd" class="prop-clear" @click="updateTableStyle('zebraOdd', undefined)">x</button>
<div class="ts-form">
<!-- Font sizes -->
<label class="ts-lbl" data-tip="Icerik ve header yazi boyutu (pt)">Yazi boyutu</label>
<div class="ts-val ts-val--pair">
<span class="ts-sep">Icerik</span>
<span class="ts-tip-wrap" data-tip="Icerik yazi boyutu (pt)">
<input class="ts-num" type="number" step="1" min="6" max="99"
:value="element.style.fontSize ?? 10"
@input="(e) => updateTableStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</span>
<span class="ts-sep">Header</span>
<span class="ts-tip-wrap" data-tip="Header yazi boyutu (pt)">
<input class="ts-num" type="number" step="1" min="6" max="99"
:value="element.style.headerFontSize ?? element.style.fontSize ?? 10"
@input="(e) => updateTableStyle('headerFontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</span>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Kenarlik rengi</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
:value="element.style.borderColor ?? '#cccccc'"
@input="(e) => updateTableStyle('borderColor', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.borderColor" class="prop-clear" @click="updateTableStyle('borderColor', undefined)">x</button>
<!-- Colors -->
<label class="ts-lbl" data-tip="Header, metin ve zebra satirlari renkleri">Renkler</label>
<div class="ts-val ts-val--colors">
<div class="ts-color-item" data-tip="Header arkaplan rengi">
<input class="ts-swatch" type="color"
:value="element.style.headerBg ?? '#f0f0f0'"
@input="(e) => updateTableStyle('headerBg', (e.target as HTMLInputElement).value)" />
<span class="ts-clbl">Arkaplan</span>
</div>
<div class="ts-color-item" data-tip="Header metin rengi">
<input class="ts-swatch" type="color"
:value="element.style.headerColor ?? '#000000'"
@input="(e) => updateTableStyle('headerColor', (e.target as HTMLInputElement).value)" />
<span class="ts-clbl">Metin</span>
</div>
<div class="ts-color-item" data-tip="Zebra satir rengi tek satirlar">
<div class="ts-swatch-wrap">
<input class="ts-swatch" type="color"
:value="element.style.zebraOdd ?? '#fafafa'"
@input="(e) => updateTableStyle('zebraOdd', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.zebraOdd" class="ts-swatch-clr" @click="updateTableStyle('zebraOdd', undefined)">&times;</button>
</div>
<span class="ts-clbl">Zebra</span>
</div>
</div>
<!-- Border -->
<label class="ts-lbl" data-tip="Tablo kenarlik rengi ve kalinligi">Kenarlik</label>
<div class="ts-val ts-val--pair">
<div class="ts-swatch-wrap" data-tip="Kenarlik rengi">
<input class="ts-swatch" type="color"
:value="element.style.borderColor ?? '#cccccc'"
@input="(e) => updateTableStyle('borderColor', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.borderColor" class="ts-swatch-clr" @click="updateTableStyle('borderColor', undefined)">&times;</button>
</div>
<span class="ts-tip-wrap" data-tip="Kenarlik kalinligi (mm)">
<input class="ts-num" type="number" step="0.1" min="0" max="99"
:value="element.style.borderWidth ?? 0.5"
@input="(e) => updateTableStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</span>
<span class="ts-unit">mm</span>
</div>
<!-- Cell padding -->
<label class="ts-lbl" data-tip="Hucre ic bosluklari yatay ve dikey (mm)">Ic bosluk</label>
<div class="ts-val ts-val--pair">
<span class="ts-pad-icon" data-tip="Yatay bosluk (mm)">&#8596;</span>
<span class="ts-tip-wrap" data-tip="Yatay ic bosluk (mm)">
<input class="ts-num" type="number" step="0.5" min="0" max="99"
:value="element.style.cellPaddingH ?? 2"
@input="(e) => updateTableStyle('cellPaddingH', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</span>
<span class="ts-pad-icon" data-tip="Dikey bosluk (mm)">&#8597;</span>
<span class="ts-tip-wrap" data-tip="Dikey ic bosluk (mm)">
<input class="ts-num" type="number" step="0.5" min="0" max="99"
:value="element.style.cellPaddingV ?? 1"
@input="(e) => updateTableStyle('cellPaddingV', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</span>
</div>
<!-- Header padding -->
<label class="ts-lbl" data-tip="Header hucre bosluklari yatay ve dikey (mm)">Header bosluk</label>
<div class="ts-val ts-val--pair">
<span class="ts-pad-icon" data-tip="Yatay bosluk (mm)">&#8596;</span>
<span class="ts-tip-wrap" data-tip="Header yatay bosluk (mm)">
<input class="ts-num" type="number" step="0.5" min="0" max="99"
:value="element.style.headerPaddingH ?? element.style.cellPaddingH ?? 2"
@input="(e) => updateTableStyle('headerPaddingH', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</span>
<span class="ts-pad-icon" data-tip="Dikey bosluk (mm)">&#8597;</span>
<span class="ts-tip-wrap" data-tip="Header dikey bosluk (mm)">
<input class="ts-num" type="number" step="0.5" min="0" max="99"
:value="element.style.headerPaddingV ?? element.style.cellPaddingV ?? 1"
@input="(e) => updateTableStyle('headerPaddingV', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</span>
</div>
<!-- Repeat header -->
<label class="ts-lbl" data-tip="Cok sayfali tablolarda header'i her sayfada tekrarla">Header tekrarla</label>
<div class="ts-val">
<label class="ts-toggle">
<input type="checkbox"
:checked="element.repeatHeader !== false"
@change="(e) => update({ repeatHeader: (e.target as HTMLInputElement).checked } as any)" />
<span class="ts-toggle__track"></span>
</label>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Kenarlik (mm)</label>
<input class="prop-input" type="number" step="0.1" min="0"
:value="element.style.borderWidth ?? 0.5"
@input="(e) => updateTableStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</div>
</div>
</template>
<style scoped>
/* Column card - compact */
.tbl-col {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 5px;
padding: 5px 6px;
margin-bottom: 5px;
}
.tbl-col__head {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 4px;
}
.tbl-col__title {
flex: 1;
min-width: 0;
border: none;
background: transparent;
font-size: 12px;
font-weight: 500;
color: #334155;
padding: 1px 0;
outline: none;
}
.tbl-col__title:focus {
border-bottom: 1px solid #93c5fd;
}
.tbl-col__actions {
display: flex;
gap: 1px;
flex-shrink: 0;
}
.tbl-col__act {
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border: none;
border-radius: 3px;
background: transparent;
color: #94a3b8;
cursor: pointer;
padding: 0;
}
.tbl-col__act:hover {
background: #e2e8f0;
color: #475569;
}
.tbl-col__act--del:hover {
background: #fef2f2;
color: #dc2626;
}
.tbl-col__controls {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 3px;
}
.tbl-col__field {
flex: 1;
min-width: 0;
padding: 2px 4px;
border: 1px solid #e2e8f0;
border-radius: 3px;
font-size: 11px;
background: white;
color: #334155;
}
.tbl-col__field:focus {
outline: none;
border-color: #93c5fd;
}
.tbl-col__align {
display: flex;
gap: 0;
flex-shrink: 0;
}
.tbl-col__align-btn {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border: 1px solid #e2e8f0;
background: white;
color: #94a3b8;
cursor: pointer;
padding: 0;
}
.tbl-col__align-btn:first-child {
border-radius: 3px 0 0 3px;
}
.tbl-col__align-btn:last-child {
border-radius: 0 3px 3px 0;
}
.tbl-col__align-btn:not(:first-child) {
border-left: none;
}
.tbl-col__align-btn--on {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
.tbl-col__extra {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 3px;
}
.tbl-col__elabel {
font-size: 11px;
color: #64748b;
flex-shrink: 0;
}
.tbl-col__fmt {
flex: 1;
min-width: 0;
padding: 2px 4px;
border: 1px solid #e2e8f0;
border-radius: 3px;
font-size: 11px;
background: white;
color: #334155;
cursor: pointer;
}
.tbl-col__wtype {
width: 80px;
padding: 2px 4px;
border: 1px solid #e2e8f0;
border-radius: 3px;
font-size: 11px;
background: white;
color: #334155;
cursor: pointer;
}
.tbl-col__wval {
width: 36px;
padding: 2px 3px;
border: 1px solid #e2e8f0;
border-radius: 3px;
font-size: 11px;
background: white;
color: #334155;
text-align: center;
-moz-appearance: textfield;
}
.tbl-col__wval::-webkit-inner-spin-button,
.tbl-col__wval::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.tbl-col__wval:focus {
outline: none;
border-color: #93c5fd;
}
/* Table style — aligned 2-column form */
.ts-form {
display: grid;
grid-template-columns: auto 1fr;
gap: 5px 8px;
align-items: center;
}
.ts-lbl {
font-size: 11px;
color: #64748b;
white-space: nowrap;
}
.ts-val {
display: flex;
align-items: center;
justify-content: flex-end;
}
.ts-val--pair {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
}
.ts-val--colors {
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: 6px;
}
.ts-sep {
font-size: 10px;
color: #94a3b8;
}
.ts-num {
width: 32px;
padding: 2px 3px;
border: 1px solid #e2e8f0;
border-radius: 3px;
font-size: 11px;
background: white;
color: #334155;
text-align: center;
-moz-appearance: textfield;
}
.ts-num::-webkit-inner-spin-button,
.ts-num::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.ts-num:focus {
outline: none;
border-color: #93c5fd;
}
.ts-unit {
font-size: 10px;
color: #94a3b8;
}
/* Color swatches */
.ts-color-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.ts-clbl {
font-size: 9px;
color: #94a3b8;
white-space: nowrap;
}
.ts-swatch {
width: 22px;
height: 22px;
padding: 0;
cursor: pointer;
border: 1px solid #e2e8f0;
border-radius: 3px;
}
.ts-swatch-wrap {
position: relative;
display: inline-flex;
}
.ts-swatch-clr {
position: absolute;
top: -4px;
right: -4px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #f1f5f9;
border: 1px solid #e2e8f0;
font-size: 9px;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #94a3b8;
padding: 0;
}
.ts-swatch-clr:hover {
background: #fef2f2;
color: #dc2626;
border-color: #fecaca;
}
.ts-pad-icon {
font-size: 11px;
color: #94a3b8;
line-height: 1;
}
.ts-tip-wrap {
position: relative;
display: inline-flex;
}
/* Toggle switch */
.ts-toggle {
position: relative;
display: inline-block;
cursor: pointer;
}
.ts-toggle input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.ts-toggle__track {
display: block;
width: 28px;
height: 16px;
background: #e2e8f0;
border-radius: 8px;
transition: background 0.15s;
position: relative;
}
.ts-toggle__track::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 12px;
height: 12px;
background: white;
border-radius: 50%;
transition: transform 0.15s;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.ts-toggle input:checked + .ts-toggle__track {
background: #3b82f6;
}
.ts-toggle input:checked + .ts-toggle__track::after {
transform: translateX(12px);
}
</style>

View File

@@ -44,19 +44,19 @@ function removeSpan(index: number) {
<template>
<div class="prop-section">
<div class="prop-section__title">Varsayilan Stil</div>
<div class="prop-row">
<div class="prop-row" data-tip="Varsayilan yazi tipi boyutu (point)">
<label class="prop-label">Boyut (pt)</label>
<input class="prop-input" type="number" step="1" min="1"
:value="element.style.fontSize ?? 11"
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Varsayilan metin rengi">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="element.style.color ?? '#000000'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select"
:value="element.style.align ?? 'left'"
@@ -85,13 +85,13 @@ function removeSpan(index: number) {
>&times;</button>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Span metin icerigi">
<label class="prop-label">Metin</label>
<input class="prop-input" type="text"
:value="span.text ?? ''"
@input="(e) => updateSpan(idx, { text: (e.target as HTMLInputElement).value })" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Span yazi boyutu — bos birakilirsa varsayilan kullanilir">
<label class="prop-label">Boyut</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(span.style as TextStyle).fontSize ?? ''"
@@ -101,7 +101,7 @@ function removeSpan(index: number) {
updateSpanStyle(idx, 'fontSize', v ? parseFloat(v) : undefined)
}" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Span yazi kalinligi">
<label class="prop-label">Kalinlik</label>
<select class="prop-input prop-select"
:value="(span.style as TextStyle).fontWeight ?? ''"
@@ -114,7 +114,7 @@ function removeSpan(index: number) {
<option value="bold">Kalin</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Span metin rengi">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="(span.style as TextStyle).color ?? element.style.color ?? '#000000'"

View File

@@ -22,7 +22,7 @@ function updateStyle(key: string, value: unknown) {
<template>
<div class="prop-section">
<div class="prop-section__title">Sekil</div>
<div class="prop-row">
<div class="prop-row" data-tip="Sekil tipi">
<label class="prop-label">Tip</label>
<select class="prop-input prop-select"
:value="element.shapeType"
@@ -32,25 +32,25 @@ function updateStyle(key: string, value: unknown) {
<option value="ellipse">Elips</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Sekil arka plan rengi">
<label class="prop-label">Arka Plan</label>
<input class="prop-input prop-color" type="color"
:value="element.style.backgroundColor ?? '#f0f0f0'"
@input="(e) => updateStyle('backgroundColor', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Kenarlik cizgisi rengi">
<label class="prop-label">Kenar Rengi</label>
<input class="prop-input prop-color" type="color"
:value="element.style.borderColor ?? '#333333'"
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Kenarlik cizgi kalinligi (mm)">
<label class="prop-label">Kenar Kalinligi</label>
<input class="prop-input" type="number" step="0.25" min="0"
:value="element.style.borderWidth ?? 0.5"
@input="(e) => updateStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</div>
<div v-if="element.shapeType === 'rounded_rectangle'" class="prop-row">
<div v-if="element.shapeType === 'rounded_rectangle'" class="prop-row" data-tip="Kose yuvarlakligi (mm)">
<label class="prop-label">Kose Yuvarlakligi</label>
<input class="prop-input" type="number" step="0.5" min="0"
:value="element.style.borderRadius ?? 2"

View File

@@ -14,7 +14,7 @@ function updateSize(axis: 'width' | 'height', sv: SizeValue) {
<template>
<div class="prop-section">
<div class="prop-section__title">Boyut</div>
<div class="prop-row">
<div class="prop-row" data-tip="Genislik boyutlandirma modu">
<label class="prop-label">Genislik</label>
<select class="prop-input prop-select"
:value="element.size.width.type"
@@ -29,20 +29,20 @@ function updateSize(axis: 'width' | 'height', sv: SizeValue) {
<option value="fr">Oran (fr)</option>
</select>
</div>
<div v-if="element.size.width.type === 'fixed'" class="prop-row">
<div v-if="element.size.width.type === 'fixed'" class="prop-row" data-tip="Sabit genislik degeri (mm)">
<label class="prop-label">mm</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.size.width as any).value"
@input="(e) => updateSize('width', { type: 'fixed', value: parseFloat((e.target as HTMLInputElement).value) || 10 })" />
</div>
<div v-if="element.size.width.type === 'fr'" class="prop-row">
<div v-if="element.size.width.type === 'fr'" class="prop-row" data-tip="Kalan alani oransal doldurma degeri">
<label class="prop-label">fr</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.size.width as any).value"
@input="(e) => updateSize('width', { type: 'fr', value: parseFloat((e.target as HTMLInputElement).value) || 1 })" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Yukseklik boyutlandirma modu">
<label class="prop-label">Yukseklik</label>
<select class="prop-input prop-select"
:value="element.size.height.type"
@@ -57,7 +57,7 @@ function updateSize(axis: 'width' | 'height', sv: SizeValue) {
<option value="fr">Oran (fr)</option>
</select>
</div>
<div v-if="element.size.height.type === 'fixed'" class="prop-row">
<div v-if="element.size.height.type === 'fixed'" class="prop-row" data-tip="Sabit yukseklik degeri (mm)">
<label class="prop-label">mm</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.size.height as any).value"

View File

@@ -23,20 +23,20 @@ function updateStyle(key: string, value: unknown) {
<div class="prop-section">
<div class="prop-section__title">Metin Stili</div>
<div v-if="element.type === 'static_text'" class="prop-row">
<div v-if="element.type === 'static_text'" class="prop-row" data-tip="Sabit metin icerigi">
<label class="prop-label">Metin</label>
<input class="prop-input" type="text"
:value="(element as StaticTextElement).content"
@input="(e) => update({ content: (e.target as HTMLInputElement).value } as any)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
<label class="prop-label">Boyut (pt)</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.style as TextStyle).fontSize ?? 11"
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Yazi tipi kalinligi">
<label class="prop-label">Kalinlik</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).fontWeight ?? 'normal'"
@@ -45,13 +45,13 @@ function updateStyle(key: string, value: unknown) {
<option value="bold">Kalin</option>
</select>
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metin rengi">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="(element.style as TextStyle).color ?? '#000000'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).align ?? 'left'"

View File

@@ -101,6 +101,10 @@ export interface TableStyle {
borderWidth?: number // pt
fontSize?: number // pt
headerFontSize?: number // pt
cellPaddingH?: number // mm — hücre yatay iç boşluk (sol+sağ). Default: 2
cellPaddingV?: number // mm — hücre dikey iç boşluk (üst+alt). Default: 1
headerPaddingH?: number // mm — header yatay iç boşluk. Default: cellPaddingH
headerPaddingV?: number // mm — header dikey iç boşluk. Default: cellPaddingV
}
// --- Barcode ---
@@ -208,9 +212,9 @@ export interface RichTextElement extends BaseElement {
style: TextStyle // varsayılan stil
}
export interface PageBreakElement {
export interface PageBreakElement extends BaseElement {
type: 'page_break'
id: string
style: Record<string, never>
}
export interface ContainerElement extends BaseElement {

View File

@@ -0,0 +1,80 @@
let tooltipEl: HTMLDivElement | null = null
let currentTarget: HTMLElement | null = null
function getTooltip(): HTMLDivElement {
if (!tooltipEl) {
tooltipEl = document.createElement('div')
tooltipEl.className = 'prop-tooltip'
document.body.appendChild(tooltipEl)
}
return tooltipEl
}
function show(el: HTMLElement) {
const text = el.getAttribute('data-tip')
if (!text) return
currentTarget = el
const tip = getTooltip()
tip.textContent = text
// Position before showing so we can measure
tip.style.top = '0px'
tip.style.left = '0px'
tip.classList.add('prop-tooltip--visible')
const rect = el.getBoundingClientRect()
const tipRect = tip.getBoundingClientRect()
let top = rect.top - tipRect.height - 6
let left = rect.left + rect.width / 2 - tipRect.width / 2
// Clamp to viewport
if (top < 4) top = rect.bottom + 6
if (left < 4) left = 4
if (left + tipRect.width > window.innerWidth - 4) {
left = window.innerWidth - tipRect.width - 4
}
tip.style.top = `${top}px`
tip.style.left = `${left}px`
}
function hide() {
currentTarget = null
if (tooltipEl) {
tooltipEl.classList.remove('prop-tooltip--visible')
}
}
function closest(el: EventTarget | null): HTMLElement | null {
if (!(el instanceof HTMLElement)) return null
return el.closest('[data-tip]')
}
let installed = false
/** Call once to enable data-tip tooltips globally via event delegation. */
export function setupTooltips() {
if (installed) return
installed = true
document.addEventListener('pointerenter', (e) => {
const target = closest(e.target)
if (target) show(target)
}, true)
document.addEventListener('pointerleave', (e) => {
const target = closest(e.target)
if (target && target === currentTarget) hide()
}, true)
document.addEventListener('focusin', (e) => {
const target = closest(e.target)
if (target) show(target)
}, true)
document.addEventListener('focusout', (e) => {
const target = closest(e.target)
if (target && target === currentTarget) hide()
}, true)
}

View File

@@ -8,6 +8,7 @@ import { useEditorStore } from '../stores/editor'
import EditorCanvas from '../components/editor/EditorCanvas.vue'
import ToolboxPanel from '../components/panels/ToolboxPanel.vue'
import SchemaTreePanel from '../components/panels/SchemaTreePanel.vue'
import { setupTooltips } from '../directives/tip'
import PropertiesPanel from '../components/panels/PropertiesPanel.vue'
export interface DreportEditorConfig {
@@ -46,6 +47,7 @@ onMounted(() => {
templateStore.template = JSON.parse(JSON.stringify(props.modelValue))
nextTick(() => { syncing = false })
templateStore.setOverrideData(props.data ?? null)
setupTooltips()
})
watch(() => props.schema, (val) => {
@@ -81,8 +83,9 @@ function onCompileError(error: string | null) {
// --- Keyboard shortcuts ---
function onKeyDown(e: KeyboardEvent) {
const tag = (e.target as HTMLElement)?.tagName
const isInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'
const target = e.target as HTMLElement
const tag = target?.tagName
const isInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target?.isContentEditable
// Delete / Backspace
if ((e.key === 'Delete' || e.key === 'Backspace') && editorStore.selectedElementId) {

View File

@@ -1,8 +1,11 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import { setupTooltips } from './directives/tip'
import './styles/editor.css'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
setupTooltips()

View File

@@ -46,6 +46,13 @@
gap: 4px;
}
.prop-row-stack {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 6px;
}
.prop-label {
font-size: 12px;
color: #475569;
@@ -205,3 +212,22 @@
color: #dc2626;
border-color: #fecaca;
}
/* Tooltip — global portal element */
.prop-tooltip {
position: fixed;
background: #0f172a;
color: #e2e8f0;
font-size: 10px;
padding: 3px 6px;
border-radius: 4px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
z-index: 9999;
}
.prop-tooltip--visible {
opacity: 1;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -3,6 +3,17 @@ import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
dedupe: [
'@codemirror/state',
'@codemirror/view',
'@codemirror/language',
'@codemirror/autocomplete',
'@lezer/common',
'@lezer/lr',
'@lezer/highlight',
],
},
worker: {
format: 'es',
},