From 9e8088f18637a2c893c6ba0958cc66fb7e2b4ded Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Nov 2025 15:07:23 +0000 Subject: [PATCH 1/7] JS: Bumped up JS package versions, and node version --- dev/docs/development.md | 2 +- package-lock.json | 1157 +++++++++++++++++++-------------------- package.json | 30 +- 3 files changed, 590 insertions(+), 599 deletions(-) diff --git a/dev/docs/development.md b/dev/docs/development.md index 6c250902a9f..418e9ead672 100644 --- a/dev/docs/development.md +++ b/dev/docs/development.md @@ -3,7 +3,7 @@ All development on BookStack is currently done on the `development` branch. When it's time for a release the `development` branch is merged into release with built & minified CSS & JS then tagged at its version. Here are the current development requirements: -* [Node.js](https://nodejs.org/en/) v20.0+ +* [Node.js](https://nodejs.org/en/) v22.0+ ## Building CSS & JavaScript Assets diff --git a/package-lock.json b/package-lock.json index fb6a1d4fa01..44ebf7eabbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,21 +4,22 @@ "requires": true, "packages": { "": { + "name": "bookstack", "dependencies": { - "@codemirror/commands": "^6.8.1", + "@codemirror/commands": "^6.10.0", "@codemirror/lang-css": "^6.3.1", - "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-javascript": "^6.2.4", "@codemirror/lang-json": "^6.0.2", - "@codemirror/lang-markdown": "^6.3.4", + "@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", "@codemirror/language": "^6.11.3", - "@codemirror/legacy-modes": "^6.5.1", + "@codemirror/legacy-modes": "^6.5.2", "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.38.1", - "@lezer/highlight": "^1.2.1", + "@codemirror/view": "^6.38.8", + "@lezer/highlight": "^1.2.3", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "@types/jest": "^30.0.0", @@ -26,24 +27,24 @@ "idb-keyval": "^6.2.2", "markdown-it": "^14.1.0", "markdown-it-task-lists": "^2.1.1", - "snabbdom": "^3.6.2", + "snabbdom": "^3.6.3", "sortablejs": "^1.15.6" }, "devDependencies": { - "@eslint/js": "^9.34.0", + "@eslint/js": "^9.39.1", "@lezer/generator": "^1.8.0", "@types/markdown-it": "^14.1.2", - "@types/sortablejs": "^1.15.8", + "@types/sortablejs": "^1.15.9", "chokidar-cli": "^3.0", - "esbuild": "^0.25.9", - "eslint": "^9.34.0", + "esbuild": "^0.27.0", + "eslint": "^9.39.1", "eslint-plugin-import": "^2.32.0", - "jest": "^30.1.1", - "jest-environment-jsdom": "^30.1.1", + "jest": "^30.2.0", + "jest-environment-jsdom": "^30.2.0", "livereload": "^0.10.3", "npm-run-all": "^4.1.5", - "sass": "^1.91.0", - "ts-jest": "^29.4.1", + "sass": "^1.94.2", + "ts-jest": "^29.4.5", "ts-node": "^10.9.2", "typescript": "5.9.*" } @@ -590,9 +591,9 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", - "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", + "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -615,9 +616,9 @@ } }, "node_modules/@codemirror/lang-html": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", - "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -628,7 +629,7 @@ "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", - "@lezer/html": "^1.3.0" + "@lezer/html": "^1.3.12" } }, "node_modules/@codemirror/lang-javascript": { @@ -657,9 +658,9 @@ } }, "node_modules/@codemirror/lang-markdown": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.4.tgz", - "integrity": "sha512-fBm0BO03azXnTAsxhONDYHi/qWSI+uSEIpzKM7h/bkIc9fHnFp9y7KTMXKON0teNT97pFhc1a9DQTtWBYEZ7ug==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.7.1", @@ -713,9 +714,9 @@ } }, "node_modules/@codemirror/legacy-modes": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.1.tgz", - "integrity": "sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz", + "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0" @@ -765,9 +766,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.38.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", - "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", + "version": "6.38.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", + "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.5.0", @@ -916,9 +917,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "dev": true, "license": "MIT", "optional": true, @@ -928,9 +929,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "dev": true, "license": "MIT", "optional": true, @@ -950,9 +951,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", "cpu": [ "ppc64" ], @@ -967,9 +968,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", "cpu": [ "arm" ], @@ -984,9 +985,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", "cpu": [ "arm64" ], @@ -1001,9 +1002,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", "cpu": [ "x64" ], @@ -1018,9 +1019,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", "cpu": [ "arm64" ], @@ -1035,9 +1036,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", "cpu": [ "x64" ], @@ -1052,9 +1053,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", "cpu": [ "arm64" ], @@ -1069,9 +1070,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", "cpu": [ "x64" ], @@ -1086,9 +1087,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", "cpu": [ "arm" ], @@ -1103,9 +1104,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", "cpu": [ "arm64" ], @@ -1120,9 +1121,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", "cpu": [ "ia32" ], @@ -1137,9 +1138,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", "cpu": [ "loong64" ], @@ -1154,9 +1155,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", "cpu": [ "mips64el" ], @@ -1171,9 +1172,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", "cpu": [ "ppc64" ], @@ -1188,9 +1189,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", "cpu": [ "riscv64" ], @@ -1205,9 +1206,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", "cpu": [ "s390x" ], @@ -1222,9 +1223,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", "cpu": [ "x64" ], @@ -1239,9 +1240,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", "cpu": [ "arm64" ], @@ -1256,9 +1257,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", "cpu": [ "x64" ], @@ -1273,9 +1274,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", "cpu": [ "arm64" ], @@ -1290,9 +1291,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", "cpu": [ "x64" ], @@ -1307,9 +1308,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", "cpu": [ "arm64" ], @@ -1324,9 +1325,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", "cpu": [ "x64" ], @@ -1341,9 +1342,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", "cpu": [ "arm64" ], @@ -1358,9 +1359,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", "cpu": [ "ia32" ], @@ -1375,9 +1376,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", "cpu": [ "x64" ], @@ -1392,9 +1393,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1434,13 +1435,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1449,19 +1450,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1496,9 +1500,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", - "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", "dev": true, "license": "MIT", "engines": { @@ -1509,9 +1513,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1519,13 +1523,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -1617,9 +1621,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -1714,9 +1718,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -1790,17 +1794,17 @@ } }, "node_modules/@jest/console": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.1.1.tgz", - "integrity": "sha512-f7TGqR1k4GtN5pyFrKmq+ZVndesiwLU33yDpJIGMS9aW+j6hKjue7ljeAdznBsH9kAnxUWe2Y+Y3fLV/FJt3gA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -1808,39 +1812,39 @@ } }, "node_modules/@jest/core": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.1.1.tgz", - "integrity": "sha512-3ncU9peZ3D2VdgRkdZtUceTrDgX5yiDRwAFjtxNfU22IiZrpVWlv/FogzDLYSJQptQGfFo3PcHK86a2oG6WUGg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.1", + "@jest/console": "30.2.0", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.5", - "jest-config": "30.1.1", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.0", - "jest-resolve-dependencies": "30.1.1", - "jest-runner": "30.1.1", - "jest-runtime": "30.1.1", - "jest-snapshot": "30.1.1", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", - "jest-watcher": "30.1.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -1865,35 +1869,35 @@ } }, "node_modules/@jest/environment": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.1.1.tgz", - "integrity": "sha512-yWHbU+3j7ehQE+NRpnxRvHvpUhoohIjMePBbIr8lfe0cWVb0WeTf80DNux1GPJa18CDHiIU5DtksGUfxcDE+Rw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.1.1", - "@jest/types": "30.0.5", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5" + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment-jsdom-abstract": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.1.1.tgz", - "integrity": "sha512-d7pP9SeIOI6qnrNIS/ds1hlS9jpqh8EywHK0dALSLODZKo2QEGnDNvnPvhRKI0FHWDnE2EMl8CDTP0jM9lhlOA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.2.0.tgz", + "integrity": "sha512-kazxw2L9IPuZpQ0mEt9lu9Z98SqR74xcagANmMBU16X0lS23yPc0+S6hGLUz8kVRlomZEs/5S/Zlpqwf5yu6OQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/fake-timers": "30.1.1", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/jsdom": "^21.1.7", "@types/node": "*", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1909,23 +1913,23 @@ } }, "node_modules/@jest/expect": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.1.1.tgz", - "integrity": "sha512-3vHIHsF+qd3D8FU2c7U5l3rg1fhDwAYcGyHyZAi94YIlTwcJ+boNhRyJf373cl4wxbOX+0Q7dF40RTrTFTSuig==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.1.1", - "jest-snapshot": "30.1.1" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.1.tgz", - "integrity": "sha512-5YUHr27fpJ64dnvtu+tt11ewATynrHkGYD+uSFgRr8V2eFJis/vEXgToyLwccIwqBihVfz9jwio+Zr1ab1Zihw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0" @@ -1935,18 +1939,18 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.1.1.tgz", - "integrity": "sha512-fK/25dNgBNYPw3eLi2CRs57g1H04qBAFNMsUY3IRzkfx/m4THe0E1zF+yGQBOMKKc2XQVdc9EYbJ4hEm7/2UtA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1962,16 +1966,16 @@ } }, "node_modules/@jest/globals": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.1.1.tgz", - "integrity": "sha512-NNUUkHT2TU/xztZl6r1UXvJL+zvCwmZsQDmK69fVHHcB9fBtlu3FInnzOve/ZoyKnWY8JXWJNT+Lkmu1+ubXUA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/expect": "30.1.1", - "@jest/types": "30.0.5", - "jest-mock": "30.0.5" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1991,17 +1995,17 @@ } }, "node_modules/@jest/reporters": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.1.1.tgz", - "integrity": "sha512-Hb2Bq80kahOC6Sv2waEaH1rEU6VdFcM6WHaRBWQF9tf30+nJHxhl/Upbgo9+25f0mOgbphxvbwSMjSgy9gW/FA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -2014,9 +2018,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -2046,13 +2050,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.1.1.tgz", - "integrity": "sha512-TkVBc9wuN22TT8hESRFmjjg/xIMu7z0J3UDYtIRydzCqlLPTB7jK1DDBKdnTUZ4zL3z3rnPpzV6rL1Uzh87sXg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -2077,14 +2081,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.1.1.tgz", - "integrity": "sha512-bMdj7fNu8iZuBPSnbVir5ezvWmVo4jrw7xDE+A33Yb3ENCoiJK9XgOLgal+rJ9XSKjsL7aPUMIo87zhN7I5o2w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.1", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -2093,15 +2097,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.1.1.tgz", - "integrity": "sha512-yruRdLXSA3HYD/MTNykgJ6VYEacNcXDFRMqKVAwlYegmxICUiT/B++CNuhJnYJzKYks61iYnjVsMwbUqmmAYJg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.1.1", + "@jest/test-result": "30.2.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -2109,23 +2113,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.1.1.tgz", - "integrity": "sha512-PHIA2AbAASBfk6evkNifvmx9lkOSkmvaQoO6VSpuL8+kQqDMHeDoJ7RU3YP1wWAMD7AyQn9UL5iheuFYCC4lqQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", @@ -2136,9 +2140,9 @@ } }, "node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", @@ -2193,9 +2197,9 @@ } }, "node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", + "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==", "license": "MIT" }, "node_modules/@lezer/css": { @@ -2224,18 +2228,18 @@ } }, "node_modules/@lezer/highlight": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", - "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", "license": "MIT", "dependencies": { - "@lezer/common": "^1.0.0" + "@lezer/common": "^1.3.0" } }, "node_modules/@lezer/html": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", - "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", + "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2738,9 +2742,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", - "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", "optional": true, @@ -2895,9 +2899,9 @@ } }, "node_modules/@types/sortablejs": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", - "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.9.tgz", + "integrity": "sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==", "dev": true, "license": "MIT" }, @@ -3285,9 +3289,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -3488,16 +3492,16 @@ } }, "node_modules/babel-jest": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.1.tgz", - "integrity": "sha512-1bZfC/V03qBCzASvZpNFhx3Ouj6LgOd4KFJm4br/fYOS+tSSvVCE61QmcAVbMTwq/GoB7KN4pzGMoyr9cMxSvQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.1.1", + "@jest/transform": "30.2.0", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -3506,15 +3510,18 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", - "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -3527,14 +3534,12 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", - "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", "@types/babel__core": "^7.20.5" }, "engines": { @@ -3542,9 +3547,9 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -3565,24 +3570,24 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", - "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -3868,9 +3873,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", - "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", + "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", "dev": true, "license": "MIT" }, @@ -3936,9 +3941,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, @@ -4120,9 +4125,9 @@ "license": "MIT" }, "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4455,9 +4460,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4468,32 +4473,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" } }, "node_modules/escalade": { @@ -4520,25 +4525,24 @@ } }, "node_modules/eslint": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", - "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.34.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -4819,6 +4823,13 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/exit-x": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", @@ -4830,17 +4841,17 @@ } }, "node_modules/expect": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.1.tgz", - "integrity": "sha512-OKe7cdic4qbfWd/CcgwJvvCrNX2KWfuMZee9AfJHL1gTYmvqjBjZG1a2NwfhspBzxzlXwsN75WWpKTYfsJpBxg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.1.1", + "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.1.1", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -4973,19 +4984,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5150,9 +5148,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -6120,16 +6118,16 @@ } }, "node_modules/jest": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.1.1.tgz", - "integrity": "sha512-yC3JvpP/ZcAZX5rYCtXO/g9k6VTCQz0VFE2v1FpxytWzUqfDtu0XL/pwnNvptzYItvGwomh1ehomRNMOyhCJKw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.1.1", - "@jest/types": "30.0.5", + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", "import-local": "^3.2.0", - "jest-cli": "30.1.1" + "jest-cli": "30.2.0" }, "bin": { "jest": "bin/jest.js" @@ -6147,14 +6145,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", - "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "p-limit": "^3.1.0" }, "engines": { @@ -6162,29 +6160,29 @@ } }, "node_modules/jest-circus": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.1.1.tgz", - "integrity": "sha512-M3Vd4x5wD7eSJspuTvRF55AkOOBndRxgW3gqQBDlFvbH3X+ASdi8jc+EqXEeAFd/UHulVYIlC4XKJABOhLw6UA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/expect": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.1.0", - "jest-matcher-utils": "30.1.1", - "jest-message-util": "30.1.0", - "jest-runtime": "30.1.1", - "jest-snapshot": "30.1.1", - "jest-util": "30.0.5", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "p-limit": "^3.1.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -6194,21 +6192,21 @@ } }, "node_modules/jest-cli": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.1.1.tgz", - "integrity": "sha512-xm9llxuh5OoI5KZaYzlMhklryHBwg9LZy/gEaaMlXlxb+cZekGNzukU0iblbDo3XOBuN6N0CgK4ykgNRYSEb6g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/types": "30.0.5", + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.1.1", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "yargs": "^17.7.2" }, "bin": { @@ -6344,34 +6342,34 @@ } }, "node_modules/jest-config": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.1.1.tgz", - "integrity": "sha512-xuPGUGDw+9fPPnGmddnLnHS/mhKUiJOW7K65vErYmglEPKq65NKwSRchkQ7iv6gqjs2l+YNEsAtbsplxozdOWg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.1.1", - "@jest/types": "30.0.5", - "babel-jest": "30.1.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.1.1", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.1", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.0", - "jest-runner": "30.1.1", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -6396,24 +6394,24 @@ } }, "node_modules/jest-diff": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.1.tgz", - "integrity": "sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", - "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { @@ -6424,31 +6422,31 @@ } }, "node_modules/jest-each": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.1.0.tgz", - "integrity": "sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "chalk": "^4.1.2", - "jest-util": "30.0.5", - "pretty-format": "30.0.5" + "jest-util": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-jsdom": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.1.1.tgz", - "integrity": "sha512-fInyXsHSuPaERmRiub4V6jl6KERXowGqY8AISJrXZjOq7vdP46qecm+GnTngjcUPeHFqrxp1PfP0XuFfKTzA2A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.2.0.tgz", + "integrity": "sha512-zbBTiqr2Vl78pKp/laGBREYzbZx9ZtqPjOK4++lL4BNDhxRnahg51HtoDrk9/VjIy9IthNEWdKVd7H5bqBhiWQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/environment-jsdom-abstract": "30.1.1", + "@jest/environment": "30.2.0", + "@jest/environment-jsdom-abstract": "30.2.0", "@types/jsdom": "^21.1.7", "@types/node": "*", "jsdom": "^26.1.0" @@ -6466,39 +6464,39 @@ } }, "node_modules/jest-environment-node": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.1.1.tgz", - "integrity": "sha512-IaMoaA6saxnJimqCppUDqKck+LKM0Jg+OxyMUIvs1yGd2neiC22o8zXo90k04+tO+49OmgMR4jTgM5e4B0S62Q==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/fake-timers": "30.1.1", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5", - "jest-util": "30.0.5", - "jest-validate": "30.1.0" + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.1.0.tgz", - "integrity": "sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "micromatch": "^4.0.8", "walker": "^1.0.8" }, @@ -6510,47 +6508,47 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.1.0.tgz", - "integrity": "sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.1.tgz", - "integrity": "sha512-SuH2QVemK48BNTqReti6FtjsMPFsSOD/ZzRxU1TttR7RiRsRSe78d03bb4Cx6D4bQC/80Q8U4VnaaAH9FlbZ9w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.1.1", - "pretty-format": "30.0.5" + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz", - "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -6559,14 +6557,14 @@ } }, "node_modules/jest-mock": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-util": "30.0.5" + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6600,18 +6598,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.1.0.tgz", - "integrity": "sha512-hASe7D/wRtZw8Cm607NrlF7fi3HWC5wmA5jCVc2QjQAB2pTwP9eVZILGEi6OeSLNUtE1zb04sXRowsdh5CUjwA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -6620,46 +6618,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.1.1.tgz", - "integrity": "sha512-tRtaaoH8Ws1Gn1o/9pedt19dvVgr81WwdmvJSP9Ow3amOUOP2nN9j94u5jC9XlIfa2Q1FQKIWWQwL4ajqsjCGQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.1.1" + "jest-snapshot": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.1.1.tgz", - "integrity": "sha512-ATe6372SOfJvCRExtCAr06I4rGujwFdKg44b6i7/aOgFnULwjxzugJ0Y4AnG+jeSeQi8dU7R6oqLGmsxRUbErQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.1", - "@jest/environment": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.1", - "jest-haste-map": "30.1.0", - "jest-leak-detector": "30.1.0", - "jest-message-util": "30.1.0", - "jest-resolve": "30.1.0", - "jest-runtime": "30.1.1", - "jest-util": "30.0.5", - "jest-watcher": "30.1.1", - "jest-worker": "30.1.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -6668,32 +6666,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.1.1.tgz", - "integrity": "sha512-7sOyR0Oekw4OesQqqBHuYJRB52QtXiq0NNgLRzVogiMSxKCMiliUd6RrXHCnG5f12Age/ggidCBiQftzcA9XKw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/fake-timers": "30.1.1", - "@jest/globals": "30.1.1", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.0", - "jest-snapshot": "30.1.1", - "jest-util": "30.0.5", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -6702,9 +6700,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.1.1.tgz", - "integrity": "sha512-7/iBEzoJqEt2TjkQY+mPLHP8cbPhLReZVkkxjTMzIzoTC4cZufg7HzKo/n9cIkXKj2LG0x3mmBHsZto+7TOmFg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, "license": "MIT", "dependencies": { @@ -6713,20 +6711,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.1.1", + "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", - "babel-preset-current-node-syntax": "^1.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.1.1", + "expect": "30.2.0", "graceful-fs": "^4.2.11", - "jest-diff": "30.1.1", - "jest-matcher-utils": "30.1.1", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "pretty-format": "30.0.5", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -6735,9 +6733,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -6748,12 +6746,12 @@ } }, "node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -6777,18 +6775,18 @@ } }, "node_modules/jest-validate": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.1.0.tgz", - "integrity": "sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6808,19 +6806,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.1.1.tgz", - "integrity": "sha512-CrAQ73LlaS6KGQQw6NBi71g7qvP7scy+4+2c0jKX6+CWaYg85lZiig5nQQVTsS5a5sffNPL3uxXnaE9d7v9eQg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.1.1", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "string-length": "^4.0.2" }, "engines": { @@ -6828,15 +6826,15 @@ } }, "node_modules/jest-worker": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.1.0.tgz", - "integrity": "sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -6867,9 +6865,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -7197,9 +7195,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -7345,9 +7343,9 @@ "license": "MIT" }, "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -7627,9 +7625,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.21", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", - "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", "dev": true, "license": "MIT" }, @@ -8121,9 +8119,9 @@ } }, "node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "license": "MIT", "dependencies": { "@jest/schemas": "30.0.5", @@ -8401,9 +8399,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.91.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.91.0.tgz", - "integrity": "sha512-aFOZHGf+ur+bp1bCHZ+u8otKGh77ZtmFyXDo4tlYvT7PWql41Kwd8wdkPqhhT+h2879IVblcHFglIMofsFd1EA==", + "version": "1.94.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz", + "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", "dev": true, "license": "MIT", "dependencies": { @@ -8643,11 +8641,17 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/slash": { "version": "3.0.0", @@ -8659,9 +8663,9 @@ } }, "node_modules/snabbdom": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.6.2.tgz", - "integrity": "sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.6.3.tgz", + "integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==", "license": "MIT", "engines": { "node": ">=12.17.0" @@ -8992,9 +8996,9 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -9221,9 +9225,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", - "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==", + "version": "29.4.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", + "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9233,7 +9237,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.2", + "semver": "^7.7.3", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -9274,9 +9278,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -10037,19 +10041,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", diff --git a/package.json b/package.json index 9ec94acc29a..964e3ee7ec8 100644 --- a/package.json +++ b/package.json @@ -19,38 +19,38 @@ "test": "jest" }, "devDependencies": { - "@eslint/js": "^9.34.0", + "@eslint/js": "^9.39.1", "@lezer/generator": "^1.8.0", "@types/markdown-it": "^14.1.2", - "@types/sortablejs": "^1.15.8", + "@types/sortablejs": "^1.15.9", "chokidar-cli": "^3.0", - "esbuild": "^0.25.9", - "eslint": "^9.34.0", + "esbuild": "^0.27.0", + "eslint": "^9.39.1", "eslint-plugin-import": "^2.32.0", - "jest": "^30.1.1", - "jest-environment-jsdom": "^30.1.1", + "jest": "^30.2.0", + "jest-environment-jsdom": "^30.2.0", "livereload": "^0.10.3", "npm-run-all": "^4.1.5", - "sass": "^1.91.0", - "ts-jest": "^29.4.1", + "sass": "^1.94.2", + "ts-jest": "^29.4.5", "ts-node": "^10.9.2", "typescript": "5.9.*" }, "dependencies": { - "@codemirror/commands": "^6.8.1", + "@codemirror/commands": "^6.10.0", "@codemirror/lang-css": "^6.3.1", - "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-javascript": "^6.2.4", "@codemirror/lang-json": "^6.0.2", - "@codemirror/lang-markdown": "^6.3.4", + "@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", "@codemirror/language": "^6.11.3", - "@codemirror/legacy-modes": "^6.5.1", + "@codemirror/legacy-modes": "^6.5.2", "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.38.1", - "@lezer/highlight": "^1.2.1", + "@codemirror/view": "^6.38.8", + "@lezer/highlight": "^1.2.3", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "@types/jest": "^30.0.0", @@ -58,7 +58,7 @@ "idb-keyval": "^6.2.2", "markdown-it": "^14.1.0", "markdown-it-task-lists": "^2.1.1", - "snabbdom": "^3.6.2", + "snabbdom": "^3.6.3", "sortablejs": "^1.15.6" } } From 9d732d8dd826af6b988ab103560b9b84eab8640e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Nov 2025 17:02:17 +0000 Subject: [PATCH 2/7] Lexical: Started dev API outline --- dev/docs/wysiwyg-js-api.md | 8 ++ resources/js/wysiwyg/api/api.ts | 11 +++ resources/js/wysiwyg/api/ui.ts | 79 +++++++++++++++++++ resources/js/wysiwyg/index.ts | 7 ++ resources/js/wysiwyg/ui/defaults/toolbars.ts | 18 ++--- .../ui/framework/blocks/overflow-container.ts | 12 ++- resources/js/wysiwyg/ui/framework/manager.ts | 4 + 7 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 dev/docs/wysiwyg-js-api.md create mode 100644 resources/js/wysiwyg/api/api.ts create mode 100644 resources/js/wysiwyg/api/ui.ts diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md new file mode 100644 index 00000000000..7fd91a5e6cd --- /dev/null +++ b/dev/docs/wysiwyg-js-api.md @@ -0,0 +1,8 @@ +# WYSIWYG JavaScript API + +TODO - Link to this from JS code doc. +TODO - Create JS events and add to the js public events doc. + +TODO - Document the JS API. + +TODO - Add testing coverage \ No newline at end of file diff --git a/resources/js/wysiwyg/api/api.ts b/resources/js/wysiwyg/api/api.ts new file mode 100644 index 00000000000..434109c7ac7 --- /dev/null +++ b/resources/js/wysiwyg/api/api.ts @@ -0,0 +1,11 @@ +import {EditorApiUiModule} from "./ui"; +import {EditorUiContext} from "../ui/framework/core"; + +export class EditorApi { + + public ui: EditorApiUiModule; + + constructor(context: EditorUiContext) { + this.ui = new EditorApiUiModule(context); + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/api/ui.ts b/resources/js/wysiwyg/api/ui.ts new file mode 100644 index 00000000000..faad5d3160b --- /dev/null +++ b/resources/js/wysiwyg/api/ui.ts @@ -0,0 +1,79 @@ +import {EditorButton} from "../ui/framework/buttons"; +import {EditorUiContext} from "../ui/framework/core"; +import {EditorOverflowContainer} from "../ui/framework/blocks/overflow-container"; + +type EditorApiButtonOptions = { + label?: string; + icon?: string; + onClick: () => void; +}; + +class EditorApiButton { + #button: EditorButton; + #isActive: boolean = false; + + constructor(options: EditorApiButtonOptions, context: EditorUiContext) { + this.#button = new EditorButton({ + label: options.label || '', + icon: options.icon || '', + action: () => { + options.onClick(); + }, + isActive: () => this.#isActive, + }); + this.#button.setContext(context); + } + + setActive(active: boolean = true): void { + this.#isActive = active; + this.#button.setActiveState(active); + } + + _getOriginalModel() { + return this.#button; + } +} + +class EditorApiToolbarSection { + #section: EditorOverflowContainer; + label: string; + + constructor(section: EditorOverflowContainer) { + this.#section = section; + this.label = section.getLabel(); + } + + getLabel(): string { + return this.#section.getLabel(); + } + + addButton(button: EditorApiButton, targetIndex: number = -1): void { + this.#section.addChild(button._getOriginalModel(), targetIndex); + this.#section.rebuildDOM(); + } +} + + +export class EditorApiUiModule { + #context: EditorUiContext; + + constructor(context: EditorUiContext) { + this.#context = context; + } + + createButton(options: EditorApiButtonOptions): EditorApiButton { + return new EditorApiButton(options, this.#context); + } + + getToolbarSections(): EditorApiToolbarSection[] { + const toolbar = this.#context.manager.getToolbar(); + if (!toolbar) { + return []; + } + + const sections = toolbar.getChildren(); + return sections.filter(section => { + return section instanceof EditorOverflowContainer; + }).map(section => new EditorApiToolbarSection(section)); + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index 2c8c3b9525b..c3ff37e7edd 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -21,6 +21,7 @@ import {CodeBlockDecorator} from "./ui/decorators/code-block"; import {DiagramDecorator} from "./ui/decorators/diagram"; import {registerMouseHandling} from "./services/mouse-handling"; import {registerSelectionHandling} from "./services/selection-handling"; +import {EditorApi} from "./api/api"; const theme = { text: { @@ -94,6 +95,12 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st registerCommonNodeMutationListeners(context); + // TODO - Emit this as a public event instead + // TODO - Add support to basic editor below + const api = new EditorApi(context); + // @ts-ignore + window.editorApi = api; + return new SimpleWysiwygEditorInterface(context); } diff --git a/resources/js/wysiwyg/ui/defaults/toolbars.ts b/resources/js/wysiwyg/ui/defaults/toolbars.ts index 33468e0a23a..0c48f595499 100644 --- a/resources/js/wysiwyg/ui/defaults/toolbars.ts +++ b/resources/js/wysiwyg/ui/defaults/toolbars.ts @@ -88,7 +88,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai return new EditorSimpleClassContainer('editor-toolbar-main', [ // History state - new EditorOverflowContainer(2, [ + new EditorOverflowContainer('history', 2, [ new EditorButton(undo), new EditorButton(redo), ]), @@ -110,7 +110,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Inline formats - new EditorOverflowContainer(6, [ + new EditorOverflowContainer('inline_formats', 6, [ new EditorButton(bold), new EditorButton(italic), new EditorButton(underline), @@ -128,7 +128,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Alignment - new EditorOverflowContainer(6, [ + new EditorOverflowContainer('alignment', 6, [ new EditorButton(alignLeft), new EditorButton(alignCenter), new EditorButton(alignRight), @@ -138,7 +138,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ].filter(x => x !== null)), // Lists - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('lists', 3, [ new EditorButton(bulletList), new EditorButton(numberList), new EditorButton(taskList), @@ -147,7 +147,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Insert types - new EditorOverflowContainer(4, [ + new EditorOverflowContainer('inserts', 4, [ new EditorButton(link), new EditorDropdownButton({button: table, direction: 'vertical', showAside: false}, [ @@ -200,7 +200,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Meta elements - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('meta', 3, [ new EditorButton(source), new EditorButton(about), new EditorButton(fullscreen), @@ -261,16 +261,16 @@ export const contextToolbars: Record = { selector: 'td,th', content() { return [ - new EditorOverflowContainer(2, [ + new EditorOverflowContainer('table', 2, [ new EditorButton(tableProperties), new EditorButton(deleteTable), ]), - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('table_row',3, [ new EditorButton(insertRowAbove), new EditorButton(insertRowBelow), new EditorButton(deleteRow), ]), - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('table_column', 3, [ new EditorButton(insertColumnBefore), new EditorButton(insertColumnAfter), new EditorButton(deleteColumn), diff --git a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts index 1c96645058e..3b582eee671 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts @@ -9,9 +9,11 @@ export class EditorOverflowContainer extends EditorContainerUiElement { protected size: number; protected overflowButton: EditorDropdownButton; protected content: EditorUiElement[]; + protected label: string; - constructor(size: number, children: EditorUiElement[]) { + constructor(label: string, size: number, children: EditorUiElement[]) { super(children); + this.label = label; this.size = size; this.content = children; this.overflowButton = new EditorDropdownButton({ @@ -24,6 +26,11 @@ export class EditorOverflowContainer extends EditorContainerUiElement { this.addChildren(this.overflowButton); } + addChild(child: EditorUiElement, targetIndex: number = -1): void { + this.content.splice(targetIndex, 0, child); + this.addChildren(child); + } + protected buildDOM(): HTMLElement { const slicePosition = this.content.length > this.size ? this.size - 1 : this.size; const visibleChildren = this.content.slice(0, slicePosition); @@ -41,5 +48,8 @@ export class EditorOverflowContainer extends EditorContainerUiElement { }, visibleElements); } + getLabel(): string { + return this.label; + } } \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 3f46455da63..1adc0b619de 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -109,6 +109,10 @@ export class EditorUIManager { this.getContext().containerDOM.prepend(toolbar.getDOMElement()); } + getToolbar(): EditorContainerUiElement|null { + return this.toolbar; + } + registerContextToolbar(key: string, definition: EditorContextToolbarDefinition) { this.contextToolbarDefinitionsByKey[key] = definition; } From ebceba0afe50ad4c1d10d1542c24e189857371a0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 4 Dec 2025 21:13:17 +0000 Subject: [PATCH 3/7] Lexical API: Started working on docs format and jest testing --- dev/docs/wysiwyg-js-api.md | 91 ++++++++++++++- package-lock.json | 1 - .../wysiwyg/api/__tests__/api-test-utils.ts | 13 +++ resources/js/wysiwyg/api/__tests__/ui.test.ts | 109 ++++++++++++++++++ resources/js/wysiwyg/api/ui.ts | 16 +-- .../lexical/core/__tests__/utils/index.ts | 2 +- 6 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 resources/js/wysiwyg/api/__tests__/api-test-utils.ts create mode 100644 resources/js/wysiwyg/api/__tests__/ui.test.ts diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md index 7fd91a5e6cd..dec09b1a8ab 100644 --- a/dev/docs/wysiwyg-js-api.md +++ b/dev/docs/wysiwyg-js-api.md @@ -5,4 +5,93 @@ TODO - Create JS events and add to the js public events doc. TODO - Document the JS API. -TODO - Add testing coverage \ No newline at end of file +TODO - Add testing coverage + +**Warning: This API is currently in development and may change without notice.** + +This document covers the JavaScript API for the (newer Lexical-based) WYSIWYG editor. +This API is custom-built, and designed to abstract the internals of the editor away +to provide a stable interface for performing common customizations. + +Only the methods and properties documented here are guaranteed to be stable **once this API +is out of initial development**. +Other elements may be accessible but are not designed to be used directly, and therefore may change +without notice. +Stable parts of the API may still change where needed, but such changes would be noted as part of BookStack update advisories. + +## Overview + +The API is provided as an object, which itself provides a number of modules +via its properties: + +- `ui` - Provides all actions related to the UI of the editor, like buttons and toolbars. + +Each of these modules, and the relevant types used within, can be found detailed below. + +--- + +## UI Module + +This module provides all actions related to the UI of the editor, like buttons and toolbars. + +### Methods + +#### createButton(options) + +Creates a new button which can be used by other methods. +This takes an option object with the following properties: + +- `label` - string, optional - Used for the button text if no icon provided, or the button tooltip if an icon is provided. +- `icon` - string, optional - The icon to use for the button. Expected to be an SVG string. +- `action` - callback, required - The action to perform when the button is clicked. + +The function returns an [EditorApiButton](#editorapibutton) object. + +**Example** + +```javascript +const button = api.ui.createButton({ + label: 'Warn', + icon: '...', + action: () => { + window.alert('You clicked the button!'); + } +}); +``` + +### getMainToolbarSections() + +Get the sections of the main editor toolbar. These are those which contain groups of buttons +with overflow control. + +The function returns an array of [EditorToolbarSection](#editortoolbarsection) objects. + +**Example** + +```javascript +const sections = api.ui.getMainToolbarSections(); +if (sections.length > 0) { + sections[0].addButton(button); +} +``` + +### Types + +These are types which may be provided from UI module methods. +The methods on these types are documented using standard TypeScript notation. + +#### EditorApiButton + +Represents a button created via the `createButton` method. +This has the following methods: + +- `setActive(isActive: boolean): void` - Sets whether the button should be in an active state or not (typically active buttons appear as pressed). + +#### EditorToolbarSection + +Represents a section of the main editor toolbar, which contains a set of buttons. +This has the following methods: + +- `getLabel(): string` - Gets the label of the section. +- `addButton(button: EditorApiButton, targetIndex: number = -1): void` - Adds a button to the section. + - By default, this will append the button, although a target index can be provided to insert the button at a specific position. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 44ebf7eabbc..b85d1f5e28f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "bookstack", "dependencies": { "@codemirror/commands": "^6.10.0", "@codemirror/lang-css": "^6.3.1", diff --git a/resources/js/wysiwyg/api/__tests__/api-test-utils.ts b/resources/js/wysiwyg/api/__tests__/api-test-utils.ts new file mode 100644 index 00000000000..beff8504f78 --- /dev/null +++ b/resources/js/wysiwyg/api/__tests__/api-test-utils.ts @@ -0,0 +1,13 @@ +import {createTestContext} from "lexical/__tests__/utils"; +import {EditorApi} from "../api"; +import {EditorUiContext} from "../../ui/framework/core"; + + +/** + * Create an instance of the EditorApi and EditorUiContext. + */ +export function createEditorApiInstance(): { api: EditorApi; context: EditorUiContext } { + const context = createTestContext(); + const api = new EditorApi(context); + return {api, context}; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/api/__tests__/ui.test.ts b/resources/js/wysiwyg/api/__tests__/ui.test.ts new file mode 100644 index 00000000000..6cd80895b4e --- /dev/null +++ b/resources/js/wysiwyg/api/__tests__/ui.test.ts @@ -0,0 +1,109 @@ +import {createEditorApiInstance} from "./api-test-utils"; +import {EditorApiButton, EditorApiToolbarSection} from "../ui"; +import {getMainEditorFullToolbar} from "../../ui/defaults/toolbars"; +import {EditorContainerUiElement} from "../../ui/framework/core"; +import {EditorOverflowContainer} from "../../ui/framework/blocks/overflow-container"; + + +describe('Editor API: UI Module', () => { + + describe('createButton()', () => { + it('should return a button', () => { + const {api} = createEditorApiInstance(); + const button = api.ui.createButton({label: 'Test', icon: 'test', action: () => ''}); + expect(button).toBeInstanceOf(EditorApiButton); + }); + + it('should only need action to be required', () => { + const {api} = createEditorApiInstance(); + const button = api.ui.createButton({action: () => ''}); + expect(button).toBeInstanceOf(EditorApiButton); + }); + + it('should pass the label and icon to the button', () => { + const {api} = createEditorApiInstance(); + const button = api.ui.createButton({label: 'TestLabel', icon: 'cat', action: () => ''}); + const html = button._getOriginalModel().getDOMElement().outerHTML; + expect(html).toContain('TestLabel'); + expect(html).toContain('cat'); + }) + }); + + describe('EditorApiButton', () => { + + describe('setActive()', () => { + it('should update the active state of the button', () => { + const {api} = createEditorApiInstance(); + const button = api.ui.createButton({label: 'Test', icon: 'test', action: () => ''}); + + button.setActive(true); + expect(button._getOriginalModel().isActive()).toBe(true); + + button.setActive(false); + expect(button._getOriginalModel().isActive()).toBe(false); + }) + }); + + it('should call the provided action on click', () => { + const {api} = createEditorApiInstance(); + let count = 0; + const button = api.ui.createButton({label: 'Test', icon: 'test', action: () => { + count++; + }}); + + const dom = button._getOriginalModel().getDOMElement(); + dom.click(); + dom.click(); + expect(count).toBe(2); + }); + + }); + + describe('getMainToolbarSections()', () => { + it('should return an array of toolbar sections', () => { + const {api, context} = createEditorApiInstance(); + context.manager.setToolbar(getMainEditorFullToolbar(context)); + + const sections = api.ui.getMainToolbarSections(); + expect(Array.isArray(sections)).toBe(true); + + expect(sections[0]).toBeInstanceOf(EditorApiToolbarSection); + }); + }); + + describe('EditorApiToolbarSection', () => { + + describe('getLabel()', () => { + it('should return the label of the section', () => { + const {api, context} = createEditorApiInstance(); + context.manager.setToolbar(testToolbar()); + const section = api.ui.getMainToolbarSections()[0]; + expect(section.getLabel()).toBe('section-a'); + }) + }); + + describe('addButton()', () => { + it('should add a button to the section', () => { + const {api, context} = createEditorApiInstance(); + const toolbar = testToolbar(); + context.manager.setToolbar(toolbar); + const section = api.ui.getMainToolbarSections()[0]; + + const button = api.ui.createButton({label: 'TestButtonText!', action: () => ''}); + section.addButton(button); + + const toolbarRendered = toolbar.getDOMElement().innerHTML; + expect(toolbarRendered).toContain('TestButtonText!'); + }); + }); + + }); + + function testToolbar(): EditorContainerUiElement { + return new EditorContainerUiElement([ + new EditorOverflowContainer('section-a', 1, []), + new EditorOverflowContainer('section-b', 1, []), + ]); + } + +}); \ No newline at end of file diff --git a/resources/js/wysiwyg/api/ui.ts b/resources/js/wysiwyg/api/ui.ts index faad5d3160b..cf559269fa8 100644 --- a/resources/js/wysiwyg/api/ui.ts +++ b/resources/js/wysiwyg/api/ui.ts @@ -5,11 +5,11 @@ import {EditorOverflowContainer} from "../ui/framework/blocks/overflow-container type EditorApiButtonOptions = { label?: string; icon?: string; - onClick: () => void; + action: () => void; }; -class EditorApiButton { - #button: EditorButton; +export class EditorApiButton { + readonly #button: EditorButton; #isActive: boolean = false; constructor(options: EditorApiButtonOptions, context: EditorUiContext) { @@ -17,7 +17,7 @@ class EditorApiButton { label: options.label || '', icon: options.icon || '', action: () => { - options.onClick(); + options.action(); }, isActive: () => this.#isActive, }); @@ -34,8 +34,8 @@ class EditorApiButton { } } -class EditorApiToolbarSection { - #section: EditorOverflowContainer; +export class EditorApiToolbarSection { + readonly #section: EditorOverflowContainer; label: string; constructor(section: EditorOverflowContainer) { @@ -55,7 +55,7 @@ class EditorApiToolbarSection { export class EditorApiUiModule { - #context: EditorUiContext; + readonly #context: EditorUiContext; constructor(context: EditorUiContext) { this.#context = context; @@ -65,7 +65,7 @@ export class EditorApiUiModule { return new EditorApiButton(options, this.#context); } - getToolbarSections(): EditorApiToolbarSection[] { + getMainToolbarSections(): EditorApiToolbarSection[] { const toolbar = this.#context.manager.getToolbar(); if (!toolbar) { return []; diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index 00c5ec79646..4f08f35edeb 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -504,7 +504,7 @@ export function createTestContext(): EditorUiContext { options: {}, scrollDOM: scrollWrap, translate(text: string): string { - return ""; + return text; } }; From dfdcfcfdb86007a76ed33ed8749b58ea00d54158 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 5 Dec 2025 12:15:18 +0000 Subject: [PATCH 4/7] Lexical API: Added content module, testing and documented --- dev/docs/wysiwyg-js-api.md | 42 +++++++-- .../wysiwyg/api/__tests__/api-test-utils.ts | 5 +- .../js/wysiwyg/api/__tests__/content.test.ts | 93 +++++++++++++++++++ resources/js/wysiwyg/api/api.ts | 3 + resources/js/wysiwyg/api/content.ts | 26 ++++++ .../lexical/core/__tests__/utils/index.ts | 9 +- .../js/wysiwyg/services/common-events.ts | 2 +- resources/js/wysiwyg/utils/actions.ts | 38 ++++++-- resources/js/wysiwyg/utils/nodes.ts | 9 +- 9 files changed, 202 insertions(+), 25 deletions(-) create mode 100644 resources/js/wysiwyg/api/__tests__/content.test.ts create mode 100644 resources/js/wysiwyg/api/content.ts diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md index dec09b1a8ab..44e4a52c0df 100644 --- a/dev/docs/wysiwyg-js-api.md +++ b/dev/docs/wysiwyg-js-api.md @@ -3,14 +3,10 @@ TODO - Link to this from JS code doc. TODO - Create JS events and add to the js public events doc. -TODO - Document the JS API. - -TODO - Add testing coverage - **Warning: This API is currently in development and may change without notice.** This document covers the JavaScript API for the (newer Lexical-based) WYSIWYG editor. -This API is custom-built, and designed to abstract the internals of the editor away +This API is built and designed to abstract the internals of the editor away to provide a stable interface for performing common customizations. Only the methods and properties documented here are guaranteed to be stable **once this API @@ -25,8 +21,9 @@ The API is provided as an object, which itself provides a number of modules via its properties: - `ui` - Provides all actions related to the UI of the editor, like buttons and toolbars. +- `content` - Provides all actions related to the live user content being edited upon. -Each of these modules, and the relevant types used within, can be found detailed below. +Each of these modules, and the relevant types used within, are documented in detail below. --- @@ -36,7 +33,7 @@ This module provides all actions related to the UI of the editor, like buttons a ### Methods -#### createButton(options) +#### createButton(options: object) Creates a new button which can be used by other methods. This takes an option object with the following properties: @@ -92,6 +89,33 @@ This has the following methods: Represents a section of the main editor toolbar, which contains a set of buttons. This has the following methods: -- `getLabel(): string` - Gets the label of the section. +- `getLabel(): string` - Provides the string label of the section. - `addButton(button: EditorApiButton, targetIndex: number = -1): void` - Adds a button to the section. - - By default, this will append the button, although a target index can be provided to insert the button at a specific position. \ No newline at end of file + - By default, this will append the button, although a target index can be provided to insert the button at a specific position. + +--- + +## Content Module + +This module provides all actions related to the live user content being edited within the editor. + +### Methods + +#### insertHtml(html, position) + +Inserts the given HTML string at the given position string. +The position, if not provided, will default to `'selection'`, replacing any existing selected content (or inserting at the selection if there's no active selection range). +Valid position string values are: `selection`, `start` and `end`. `start` & `end` are relative to the whole editor document. +The HTML is not assured to be added to the editor exactly as provided, since it will be parsed and serialised to fit the editor's internal known model format. Different parts of the HTML content may be handled differently depending on if it's block or inline content. + +The function does not return anything. + +**Example** + +```javascript +// Basic insert at selection +api.content.insertHtml('

Hello world!

'); + +// Insert at the start of the editor content +api.content.insertHtml('

I\'m at the start!

', 'start'); +``` \ No newline at end of file diff --git a/resources/js/wysiwyg/api/__tests__/api-test-utils.ts b/resources/js/wysiwyg/api/__tests__/api-test-utils.ts index beff8504f78..dacec339289 100644 --- a/resources/js/wysiwyg/api/__tests__/api-test-utils.ts +++ b/resources/js/wysiwyg/api/__tests__/api-test-utils.ts @@ -1,13 +1,14 @@ import {createTestContext} from "lexical/__tests__/utils"; import {EditorApi} from "../api"; import {EditorUiContext} from "../../ui/framework/core"; +import {LexicalEditor} from "lexical"; /** * Create an instance of the EditorApi and EditorUiContext. */ -export function createEditorApiInstance(): { api: EditorApi; context: EditorUiContext } { +export function createEditorApiInstance(): { api: EditorApi; context: EditorUiContext, editor: LexicalEditor} { const context = createTestContext(); const api = new EditorApi(context); - return {api, context}; + return {api, context, editor: context.editor}; } \ No newline at end of file diff --git a/resources/js/wysiwyg/api/__tests__/content.test.ts b/resources/js/wysiwyg/api/__tests__/content.test.ts new file mode 100644 index 00000000000..0915b6685bb --- /dev/null +++ b/resources/js/wysiwyg/api/__tests__/content.test.ts @@ -0,0 +1,93 @@ +import {createEditorApiInstance} from "./api-test-utils"; +import {$createParagraphNode, $createTextNode, $getRoot, IS_BOLD, LexicalEditor} from "lexical"; +import {expectNodeShapeToMatch} from "lexical/__tests__/utils"; + + +describe('Editor API: Content Module', () => { + + describe('insertHtml()', () => { + it('should insert html at selection by default', () => { + const {api, editor} = createEditorApiInstance(); + insertAndSelectSampleBlock(editor); + + api.content.insertHtml('pp'); + editor.commitUpdates(); + + expectNodeShapeToMatch(editor, [ + {type: 'paragraph', children: [ + {text: 'He'}, + {text: 'pp', format: IS_BOLD}, + {text: 'o World'} + ]} + ]); + }); + + it('should handle a mix of inline and block elements', () => { + const {api, editor} = createEditorApiInstance(); + insertAndSelectSampleBlock(editor); + + api.content.insertHtml('

cat

pp

dog

'); + editor.commitUpdates(); + + expectNodeShapeToMatch(editor, [ + {type: 'paragraph', children: [{text: 'cat'}]}, + {type: 'paragraph', children: [ + {text: 'He'}, + {text: 'pp', format: IS_BOLD}, + {text: 'o World'} + ]}, + {type: 'paragraph', children: [{text: 'dog'}]}, + ]); + }); + + it('should throw and error if an invalid position is provided', () => { + const {api, editor} = createEditorApiInstance(); + insertAndSelectSampleBlock(editor); + + + expect(() => { + api.content.insertHtml('happy

cat

', 'near-the-end'); + }).toThrow('Invalid position: near-the-end. Valid positions are: start, end, selection'); + }); + + it('should append html if end provided as a position', () => { + const {api, editor} = createEditorApiInstance(); + insertAndSelectSampleBlock(editor); + + api.content.insertHtml('happy

cat

', 'end'); + editor.commitUpdates(); + + expectNodeShapeToMatch(editor, [ + {type: 'paragraph', children: [{text: 'Hello World'}]}, + {type: 'paragraph', children: [{text: 'happy'}]}, + {type: 'paragraph', children: [{text: 'cat'}]}, + ]); + }); + + it('should prepend html if start provided as a position', () => { + const {api, editor} = createEditorApiInstance(); + insertAndSelectSampleBlock(editor); + + api.content.insertHtml('happy

cat

', 'start'); + editor.commitUpdates(); + + expectNodeShapeToMatch(editor, [ + {type: 'paragraph', children: [{text: 'happy'}]}, + {type: 'paragraph', children: [{text: 'cat'}]}, + {type: 'paragraph', children: [{text: 'Hello World'}]}, + ]); + }); + }); + + function insertAndSelectSampleBlock(editor: LexicalEditor) { + editor.updateAndCommit(() => { + const p = $createParagraphNode(); + const text = $createTextNode('Hello World'); + p.append(text); + $getRoot().append(p); + + text.select(2, 4); + }); + } + +}); \ No newline at end of file diff --git a/resources/js/wysiwyg/api/api.ts b/resources/js/wysiwyg/api/api.ts index 434109c7ac7..f732170b353 100644 --- a/resources/js/wysiwyg/api/api.ts +++ b/resources/js/wysiwyg/api/api.ts @@ -1,11 +1,14 @@ import {EditorApiUiModule} from "./ui"; import {EditorUiContext} from "../ui/framework/core"; +import {EditorApiContentModule} from "./content"; export class EditorApi { public ui: EditorApiUiModule; + public content: EditorApiContentModule; constructor(context: EditorUiContext) { this.ui = new EditorApiUiModule(context); + this.content = new EditorApiContentModule(context); } } \ No newline at end of file diff --git a/resources/js/wysiwyg/api/content.ts b/resources/js/wysiwyg/api/content.ts new file mode 100644 index 00000000000..167ec790590 --- /dev/null +++ b/resources/js/wysiwyg/api/content.ts @@ -0,0 +1,26 @@ +import {EditorUiContext} from "../ui/framework/core"; +import {appendHtmlToEditor, insertHtmlIntoEditor, prependHtmlToEditor} from "../utils/actions"; + + +export class EditorApiContentModule { + readonly #context: EditorUiContext; + + constructor(context: EditorUiContext) { + this.#context = context; + } + + insertHtml(html: string, position: string = 'selection'): void { + const validPositions = ['start', 'end', 'selection']; + if (!validPositions.includes(position)) { + throw new Error(`Invalid position: ${position}. Valid positions are: ${validPositions.join(', ')}`); + } + + if (position === 'start') { + prependHtmlToEditor(this.#context.editor, html); + } else if (position === 'end') { + appendHtmlToEditor(this.#context.editor, html); + } else { + insertHtmlIntoEditor(this.#context.editor, html); + } + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index 4f08f35edeb..ab54bdb31d5 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -769,6 +769,7 @@ export function expectHtmlToBeEqual(expected: string, actual: string): void { type nodeTextShape = { text: string; + format?: number; }; type nodeShape = { @@ -786,7 +787,13 @@ export function getNodeShape(node: SerializedLexicalNode): nodeShape|nodeTextSha if (shape.type === 'text') { // @ts-ignore - return {text: node.text} + const shape: nodeTextShape = {text: node.text} + // @ts-ignore + if (node && node.format) { + // @ts-ignore + shape.format = node.format; + } + return shape; } if (children.length > 0) { diff --git a/resources/js/wysiwyg/services/common-events.ts b/resources/js/wysiwyg/services/common-events.ts index 2ffa722e40c..f7fc81cb3cf 100644 --- a/resources/js/wysiwyg/services/common-events.ts +++ b/resources/js/wysiwyg/services/common-events.ts @@ -1,4 +1,4 @@ -import {$getSelection, LexicalEditor} from "lexical"; +import {LexicalEditor} from "lexical"; import { appendHtmlToEditor, focusEditor, diff --git a/resources/js/wysiwyg/utils/actions.ts b/resources/js/wysiwyg/utils/actions.ts index b7ce65eeb6c..e18ac515f51 100644 --- a/resources/js/wysiwyg/utils/actions.ts +++ b/resources/js/wysiwyg/utils/actions.ts @@ -1,6 +1,6 @@ -import {$getRoot, $getSelection, LexicalEditor} from "lexical"; +import {$getRoot, $getSelection, $insertNodes, $isBlockElementNode, LexicalEditor} from "lexical"; import {$generateHtmlFromNodes} from "@lexical/html"; -import {$htmlToBlockNodes} from "./nodes"; +import {$getNearestNodeBlockParent, $htmlToBlockNodes, $htmlToNodes} from "./nodes"; export function setEditorContentFromHtml(editor: LexicalEditor, html: string) { editor.update(() => { @@ -42,14 +42,34 @@ export function prependHtmlToEditor(editor: LexicalEditor, html: string) { export function insertHtmlIntoEditor(editor: LexicalEditor, html: string) { editor.update(() => { const selection = $getSelection(); - const nodes = $htmlToBlockNodes(editor, html); + const nodes = $htmlToNodes(editor, html); + + let reference = selection?.getNodes()[0]; + let replacedReference = false; + let parentBlock = reference ? $getNearestNodeBlockParent(reference) : null; - const reference = selection?.getNodes()[0]; - const referencesParents = reference?.getParents() || []; - const topLevel = referencesParents[referencesParents.length - 1]; - if (topLevel && reference) { - for (let i = nodes.length - 1; i >= 0; i--) { - reference.insertAfter(nodes[i]); + for (let i = nodes.length - 1; i >= 0; i--) { + const toInsert = nodes[i]; + if ($isBlockElementNode(toInsert) && parentBlock) { + // Insert at a block level, before or after the referenced block + // depending on if the reference has been replaced. + if (replacedReference) { + parentBlock.insertBefore(toInsert); + } else { + parentBlock.insertAfter(toInsert); + } + } else if ($isBlockElementNode(toInsert)) { + // Otherwise append blocks to the root + $getRoot().append(toInsert); + } else if (!replacedReference) { + // First inline node, replacing existing selection + $insertNodes([toInsert]); + reference = toInsert; + parentBlock = $getNearestNodeBlockParent(reference); + replacedReference = true; + } else { + // For other inline nodes, insert before the reference node + reference?.insertBefore(toInsert) } } }); diff --git a/resources/js/wysiwyg/utils/nodes.ts b/resources/js/wysiwyg/utils/nodes.ts index 116a3f4e5c0..ed70bf6996e 100644 --- a/resources/js/wysiwyg/utils/nodes.ts +++ b/resources/js/wysiwyg/utils/nodes.ts @@ -25,10 +25,13 @@ function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] { }); } -export function $htmlToBlockNodes(editor: LexicalEditor, html: string): LexicalNode[] { +export function $htmlToNodes(editor: LexicalEditor, html: string): LexicalNode[] { const dom = htmlToDom(html); - const nodes = $generateNodesFromDOM(editor, dom); - return wrapTextNodes(nodes); + return $generateNodesFromDOM(editor, dom); +} + +export function $htmlToBlockNodes(editor: LexicalEditor, html: string): LexicalNode[] { + return wrapTextNodes($htmlToNodes(editor, html)); } export function $getParentOfType(node: LexicalNode, matcher: LexicalNodeMatcher): LexicalNode | null { From 889074627840c4ccd14a9b525e33d571d56e3191 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 5 Dec 2025 14:07:24 +0000 Subject: [PATCH 5/7] Lexical API: Added public event to access editor API Updated documentation to match. Ran manual testing of examples. --- dev/docs/javascript-code.md | 4 ++ dev/docs/javascript-public-events.md | 46 ++++++++++++++++++-- dev/docs/wysiwyg-js-api.md | 1 - resources/js/wysiwyg/index.ts | 14 +++--- resources/js/wysiwyg/ui/defaults/toolbars.ts | 12 ++--- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/dev/docs/javascript-code.md b/dev/docs/javascript-code.md index e5f491839f0..4de6223d811 100644 --- a/dev/docs/javascript-code.md +++ b/dev/docs/javascript-code.md @@ -161,3 +161,7 @@ window.$components.firstOnElement(element, name); There are a range of available events that are emitted as part of a public & supported API for accessing or extending JavaScript libraries & components used in the system. Details on these events can be found in the [JavaScript Public Events file](javascript-public-events.md). + +## WYSIWYG Editor API + +Details on the API for our custom-built WYSIWYG editor can be found in the [WYSIWYG JavaScript API file](./wysiwyg-js-api.md). \ No newline at end of file diff --git a/dev/docs/javascript-public-events.md b/dev/docs/javascript-public-events.md index 4f68daaebf0..85a4caad488 100644 --- a/dev/docs/javascript-public-events.md +++ b/dev/docs/javascript-public-events.md @@ -60,7 +60,7 @@ This event is called when the markdown editor loads, post configuration but befo #### Event Data -- `markdownIt` - A references to the [MarkdownIt](https://markdown-it.github.io/markdown-it/#MarkdownIt) instance used to render markdown to HTML (Just for the preview). +- `markdownIt` - A reference to the [MarkdownIt](https://markdown-it.github.io/markdown-it/#MarkdownIt) instance used to render markdown to HTML (Just for the preview). - `displayEl` - The IFrame Element that wraps the HTML preview display. - `cmEditorView` - The CodeMirror [EditorView](https://codemirror.net/docs/ref/#view.EditorView) instance used for the markdown input editor. @@ -79,7 +79,7 @@ window.addEventListener('editor-markdown::setup', event => { This event is called as the embedded diagrams.net drawing editor loads, to allow configuration of the diagrams.net interface. See [this diagrams.net page](https://www.diagrams.net/doc/faq/configure-diagram-editor) for details on the available options for the configure event. -If using a custom diagrams.net instance, via the `DRAWIO` option, you will need to ensure your DRAWIO option URL has the `configure=1` query parameter. +If using a custom diagrams.net instance, via the `DRAWIO` option, you will need to ensure your DRAWIO option URL has the `configure=1` query parameter. #### Event Data @@ -142,7 +142,7 @@ This event is called whenever a CodeMirror instance is loaded, as a method to co - `darkModeActive` - A boolean to indicate if the current view/page is being loaded with dark mode active. - `registerViewTheme(builder)` - A method that can be called to register a new view (CodeMirror UI) theme. - - `builder` - A function that will return an object that will be passed into the CodeMirror [EditorView.theme()](https://codemirror.net/docs/ref/#view.EditorView^theme) function as a StyleSpec. + - `builder` - A function that will return an object that will be passed into the CodeMirror [EditorView.theme()](https://codemirror.net/docs/ref/#view.EditorView^theme) function as a StyleSpec. - `registerHighlightStyle(builder)` - A method that can be called to register a new HighlightStyle (code highlighting) theme. - `builder` - A function, that receives a reference to [Tag.tags](https://lezer.codemirror.net/docs/ref/#highlight.tags) and returns an array of [TagStyle](https://codemirror.net/docs/ref/#language.TagStyle) objects. @@ -301,7 +301,7 @@ This event is called just after any CodeMirror instances are initialised so that ##### Example -The below shows how you'd prepend some default text to all content (page) code blocks. +The below example shows how you'd prepend some default text to all content (page) code blocks.
Show Example @@ -318,4 +318,42 @@ window.addEventListener('library-cm6::post-init', event => { } }); ``` +
+ +### `editor-wysiwyg::post-init` + +This is called after the (new custom-built Lexical-based) WYSIWYG editor has been initialised. + +#### Event Data + +- `usage` - A string label to identify the usage type of the WYSIWYG editor in BookStack. +- `api` - An instance to the WYSIWYG editor API, as documented in the [WYSIWYG JavaScript API file](./wysiwyg-js-api.md). + +##### Example + +The below shows how you'd use this API to create a button, with that button added to the toolbar of the page editor, which inserts bold hello text on press: + +
+Show Example + +```javascript +window.addEventListener('editor-wysiwyg::post-init', event => { + const {usage, api} = event.detail; + // Check that it's the page editor being loaded. + if (usage !== 'page-editor') { + return; + } + + // Create a custom button which inserts bold hello text on press. + const button = api.ui.createButton({ + label: 'Greet', + action: () => { + api.content.insertHtml(`Hello!`); + } + }); + + // Add the button to the start of the first section within the main toolbar. + api.ui.getMainToolbarSections()[0]?.addButton(button, 0); +}); +```
\ No newline at end of file diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md index 44e4a52c0df..7286f943ff4 100644 --- a/dev/docs/wysiwyg-js-api.md +++ b/dev/docs/wysiwyg-js-api.md @@ -1,6 +1,5 @@ # WYSIWYG JavaScript API -TODO - Link to this from JS code doc. TODO - Create JS events and add to the js public events doc. **Warning: This API is currently in development and may change without notice.** diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index c3ff37e7edd..5d1762ff867 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -95,11 +95,10 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st registerCommonNodeMutationListeners(context); - // TODO - Emit this as a public event instead - // TODO - Add support to basic editor below - const api = new EditorApi(context); - // @ts-ignore - window.editorApi = api; + window.$events.emitPublic(container, 'editor-wysiwyg::post-init', { + usage: 'page-editor', + api: new EditorApi(context), + }); return new SimpleWysiwygEditorInterface(context); } @@ -129,6 +128,11 @@ export function createBasicEditorInstance(container: HTMLElement, htmlContent: s setEditorContentFromHtml(editor, htmlContent); + window.$events.emitPublic(container, 'editor-wysiwyg::post-init', { + usage: 'description-editor', + api: new EditorApi(context), + }); + return new SimpleWysiwygEditorInterface(context); } diff --git a/resources/js/wysiwyg/ui/defaults/toolbars.ts b/resources/js/wysiwyg/ui/defaults/toolbars.ts index 0c48f595499..9302e7beda9 100644 --- a/resources/js/wysiwyg/ui/defaults/toolbars.ts +++ b/resources/js/wysiwyg/ui/defaults/toolbars.ts @@ -223,11 +223,13 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai export function getBasicEditorToolbar(context: EditorUiContext): EditorContainerUiElement { return new EditorSimpleClassContainer('editor-toolbar-main', [ - new EditorButton(bold), - new EditorButton(italic), - new EditorButton(link), - new EditorButton(bulletList), - new EditorButton(numberList), + new EditorOverflowContainer('formats', 7, [ + new EditorButton(bold), + new EditorButton(italic), + new EditorButton(link), + new EditorButton(bulletList), + new EditorButton(numberList), + ]) ]); } From ab4b1c8efa64b89a549ade67c713e43f7d34fb57 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 5 Dec 2025 14:37:46 +0000 Subject: [PATCH 6/7] Lexical API: Reviewed docs, Made toolbar its own UI class --- dev/docs/javascript-public-events.md | 79 ++++++++++--------- dev/docs/wysiwyg-js-api.md | 34 ++++---- resources/js/wysiwyg/api/__tests__/ui.test.ts | 30 +++++-- resources/js/wysiwyg/api/ui.ts | 28 ++++--- 4 files changed, 102 insertions(+), 69 deletions(-) diff --git a/dev/docs/javascript-public-events.md b/dev/docs/javascript-public-events.md index 85a4caad488..e9ea014ef46 100644 --- a/dev/docs/javascript-public-events.md +++ b/dev/docs/javascript-public-events.md @@ -134,6 +134,47 @@ window.addEventListener('editor-tinymce::setup', event => { }); ``` +### `editor-wysiwyg::post-init` + +This is called after the (new custom-built Lexical-based) WYSIWYG editor has been initialised. + +#### Event Data + +- `usage` - A string label to identify the usage type of the WYSIWYG editor in BookStack. +- `api` - An instance to the WYSIWYG editor API, as documented in the [WYSIWYG JavaScript API file](./wysiwyg-js-api.md). + +##### Example + +The below example shows how you'd use this API to create a button, with that button added to the main toolbar of the page editor, which inserts bold "Hello!" text on press: + +
+Show Example + +```javascript +window.addEventListener('editor-wysiwyg::post-init', event => { + const {usage, api} = event.detail; + // Check that it's the page editor which is being loaded + if (usage !== 'page-editor') { + return; + } + + // Create a custom button which inserts bold hello text on press + const button = api.ui.createButton({ + label: 'Greet', + action: () => { + api.content.insertHtml(`Hello!`); + } + }); + + // Add the button to the start of the first section within the main toolbar + const toolbar = api.ui.getMainToolbar(); + if (toolbar) { + toolbar.getSections()[0]?.addButton(button, 0); + } +}); +``` +
+ ### `library-cm6::configure-theme` This event is called whenever a CodeMirror instance is loaded, as a method to configure the theme used by CodeMirror. This applies to all CodeMirror instances including in-page code blocks, editors using in BookStack settings, and the Page markdown editor. @@ -319,41 +360,3 @@ window.addEventListener('library-cm6::post-init', event => { }); ``` - -### `editor-wysiwyg::post-init` - -This is called after the (new custom-built Lexical-based) WYSIWYG editor has been initialised. - -#### Event Data - -- `usage` - A string label to identify the usage type of the WYSIWYG editor in BookStack. -- `api` - An instance to the WYSIWYG editor API, as documented in the [WYSIWYG JavaScript API file](./wysiwyg-js-api.md). - -##### Example - -The below shows how you'd use this API to create a button, with that button added to the toolbar of the page editor, which inserts bold hello text on press: - -
-Show Example - -```javascript -window.addEventListener('editor-wysiwyg::post-init', event => { - const {usage, api} = event.detail; - // Check that it's the page editor being loaded. - if (usage !== 'page-editor') { - return; - } - - // Create a custom button which inserts bold hello text on press. - const button = api.ui.createButton({ - label: 'Greet', - action: () => { - api.content.insertHtml(`Hello!`); - } - }); - - // Add the button to the start of the first section within the main toolbar. - api.ui.getMainToolbarSections()[0]?.addButton(button, 0); -}); -``` -
\ No newline at end of file diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md index 7286f943ff4..394f2f15f04 100644 --- a/dev/docs/wysiwyg-js-api.md +++ b/dev/docs/wysiwyg-js-api.md @@ -1,9 +1,9 @@ # WYSIWYG JavaScript API -TODO - Create JS events and add to the js public events doc. - **Warning: This API is currently in development and may change without notice.** +Feedback is very much welcomed via this issue: https://github.com/BookStackApp/BookStack/issues/5937 + This document covers the JavaScript API for the (newer Lexical-based) WYSIWYG editor. This API is built and designed to abstract the internals of the editor away to provide a stable interface for performing common customizations. @@ -19,8 +19,8 @@ Stable parts of the API may still change where needed, but such changes would be The API is provided as an object, which itself provides a number of modules via its properties: -- `ui` - Provides all actions related to the UI of the editor, like buttons and toolbars. -- `content` - Provides all actions related to the live user content being edited upon. +- `ui` - Provides methods related to the UI of the editor, like buttons and toolbars. +- `content` - Provides methods related to the live user content being edited upon. Each of these modules, and the relevant types used within, are documented in detail below. @@ -28,7 +28,7 @@ Each of these modules, and the relevant types used within, are documented in det ## UI Module -This module provides all actions related to the UI of the editor, like buttons and toolbars. +This module provides methods related to the UI of the editor, like buttons and toolbars. ### Methods @@ -55,17 +55,16 @@ const button = api.ui.createButton({ }); ``` -### getMainToolbarSections() +### getMainToolbar() -Get the sections of the main editor toolbar. These are those which contain groups of buttons -with overflow control. - -The function returns an array of [EditorToolbarSection](#editortoolbarsection) objects. +Get the main editor toolbar. This is typically the toolbar at the top of the editor. +The function returns an [EditorApiToolbar](#editorapitoolbar) object, or null if no toolbar is found. **Example** ```javascript -const sections = api.ui.getMainToolbarSections(); +const toolbar = api.ui.getMainToolbar(); +const sections = toolbar?.getSections() || []; if (sections.length > 0) { sections[0].addButton(button); } @@ -83,20 +82,27 @@ This has the following methods: - `setActive(isActive: boolean): void` - Sets whether the button should be in an active state or not (typically active buttons appear as pressed). -#### EditorToolbarSection +#### EditorApiToolbar + +Represents a toolbar within the editor. This is a bar typically containing sets of buttons. +This has the following methods: + +- `getSections(): EditorApiToolbarSection[]` - Provides the main [EditorApiToolbarSections](#editorapitoolbarsection) contained within this toolbar. + +#### EditorApiToolbarSection Represents a section of the main editor toolbar, which contains a set of buttons. This has the following methods: - `getLabel(): string` - Provides the string label of the section. - `addButton(button: EditorApiButton, targetIndex: number = -1): void` - Adds a button to the section. - - By default, this will append the button, although a target index can be provided to insert the button at a specific position. + - By default, this will append the button, although a target index can be provided to insert at a specific position. --- ## Content Module -This module provides all actions related to the live user content being edited within the editor. +This module provides methods related to the live user content being edited within the editor. ### Methods diff --git a/resources/js/wysiwyg/api/__tests__/ui.test.ts b/resources/js/wysiwyg/api/__tests__/ui.test.ts index 6cd80895b4e..80045bb48e9 100644 --- a/resources/js/wysiwyg/api/__tests__/ui.test.ts +++ b/resources/js/wysiwyg/api/__tests__/ui.test.ts @@ -1,5 +1,5 @@ import {createEditorApiInstance} from "./api-test-utils"; -import {EditorApiButton, EditorApiToolbarSection} from "../ui"; +import {EditorApiButton, EditorApiToolbar, EditorApiToolbarSection} from "../ui"; import {getMainEditorFullToolbar} from "../../ui/defaults/toolbars"; import {EditorContainerUiElement} from "../../ui/framework/core"; import {EditorOverflowContainer} from "../../ui/framework/blocks/overflow-container"; @@ -59,25 +59,39 @@ describe('Editor API: UI Module', () => { }); - describe('getMainToolbarSections()', () => { - it('should return an array of toolbar sections', () => { + describe('getMainToolbar()', () => { + it('should return the main editor toolbar', () => { const {api, context} = createEditorApiInstance(); context.manager.setToolbar(getMainEditorFullToolbar(context)); - const sections = api.ui.getMainToolbarSections(); - expect(Array.isArray(sections)).toBe(true); + const toolbar = api.ui.getMainToolbar(); - expect(sections[0]).toBeInstanceOf(EditorApiToolbarSection); + expect(toolbar).toBeInstanceOf(EditorApiToolbar); }); }); + describe('EditorApiToolbar', () => { + describe('getSections()', () => { + it('should return the sections of the toolbar', () => { + const {api, context} = createEditorApiInstance(); + context.manager.setToolbar(testToolbar()); + const toolbar = api.ui.getMainToolbar(); + + const sections = toolbar?.getSections() || []; + + expect(sections.length).toBe(2); + expect(sections[0]).toBeInstanceOf(EditorApiToolbarSection); + }) + }) + }) + describe('EditorApiToolbarSection', () => { describe('getLabel()', () => { it('should return the label of the section', () => { const {api, context} = createEditorApiInstance(); context.manager.setToolbar(testToolbar()); - const section = api.ui.getMainToolbarSections()[0]; + const section = api.ui.getMainToolbar()?.getSections()[0] as EditorApiToolbarSection; expect(section.getLabel()).toBe('section-a'); }) }); @@ -87,7 +101,7 @@ describe('Editor API: UI Module', () => { const {api, context} = createEditorApiInstance(); const toolbar = testToolbar(); context.manager.setToolbar(toolbar); - const section = api.ui.getMainToolbarSections()[0]; + const section = api.ui.getMainToolbar()?.getSections()[0] as EditorApiToolbarSection; const button = api.ui.createButton({label: 'TestButtonText!', action: () => ''}); section.addButton(button); diff --git a/resources/js/wysiwyg/api/ui.ts b/resources/js/wysiwyg/api/ui.ts index cf559269fa8..c5b822a46bc 100644 --- a/resources/js/wysiwyg/api/ui.ts +++ b/resources/js/wysiwyg/api/ui.ts @@ -1,5 +1,5 @@ import {EditorButton} from "../ui/framework/buttons"; -import {EditorUiContext} from "../ui/framework/core"; +import {EditorContainerUiElement, EditorUiContext} from "../ui/framework/core"; import {EditorOverflowContainer} from "../ui/framework/blocks/overflow-container"; type EditorApiButtonOptions = { @@ -34,13 +34,26 @@ export class EditorApiButton { } } +export class EditorApiToolbar { + readonly #toolbar: EditorContainerUiElement; + + constructor(toolbar: EditorContainerUiElement) { + this.#toolbar = toolbar; + } + + getSections(): EditorApiToolbarSection[] { + const sections = this.#toolbar.getChildren(); + return sections.filter(section => { + return section instanceof EditorOverflowContainer; + }).map(section => new EditorApiToolbarSection(section)); + } +} + export class EditorApiToolbarSection { readonly #section: EditorOverflowContainer; - label: string; constructor(section: EditorOverflowContainer) { this.#section = section; - this.label = section.getLabel(); } getLabel(): string { @@ -65,15 +78,12 @@ export class EditorApiUiModule { return new EditorApiButton(options, this.#context); } - getMainToolbarSections(): EditorApiToolbarSection[] { + getMainToolbar(): EditorApiToolbar|null { const toolbar = this.#context.manager.getToolbar(); if (!toolbar) { - return []; + return null; } - const sections = toolbar.getChildren(); - return sections.filter(section => { - return section instanceof EditorOverflowContainer; - }).map(section => new EditorApiToolbarSection(section)); + return new EditorApiToolbar(toolbar); } } \ No newline at end of file From b5246a28f0268460588ae33e555337493426e2b0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 6 Dec 2025 15:18:28 +0000 Subject: [PATCH 7/7] Lexical API: Updated docs to align method format --- dev/docs/wysiwyg-js-api.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md index 394f2f15f04..e899aa0c8c3 100644 --- a/dev/docs/wysiwyg-js-api.md +++ b/dev/docs/wysiwyg-js-api.md @@ -14,6 +14,8 @@ Other elements may be accessible but are not designed to be used directly, and t without notice. Stable parts of the API may still change where needed, but such changes would be noted as part of BookStack update advisories. +The methods shown here are documented using standard TypeScript notation. + ## Overview The API is provided as an object, which itself provides a number of modules @@ -32,7 +34,7 @@ This module provides methods related to the UI of the editor, like buttons and t ### Methods -#### createButton(options: object) +#### createButton(options: object): EditorApiButton Creates a new button which can be used by other methods. This takes an option object with the following properties: @@ -55,7 +57,7 @@ const button = api.ui.createButton({ }); ``` -### getMainToolbar() +### getMainToolbar(): EditorApiToolbar Get the main editor toolbar. This is typically the toolbar at the top of the editor. The function returns an [EditorApiToolbar](#editorapitoolbar) object, or null if no toolbar is found. @@ -73,7 +75,6 @@ if (sections.length > 0) { ### Types These are types which may be provided from UI module methods. -The methods on these types are documented using standard TypeScript notation. #### EditorApiButton @@ -106,7 +107,7 @@ This module provides methods related to the live user content being edited withi ### Methods -#### insertHtml(html, position) +#### insertHtml(html: string, position: string = 'selection'): void Inserts the given HTML string at the given position string. The position, if not provided, will default to `'selection'`, replacing any existing selected content (or inserting at the selection if there's no active selection range).