From 99125f1fea1c8c72c61f8cba94d847ed3471a4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Tue, 21 Jan 2025 18:37:18 +0100 Subject: [PATCH] UPDATE: Adds cell context menu --- CHANGELOG.md | 8 + Cargo.lock | 2 +- bindings/wasm/Cargo.toml | 2 +- webapp/IronCalc/package-lock.json | 188 ++++----- .../src/components/CellContextMenu.tsx | 286 +++++++++++++ webapp/IronCalc/src/components/usePointer.ts | 1 - webapp/IronCalc/src/components/worksheet.tsx | 388 ++++++++++-------- webapp/IronCalc/src/locale/en_us.json | 15 + 8 files changed, 632 insertions(+), 258 deletions(-) create mode 100644 webapp/IronCalc/src/components/CellContextMenu.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f2179b..1a07f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,20 @@ - New document server (Thanks Dani!) - New function FORMULATEXT - Name Manager ([#212](https://github.com/ironcalc/IronCalc/pull/212) [#220](https://github.com/ironcalc/IronCalc/pull/220)) +- Add context menu. We can now insert rows and columns. Freeze and unfreeze rows and columns. Delete rows and columns [#271] +- Add nodejs bindings [#254] +- Add python bindings for all platforms +- Add is split into the product and widget +- Add Python documentation [#260] ### Fixed - Fixed several issues with pasting content - Fixed several issues with borders - Fixed bug where columns and rows could be resized to negative width and height, respectively +- Undo/redo when add/delete sheet now works [#270] +- Numerous small fixes +- Multiple fixes to the documentation ## [0.2.0] - 2024-11-06 (The HN release) diff --git a/Cargo.lock b/Cargo.lock index 0f61571..6380c37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1070,7 +1070,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm" -version = "0.3.0" +version = "0.3.2" dependencies = [ "ironcalc_base", "serde", diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index e929a24..000ca65 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm" -version = "0.3.0" +version = "0.3.2" authors = ["Nicolas Hatcher "] description = "IronCalc Web bindings" license = "MIT/Apache-2.0" diff --git a/webapp/IronCalc/package-lock.json b/webapp/IronCalc/package-lock.json index 891c927..7c4edb9 100644 --- a/webapp/IronCalc/package-lock.json +++ b/webapp/IronCalc/package-lock.json @@ -45,7 +45,7 @@ } }, "../../bindings/wasm/pkg": { - "version": "0.3.0", + "version": "0.3.2", "license": "MIT/Apache-2.0" }, "node_modules/@adobe/css-tools": { @@ -1383,9 +1383,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.2.tgz", - "integrity": "sha512-6Fyg9yQbwJR+ykVdT9sid1oc2ewejS6h4wzQltmJfSW53N60G/ah9pngXGANdy9/aaE/TcUFpWosdm7JXS1WTQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", + "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", "cpu": [ "arm" ], @@ -1396,9 +1396,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.2.tgz", - "integrity": "sha512-K5GfWe+vtQ3kyEbihrimM38UgX57UqHp+oME7X/EX9Im6suwZfa7Hsr8AtzbJvukTpwMGs+4s29YMSO3rwWtsw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", + "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", "cpu": [ "arm64" ], @@ -1409,9 +1409,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.2.tgz", - "integrity": "sha512-PSN58XG/V/tzqDb9kDGutUruycgylMlUE59f40ny6QIRNsTEIZsrNQTJKUN2keMMSmlzgunMFqyaGLmly39sug==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", + "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", "cpu": [ "arm64" ], @@ -1422,9 +1422,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.2.tgz", - "integrity": "sha512-gQhK788rQJm9pzmXyfBB84VHViDERhAhzGafw+E5mUpnGKuxZGkMVDa3wgDFKT6ukLC5V7QTifzsUKdNVxp5qQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", + "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", "cpu": [ "x64" ], @@ -1435,9 +1435,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.2.tgz", - "integrity": "sha512-eiaHgQwGPpxLC3+zTAcdKl4VsBl3r0AiJOd1Um/ArEzAjN/dbPK1nROHrVkdnoE6p7Svvn04w3f/jEZSTVHunA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", + "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", "cpu": [ "arm64" ], @@ -1448,9 +1448,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.2.tgz", - "integrity": "sha512-lhdiwQ+jf8pewYOTG4bag0Qd68Jn1v2gO1i0mTuiD+Qkt5vNfHVK/jrT7uVvycV8ZchlzXp5HDVmhpzjC6mh0g==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", + "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", "cpu": [ "x64" ], @@ -1461,9 +1461,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.2.tgz", - "integrity": "sha512-lfqTpWjSvbgQP1vqGTXdv+/kxIznKXZlI109WkIFPbud41bjigjNmOAAKoazmRGx+k9e3rtIdbq2pQZPV1pMig==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", + "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", "cpu": [ "arm" ], @@ -1474,9 +1474,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.2.tgz", - "integrity": "sha512-RGjqULqIurqqv+NJTyuPgdZhka8ImMLB32YwUle2BPTDqDoXNgwFjdjQC59FbSk08z0IqlRJjrJ0AvDQ5W5lpw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", + "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", "cpu": [ "arm" ], @@ -1487,9 +1487,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.2.tgz", - "integrity": "sha512-ZvkPiheyXtXlFqHpsdgscx+tZ7hoR59vOettvArinEspq5fxSDSgfF+L5wqqJ9R4t+n53nyn0sKxeXlik7AY9Q==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", + "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", "cpu": [ "arm64" ], @@ -1500,9 +1500,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.2.tgz", - "integrity": "sha512-UlFk+E46TZEoxD9ufLKDBzfSG7Ki03fo6hsNRRRHF+KuvNZ5vd1RRVQm8YZlGsjcJG8R252XFK0xNPay+4WV7w==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", + "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", "cpu": [ "arm64" ], @@ -1513,9 +1513,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.2.tgz", - "integrity": "sha512-hJhfsD9ykx59jZuuoQgYT1GEcNNi3RCoEmbo5OGfG8RlHOiVS7iVNev9rhLKh7UBYq409f4uEw0cclTXx8nh8Q==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", + "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", "cpu": [ "loong64" ], @@ -1526,9 +1526,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.2.tgz", - "integrity": "sha512-g/O5IpgtrQqPegvqopvmdCF9vneLE7eqYfdPWW8yjPS8f63DNam3U4ARL1PNNB64XHZDHKpvO2Giftf43puB8Q==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", + "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", "cpu": [ "ppc64" ], @@ -1539,9 +1539,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.2.tgz", - "integrity": "sha512-bSQijDC96M6PuooOuXHpvXUYiIwsnDmqGU8+br2U7iPoykNi9JtMUpN7K6xml29e0evK0/g0D1qbAUzWZFHY5Q==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", + "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", "cpu": [ "riscv64" ], @@ -1552,9 +1552,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.2.tgz", - "integrity": "sha512-49TtdeVAsdRuiUHXPrFVucaP4SivazetGUVH8CIxVsNsaPHV4PFkpLmH9LeqU/R4Nbgky9lzX5Xe1NrzLyraVA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", + "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", "cpu": [ "s390x" ], @@ -1565,9 +1565,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.2.tgz", - "integrity": "sha512-j+jFdfOycLIQ7FWKka9Zd3qvsIyugg5LeZuHF6kFlXo6MSOc6R1w37YUVy8VpAKd81LMWGi5g9J25P09M0SSIw==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", + "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", "cpu": [ "x64" ], @@ -1578,9 +1578,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.2.tgz", - "integrity": "sha512-aDPHyM/D2SpXfSNCVWCxyHmOqN9qb7SWkY1+vaXqMNMXslZYnwh9V/UCudl6psyG0v6Ukj7pXanIpfZwCOEMUg==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", + "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", "cpu": [ "x64" ], @@ -1591,9 +1591,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.2.tgz", - "integrity": "sha512-LQRkCyUBnAo7r8dbEdtNU08EKLCJMgAk2oP5H3R7BnUlKLqgR3dUjrLBVirmc1RK6U6qhtDw29Dimeer8d5hzQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", + "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", "cpu": [ "arm64" ], @@ -1604,9 +1604,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.2.tgz", - "integrity": "sha512-wt8OhpQUi6JuPFkm1wbVi1BByeag87LDFzeKSXzIdGcX4bMLqORTtKxLoCbV57BHYNSUSOKlSL4BYYUghainYA==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", + "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", "cpu": [ "ia32" ], @@ -1617,9 +1617,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.2.tgz", - "integrity": "sha512-rUrqINax0TvrPBXrFKg0YbQx18NpPN3NNrgmaao9xRNbTwek7lOXObhx8tQy8gelmQ/gLaGy1WptpU2eKJZImg==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", + "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", "cpu": [ "x64" ], @@ -3020,9 +3020,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001697", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz", - "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==", + "version": "1.0.30001698", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001698.tgz", + "integrity": "sha512-xJ3km2oiG/MbNU8G6zIq6XRZ6HtAOVXsbOrP/blGazi52kc5Yy7b6sDA5O+FbROzRrV7BSTllLHuNvmawYUJjw==", "dev": true, "funding": [ { @@ -3295,9 +3295,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.91", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.91.tgz", - "integrity": "sha512-sNSHHyq048PFmZY4S90ax61q+gLCs0X0YmcOII9wG9S2XwbVr+h4VW2wWhnbp/Eys3cCwTxVF292W3qPaxIapQ==", + "version": "1.5.96", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.96.tgz", + "integrity": "sha512-8AJUW6dh75Fm/ny8+kZKJzI1pgoE8bKLZlzDU2W1ENd+DXKJrx7I7l9hb8UWR4ojlnb5OlixMt00QWiYJoVw1w==", "dev": true }, "node_modules/entities": { @@ -4252,9 +4252,9 @@ } }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "engines": { "node": ">= 0.4" @@ -4550,9 +4550,9 @@ } }, "node_modules/rollup": { - "version": "4.34.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.2.tgz", - "integrity": "sha512-sBDUoxZEaqLu9QeNalL8v3jw6WjPku4wfZGyTU7l7m1oC+rpRihXc/n/H+4148ZkGz5Xli8CHMns//fFGKvpIQ==", + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", + "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", "dev": true, "dependencies": { "@types/estree": "1.0.6" @@ -4565,25 +4565,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.2", - "@rollup/rollup-android-arm64": "4.34.2", - "@rollup/rollup-darwin-arm64": "4.34.2", - "@rollup/rollup-darwin-x64": "4.34.2", - "@rollup/rollup-freebsd-arm64": "4.34.2", - "@rollup/rollup-freebsd-x64": "4.34.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.2", - "@rollup/rollup-linux-arm-musleabihf": "4.34.2", - "@rollup/rollup-linux-arm64-gnu": "4.34.2", - "@rollup/rollup-linux-arm64-musl": "4.34.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.2", - "@rollup/rollup-linux-riscv64-gnu": "4.34.2", - "@rollup/rollup-linux-s390x-gnu": "4.34.2", - "@rollup/rollup-linux-x64-gnu": "4.34.2", - "@rollup/rollup-linux-x64-musl": "4.34.2", - "@rollup/rollup-win32-arm64-msvc": "4.34.2", - "@rollup/rollup-win32-ia32-msvc": "4.34.2", - "@rollup/rollup-win32-x64-msvc": "4.34.2", + "@rollup/rollup-android-arm-eabi": "4.34.6", + "@rollup/rollup-android-arm64": "4.34.6", + "@rollup/rollup-darwin-arm64": "4.34.6", + "@rollup/rollup-darwin-x64": "4.34.6", + "@rollup/rollup-freebsd-arm64": "4.34.6", + "@rollup/rollup-freebsd-x64": "4.34.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", + "@rollup/rollup-linux-arm-musleabihf": "4.34.6", + "@rollup/rollup-linux-arm64-gnu": "4.34.6", + "@rollup/rollup-linux-arm64-musl": "4.34.6", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", + "@rollup/rollup-linux-riscv64-gnu": "4.34.6", + "@rollup/rollup-linux-s390x-gnu": "4.34.6", + "@rollup/rollup-linux-x64-gnu": "4.34.6", + "@rollup/rollup-linux-x64-musl": "4.34.6", + "@rollup/rollup-win32-arm64-msvc": "4.34.6", + "@rollup/rollup-win32-ia32-msvc": "4.34.6", + "@rollup/rollup-win32-x64-msvc": "4.34.6", "fsevents": "~2.3.2" } }, @@ -5031,14 +5031,14 @@ "dev": true }, "node_modules/vite": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", - "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", + "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", "dev": true, "dependencies": { "esbuild": "^0.24.2", - "postcss": "^8.4.49", - "rollup": "^4.23.0" + "postcss": "^8.5.1", + "rollup": "^4.30.1" }, "bin": { "vite": "bin/vite.js" diff --git a/webapp/IronCalc/src/components/CellContextMenu.tsx b/webapp/IronCalc/src/components/CellContextMenu.tsx new file mode 100644 index 0000000..3fe3f8b --- /dev/null +++ b/webapp/IronCalc/src/components/CellContextMenu.tsx @@ -0,0 +1,286 @@ +import { Menu, MenuItem, styled } from "@mui/material"; +import { + BetweenHorizontalStart, + BetweenVerticalStart, + ChevronRight, + Snowflake, + Trash2, +} from "lucide-react"; +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; + +const red_color = "rgb(235, 12, 12)"; + +interface CellContextMenuProps { + open: boolean; + onClose: () => void; + anchorEl: HTMLDivElement | null; + onInsertRowAbove: () => void; + onInsertRowBelow: () => void; + onInsertColumnLeft: () => void; + onInsertColumnRight: () => void; + onFreezeColumns: () => void; + onFreezeRows: () => void; + onUnfreezeColumns: () => void; + onUnfreezeRows: () => void; + onDeleteRow: () => void; + onDeleteColumn: () => void; + row: number; + column: string; +} + +const CellContextMenu = (properties: CellContextMenuProps) => { + const { t } = useTranslation(); + const { + open, + onClose, + anchorEl, + onInsertRowAbove, + onInsertRowBelow, + onInsertColumnLeft, + onInsertColumnRight, + onFreezeColumns, + onFreezeRows, + onUnfreezeColumns, + onUnfreezeRows, + onDeleteRow, + onDeleteColumn, + row, + column, + } = properties; + const [freezeMenuOpen, setFreezeMenuOpen] = useState(false); + const freezeRef = useRef(null); + + const [insertRowMenuOpen, setInsertRowMenuOpen] = useState(false); + const insertRowRef = useRef(null); + + const [insertColumnMenuOpen, setInsertColumnMenuOpen] = useState(false); + const insertColumnRef = useRef(null); + + return ( + <> + + setInsertColumnMenuOpen(true)} + > + + {t("cell_context.insert_column")} + + + setInsertRowMenuOpen(true)} + > + + {t("cell_context.insert_row")} + + + + setFreezeMenuOpen(true)}> + + {t("cell_context.freeze")} + + + + + + + {t("cell_context.delete_row", { row })} + + + + + + {t("cell_context.delete_column", { column })} + + + + setInsertRowMenuOpen(false)} + anchorEl={insertRowRef.current} + anchorOrigin={{ + vertical: "top", + horizontal: "right", + }} + > + { + setInsertRowMenuOpen(false); + onInsertRowAbove(); + }} + > + {t("cell_context.insert_row_above")} + + { + setInsertRowMenuOpen(false); + onInsertRowBelow(); + }} + > + {t("cell_context.insert_row_below")} + + + setInsertColumnMenuOpen(false)} + anchorEl={insertColumnRef.current} + anchorOrigin={{ + vertical: "top", + horizontal: "right", + }} + > + { + setInsertColumnMenuOpen(false); + onInsertColumnLeft(); + }} + > + + {t("cell_context.insert_column_before")} + + + { + setInsertColumnMenuOpen(false); + onInsertColumnRight(); + }} + > + + {t("cell_context.insert_column_after")} + + + + setFreezeMenuOpen(false)} + anchorEl={freezeRef.current} + anchorOrigin={{ + vertical: "top", + horizontal: "right", + }} + > + { + onFreezeColumns(); + setFreezeMenuOpen(false); + }} + > + + {t("cell_context.freeze_columns", { column })} + + + { + onFreezeRows(); + setFreezeMenuOpen(false); + }} + > + + {t("cell_context.freeze_rows", { row })} + + + { + onUnfreezeColumns(); + setFreezeMenuOpen(false); + }} + > + {t("cell_context.unfreeze_columns")} + + { + onUnfreezeRows(); + setFreezeMenuOpen(false); + }} + > + {t("cell_context.unfreeze_rows")} + + + + ); +}; + +const BetweenVerticalStartStyled = styled(BetweenVerticalStart)` + width: 16px; + height: 16px; + color: #333333; + padding-right: 10px; +`; + +const BetweenHorizontalStartStyled = styled(BetweenHorizontalStart)` + width: 16px; + height: 16px; + color: #333333; + padding-right: 10px; +`; + +const StyledSnowflake = styled(Snowflake)` + width: 16px; + height: 16px; + color: #333333; + padding-right: 10px; +`; + +const StyledTrash = styled(Trash2)` + width: 16px; + height: 16px; + color: ${red_color}; + padding-right: 10px; +`; + +const StyledMenu = styled(Menu)({ + "& .MuiPaper-root": { + borderRadius: 8, + padding: 4, + }, + "& .MuiList-padding": { + padding: 0, + }, +}); + +const StyledMenuItem = styled(MenuItem)` + display: flex; + justify-content: flex-start; + font-size: 14px; + width: calc(100% - 8px); + min-width: 172px; + margin: 0px 4px; + border-radius: 4px; + padding: 8px; + height: 32px; +`; + +const MenuDivider = styled("div")` + width: 100%; + margin: auto; + margin-top: 4px; + margin-bottom: 4px; + border-top: 1px solid #eeeeee; +`; + +const ItemNameStyled = styled("div")` + font-size: 12px; + color: #333; + flex-grow: 2; +`; + +const ChevronRightStyled = styled(ChevronRight)` + width: 16px; + height: 16px; +`; + +export default CellContextMenu; diff --git a/webapp/IronCalc/src/components/usePointer.ts b/webapp/IronCalc/src/components/usePointer.ts index 2864105..ff003c3 100644 --- a/webapp/IronCalc/src/components/usePointer.ts +++ b/webapp/IronCalc/src/components/usePointer.ts @@ -240,7 +240,6 @@ const usePointer = (options: PointerSettings): PointerEvents => { onPointerMove, onPointerUp, onPointerHandleDown, - // onContextMenu, }; }; diff --git a/webapp/IronCalc/src/components/worksheet.tsx b/webapp/IronCalc/src/components/worksheet.tsx index 57bb7dd..89c86e4 100644 --- a/webapp/IronCalc/src/components/worksheet.tsx +++ b/webapp/IronCalc/src/components/worksheet.tsx @@ -1,6 +1,7 @@ -import type { Model } from "@ironcalc/wasm"; +import { type Model, columnNameFromNumber } from "@ironcalc/wasm"; import { styled } from "@mui/material/styles"; import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import CellContextMenu from "./CellContextMenu"; import { COLUMN_WIDTH_SCALE, ROW_HEIGH_SCALE, @@ -52,6 +53,8 @@ function Worksheet(props: { const columnHeaders = useRef(null); const worksheetCanvas = useRef(null); + const [contextMenuOpen, setContextMenuOpen] = useState(false); + const ignoreScrollEventRef = useRef(false); const { model, workbookState, refresh } = props; @@ -147,174 +150,175 @@ function Worksheet(props: { worksheetCanvas.current = canvas; }); - const { - onPointerMove, - onPointerDown, - onPointerHandleDown, - onPointerUp, - // onContextMenu, - } = usePointer({ - model, - workbookState, - refresh, - onCellSelected: (cell: Cell, event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - model.setSelectedCell(cell.row, cell.column); - refresh(); - }, - onAreaSelecting: (cell: Cell) => { - const canvas = worksheetCanvas.current; - if (!canvas) { - return; - } - const { row, column } = cell; - model.onAreaSelecting(row, column); - canvas.renderSheet(); - refresh(); - }, - onAreaSelected: () => { - const styles = workbookState.getCopyStyles(); - if (styles?.length) { - model.onPasteStyles(styles); + const { onPointerMove, onPointerDown, onPointerHandleDown, onPointerUp } = + usePointer({ + model, + workbookState, + refresh, + onCellSelected: (cell: Cell, event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + model.setSelectedCell(cell.row, cell.column); + refresh(); + }, + onAreaSelecting: (cell: Cell) => { const canvas = worksheetCanvas.current; if (!canvas) { return; } + const { row, column } = cell; + model.onAreaSelecting(row, column); canvas.renderSheet(); - } - workbookState.setCopyStyles(null); - if (worksheetElement.current) { - worksheetElement.current.style.cursor = "auto"; - } - refresh(); - }, - onExtendToCell: (cell) => { - const canvas = worksheetCanvas.current; - if (!canvas) { - return; - } - const { row, column } = cell; - const { - range: [rowStart, columnStart, rowEnd, columnEnd], - } = model.getSelectedView(); - // We are either extending by rows or by columns - // And we could be doing it in the positive direction (downwards or right) - // or the negative direction (upwards or left) - - if ( - row > rowEnd && - ((column <= columnEnd && column >= columnStart) || - (column < columnStart && columnStart - column < row - rowEnd) || - (column > columnEnd && column - columnEnd < row - rowEnd)) - ) { - // rows downwards - const area = { - type: AreaType.rowsDown, - rowStart: rowEnd + 1, - rowEnd: row, - columnStart, - columnEnd, - }; - workbookState.setExtendToArea(area); - canvas.renderSheet(); - } else if ( - row < rowStart && - ((column <= columnEnd && column >= columnStart) || - (column < columnStart && columnStart - column < rowStart - row) || - (column > columnEnd && column - columnEnd < rowStart - row)) - ) { - // rows upwards - const area = { - type: AreaType.rowsUp, - rowStart: row, - rowEnd: rowStart, - columnStart, - columnEnd, - }; - workbookState.setExtendToArea(area); - canvas.renderSheet(); - } else if ( - column > columnEnd && - ((row <= rowEnd && row >= rowStart) || - (row < rowStart && rowStart - row < column - columnEnd) || - (row > rowEnd && row - rowEnd < column - columnEnd)) - ) { - // columns right - const area = { - type: AreaType.columnsRight, - rowStart, - rowEnd, - columnStart: columnEnd + 1, - columnEnd: column, - }; - workbookState.setExtendToArea(area); - canvas.renderSheet(); - } else if ( - column < columnStart && - ((row <= rowEnd && row >= rowStart) || - (row < rowStart && rowStart - row < columnStart - column) || - (row > rowEnd && row - rowEnd < columnStart - column)) - ) { - // columns left - const area = { - type: AreaType.columnsLeft, - rowStart, - rowEnd, - columnStart: column, - columnEnd: columnStart, - }; - workbookState.setExtendToArea(area); - canvas.renderSheet(); - } - }, - onExtendToEnd: () => { - const canvas = worksheetCanvas.current; - if (!canvas) { - return; - } - const { sheet, range } = model.getSelectedView(); - const extendedArea = workbookState.getExtendToArea(); - if (!extendedArea) { - return; - } - const rowStart = Math.min(range[0], range[2]); - const height = Math.abs(range[2] - range[0]) + 1; - const width = Math.abs(range[3] - range[1]) + 1; - const columnStart = Math.min(range[1], range[3]); - - const area = { sheet, row: rowStart, column: columnStart, width, height }; - - switch (extendedArea.type) { - case AreaType.rowsDown: - model.autoFillRows(area, extendedArea.rowEnd); - break; - case AreaType.rowsUp: { - model.autoFillRows(area, extendedArea.rowStart); - break; + refresh(); + }, + onAreaSelected: () => { + const styles = workbookState.getCopyStyles(); + if (styles?.length) { + model.onPasteStyles(styles); + const canvas = worksheetCanvas.current; + if (!canvas) { + return; + } + canvas.renderSheet(); } - case AreaType.columnsRight: { - model.autoFillColumns(area, extendedArea.columnEnd); - break; + workbookState.setCopyStyles(null); + if (worksheetElement.current) { + worksheetElement.current.style.cursor = "auto"; } - case AreaType.columnsLeft: { - model.autoFillColumns(area, extendedArea.columnStart); - break; + refresh(); + }, + onExtendToCell: (cell) => { + const canvas = worksheetCanvas.current; + if (!canvas) { + return; } - } - model.setSelectedRange( - Math.min(rowStart, extendedArea.rowStart), - Math.min(columnStart, extendedArea.columnStart), - Math.max(rowStart + height - 1, extendedArea.rowEnd), - Math.max(columnStart + width - 1, extendedArea.columnEnd), - ); - workbookState.clearExtendToArea(); - canvas.renderSheet(); - }, - canvasElement, - worksheetElement, - worksheetCanvas, - }); + const { row, column } = cell; + const { + range: [rowStart, columnStart, rowEnd, columnEnd], + } = model.getSelectedView(); + // We are either extending by rows or by columns + // And we could be doing it in the positive direction (downwards or right) + // or the negative direction (upwards or left) + + if ( + row > rowEnd && + ((column <= columnEnd && column >= columnStart) || + (column < columnStart && columnStart - column < row - rowEnd) || + (column > columnEnd && column - columnEnd < row - rowEnd)) + ) { + // rows downwards + const area = { + type: AreaType.rowsDown, + rowStart: rowEnd + 1, + rowEnd: row, + columnStart, + columnEnd, + }; + workbookState.setExtendToArea(area); + canvas.renderSheet(); + } else if ( + row < rowStart && + ((column <= columnEnd && column >= columnStart) || + (column < columnStart && columnStart - column < rowStart - row) || + (column > columnEnd && column - columnEnd < rowStart - row)) + ) { + // rows upwards + const area = { + type: AreaType.rowsUp, + rowStart: row, + rowEnd: rowStart, + columnStart, + columnEnd, + }; + workbookState.setExtendToArea(area); + canvas.renderSheet(); + } else if ( + column > columnEnd && + ((row <= rowEnd && row >= rowStart) || + (row < rowStart && rowStart - row < column - columnEnd) || + (row > rowEnd && row - rowEnd < column - columnEnd)) + ) { + // columns right + const area = { + type: AreaType.columnsRight, + rowStart, + rowEnd, + columnStart: columnEnd + 1, + columnEnd: column, + }; + workbookState.setExtendToArea(area); + canvas.renderSheet(); + } else if ( + column < columnStart && + ((row <= rowEnd && row >= rowStart) || + (row < rowStart && rowStart - row < columnStart - column) || + (row > rowEnd && row - rowEnd < columnStart - column)) + ) { + // columns left + const area = { + type: AreaType.columnsLeft, + rowStart, + rowEnd, + columnStart: column, + columnEnd: columnStart, + }; + workbookState.setExtendToArea(area); + canvas.renderSheet(); + } + }, + onExtendToEnd: () => { + const canvas = worksheetCanvas.current; + if (!canvas) { + return; + } + const { sheet, range } = model.getSelectedView(); + const extendedArea = workbookState.getExtendToArea(); + if (!extendedArea) { + return; + } + const rowStart = Math.min(range[0], range[2]); + const height = Math.abs(range[2] - range[0]) + 1; + const width = Math.abs(range[3] - range[1]) + 1; + const columnStart = Math.min(range[1], range[3]); + + const area = { + sheet, + row: rowStart, + column: columnStart, + width, + height, + }; + + switch (extendedArea.type) { + case AreaType.rowsDown: + model.autoFillRows(area, extendedArea.rowEnd); + break; + case AreaType.rowsUp: { + model.autoFillRows(area, extendedArea.rowStart); + break; + } + case AreaType.columnsRight: { + model.autoFillColumns(area, extendedArea.columnEnd); + break; + } + case AreaType.columnsLeft: { + model.autoFillColumns(area, extendedArea.columnStart); + break; + } + } + model.setSelectedRange( + Math.min(rowStart, extendedArea.rowStart), + Math.min(columnStart, extendedArea.columnStart), + Math.max(rowStart + height - 1, extendedArea.rowEnd), + Math.max(columnStart + width - 1, extendedArea.columnEnd), + ); + workbookState.clearExtendToArea(); + canvas.renderSheet(); + }, + canvasElement, + worksheetElement, + worksheetCanvas, + }); const onScroll = (): void => { if (!scrollElement.current || !worksheetCanvas.current) { @@ -340,6 +344,11 @@ function Worksheet(props: { onPointerDown={onPointerDown} onPointerMove={onPointerMove} onPointerUp={onPointerUp} + onContextMenu={(event) => { + event.preventDefault(); + event.stopPropagation(); + setContextMenuOpen(true); + }} onDoubleClick={(event) => { // Starts editing cell const { sheet, row, column } = model.getSelectedView(); @@ -392,6 +401,63 @@ function Worksheet(props: { + setContextMenuOpen(false)} + anchorEl={cellOutline.current} + onInsertRowAbove={(): void => { + const view = model.getSelectedView(); + model.insertRow(view.sheet, view.row); + setContextMenuOpen(false); + }} + onInsertRowBelow={(): void => { + const view = model.getSelectedView(); + model.insertRow(view.sheet, view.row + 1); + setContextMenuOpen(false); + }} + onInsertColumnLeft={(): void => { + const view = model.getSelectedView(); + model.insertColumn(view.sheet, view.column); + setContextMenuOpen(false); + }} + onInsertColumnRight={(): void => { + const view = model.getSelectedView(); + model.insertColumn(view.sheet, view.column + 1); + setContextMenuOpen(false); + }} + onFreezeColumns={(): void => { + const view = model.getSelectedView(); + model.setFrozenColumnsCount(view.sheet, view.column); + setContextMenuOpen(false); + }} + onFreezeRows={(): void => { + const view = model.getSelectedView(); + model.setFrozenRowsCount(view.sheet, view.row); + setContextMenuOpen(false); + }} + onUnfreezeColumns={(): void => { + const sheet = model.getSelectedSheet(); + model.setFrozenColumnsCount(sheet, 0); + setContextMenuOpen(false); + }} + onUnfreezeRows={(): void => { + const sheet = model.getSelectedSheet(); + model.setFrozenRowsCount(sheet, 0); + setContextMenuOpen(false); + }} + onDeleteRow={(): void => { + const view = model.getSelectedView(); + model.deleteRow(view.sheet, view.row); + setContextMenuOpen(false); + }} + onDeleteColumn={(): void => { + const view = model.getSelectedView(); + model.deleteColumn(view.sheet, view.column); + setContextMenuOpen(false); + }} + row={model.getSelectedView().row} + column={columnNameFromNumber(model.getSelectedView().column)} + /> ); } diff --git a/webapp/IronCalc/src/locale/en_us.json b/webapp/IronCalc/src/locale/en_us.json index 336e02b..ce8e8c2 100644 --- a/webapp/IronCalc/src/locale/en_us.json +++ b/webapp/IronCalc/src/locale/en_us.json @@ -100,5 +100,20 @@ "edit": "Edit Range", "apply": "Apply changes", "discard": "Discard changes" + }, + "cell_context": { + "insert_row_above": "Insert 1 row above", + "insert_row_below": "Insert 1 row below", + "insert_column_before": "Insert 1 column left", + "insert_column_after": "Insert 1 column right", + "freeze_columns": "Freeze up to column '{{column}}'", + "freeze_rows": "Freeze up to row '{{row}}'", + "unfreeze_rows": "Unfreeze rows", + "unfreeze_columns": "Unfreeze columns", + "delete_row": "Delete row '{{row}}'", + "delete_column": "Delete column '{{column}}'", + "freeze": "Freeze", + "insert_row": "Insert row", + "insert_column": "Insert column" } }