diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e0ff474ef..f9ad66079 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest outputs: - publish: ${{ steps.publish_vars.outputs.release != 'true' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/6.')) }} + publish: ${{ steps.publish_vars.outputs.release != 'true' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/epic-enonic-ui') }} repo: ${{ steps.publish_vars.outputs.repo }} steps: diff --git a/gradle.properties b/gradle.properties index ec1bfda44..768d56917 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=6.0.0-SNAPSHOT +version=0.0.0-enonic-ui-SNAPSHOT org.gradle.parallel=true org.gradle.caching=true org.gradle.configuration-cache=true diff --git a/gradle/node.gradle b/gradle/node.gradle index be840a70e..be13512f5 100644 --- a/gradle/node.gradle +++ b/gradle/node.gradle @@ -2,6 +2,6 @@ apply plugin: 'com.github.node-gradle.node' node { version = '24.13.0' - pnpmVersion = '10.16.0' + pnpmVersion = '10.26.1' download = true } diff --git a/package.json b/package.json index 033694385..053d4ac9a 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,23 @@ }, "dependencies": { "dompurify": "~3.3.1", + "@enonic/ui": "~0.26.0", "fine-uploader": "^5.16.2", "jquery": "~3.7.1", "jquery-simulate": "^1.0.2", "jquery-ui": "^1.14.1", + "lucide-react": "^0.540.0", "mousetrap": "^1.6.5", + "nanoid": "~5.1.5", + "nanostores": "~1.0.1", + "preact": "^10.27.2", "q": "^1.5.1" }, + "peerDependencies": { + "@radix-ui/react-slot": "^1.2.0", + "focus-trap-react": "^11.0.0", + "lucide-react": ">=0.500.0" + }, "devDependencies": { "@enonic/eslint-config": "^2.2.1", "@rollup/plugin-inject": "^5.0.5", @@ -53,9 +63,9 @@ "browserslist": [ "extends browserslist-config-enonic" ], + "packageManager": "pnpm@10.26.1", "engines": { "node": ">= 24.13.0", - "pnpm": ">= 10.16.0", - "npm": ">= 11.6.2" + "pnpm": ">= 10.26.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index efdd07581..a0b86b69e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,21 @@ importers: .: dependencies: + '@enonic/ui': + specifier: ~0.26.0 + version: 0.26.0(@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0))(focus-trap-react@11.0.4(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(lucide-react@0.540.0(react@19.2.0))(preact@10.27.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': + specifier: ^1.2.0 + version: 1.2.3(@types/react@19.2.2)(react@19.2.0) dompurify: specifier: ~3.3.1 version: 3.3.1 fine-uploader: specifier: ^5.16.2 version: 5.16.2 + focus-trap-react: + specifier: ^11.0.0 + version: 11.0.4(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) jquery: specifier: ~3.7.1 version: 3.7.1 @@ -23,9 +32,21 @@ importers: jquery-ui: specifier: ^1.14.1 version: 1.14.1 + lucide-react: + specifier: ^0.540.0 + version: 0.540.0(react@19.2.0) mousetrap: specifier: ^1.6.5 version: 1.6.5 + nanoid: + specifier: ~5.1.5 + version: 5.1.6 + nanostores: + specifier: ~1.0.1 + version: 1.0.1 + preact: + specifier: ^10.27.2 + version: 10.27.2 q: specifier: ^1.5.1 version: 1.5.1 @@ -86,7 +107,7 @@ importers: version: 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@24.2.0)(jiti@2.6.1)(less@4.5.1)(terser@5.43.1) + version: 7.3.1(@types/node@24.2.0)(jiti@2.6.1)(less@4.5.1) packages: @@ -100,6 +121,24 @@ packages: typescript: ^5.8.3 typescript-eslint: ^8.39.0 + '@enonic/ui@0.26.0': + resolution: {integrity: sha512-ik6UmXEi7hrDCIWbs82RCspWiZQAu1zRf6uRBBX8xgU+olpmqGdZk+ElXp9MzKfJJCLiiSZL+BBR565Qxo4tsQ==} + engines: {node: '>=24.11.1', npm: '>=11.6.2', pnpm: '>=10.24.0'} + peerDependencies: + '@radix-ui/react-slot': ^1.2.0 + focus-trap-react: ^11.0.0 + lucide-react: '>=0.500.0' + preact: '>=10.0.0' + react: ^19.0.0 + react-dom: ^19.0.0 + peerDependenciesMeta: + preact: + optional: true + react: + optional: true + react-dom: + optional: true + '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} @@ -319,21 +358,26 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true '@rollup/plugin-inject@5.0.5': resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} @@ -353,56 +397,111 @@ packages: rollup: optional: true + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm-eabi@4.55.1': resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} cpu: [arm] os: [android] + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + cpu: [arm64] + os: [android] + '@rollup/rollup-android-arm64@4.55.1': resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} cpu: [arm64] os: [android] + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-arm64@4.55.1': resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.55.1': resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} cpu: [x64] os: [darwin] + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.55.1': resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.55.1': resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} cpu: [x64] os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.55.1': resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.55.1': resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.55.1': resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + cpu: [loong64] + os: [linux] + '@rollup/rollup-linux-loong64-gnu@4.55.1': resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} cpu: [loong64] @@ -413,6 +512,11 @@ packages: cpu: [loong64] os: [linux] + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-ppc64-gnu@4.55.1': resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] @@ -423,26 +527,51 @@ packages: cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.55.1': resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-riscv64-musl@4.55.1': resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.55.1': resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.55.1': resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.55.1': resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] @@ -453,26 +582,51 @@ packages: cpu: [x64] os: [openbsd] + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-openharmony-arm64@4.55.1': resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} cpu: [arm64] os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.55.1': resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + cpu: [ia32] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.55.1': resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-gnu@4.55.1': resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.55.1': resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} cpu: [x64] @@ -499,6 +653,14 @@ packages: '@types/q@1.5.8': resolution: {integrity: sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==} + '@types/react-dom@19.2.1': + resolution: {integrity: sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/sizzle@2.3.10': resolution: {integrity: sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==} @@ -625,9 +787,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -642,6 +801,13 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -656,9 +822,6 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -722,6 +885,9 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -861,6 +1027,17 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + focus-trap-react@11.0.4: + resolution: {integrity: sha512-tC7jC/yqeAqhe4irNIzdyDf9XCtGSeECHiBSYJBO/vIN0asizbKZCt8TarB6/XqIceu42ajQ/U4lQJ9pZlWjrg==} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + focus-trap@7.6.5: + resolution: {integrity: sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -982,6 +1159,11 @@ packages: lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + lucide-react@0.540.0: + resolution: {integrity: sha512-armkCAqQvO62EIX4Hq7hqX/q11WSZu0Jd23cnnqx0/49yIxGXyL/zyZfBxNN9YDx0ensPTb4L+DjTh3yQXUxtQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -1018,6 +1200,15 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + engines: {node: ^18 || >=20} + hasBin: true + + nanostores@1.0.1: + resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==} + engines: {node: ^20.0.0 || >=22.0.0} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -1264,6 +1455,9 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + preact@10.27.2: + resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1280,13 +1474,26 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + peerDependencies: + react: ^19.2.0 + + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rollup@4.55.1: resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1301,6 +1508,9 @@ packages: sax@1.4.3: resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -1326,9 +1536,6 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -1337,8 +1544,8 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - stylehacks@7.0.7: - resolution: {integrity: sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==} + stylehacks@7.0.6: + resolution: {integrity: sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.4.32 @@ -1352,10 +1559,11 @@ packages: engines: {node: '>=16'} hasBin: true - terser@5.43.1: - resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} - engines: {node: '>=10'} - hasBin: true + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} @@ -1471,6 +1679,19 @@ snapshots: typescript: 5.9.3 typescript-eslint: 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@enonic/ui@0.26.0(@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0))(focus-trap-react@11.0.4(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(lucide-react@0.540.0(react@19.2.0))(preact@10.27.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + class-variance-authority: 0.7.1 + clsx: 2.1.1 + focus-trap-react: 11.0.4(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + lucide-react: 0.540.0(react@19.2.0) + tailwind-merge: 3.4.0 + optionalDependencies: + preact: 10.27.2 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + '@epic-web/invariant@1.0.0': {} '@esbuild/aix-ppc64@0.27.2': @@ -1613,28 +1834,20 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - optional: true - - '@jridgewell/resolve-uri@3.1.2': - optional: true + '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/source-map@0.3.11': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - optional: true - - '@jridgewell/sourcemap-codec@1.5.5': {} + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 - '@jridgewell/trace-mapping@0.3.31': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - optional: true + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 '@rollup/plugin-inject@5.0.5(rollup@4.55.1)': dependencies: @@ -1652,78 +1865,144 @@ snapshots: optionalDependencies: rollup: 4.55.1 + '@rollup/rollup-android-arm-eabi@4.54.0': + optional: true + '@rollup/rollup-android-arm-eabi@4.55.1': optional: true + '@rollup/rollup-android-arm64@4.54.0': + optional: true + '@rollup/rollup-android-arm64@4.55.1': optional: true + '@rollup/rollup-darwin-arm64@4.54.0': + optional: true + '@rollup/rollup-darwin-arm64@4.55.1': optional: true + '@rollup/rollup-darwin-x64@4.54.0': + optional: true + '@rollup/rollup-darwin-x64@4.55.1': optional: true + '@rollup/rollup-freebsd-arm64@4.54.0': + optional: true + '@rollup/rollup-freebsd-arm64@4.55.1': optional: true + '@rollup/rollup-freebsd-x64@4.54.0': + optional: true + '@rollup/rollup-freebsd-x64@4.55.1': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.55.1': optional: true + '@rollup/rollup-linux-arm64-gnu@4.54.0': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.55.1': optional: true + '@rollup/rollup-linux-arm64-musl@4.54.0': + optional: true + '@rollup/rollup-linux-arm64-musl@4.55.1': optional: true + '@rollup/rollup-linux-loong64-gnu@4.54.0': + optional: true + '@rollup/rollup-linux-loong64-gnu@4.55.1': optional: true '@rollup/rollup-linux-loong64-musl@4.55.1': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + optional: true + '@rollup/rollup-linux-ppc64-gnu@4.55.1': optional: true '@rollup/rollup-linux-ppc64-musl@4.55.1': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.55.1': optional: true + '@rollup/rollup-linux-riscv64-musl@4.54.0': + optional: true + '@rollup/rollup-linux-riscv64-musl@4.55.1': optional: true + '@rollup/rollup-linux-s390x-gnu@4.54.0': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.55.1': optional: true + '@rollup/rollup-linux-x64-gnu@4.54.0': + optional: true + '@rollup/rollup-linux-x64-gnu@4.55.1': optional: true + '@rollup/rollup-linux-x64-musl@4.54.0': + optional: true + '@rollup/rollup-linux-x64-musl@4.55.1': optional: true '@rollup/rollup-openbsd-x64@4.55.1': optional: true + '@rollup/rollup-openharmony-arm64@4.54.0': + optional: true + '@rollup/rollup-openharmony-arm64@4.55.1': optional: true + '@rollup/rollup-win32-arm64-msvc@4.54.0': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.55.1': optional: true + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.55.1': optional: true + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + '@rollup/rollup-win32-x64-gnu@4.55.1': optional: true + '@rollup/rollup-win32-x64-msvc@4.54.0': + optional: true + '@rollup/rollup-win32-x64-msvc@4.55.1': optional: true @@ -1748,6 +2027,14 @@ snapshots: '@types/q@1.5.8': {} + '@types/react-dom@19.2.1(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@types/react@19.2.2': + dependencies: + csstype: 3.1.3 + '@types/sizzle@2.3.10': {} '@types/trusted-types@2.0.7': @@ -1907,9 +2194,6 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) - buffer-from@1.1.2: - optional: true - callsites@3.1.0: {} caniuse-api@3.0.0: @@ -1926,6 +2210,12 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -1936,9 +2226,6 @@ snapshots: commander@11.1.0: {} - commander@2.20.3: - optional: true - concat-map@0.0.1: {} copy-anything@2.0.6: @@ -2030,6 +2317,8 @@ snapshots: dependencies: css-tree: 2.2.1 + csstype@3.1.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2202,6 +2491,19 @@ snapshots: flatted@3.3.3: {} + focus-trap-react@11.0.4(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.1(@types/react@19.2.2) + focus-trap: 7.6.5 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + tabbable: 6.2.0 + + focus-trap@7.6.5: + dependencies: + tabbable: 6.2.0 + fraction.js@5.3.4: {} fsevents@2.3.3: @@ -2304,6 +2606,10 @@ snapshots: lodash.uniq@4.5.0: {} + lucide-react@0.540.0(react@19.2.0): + dependencies: + react: 19.2.0 + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2335,6 +2641,10 @@ snapshots: nanoid@3.3.11: {} + nanoid@5.1.6: {} + + nanostores@1.0.1: {} + natural-compare@1.4.0: {} needle@3.3.1: @@ -2429,7 +2739,7 @@ snapshots: dependencies: postcss: 8.5.6 postcss-value-parser: 4.2.0 - stylehacks: 7.0.7(postcss@8.5.6) + stylehacks: 7.0.6(postcss@8.5.6) postcss-merge-rules@7.0.7(postcss@8.5.6): dependencies: @@ -2563,6 +2873,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact@10.27.2: {} + prelude-ls@1.2.1: {} prr@1.0.1: @@ -2572,8 +2884,43 @@ snapshots: q@1.5.1: {} + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react@19.2.0: {} + resolve-from@4.0.0: {} + rollup@4.54.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 + fsevents: 2.3.3 + rollup@4.55.1: dependencies: '@types/estree': 1.0.8 @@ -2604,6 +2951,7 @@ snapshots: '@rollup/rollup-win32-x64-gnu': 4.55.1 '@rollup/rollup-win32-x64-msvc': 4.55.1 fsevents: 2.3.3 + optional: true safer-buffer@2.1.2: optional: true @@ -2612,6 +2960,8 @@ snapshots: sax@1.4.3: {} + scheduler@0.27.0: {} + semver@5.7.2: optional: true @@ -2627,18 +2977,12 @@ snapshots: source-map-js@1.2.1: {} - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - optional: true - source-map@0.6.1: optional: true strip-json-comments@3.1.1: {} - stylehacks@7.0.7(postcss@8.5.6): + stylehacks@7.0.6(postcss@8.5.6): dependencies: browserslist: 4.28.1 postcss: 8.5.6 @@ -2658,13 +3002,9 @@ snapshots: picocolors: 1.1.1 sax: 1.4.3 - terser@5.43.1: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true + tabbable@6.2.0: {} + + tailwind-merge@3.4.0: {} tinyglobby@0.2.15: dependencies: @@ -2715,20 +3055,19 @@ snapshots: util-deprecate@1.0.2: {} - vite@7.3.1(@types/node@24.2.0)(jiti@2.6.1)(less@4.5.1)(terser@5.43.1): + vite@7.3.1(@types/node@24.2.0)(jiti@2.6.1)(less@4.5.1): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.55.1 + rollup: 4.54.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.2.0 fsevents: 2.3.3 jiti: 2.6.1 less: 4.5.1 - terser: 5.43.1 which@2.0.2: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index efc037aa8..4d6c6555a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,5 @@ +ignoredBuiltDependencies: + - less + onlyBuiltDependencies: - esbuild diff --git a/src/main/resources/assets/admin/common/fonts/OpenSans-Italic.woff2 b/src/main/resources/assets/admin/common/fonts/OpenSans-Italic.woff2 new file mode 100644 index 000000000..ed458c095 Binary files /dev/null and b/src/main/resources/assets/admin/common/fonts/OpenSans-Italic.woff2 differ diff --git a/src/main/resources/assets/admin/common/fonts/OpenSans.woff2 b/src/main/resources/assets/admin/common/fonts/OpenSans.woff2 index 744506e24..bdcb172dd 100644 Binary files a/src/main/resources/assets/admin/common/fonts/OpenSans.woff2 and b/src/main/resources/assets/admin/common/fonts/OpenSans.woff2 differ diff --git a/src/main/resources/assets/admin/common/js/aggregation/BucketView.ts b/src/main/resources/assets/admin/common/js/aggregation/BucketView.ts index d65f9be0e..cafe88406 100644 --- a/src/main/resources/assets/admin/common/js/aggregation/BucketView.ts +++ b/src/main/resources/assets/admin/common/js/aggregation/BucketView.ts @@ -1,9 +1,8 @@ -import {Tooltip} from '../ui/Tooltip'; import {DivEl} from '../dom/DivEl'; -import {Bucket} from './Bucket'; -import {Checkbox} from '../ui/Checkbox'; -import {ValueChangedEvent} from '../ValueChangedEvent'; +import {Tooltip} from '../ui/Tooltip'; +import {Checkbox} from '../ui2/Checkbox'; import {StringHelper} from '../util/StringHelper'; +import {Bucket} from './Bucket'; import {BucketViewSelectionChangedEvent} from './BucketViewSelectionChangedEvent'; export class BucketView @@ -24,15 +23,18 @@ export class BucketView this.bucket = bucket; this.displayName = bucket.getDisplayName(); - this.checkbox = Checkbox.create().setLabelText(this.resolveLabelValue()).build(); + this.checkbox = new Checkbox({ + label: this.resolveLabelValue(), + onCheckedChange: (checked) => { + const isChecked = checked === true; + const event = new BucketViewSelectionChangedEvent(!isChecked, isChecked, this); + this.selectionChangedListeners.forEach(l => l(event)); + } + }); + this.tooltip = new Tooltip(this.checkbox, bucket.getKey(), 1000); this.tooltip.setActive(false); - this.checkbox.onValueChanged((event: ValueChangedEvent) => { - const oldValue = event.getOldValue() === 'true'; - const newValue = event.getNewValue() === 'true'; - this.notifySelectionChanged(oldValue, newValue); - }); this.appendChild(this.checkbox); this.updateUI(); @@ -64,19 +66,14 @@ export class BucketView return this.checkbox.isChecked(); } - deselect(supressEvent?: boolean): void { - this.checkbox.setChecked(false, supressEvent); + deselect(suppressEvent?: boolean): void { + this.checkbox.setChecked(false, suppressEvent); } select(suppressEvent?: boolean): void { this.checkbox.setChecked(true, suppressEvent); } - notifySelectionChanged(oldValue: boolean, newValue: boolean): void { - this.selectionChangedListeners.forEach((listener: (event: BucketViewSelectionChangedEvent) => void) => { - listener(new BucketViewSelectionChangedEvent(oldValue, newValue, this)); - }); - } unSelectionChanged(listener: (event: BucketViewSelectionChangedEvent) => void): void { this.selectionChangedListeners = this.selectionChangedListeners diff --git a/src/main/resources/assets/admin/common/js/app/browse/BrowsePanel.ts b/src/main/resources/assets/admin/common/js/app/browse/BrowsePanel.ts index a4f666857..e9f9988b7 100644 --- a/src/main/resources/assets/admin/common/js/app/browse/BrowsePanel.ts +++ b/src/main/resources/assets/admin/common/js/app/browse/BrowsePanel.ts @@ -1,26 +1,26 @@ import Q from 'q'; +import {DefaultErrorHandler} from '../../DefaultErrorHandler'; +import {IDentifiable} from '../../IDentifiable'; +import {Action} from '../../ui/Action'; +import {Panel} from '../../ui/panel/Panel'; +import {SelectableListBoxPanel} from '../../ui/panel/SelectableListBoxPanel'; +import {SplitPanel, SplitPanelAlignment, SplitPanelBuilder} from '../../ui/panel/SplitPanel'; +import {SplitPanelSize} from '../../ui/panel/SplitPanelSize'; +import {ResponsiveItem} from '../../ui/responsive/ResponsiveItem'; import {ResponsiveManager} from '../../ui/responsive/ResponsiveManager'; import {ResponsiveRanges} from '../../ui/responsive/ResponsiveRanges'; -import {ResponsiveItem} from '../../ui/responsive/ResponsiveItem'; -import {ActionButton} from '../../ui/button/ActionButton'; -import {TreeGridActions} from '../../ui/treegrid/actions/TreeGridActions'; -import {SplitPanel, SplitPanelAlignment, SplitPanelBuilder} from '../../ui/panel/SplitPanel'; -import {Panel} from '../../ui/panel/Panel'; +import {SelectableListBoxKeyNavigator} from '../../ui/selector/list/SelectableListBoxKeyNavigator'; import {Toolbar, ToolbarConfig} from '../../ui/toolbar/Toolbar'; -import {BrowseFilterPanel} from './filter/BrowseFilterPanel'; -import {Action} from '../../ui/Action'; -import {DefaultErrorHandler} from '../../DefaultErrorHandler'; -import {ToggleFilterPanelAction} from './action/ToggleFilterPanelAction'; -import {BrowseItemPanel} from './BrowseItemPanel'; -import {i18n} from '../../util/Messages'; -import {IDentifiable} from '../../IDentifiable'; +import {TreeGridActions} from '../../ui/treegrid/actions/TreeGridActions'; import {DataChangedEvent} from '../../ui/treegrid/DataChangedEvent'; -import {ViewItem} from '../view/ViewItem'; +import {ActionButton} from '../../ui2/ActionButton'; import {AppHelper} from '../../util/AppHelper'; -import {SplitPanelSize} from '../../ui/panel/SplitPanelSize'; -import {SelectableListBoxPanel} from '../../ui/panel/SelectableListBoxPanel'; +import {i18n} from '../../util/Messages'; import {SelectionChange} from '../../util/SelectionChange'; -import {SelectableListBoxKeyNavigator} from '../../ui/selector/list/SelectableListBoxKeyNavigator'; +import {ViewItem} from '../view/ViewItem'; +import {ToggleFilterPanelAction} from './action/ToggleFilterPanelAction'; +import {BrowseItemPanel} from './BrowseItemPanel'; +import {BrowseFilterPanel} from './filter/BrowseFilterPanel'; export class BrowsePanel extends Panel { @@ -33,9 +33,8 @@ export class BrowsePanel protected treeActions: TreeGridActions; protected filterPanelToBeShownFullScreen: boolean = false; protected gridAndItemsSplitPanel: SplitPanel; - private gridAndToolbarPanel: Panel; - private browseItemPanel: BrowseItemPanel; - private filterAndGridSplitPanel: SplitPanel; + protected browseItemPanel: BrowseItemPanel; + protected filterAndGridSplitPanel: SplitPanel; private filterPanelForcedShown: boolean = false; private filterPanelForcedHidden: boolean = false; private filterPanelIsHiddenByDefault: boolean = true; @@ -63,17 +62,19 @@ export class BrowsePanel this.browseItemPanel = this.createBrowseItemPanel(); } - this.gridAndItemsSplitPanel = new SplitPanelBuilder(this.selectableListBoxPanel, this.createBrowseWithItemsPanel()) - .setAlignment(SplitPanelAlignment.VERTICAL) - .setFirstPanelSize(SplitPanelSize.PERCENTS(this.getFirstPanelSize())) - .build(); - if (this.filterPanel) { - this.gridAndToolbarPanel = new Panel(); this.filterAndGridSplitPanel = this.setupFilterPanel(); } + this.gridAndItemsSplitPanel = + new SplitPanelBuilder(this.filterAndGridSplitPanel ?? this.selectableListBoxPanel, this.createBrowseWithItemsPanel()) + .setAlignment(SplitPanelAlignment.VERTICAL) + .setFirstPanelSize(SplitPanelSize.PERCENTS(this.getFirstPanelSize())) + .setSplitterThickness(this.getSplitterThickness()) + .build(); + this.selectableListBoxPanel.getWrapper().setSkipFirstClickOnFocus(true); + this.hideFilterPanel(); } protected initListeners() { @@ -100,6 +101,10 @@ export class BrowsePanel } } + protected getSplitterThickness(): number { + return 5; + } + protected getFirstPanelSize(): number { return 38; } @@ -189,36 +194,13 @@ export class BrowsePanel this.browseToolbar.addClass('browse-toolbar'); this.gridAndItemsSplitPanel.addClass('content-grid-and-browse-split-panel'); - if (this.filterPanel) { - this.gridAndToolbarPanel.onAdded(() => { - this.gridAndItemsSplitPanel.setDoOffset(true); - }); - - if (this.filterPanelIsHiddenByDefault) { - this.hideFilterPanel(); - } - this.appendChild(this.filterAndGridSplitPanel); - - // Hack: Places the append calls farther in the engine call stack. - // Prevent toolbar and gridPanel not being visible when the width/height - // is requested and elements resize/change position/etc. + this.appendChild(this.browseToolbar); + this.browseToolbar.onRendered(() => { setTimeout(() => { - this.gridAndToolbarPanel.appendChild(this.browseToolbar); - }); - this.browseToolbar.onRendered(() => { - setTimeout(() => { - this.gridAndToolbarPanel.appendChild(this.gridAndItemsSplitPanel); - }); - }); - } else { - this.appendChild(this.browseToolbar); - // Hack: Same hack. - this.browseToolbar.onRendered(() => { - setTimeout(() => { - this.appendChild(this.gridAndItemsSplitPanel); - }); + this.appendChild(this.gridAndItemsSplitPanel); }); - } + }); + return rendered; }); } @@ -310,6 +292,10 @@ export class BrowsePanel } protected hideFilterPanel() { + if (!this.filterAndGridSplitPanel) { + return; + } + this.filterPanelForcedShown = false; this.filterPanelForcedHidden = true; this.filterAndGridSplitPanel.showSecondPanel(); @@ -329,16 +315,17 @@ export class BrowsePanel } } - private filterPanelIsHidden(): boolean { + protected filterPanelIsHidden(): boolean { return this.filterAndGridSplitPanel.isFirstPanelHidden(); } private setupFilterPanel() { - let splitPanel = new SplitPanelBuilder(this.filterPanel, this.gridAndToolbarPanel) - .setFirstPanelMinSize(SplitPanelSize.PIXELS(215)) - .setFirstPanelSize(SplitPanelSize.PIXELS(215)) + const splitPanel = new SplitPanelBuilder(this.filterPanel, this.selectableListBoxPanel) + .setFirstPanelMinSize(SplitPanelSize.PIXELS(300)) + .setFirstPanelSize(SplitPanelSize.PIXELS(300)) .setAlignment(SplitPanelAlignment.VERTICAL) .setAnimationDelay(100) // filter panel animation time + .setSplitterThickness(this.getSplitterThickness()) .build(); this.filterPanel.onHideFilterPanelButtonClicked(() => { diff --git a/src/main/resources/assets/admin/common/js/app/browse/filter/BrowseFilterPanel.ts b/src/main/resources/assets/admin/common/js/app/browse/filter/BrowseFilterPanel.ts index 5e44c5129..bc937596a 100644 --- a/src/main/resources/assets/admin/common/js/app/browse/filter/BrowseFilterPanel.ts +++ b/src/main/resources/assets/admin/common/js/app/browse/filter/BrowseFilterPanel.ts @@ -1,23 +1,19 @@ import Q from 'q'; -import {i18n} from '../../../util/Messages'; -import {Panel} from '../../../ui/panel/Panel'; +import {Aggregation} from '../../../aggregation/Aggregation'; import {AggregationContainer} from '../../../aggregation/AggregationContainer'; -import {TextSearchField} from './TextSearchField'; -import {ClearFilterButton} from './ClearFilterButton'; -import {SpanEl} from '../../../dom/SpanEl'; -import {DivEl} from '../../../dom/DivEl'; -import {Element} from '../../../dom/Element'; import {AggregationGroupView} from '../../../aggregation/AggregationGroupView'; -import {KeyBindings} from '../../../ui/KeyBindings'; -import {KeyBinding} from '../../../ui/KeyBinding'; +import {DefaultErrorHandler} from '../../../DefaultErrorHandler'; +import {DivEl} from '../../../dom/DivEl'; +import {LabelEl} from '../../../dom/LabelEl'; +import {SpanEl} from '../../../dom/SpanEl'; import {ObjectHelper} from '../../../ObjectHelper'; -import {Aggregation} from '../../../aggregation/Aggregation'; import {SearchInputValues} from '../../../query/SearchInputValues'; -import {LabelEl} from '../../../dom/LabelEl'; -import {ActionButton} from '../../../ui/button/ActionButton'; -import {Action} from '../../../ui/Action'; -import {DefaultErrorHandler} from '../../../DefaultErrorHandler'; -import {AriaRole} from '../../../ui/WCAG'; +import {KeyBinding} from '../../../ui/KeyBinding'; +import {KeyBindings} from '../../../ui/KeyBindings'; +import {Panel} from '../../../ui/panel/Panel'; +import {SearchInputComponent} from '../../../ui2/SearchInput'; +import {i18n} from '../../../util/Messages'; +import {StringHelper} from '../../../util/StringHelper'; export class BrowseFilterPanel extends Panel { @@ -28,10 +24,8 @@ export class BrowseFilterPanel private hideFilterPanelButtonClickedListeners: (() => void)[] = []; private showResultsButtonClickedListeners: (() => void)[] = []; private aggregationContainer: AggregationContainer; - private searchField: TextSearchField; - private clearFilter: ClearFilterButton; + private newSearchField: SearchInputComponent; private hitsCounterEl: SpanEl; - private hideFilterPanelButton: SpanEl; private showResultsButton: SpanEl; private searchContainer: DivEl; private refreshStartedListeners: (() => void)[] = []; @@ -40,30 +34,19 @@ export class BrowseFilterPanel super(); this.addClass('filter-panel'); - this.hideFilterPanelButton = new SpanEl('hide-filter-panel-button icon-search'); - this.hideFilterPanelButton.applyWCAGAttributes({ - ariaLabel: i18n('tooltip.filterPanel.hide'), - role: AriaRole.BUTTON, - tabbable: true - }); - this.hideFilterPanelButton.setTitle(i18n('tooltip.filterPanel.hide')); - this.hideFilterPanelButton.onClicked(() => this.notifyHidePanelButtonPressed()); - this.hideFilterPanelButton.onApplyKeyPressed(() => this.notifyHidePanelButtonPressed()); - let showResultsButtonWrapper = new DivEl('show-filter-results'); this.showResultsButton = new SpanEl('show-filter-results-button'); this.updateResultsTitle(true); this.showResultsButton.onClicked(() => this.notifyShowResultsButtonPressed()); showResultsButtonWrapper.appendChild(this.showResultsButton); - this.searchField = new TextSearchField(i18n('panel.filter.search')); - this.searchField.onValueChanged(() => { - this.search(); + this.newSearchField = new SearchInputComponent({ + onChange: (value) => { + this.search(); + }, + placeholder: i18n('panel.filter.search'), }); - this.clearFilter = new ClearFilterButton(); - this.clearFilter.onClicked(() => void this.reset()); - this.aggregationContainer = new AggregationContainer(); this.aggregationContainer.hide(); this.appendChild(this.aggregationContainer); @@ -78,7 +61,6 @@ export class BrowseFilterPanel ); this.onRendered(() => { - this.appendChild(this.hideFilterPanelButton); this.appendExtraSections(); this.appendChildren( this.createSearchContainer(), @@ -105,16 +87,8 @@ export class BrowseFilterPanel } protected createSearchContainer(): DivEl { - this.searchField = new TextSearchField(i18n('panel.filter.search')); - this.searchField.onValueChanged(() => { - this.search(); - }); - - this.clearFilter = new ClearFilterButton(); - this.clearFilter.onClicked(() => void this.reset()); - this.searchContainer = new DivEl('search-container'); - this.searchContainer.appendChildren(this.searchField, this.clearFilter as Element); + this.searchContainer.appendChild(this.newSearchField); return this.searchContainer; } @@ -154,7 +128,7 @@ export class BrowseFilterPanel } giveFocusToSearch() { - this.searchField.giveFocus(); + this.newSearchField.giveFocus(); } updateAggregations(aggregations: Aggregation[]): void { @@ -165,7 +139,7 @@ export class BrowseFilterPanel const searchInputValues: SearchInputValues = new SearchInputValues(); searchInputValues.setAggregationSelections(this.aggregationContainer.getSelectedValuesByAggregationName()); - searchInputValues.setTextSearchFieldValue(this.searchField.getEl().getValue()); + searchInputValues.setTextSearchFieldValue(this.newSearchField.getValue()); return searchInputValues; } @@ -175,13 +149,12 @@ export class BrowseFilterPanel } hasSearchStringSet(): boolean { - return this.searchField.getHTMLElement()['value'].trim() !== ''; + return !StringHelper.isBlank(this.newSearchField.getValue()); } search(lastChangedAggregation?: string): Q.Promise { const hasFilterSet = this.hasFilterSet(); - this.clearFilter.setVisible(hasFilterSet); this.searchContainer.toggleClass('has-filter-set', hasFilterSet); this.updateResultsTitle(!hasFilterSet); @@ -213,9 +186,8 @@ export class BrowseFilterPanel } resetControls() { - this.searchField.clear(); + this.newSearchField.clear(); this.aggregationContainer.deselectAll(true); - this.clearFilter.hide(); this.searchContainer.removeClass('has-filter-set'); this.updateResultsTitle(true); } @@ -300,11 +272,7 @@ export class BrowseFilterPanel protected createConstraintSection(): ConstraintSection { return new ConstraintSection(i18n( - 'panel.filter.selecteditems'), () => this.onCloseFilterInConstrainedMode()); - } - - protected onCloseFilterInConstrainedMode() { - this.notifyHidePanelButtonPressed(); + 'panel.filter.selecteditems')); } protected isFilteredOrConstrained(): boolean { @@ -350,17 +318,13 @@ export class ConstraintSection protected itemsIds: string[]; private readonly label: LabelEl; - constructor(label: string, closeCallback?: () => void) { + constructor(label: string) { super('constraint-section'); this.checkVisibilityState(); this.label = new LabelEl(label); this.appendChildren(this.label); - - if (!!closeCallback) { - this.appendCloseButton(closeCallback); - } } public reset() { @@ -385,16 +349,6 @@ export class ConstraintSection this.label.setValue(text); } - private appendCloseButton(closeCallback: () => void): ActionButton { - let action = new Action('').onExecuted(() => closeCallback()); - let button = new ActionButton(action); - - button.addClass('btn-close'); - this.appendChild(button); - - return button; - } - private checkVisibilityState() { this.setVisible(this.isActive()); } diff --git a/src/main/resources/assets/admin/common/js/notify/NotificationMessage.ts b/src/main/resources/assets/admin/common/js/notify/NotificationMessage.ts deleted file mode 100644 index 080738e91..000000000 --- a/src/main/resources/assets/admin/common/js/notify/NotificationMessage.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {DivEl} from '../dom/DivEl'; -import {SpanEl} from '../dom/SpanEl'; -import {UlEl} from '../dom/UlEl'; -import {Message, MessageAction, MessageType} from './Message'; -import {AEl} from '../dom/AEl'; - -export class NotificationMessage - extends DivEl { - - private readonly notificationText: DivEl; - - private readonly actionList: UlEl; - - private readonly message: Message; - - private readonly removeEl: SpanEl - - constructor(message: Message) { - super('notification'); - - if (message.getType()) { - this.addClass(MessageType[message.getType()].toLowerCase()); - } - - this.message = message; - const notificationInner = new DivEl('notification-inner'); - this.removeEl = new SpanEl('notification-remove icon-close'); - - this.notificationText = new DivEl('notification-text'); - this.actionList = new DivEl('notification-actions'); - this.notificationText.setHtml(message.getText()); - notificationInner.appendChildren(this.notificationText, this.actionList); - this.appendChildren(notificationInner, this.removeEl); - } - - getMessage(): Message { - return this.message; - } - - isAutoHide(): boolean { - return this.message.getAutoHide(); - } - - setText(text: string): NotificationMessage { - this.notificationText.getEl().setInnerHtml(text); - return this; - } - - addAction(action: MessageAction): NotificationMessage { - this.actionList.appendChild(NotificationMessage.createAction(action)); - return this; - } - - getRemoveEl(): SpanEl { - return this.removeEl; - } - - private static createAction(action: MessageAction): AEl { - const aEl = new AEl('action'); - aEl.setHtml(action.getName()); - aEl.onClicked(action.getHandler()); - return aEl; - } -} diff --git a/src/main/resources/assets/admin/common/js/notify/NotificationMessage.tsx b/src/main/resources/assets/admin/common/js/notify/NotificationMessage.tsx new file mode 100644 index 000000000..af9a84c53 --- /dev/null +++ b/src/main/resources/assets/admin/common/js/notify/NotificationMessage.tsx @@ -0,0 +1,75 @@ +import type {MouseEvent as ReactMouseEvent} from 'react'; +import {SpanEl} from '../dom/SpanEl'; +import {LegacyElement} from '../ui2/LegacyElement'; +import {Notification, NotificationAction, NotificationProps, NotificationTone} from '../ui2/Notification'; +import {Message, MessageAction, MessageType} from './Message'; + +export class NotificationMessage + extends LegacyElement { + + private readonly message: Message; + + constructor(message: Message, onOpenChange?: (open: boolean) => void) { + super({ + tone: getToastTone(message.getType()), + text: message.getText(), + open: true, + withClose: true, + onOpenChange, + actions: message.getActions().map(buildToastAction), + }, Notification); + + this.message = message; + } + + setOpen(open: boolean): void { + this.props.setKey('open', open); + } + + close(): void { + this.setOpen(false); + } + + getMessage(): Message { + return this.message; + } + + isAutoHide(): boolean { + return this.message.getAutoHide(); + } + + setText(text: string): void { + this.props.setKey('text', text); + } + + addAction(action: MessageAction): void { + const {actions} = this.props.get(); + this.props.setKey('actions', [...actions, buildToastAction(action)]); + } + + getRemoveEl(): SpanEl { + return null; + } +} + +function getToastTone(type: MessageType): NotificationTone { + switch (type) { + case MessageType.SUCCESS: + case MessageType.ACTION: + return 'success'; + case MessageType.ERROR: + return 'error'; + case MessageType.WARNING: + return 'warning'; + case MessageType.INFO: + default: + return 'info'; + } +} + +function buildToastAction(action: MessageAction): NotificationAction { + return { + label: action.getName(), + onClick: (event: ReactMouseEvent) => action.getHandler()?.(event), + }; +} diff --git a/src/main/resources/assets/admin/common/js/notify/NotifyManager.ts b/src/main/resources/assets/admin/common/js/notify/NotifyManager.ts index 40565db01..6b3f1cd83 100644 --- a/src/main/resources/assets/admin/common/js/notify/NotifyManager.ts +++ b/src/main/resources/assets/admin/common/js/notify/NotifyManager.ts @@ -1,7 +1,6 @@ -import $ from 'jquery'; import {Body} from '../dom/Body'; import {Store} from '../store/Store'; -import {Message, MessageAction} from './Message'; +import {Message} from './Message'; import {NotificationContainer} from './NotificationContainer'; import {NotificationMessage} from './NotificationMessage'; @@ -19,20 +18,19 @@ export class NotifyManager { private queue: NotificationMessage[] = []; - private slideDuration: number = 500; - private timers: Map = new Map(); private el: NotificationContainer; - private registry: Map = new Map(); + private registry: Map = new Map(); constructor() { this.el = new NotificationContainer(); Body.get().appendChild(this.el); - - this.el.getEl().setBottomPx(0); } static get(): NotifyManager { @@ -47,22 +45,22 @@ export class NotifyManager { } showFeedback(message: string, autoHide: boolean = true): string { - let feedback = Message.newInfo(message, autoHide); + const feedback = Message.newInfo(message, autoHide); return this.notify(feedback); } showSuccess(message: string, autoHide: boolean = true): string { - let feedback = Message.newSuccess(message, autoHide); + const feedback = Message.newSuccess(message, autoHide); return this.notify(feedback); } showError(message: string, autoHide: boolean = true): string { - let error = Message.newError(message, autoHide); + const error = Message.newError(message, autoHide); return this.notify(error); } showWarning(message: string, autoHide: boolean = true): string { - let warning = Message.newWarning(message, autoHide); + const warning = Message.newWarning(message, autoHide); return this.notify(warning); } @@ -95,7 +93,7 @@ export class NotifyManager { hide(messageId: string) { if (messageId && this.registry.has(messageId)) { - this.remove(this.registry.get(messageId).el); + this.handleOpenChange(this.registry.get(messageId).el, false); } } @@ -111,98 +109,111 @@ export class NotifyManager { } private createNotification(message: Message): NotificationMessage { - const notificationEl = new NotificationMessage(message); + const notification = new NotificationMessage(message, open => this.handleOpenChange(notification, open)); - this.registry.set(notificationEl.getEl().getId(), { + this.registry.set(notification.getEl().getId(), { opts: message, - el: notificationEl + el: notification }); - this.setListeners(notificationEl); - this.setActions(notificationEl, message.getActions()); + this.setListeners(notification); - return notificationEl; + return notification; } private renderNotification(notification: NotificationMessage): NotificationMessage { this.el.getWrapper().appendChild(notification); - notification.hide(); - - $(notification.getHTMLElement()).animate({ - height: 'toggle' - }, - this.slideDuration, - () => { - if (notification.isAutoHide()) { - this.timers.set(notification.getEl().getId(), { - remainingTime: notification.getMessage().getLifeTime() - }); - - this.startTimer(notification); - } + + const message = this.registry.get(notification.getEl().getId())?.opts; + + if (message?.getAutoHide()) { + const lifeTime = message.getLifeTime() ?? Message.shortLifeTime; + this.timers.set(notification.getEl().getId(), { + remainingTime: lifeTime }); - return notification; - } + this.startTimer(notification); + } - private setListeners(notificationMessage: NotificationMessage) { - notificationMessage.getRemoveEl().onClicked(() => this.remove(notificationMessage)); - notificationMessage.onMouseEnter(() => this.stopTimer(notificationMessage)); - notificationMessage.onMouseLeave(() => this.startTimer(notificationMessage)); - notificationMessage.onRemoved(() => this.handleNotificationRemoved()); + return notification; } - private setActions(notificationEl: NotificationMessage, actions: MessageAction[]) { - actions.forEach(action => notificationEl.addAction(action)); + private setListeners(notification: NotificationMessage) { + notification.onMouseEnter(() => this.stopTimer(notification)); + notification.onMouseLeave(() => this.startTimer(notification)); } private handleNotificationRemoved() { if (this.queue.length > 0) { const notification = this.queue.shift(); - this.renderNotification(notification); + if (notification) { + this.renderNotification(notification); + } } } - private remove(el: NotificationMessage) { - if (!el) { + private handleOpenChange(notification: NotificationMessage, open: boolean) { + notification.setOpen(open); + + if (open) { return; } - $(el.getHTMLElement()).animate({ - height: 'hide' - }, this.slideDuration, 'linear', - () => { - if (this.el.getWrapper().hasChild(el)) { - this.el.getWrapper().removeChild(el); - } else { - this.queue = this.queue.filter(q => q !== el); - } - }); + const id = notification.getEl().getId(); + + if (!this.registry.has(id)) { + return; + } + + this.clearTimer(id); - this.registry.delete(el.getEl().getId()); - this.timers.delete(el.getEl().getId()); + if (this.el.getWrapper().hasChild(notification)) { + this.el.getWrapper().removeChild(notification); + } else { + this.queue = this.queue.filter(q => q !== notification); + } + + this.registry.delete(id); + this.handleNotificationRemoved(); } private startTimer(el: NotificationMessage) { const timer: Timer = this.timers.get(el.getEl().getId()); - if (!timer) { + if (!timer || timer.id) { + return; + } + + if (timer.remainingTime <= 0) { + this.handleOpenChange(el, false); return; } - timer.id = setTimeout(() => this.remove(el), timer.remainingTime); + timer.id = window.setTimeout(() => this.handleOpenChange(el, false), timer.remainingTime); timer.startTime = Date.now(); } private stopTimer(el: NotificationMessage) { const timer: Timer = this.timers.get(el.getEl().getId()); - if (!timer || !timer.id) { + if (!timer?.id || timer.startTime == null) { return; } clearTimeout(timer.id); - timer.id = null; - timer.remainingTime -= Date.now() - timer.startTime; + timer.remainingTime = Math.max(0, timer.remainingTime - (Date.now() - timer.startTime)); + timer.id = undefined; + timer.startTime = undefined; } + + private clearTimer(id: string) { + const timer = this.timers.get(id); + + if (timer?.id) { + clearTimeout(timer.id); + } + + this.timers.delete(id); + } + } diff --git a/src/main/resources/assets/admin/common/js/ui/button/MenuButton.ts b/src/main/resources/assets/admin/common/js/ui/button/MenuButton.ts index d2a9aa089..7c8864a15 100644 --- a/src/main/resources/assets/admin/common/js/ui/button/MenuButton.ts +++ b/src/main/resources/assets/admin/common/js/ui/button/MenuButton.ts @@ -2,11 +2,11 @@ import Q from 'q'; import {Body} from '../../dom/Body'; import {DivEl} from '../../dom/DivEl'; import {Element} from '../../dom/Element'; +import {ActionButton} from '../../ui2/ActionButton'; import {Action} from '../Action'; import {Menu} from '../menu/Menu'; import {MenuItem} from '../menu/MenuItem'; import {AriaRole, WCAG} from '../WCAG'; -import {ActionButton} from './ActionButton'; import {DropdownHandle} from './DropdownHandle'; export enum MenuButtonDropdownPos { @@ -16,7 +16,7 @@ export enum MenuButtonDropdownPos { export interface MenuButtonConfig { defaultAction: Action; menuActions?: Action[]; - dropdownPosition?: MenuButtonDropdownPos + dropdownPosition?: MenuButtonDropdownPos; } export class MenuButton @@ -209,7 +209,7 @@ export class MenuButton } private initActionButton(): void { - this.actionButton = new ActionButton(this.defaultAction); + this.actionButton = new ActionButton({action: this.defaultAction}); } protected setButtonAction(action: Action): void { diff --git a/src/main/resources/assets/admin/common/js/ui/dialog/DialogButton.ts b/src/main/resources/assets/admin/common/js/ui/dialog/DialogButton.ts deleted file mode 100644 index 47e4ebeab..000000000 --- a/src/main/resources/assets/admin/common/js/ui/dialog/DialogButton.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {ActionButton} from '../button/ActionButton'; -import {Action} from '../Action'; - -export class DialogButton - extends ActionButton { - - constructor(action: Action) { - super(action); - this.addClass('dialog-button'); - } -} diff --git a/src/main/resources/assets/admin/common/js/ui/dialog/ModalDialog.ts b/src/main/resources/assets/admin/common/js/ui/dialog/ModalDialog.ts index 8c319fd3b..a3b878c84 100644 --- a/src/main/resources/assets/admin/common/js/ui/dialog/ModalDialog.ts +++ b/src/main/resources/assets/admin/common/js/ui/dialog/ModalDialog.ts @@ -1,20 +1,20 @@ import Q from 'q'; +import {Body} from '../../dom/Body'; import {DivEl} from '../../dom/DivEl'; -import {Action} from '../Action'; import {Element} from '../../dom/Element'; -import {ResponsiveManager} from '../responsive/ResponsiveManager'; -import {Body} from '../../dom/Body'; -import {ResponsiveItem} from '../responsive/ResponsiveItem'; -import {i18n} from '../../util/Messages'; -import {LoadMask} from '../mask/LoadMask'; +import {H2El} from '../../dom/H2El'; +import {Store} from '../../store/Store'; import {StyleHelper} from '../../StyleHelper'; +import {ActionButton} from '../../ui2/ActionButton'; import {AppHelper} from '../../util/AppHelper'; -import {BodyMask} from '../mask/BodyMask'; -import {KeyBindings} from '../KeyBindings'; -import {H2El} from '../../dom/H2El'; -import {DialogButton} from './DialogButton'; +import {i18n} from '../../util/Messages'; +import {Action} from '../Action'; import {KeyBinding} from '../KeyBinding'; -import {Store} from '../../store/Store'; +import {KeyBindings} from '../KeyBindings'; +import {BodyMask} from '../mask/BodyMask'; +import {LoadMask} from '../mask/LoadMask'; +import {ResponsiveItem} from '../responsive/ResponsiveItem'; +import {ResponsiveManager} from '../responsive/ResponsiveManager'; export interface ModalDialogConfig { title?: string; @@ -64,7 +64,7 @@ export abstract class ModalDialog private elementToFocusOnShow: Element; - private cancelButton: DialogButton; + private cancelButton: ActionButton; private tabbable: Element[]; @@ -453,11 +453,11 @@ export abstract class ModalDialog return this.cancelAction; } - getCancelButton(): DialogButton { + getCancelButton(): ActionButton { return this.cancelButton; } - addCancelButtonToBottom(buttonLabel?: string, useDefault?: boolean): DialogButton { + addCancelButtonToBottom(buttonLabel?: string, useDefault?: boolean): ActionButton { const cancelAction = new Action(buttonLabel || i18n('action.cancel')); cancelAction.setIconClass('cancel-button-bottom force-enabled'); cancelAction.onExecuted(() => this.cancelAction.execute()); @@ -499,11 +499,11 @@ export abstract class ModalDialog this.contentPanel.removeChild(child); } - addAction(action: Action, useDefault?: boolean, prepend?: boolean): DialogButton { + addAction(action: Action, useDefault?: boolean, prepend?: boolean): ActionButton { return this.buttonRow.addAction(action, useDefault, prepend); } - removeAction(actionButton: DialogButton) { + removeAction(actionButton: ActionButton) { if (!actionButton) { return; } @@ -752,24 +752,23 @@ export class ButtonRow this.actions.push(action); } - addAction(action: Action, useDefault?: boolean, prepend?: boolean): DialogButton { - const button = new DialogButton(action); + addActionButton(button: ActionButton, useDefault?: boolean, prepend?: boolean): ActionButton { if (useDefault) { this.setDefaultElement(button); } this.addElement(button, prepend); - action.onPropertyChanged(() => { - button.setLabel(action.getLabel()); - button.setEnabled(action.isEnabled()); - }); - - this.actions.push(action); + this.actions.push(button.getAction()); return button; } + addAction(action: Action, useDefault?: boolean, prepend?: boolean): ActionButton { + const button = new ActionButton({action}); + return this.addActionButton(button, useDefault, prepend); + } + removeAction(action: Action) { const index = this.actions.indexOf(action); if (index >= 0) { @@ -777,8 +776,8 @@ export class ButtonRow } this.buttonContainer.getChildren() - .filter((button: DialogButton) => button.getAction() === action) - .forEach((button: DialogButton) => { + .filter((button: ActionButton) => button.getAction() === action) + .forEach((button: ActionButton) => { if (this.defaultElement === button) { this.resetDefaultElement(); } diff --git a/src/main/resources/assets/admin/common/js/ui/mask/ConfirmationMask.ts b/src/main/resources/assets/admin/common/js/ui/mask/ConfirmationMask.ts index fc97d4f33..5a28bf5b7 100644 --- a/src/main/resources/assets/admin/common/js/ui/mask/ConfirmationMask.ts +++ b/src/main/resources/assets/admin/common/js/ui/mask/ConfirmationMask.ts @@ -1,11 +1,11 @@ +import {Body} from '../../dom/Body'; +import {DivEl} from '../../dom/DivEl'; import {Element} from '../../dom/Element'; +import {PEl} from '../../dom/PEl'; +import {ActionButton} from '../../ui2/ActionButton'; +import {assertState} from '../../util/Assert'; import {Action} from '../Action'; import {SplashMask} from './SplashMask'; -import {assertState} from '../../util/Assert'; -import {PEl} from '../../dom/PEl'; -import {DivEl} from '../../dom/DivEl'; -import {ActionButton} from '../button/ActionButton'; -import {Body} from '../../dom/Body'; export class ConfirmationMask extends SplashMask { @@ -28,7 +28,7 @@ export class ConfirmationMask } this.actionsEl = new DivEl('mask-actions'); - builder.getActions().forEach(action => this.actionsEl.appendChild(new ActionButton(action))); + builder.getActions().forEach(action => this.actionsEl.appendChild(new ActionButton({action}))); elements.push(this.actionsEl); this.setContents(...elements); diff --git a/src/main/resources/assets/admin/common/js/ui/panel/SplitPanel.ts b/src/main/resources/assets/admin/common/js/ui/panel/SplitPanel.ts index 8250917ee..408dc890a 100644 --- a/src/main/resources/assets/admin/common/js/ui/panel/SplitPanel.ts +++ b/src/main/resources/assets/admin/common/js/ui/panel/SplitPanel.ts @@ -226,7 +226,7 @@ export class SplitPanel this.alignment = builder.getAlignment(); this.alignmentTreshold = builder.getAlignmentTreshold(); this.splitterThickness = builder.getSplitterThickness(); - this.splitter = new DivEl('splitter'); + this.splitter = new DivEl('splitter splitter-bg-standard'); this.firstPanel.setDoOffset(false); this.secondPanel.setDoOffset(false); @@ -543,6 +543,14 @@ export class SplitPanel this.splitterIsHidden = value; } + addSplitterClass(className: string): void { + this.splitter.addClass(className); + } + + removeSplitterClass(className: string): void { + this.splitter.removeClass(className); + } + toString(): string { return ClassHelper.getClassName(this) + '[' + this.getId() + ']'; } diff --git a/src/main/resources/assets/admin/common/js/ui/selector/list/LazyListBox.ts b/src/main/resources/assets/admin/common/js/ui/selector/list/LazyListBox.ts index 6f44ed706..2c6732fdc 100644 --- a/src/main/resources/assets/admin/common/js/ui/selector/list/LazyListBox.ts +++ b/src/main/resources/assets/admin/common/js/ui/selector/list/LazyListBox.ts @@ -1,5 +1,5 @@ -import {ListBox} from './ListBox'; import {Element} from '../../../dom/Element'; +import {ListBox} from './ListBox'; export abstract class LazyListBox extends ListBox { @@ -38,7 +38,7 @@ export abstract class LazyListBox extends ListBox { } protected handleLastItemIsVisible(): void { - this.observer.unobserve(this.observedItem.getHTMLElement()); + this.observer.disconnect(); this.handleLazyLoad(); } @@ -89,10 +89,20 @@ export abstract class LazyListBox extends ListBox { } if (this.observedItem) { - this.observer.unobserve(this.observedItem.getHTMLElement()); + this.observer.disconnect(); } this.observedItem = itemView; - this.observer.observe(itemView.getHTMLElement()); + this.observer.observe(LazyListBox.getObservedHTMLElement(itemView)); + } + + protected static getObservedHTMLElement(el: Element): HTMLElement { + const element = el.getHTMLElement(); + const style = getComputedStyle(element); + if (style.display === 'contents') { + const children = Array.from(element.children); + return children.find(child => child instanceof HTMLElement) ?? element; + } + return element; } } diff --git a/src/main/resources/assets/admin/common/js/ui/tab/TabItem.ts b/src/main/resources/assets/admin/common/js/ui/tab/TabItem.ts index 1b33e0d4c..d38f85c48 100644 --- a/src/main/resources/assets/admin/common/js/ui/tab/TabItem.ts +++ b/src/main/resources/assets/admin/common/js/ui/tab/TabItem.ts @@ -1,13 +1,13 @@ -import {SpanEl} from '../../dom/SpanEl'; -import {LiEl} from '../../dom/LiEl'; -import {NavigationItem} from '../NavigationItem'; +import {XIcon} from 'lucide-react'; import {AEl} from '../../dom/AEl'; -import {Action} from '../Action'; -import {ActionButton} from '../button/ActionButton'; +import {LiEl} from '../../dom/LiEl'; +import {SpanEl} from '../../dom/SpanEl'; +import {ActionIcon} from '../../ui2/ActionIcon'; import {StringHelper} from '../../util/StringHelper'; -import {Tooltip} from '../Tooltip'; -import {TabItemLabelChangedEvent} from './TabItemLabelChangedEvent'; +import {Action} from '../Action'; +import {NavigationItem} from '../NavigationItem'; import {TabItemClosedEvent} from './TabItemClosedEvent'; +import {TabItemLabelChangedEvent} from './TabItemLabelChangedEvent'; import {TabItemSelectedEvent} from './TabItemSelectedEvent'; export class TabItem @@ -20,7 +20,7 @@ export class TabItem private labelEl: AEl; private active: boolean = false; private closeAction: Action; - private removeButton: ActionButton; + private removeButton: ActionIcon; private labelChangedListeners: ((event: TabItemLabelChangedEvent) => void)[] = []; private closedListeners: ((event: TabItemClosedEvent) => void)[] = []; private selectedListeners: ((event: TabItemSelectedEvent) => void)[] = []; @@ -173,13 +173,7 @@ export class TabItem private createRemoveButton() { if (this.closeAction && !this.removeButton) { - - this.removeButton = new ActionButton(this.closeAction); - this.removeButton.getTooltip().setSide(Tooltip.SIDE_LEFT); - this.removeButton.onClicked((event: MouseEvent) => { - event.stopPropagation(); - event.preventDefault(); - }); + this.removeButton = new ActionIcon({action: this.closeAction, icon: XIcon}); this.prependChild(this.removeButton); } } diff --git a/src/main/resources/assets/admin/common/js/ui/toolbar/Toolbar.ts b/src/main/resources/assets/admin/common/js/ui/toolbar/Toolbar.ts index 87e373fd9..af916a08a 100644 --- a/src/main/resources/assets/admin/common/js/ui/toolbar/Toolbar.ts +++ b/src/main/resources/assets/admin/common/js/ui/toolbar/Toolbar.ts @@ -2,10 +2,10 @@ import {Body} from '../../dom/Body'; import {DivEl} from '../../dom/DivEl'; import {Element} from '../../dom/Element'; import {ObjectHelper} from '../../ObjectHelper'; +import {ActionButton} from '../../ui2/ActionButton'; import {i18n} from '../../util/Messages'; import {Action} from '../Action'; import {ActionContainer} from '../ActionContainer'; -import {ActionButton} from '../button/ActionButton'; import {KeyHelper} from '../KeyHelper'; import {ResponsiveManager} from '../responsive/ResponsiveManager'; import {AriaRole, WCAG} from '../WCAG'; @@ -168,7 +168,7 @@ export class Toolbar if (action.isFoldable()) { action.onPropertyChanged(() => this.foldOrExpand()); } - return new ActionButton(action); + return new ActionButton({action}); } private initElementListeners(element: Element) { diff --git a/src/main/resources/assets/admin/common/js/ui2/ActionButton.tsx b/src/main/resources/assets/admin/common/js/ui2/ActionButton.tsx new file mode 100644 index 000000000..d18399eb0 --- /dev/null +++ b/src/main/resources/assets/admin/common/js/ui2/ActionButton.tsx @@ -0,0 +1,105 @@ +import * as UI from '@enonic/ui'; +import {render} from 'react-dom'; +import {BrowserHelper} from '../BrowserHelper'; +import {Action} from '../ui/Action'; +import {LegacyElement} from './LegacyElement'; + +export type ActionButtonProps = { + action: T; + className?: string; + startIcon?: UI.LucideIcon; + endIcon?: UI.LucideIcon; +} & Omit; + +export type ActionProps = Pick; + +export class ActionButton extends LegacyElement { + + private actionProps: ActionButtonProps; + + constructor(props: ActionButtonProps) { + const {className, action, ...rest} = props; + super({ + onClick: () => { + this.giveFocus(); + this.actionProps.action.execute(); + }, + ...rest, + ...createPropsFromAction(props), + }, UI.Button); + + this.actionProps = props; + + this.actionProps.action.onPropertyChanged(this.updateProps); + } + + private updateProps = () => { + this.setProps(createPropsFromAction(this.actionProps)); + } + + // * Backward compatibility methods + + getAction(): T { + return this.actionProps.action; + } + + setAction(action: T): void { + if (this.actionProps.action === action) return; + + this.actionProps.action?.unPropertyChanged(this.updateProps); + + this.actionProps.action = action; + this.updateProps(); + } + + setEnabled(enabled: boolean): void { + this.actionProps.action.setEnabled(enabled); + } + + protected override renderJsx(): void { + const ActionButtonComponent = this.component; + const props = this.props.get(); + + render( + + {/* */} + + {/* */} + , + this.getHTMLElement() + ); + } +} + +// +// Utils +// + +function createPropsFromAction({action, className}: ActionButtonProps): ActionProps { + return { + className: UI.cn('action-button', action.getClass(), action.getIconClass(), className), + label: createLabel(action), + title: createTooltipText(action), + disabled: !action.isEnabled(), + }; +} + +function createLabel(action: Action): string { + const label = action.getLabel(); + return action.getMnemonic()?.underlineMnemonic(label) ?? label; +} + +function createTooltipText(action: Action): string { + const titleOrLabel = action.getTitle() || action.getLabel(); + if (!action.hasShortcut()) { + return titleOrLabel; + } + + const combination = formatShortcut(action.getShortcut().getCombination()); + return `${titleOrLabel} (${combination})`; +} + +function formatShortcut(combination: string): string { + const isApple = BrowserHelper.isOSX() || BrowserHelper.isIOS(); + return combination?.replace(/mod\+/i, isApple ? 'cmd+' : 'ctrl+') ?? ''; +} diff --git a/src/main/resources/assets/admin/common/js/ui2/ActionIcon.tsx b/src/main/resources/assets/admin/common/js/ui2/ActionIcon.tsx new file mode 100644 index 000000000..4255a3941 --- /dev/null +++ b/src/main/resources/assets/admin/common/js/ui2/ActionIcon.tsx @@ -0,0 +1,104 @@ +import * as UI from '@enonic/ui'; +import {unwrap} from '@enonic/ui'; +import {render} from 'react-dom'; +import {BrowserHelper} from '../BrowserHelper'; +import {Action} from '../ui/Action'; +import {LegacyElement} from './LegacyElement'; + +export interface ActionIconProps { + action: Action; + icon: UI.LucideIcon; + className?: string; +} + +export type ActionProps = Pick; + +export class ActionIcon extends LegacyElement { + + private actionProps: ActionIconProps; + + constructor(props: ActionIconProps) { + super({ + onClick: () => { + this.giveFocus(); + this.actionProps.action.execute(); + }, + icon: props.icon, + ...createPropsFromAction(props), + }, UI.IconButton); + + this.actionProps = props; + + this.actionProps.action.onPropertyChanged(this.updateProps); + } + + private updateProps = () => { + this.setProps(createPropsFromAction(this.actionProps)); + } + + // * Backward compatibility methods + + getAction(): Action { + return this.actionProps.action; + } + + setAction(action: Action): void { + if (this.actionProps.action === action) return; + + this.actionProps.action?.unPropertyChanged(this.updateProps); + + this.actionProps.action = action; + this.updateProps(); + } + + setEnabled(enabled: boolean): void { + this.actionProps.action.setEnabled(enabled); + } + + protected override renderJsx(): void { + const ActionIconComponent = this.component; + const props = this.props.get(); + + render( + + + + + , + this.getHTMLElement() + ); + } +} + +// +// Utils +// + +function createPropsFromAction({action, className}: ActionIconProps): ActionProps { + return { + className: UI.cn('action-button', action.getClass(), action.getIconClass(), className), + label: createLabel(action), + title: createTooltipText(action), + disabled: !action.isEnabled(), + }; +} + +function createLabel(action: Action): string { + const label = action.getLabel(); + return action.getMnemonic()?.underlineMnemonic(label) ?? label; +} + +function createTooltipText(action: Action): string { + const titleOrLabel = action.getTitle() || action.getLabel(); + if (!action.hasShortcut()) { + return titleOrLabel; + } + + const combination = formatShortcut(action.getShortcut().getCombination()); + return `${titleOrLabel} (${combination})`; +} + +function formatShortcut(combination: string): string { + const isApple = BrowserHelper.isOSX() || BrowserHelper.isIOS(); + return combination?.replace(/mod\+/i, isApple ? 'cmd+' : 'ctrl+') ?? ''; +} diff --git a/src/main/resources/assets/admin/common/js/ui2/Button.tsx b/src/main/resources/assets/admin/common/js/ui2/Button.tsx new file mode 100644 index 000000000..c56a979b9 --- /dev/null +++ b/src/main/resources/assets/admin/common/js/ui2/Button.tsx @@ -0,0 +1,12 @@ +import * as UI from '@enonic/ui'; + +import {LegacyElement} from './LegacyElement'; + +export type ButtonProps = UI.ButtonProps; + +export class Button extends LegacyElement { + + constructor(props: ButtonProps) { + super(props, UI.Button); + } +} diff --git a/src/main/resources/assets/admin/common/js/ui2/Checkbox.tsx b/src/main/resources/assets/admin/common/js/ui2/Checkbox.tsx new file mode 100644 index 000000000..fcf869f66 --- /dev/null +++ b/src/main/resources/assets/admin/common/js/ui2/Checkbox.tsx @@ -0,0 +1,92 @@ +import * as UI from '@enonic/ui'; +import {LegacyElement} from './LegacyElement'; + +export type CheckboxControllerProps = Pick + & Partial>; + +export class Checkbox + extends LegacyElement { + + private currentChecked: UI.CheckboxChecked; + private suppressEvent = false; + + constructor(props: CheckboxControllerProps) { + const {checked = false, onCheckedChange, ...rest} = props; + super( + { + onCheckedChange: (newValue) => { + this.currentChecked = newValue; + this.setProps({checked: newValue}); + if (!this.suppressEvent) { + props.onCheckedChange?.(newValue); + } + }, + ...rest, + + }, + UI.Checkbox, + ); + + this.currentChecked = this.props.get().checked ?? false; + } + + // * Backward compatibility methods + + getName(): string { + return this.props.get().name ?? ''; + } + + setName(name: string): this { + this.props.setKey('name', name); + return this; + } + + getLabel(): string { + return this.props.get().label ?? ''; + } + + setLabel(label: string): this { + this.props.setKey('label', label) + return this; + } + + isChecked(): boolean { + return this.currentChecked === true; + } + + setChecked(checked: UI.CheckboxChecked, suppressEvent = false): this { + this.suppressEvent = suppressEvent; + + this.currentChecked = checked; + this.props.setKey('checked', checked); + this.suppressEvent = false; + + return this; + } + + toggleChecked(suppressEvent = false): void { + this.setChecked(!this.isChecked(), suppressEvent); + } + + clear(suppressEvent = false): void { + this.setChecked(false, suppressEvent); + } + + giveFocus(): boolean { + const input = this.getHTMLElement().querySelector('input[type="checkbox"]'); + if (input instanceof HTMLInputElement) { + input.focus(); + return true; + } + return false; + } + + giveBlur(): boolean { + const input = this.getHTMLElement().querySelector('input[type="checkbox"]'); + if (input instanceof HTMLInputElement) { + input.blur(); + return true; + } + return false; + } +} diff --git a/src/main/resources/assets/admin/common/js/ui2/LegacyElement.tsx b/src/main/resources/assets/admin/common/js/ui2/LegacyElement.tsx new file mode 100644 index 000000000..1784901b5 --- /dev/null +++ b/src/main/resources/assets/admin/common/js/ui2/LegacyElement.tsx @@ -0,0 +1,95 @@ +import {IdProvider} from '@enonic/ui'; +import {nanoid} from 'nanoid'; +import {map, type MapStore} from 'nanostores'; +import type {ComponentProps, ComponentType} from 'react'; +import {render} from 'react-dom'; +import {Element as BaseElement, NewElementBuilder} from '../dom/Element'; + +export class LegacyElement, P extends ComponentProps = ComponentProps> extends BaseElement { + + protected readonly props: MapStore

; + + protected readonly component: C; + + constructor(props: ComponentProps, component: C) { + super(new NewElementBuilder().setTagName('div').setClassName('contents')); + this.component = component; + this.props = map({...props}); + this.props.subscribe(() => { + void this.render(); + }); + } + + setProps(props: Partial

): void { + this.props.set({...this.props.get(), ...props}); + } + + protected getPrefix(): string { + const {name, displayName} = this.component; + return `${displayName ?? name ?? this.constructor.name}-${nanoid(8)}`; + } + + protected renderJsx(): void { + const Component = this.component; + + render( + + + , + this.getHTMLElement() + ); + } + + //! Overrides + + override doRender(): Q.Promise { + this.renderJsx(); + return super.doRender(); + } + + override giveFocus(): boolean { + const focusableElements = this.getHTMLElement().querySelectorAll('& > button, & > input'); + if (focusableElements.length > 0 && focusableElements[0] instanceof HTMLElement) { + focusableElements[0].focus(); + return true; + } + return false; + } + + override addClass(className: string): this { + if (hasClassName(this.props)) { + this.props.setKey('className', `${this.props.value.className} ${className}`); + } else { + console.warn(`[${this.component.name}]: className is not allowed as a property`); + } + return this; + } + + override removeClass(className: string): this { + if (hasClassName(this.props)) { + const newClassName = this.props.value.className.split(' ').filter(c => c !== className).join(' '); + this.props.setKey('className', newClassName); + } else { + console.warn(`[${this.component.name}]: className is not allowed as a property`); + } + return this; + } + + override isVisible(): boolean { + return Array.from(this.getHTMLElement().children).some(child => isVisible(child)); + } +} + +function hasClassName(props: MapStore): props is MapStore<{className: string}> { + return 'className' in props.value && typeof props.value.className === 'string'; +} + +function isVisible(child: Element): boolean { + if (!(child instanceof HTMLElement) || child.offsetParent === null) { + return false; + } + + const style = getComputedStyle(child); + + return !(style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0'); +} diff --git a/src/main/resources/assets/admin/common/js/ui2/Notification.tsx b/src/main/resources/assets/admin/common/js/ui2/Notification.tsx new file mode 100644 index 000000000..66bdd1baf --- /dev/null +++ b/src/main/resources/assets/admin/common/js/ui2/Notification.tsx @@ -0,0 +1,38 @@ +import {Toast, ToastProps, ToastTone} from '@enonic/ui'; +import type {MouseEvent as ReactMouseEvent} from 'react'; + +export type NotificationTone = ToastTone; + +export interface NotificationAction { + label: string; + onClick: (event: ReactMouseEvent) => void; +} + +export type NotificationProps = ToastProps & { + text: string; + tone: NotificationTone; + actions: NotificationAction[]; +}; + +const NOTIFICATION_NAME = 'Notification'; + +export const Notification = ({ + text, + tone = 'info', + actions = [], + children, + ...props +}: NotificationProps) => { + return ( + + + {text} + {children} + {actions.map(({label, onClick}) => ( + + ))} + + ); +}; + +Notification.displayName = NOTIFICATION_NAME; diff --git a/src/main/resources/assets/admin/common/js/ui2/SearchInput.tsx b/src/main/resources/assets/admin/common/js/ui2/SearchInput.tsx new file mode 100644 index 000000000..85169a8ac --- /dev/null +++ b/src/main/resources/assets/admin/common/js/ui2/SearchInput.tsx @@ -0,0 +1,46 @@ +import {SearchField, SearchFieldRootProps} from '@enonic/ui'; +import {LegacyElement} from './LegacyElement'; + +const SearchInput = (props: SearchFieldRootProps) => { + return ( + + + + + + ); +}; + +export class SearchInputComponent extends LegacyElement { + + private currentValue: string; + + constructor(props: SearchFieldRootProps) { + const {onChange, ...rest} = props; + + super({ + onChange: (value: string) => { + this.currentValue = value; + props.onChange?.(value); + }, + ...rest, + }, SearchInput); + + this.currentValue = props.value ?? ''; + } + + getValue(): string { + return this.currentValue; + } + + setValue(value: string): void { + this.currentValue = value; + + this.props.setKey('value', value); + } + + clear(): void { + this.setValue(''); + } + +} diff --git a/src/main/resources/assets/admin/common/styles/api/app/browse/browse-filter-panel.less b/src/main/resources/assets/admin/common/styles/api/app/browse/browse-filter-panel.less index 9836c4775..2f9d09606 100644 --- a/src/main/resources/assets/admin/common/styles/api/app/browse/browse-filter-panel.less +++ b/src/main/resources/assets/admin/common/styles/api/app/browse/browse-filter-panel.less @@ -14,27 +14,8 @@ .search-container { display: flex; width: auto; - margin-left: @margin-left; margin-bottom: 7px; - a.clear-filter-button { - .icon-close(); - text-decoration: none; - font-size: 14px; - text-align: center; - width: 15px; - margin-left: 8px; - line-height: 26px; - - &:before { - color: @admin-button-blue1; - } - - &:hover:before { - color: @admin-button-blue2; - } - } - input.text-search-field { .placeholderColor(@admin-button-blue1); @@ -75,25 +56,7 @@ padding-right: 20px; } - .hide-filter-panel-button { - position: absolute; - top: 5px; - left: 4px; - padding: 5px 8px; - font-size: 16px; - cursor: pointer; - color: @admin-button-blue1; - - &:hover { - color: @admin-button-blue2; - } - } - &.show-constraint { - .hide-filter-panel-button { - display: none; - } - .search-container { margin-left: 6px; } @@ -137,20 +100,6 @@ .dependency-item { padding-top: 12px; } - - .action-button.btn-close { - .icon-close(); - - position: absolute; - padding: 0; - top: 30px; - right: -22px; - background-color: @admin-white; - - &::before { - color: @admin-font-gray2; - } - } } .aggregation-bucket-view { diff --git a/src/main/resources/assets/admin/common/styles/api/notify.less b/src/main/resources/assets/admin/common/styles/api/notify.less index e01f4ca10..fd57273f8 100644 --- a/src/main/resources/assets/admin/common/styles/api/notify.less +++ b/src/main/resources/assets/admin/common/styles/api/notify.less @@ -1,70 +1,52 @@ -.notification-container { - font-size: 0.75rem; - position: fixed; - z-index: @z-index-notification + 3; - - @media screen and (max-width: 799px) { +@layer overrides { + .notification-container { + position: fixed; width: 100%; - } - - @media screen and (min-width: 800px) { - left: 50%; - margin-left: -400px; + max-width: 460px; + z-index: 2004; } .notification-wrapper { - margin: auto; - opacity: 0.8; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + } - @media screen and (min-width: 800px) { - width: 800px; + @media screen and (width < 40rem) { + .notification-container { + right: 0; + bottom: 0; + max-width: initial; + padding: 0 8px 8px; } + } - .notification { - overflow: hidden; - color: white; - font-size: 1.2em; - padding: 10px 10px 10px 20px; - display: flex; - align-items: center; - background-color: @admin-blue; - - &.error { - background-color: red; - } - - &.warning { - background-color: orange; - } - - &.action, - &.success { - background-color: @admin-green; - } - - .notification-inner { - flex: 1; + @media screen and (width >= 40rem) { + .notification-container { + right: 36px; + bottom: 24px; + } + } - .notification-text, - .notification-actions { - display: inline; - } + @media screen and (width >= 48rem) { + .notification-container { + right: 46px; + bottom: 30px; + } + } - .notification-actions { - a { - color: white; - margin-left: 10px; - } - } - } + @media screen and (width >= 64rem) { + .notification-container { + right: 60px; + bottom: 40px; + } + } - .notification-remove { - cursor: pointer; - color: white; - height: 18px; - width: 18px; - } + @media screen and (width >= 96rem) { + .notification-container { + right: 92px; + bottom: 60px; } } } - diff --git a/src/main/resources/assets/admin/common/styles/api/ui/dialog/modal-dialog.less b/src/main/resources/assets/admin/common/styles/api/ui/dialog/modal-dialog.less index 81ee8d066..7669dc2a0 100644 --- a/src/main/resources/assets/admin/common/styles/api/ui/dialog/modal-dialog.less +++ b/src/main/resources/assets/admin/common/styles/api/ui/dialog/modal-dialog.less @@ -213,10 +213,6 @@ .dialog-buttons { position: relative; - .@{_COMMON_PREFIX}button { - .dialog-button(); - } - .button-container { display: flex; flex-wrap: nowrap; diff --git a/src/main/resources/assets/admin/common/styles/api/ui/panel/split-panel.less b/src/main/resources/assets/admin/common/styles/api/ui/panel/split-panel.less index 8055b9cab..e6a89c5eb 100644 --- a/src/main/resources/assets/admin/common/styles/api/ui/panel/split-panel.less +++ b/src/main/resources/assets/admin/common/styles/api/ui/panel/split-panel.less @@ -1,6 +1,5 @@ .split-panel { width: 100%; - right: 0; &.horizontal > .panel { position: relative; @@ -11,9 +10,12 @@ } > .splitter { - background-color: @admin-bg-light-gray; cursor: ns-resize; .notSelectable(); + + &.splitter-bg-standard { + background-color: @admin-bg-light-gray; + } } > .ghost-dragger { diff --git a/src/main/resources/assets/admin/common/styles/api/ui/time/date-time-range-picker.less b/src/main/resources/assets/admin/common/styles/api/ui/time/date-time-range-picker.less index e2f5a70f8..281618716 100644 --- a/src/main/resources/assets/admin/common/styles/api/ui/time/date-time-range-picker.less +++ b/src/main/resources/assets/admin/common/styles/api/ui/time/date-time-range-picker.less @@ -7,11 +7,6 @@ .date-time-picker { .@{_COMMON_PREFIX}wrapper { .date-time-dialog { - /* - .ok-button { - .picker-dialog-button(); - }*/ - .time-picker-dialog, .time-picker-dialog > li:first-child { padding-left: 0; diff --git a/src/main/resources/assets/admin/common/styles/font.less b/src/main/resources/assets/admin/common/styles/font.less index 7583a7c6e..eec7de8bc 100644 --- a/src/main/resources/assets/admin/common/styles/font.less +++ b/src/main/resources/assets/admin/common/styles/font.less @@ -1,5 +1,15 @@ @font-face { font-family: 'Open Sans'; font-display: swap; + font-style: normal; + font-weight: 100 900; src: url("../fonts/OpenSans.woff2") format("woff2"); } + +@font-face { + font-family: 'Open Sans'; + font-display: swap; + font-style: italic; + font-weight: 100 900; + src: url("../fonts/OpenSans-Italic.woff2") format("woff2"); +} diff --git a/src/main/resources/assets/admin/common/styles/global.less b/src/main/resources/assets/admin/common/styles/global.less index f1d66d622..699365d6a 100644 --- a/src/main/resources/assets/admin/common/styles/global.less +++ b/src/main/resources/assets/admin/common/styles/global.less @@ -20,44 +20,48 @@ body { } } -.headers-mixin(); - -a { - color: #0070de; -} - -input, -textarea, -select { - border-radius: 0; -} - -&.@{_CLS_PREFIX}focused, -:focus-visible, -:focus, -input[type="checkbox"]:focus + label::before, -input[type="checkbox"]:focus + label::after { - outline: rgb(0, 95, 204) auto 1px; -} +@layer legacy { + a { + color: #0070de; + } -&.@{_CLS_PREFIX}focused, -:focus-visible, -:focus { - outline-offset: -1px; -} + input, + textarea, + select { + border-radius: 0; + } -input[type="checkbox"]:focus + label { - &::before, - &::after { - outline-offset: 2px; - background-size: 17px; + &.@{_CLS_PREFIX}focused, + :focus-visible, + :focus, + input[type="checkbox"]:focus + label::before, + input[type="checkbox"]:focus + label::after { + outline: rgb(0, 95, 204) auto 1px; } -} -@-moz-document url-prefix() { &.@{_CLS_PREFIX}focused, :focus-visible, :focus { - outline-offset: -2px; + outline-offset: -1px; + } + + input[type="checkbox"]:focus + label { + &::before, + &::after { + outline-offset: 2px; + background-size: 17px; + } + } + + @-moz-document url-prefix() { + &.@{_CLS_PREFIX}focused, + :focus-visible, + :focus { + outline-offset: -2px; + } + } + + .contents { + display: contents; } } diff --git a/src/main/resources/assets/admin/common/styles/main.less b/src/main/resources/assets/admin/common/styles/main.less index 356edd762..306795c40 100644 --- a/src/main/resources/assets/admin/common/styles/main.less +++ b/src/main/resources/assets/admin/common/styles/main.less @@ -8,7 +8,6 @@ @import "../icons/icons"; // :) @import "variables"; -@import "reset"; @import "mixins"; @import "font"; @import "global"; diff --git a/src/main/resources/assets/admin/common/styles/reset.less b/src/main/resources/assets/admin/common/styles/reset.less deleted file mode 100644 index 147231631..000000000 --- a/src/main/resources/assets/admin/common/styles/reset.less +++ /dev/null @@ -1,141 +0,0 @@ -body { - margin: 0; - width: 100%; - overflow-x: hidden; -} - -html, -body, -div, -dl, -dt, -dd, -ul, -ol, -li, -h1, -h2, -h3, -h4, -h5, -h6, -pre, -code, -form, -fieldset, -legend, -input, -textarea, -p, -blockquote, -th, -td { - margin: 0; - padding: 0; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -fieldset, -img { - border: 0; -} - -address, -caption, -cite, -code, -dfn, -em, -strong, -th, -var { - font-style: normal; - font-weight: normal; -} - -li { - list-style: none; -} - -caption, -th { - text-align: left; -} - -q::before, -q::after { - content: ""; -} - -abbr, -acronym { - border: 0; - font-variant: normal; -} - -sup { - vertical-align: text-top; -} - -sub { - vertical-align: text-bottom; -} - -.defaultInputs() { - -webkit-appearance: none; - -moz-appearance: none; - font-family: inherit; - font-size: inherit; - font-weight: inherit; -} - -textarea, -select { - .defaultInputs(); -} - -button::-moz-focus-inner { - border: 0; - padding: 0; -} - -div, -input, -textarea, -select, -button { - &:not([tabindex="-1"]) { - &:focus { - outline-width: 2px; - outline-style: auto; - } - } -} - -input:not([type="radio"]) { - .defaultInputs(); -} - -@-moz-document url-prefix() { - input, - textarea, - select, - button { - &:focus { - outline-color: rgba(66, 148, 222, 0.5); - } - } -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-weight: normal; -} diff --git a/tsconfig.json b/tsconfig.json index 3c73d0be2..4976556e7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,11 @@ "ES2023", "DOM" ], + "jsx": "react-jsx", + "jsxImportSource": "preact", "outDir": "build/resources/main/dev/lib-admin-ui/", - "moduleResolution": "node", + "moduleResolution": "bundler", + "baseUrl": ".", "declaration": true, "declarationDir": "build/resources/main/dev/lib-admin-ui/", "allowSyntheticDefaultImports": true, @@ -23,11 +26,16 @@ "jquery", "jqueryui", "mousetrap", - "q", - ] + "q" + ], + "paths": { + "react": ["node_modules/preact/compat"], + "react-dom": ["node_modules/preact/compat"] + } }, "include": [ - "src/main/resources/assets/admin/common/js/**/*.ts" + "src/main/resources/assets/admin/common/js/**/*.ts", + "src/main/resources/assets/admin/common/js/**/*.tsx" ], "exclude": [ ".vscode", diff --git a/vite.config.ts b/vite.config.ts index 168621dea..3c73332b0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -48,6 +48,13 @@ export default defineConfig(({mode}) => { formats: ['iife'] }, rollupOptions: { + onwarn(warning, warn) { + // Suppress INVALID_ANNOTATION warnings from @enonic/ui + if (warning.code === 'INVALID_ANNOTATION' && warning.id?.includes('@enonic/ui')) { + return; + } + warn(warning); + }, plugins: [ inject({ $: 'jquery', @@ -68,13 +75,18 @@ export default defineConfig(({mode}) => { esbuild: { minifyIdentifiers: false, keepNames: true, + jsx: 'automatic', + jsxImportSource: 'preact', + jsxDev: isDevelopment }, resolve: { alias: { 'react': 'preact/compat', - 'react-dom': 'preact/compat' + 'react-dom': 'preact/compat', + 'react/jsx-runtime': 'preact/jsx-runtime', + 'react/jsx-dev-runtime': 'preact/jsx-dev-runtime' }, - extensions: ['.ts', '.js'] + extensions: ['.tsx', '.ts', '.jsx', '.js'] }, ...(isDevelopment && { server: {