diff --git a/package-lock.json b/package-lock.json index 24d8202b6..440be5a9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2117,9 +2117,9 @@ } }, "node_modules/@csstools/cascade-layer-name-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-3.0.1.tgz", - "integrity": "sha512-EqmSJE8CcNoGXcgpiOD12PPMeuJZwiGMX69HPoam+GzltcjZjr6THQ0S+8a0NtQuefJtILzoDyQj+fp7+Qiorg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-3.0.2.tgz", + "integrity": "sha512-B731RJ9pfJj3D5GJPp8ccsaaHMdwBmlOb4QWAYQUFpmMxonxJvNeeIoo0E89CjMJaKDvQXy9MKpSXY/i7XgeFA==", "dev": true, "funding": [ { @@ -2136,7 +2136,7 @@ "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.2", "@csstools/css-tokenizer": "^4.0.2" } }, @@ -2161,9 +2161,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.1.tgz", - "integrity": "sha512-EtC7SoN1j6J4E4DCwg5QgbO5TGxgxIA1RXqe+W+qUM+BUcezx9wT+/tiQ/WO2yCX4i5X+Cuf9ciJ22aP4UEwWw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.3.tgz", + "integrity": "sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==", "devOptional": true, "funding": [ { @@ -2180,14 +2180,14 @@ "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.2", "@csstools/css-tokenizer": "^4.0.2" } }, "node_modules/@csstools/css-color-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.4.tgz", - "integrity": "sha512-DyefytAZ735mX4Dq/WcDAXFtXhaEFvme0ZS9tVEBAc2whxUthXr0R0L2rmEPm59SrMBkGrFzQviBXMs/UtnABQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.6.tgz", + "integrity": "sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==", "devOptional": true, "funding": [ { @@ -2202,20 +2202,20 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^6.1.2", - "@csstools/css-calc": "^3.4.1" + "@csstools/css-calc": "^3.4.3" }, "engines": { "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.2", "@csstools/css-tokenizer": "^4.0.2" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.1.tgz", - "integrity": "sha512-ShL8BqPfbKJrJiKFH0xBbN0i7Nrh9HXYRuF+pzyj93R/BL2YAsUxJeqANErzqM+0JGl7vjHp+3OIgd/DL69YIA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.2.tgz", + "integrity": "sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==", "devOptional": true, "funding": [ { @@ -2236,9 +2236,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.14.tgz", - "integrity": "sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.15.tgz", + "integrity": "sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==", "devOptional": true, "funding": [ { @@ -2281,9 +2281,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.1.tgz", - "integrity": "sha512-DR5NpEOOxDOhmMWPJlhYAV9gTmV4XpfYjtE+aXSCurkxS65KzNogI4wqzRIoujieQcmFkeRtK1nRIF8ImUZTDg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.2.tgz", + "integrity": "sha512-qx19O8AbbLLbDSAcI6YOby/Vq8rV51psgIvlF5OZT/L8fQJDCyQifL6k+kYU4a8ySpzJ4RBzZkIs530bAn8y4Q==", "dev": true, "funding": [ { @@ -2300,7 +2300,7 @@ "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.2", "@csstools/css-tokenizer": "^4.0.2" } }, @@ -3284,9 +3284,9 @@ } }, "node_modules/@lezer/javascript": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.5.tgz", - "integrity": "sha512-sWg4yX1J6XW67AaAynVt0iwF0M5c+np36TEu+P2ifAJ8haRYvHnWDV28r1jdwnJehWCwXECutAUy56K4RBZIyg==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.6.tgz", + "integrity": "sha512-sEvOqpl4vJQca0NugSQK02vQPbc5KrvoojnYGoqqFgFO0MtcYhwGiHKVEB+LuXYJnfyXo4QPA8ztbrEa4Fw5NA==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -3908,9 +3908,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.151.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", - "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/oxc-project" @@ -4264,9 +4264,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", - "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", "cpu": [ "arm" ], @@ -4280,9 +4280,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", - "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", "cpu": [ "arm64" ], @@ -4296,9 +4296,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", - "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", "cpu": [ "arm64" ], @@ -4312,9 +4312,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", - "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", "cpu": [ "x64" ], @@ -4328,9 +4328,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", - "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", "cpu": [ "x64" ], @@ -4344,9 +4344,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", - "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", "cpu": [ "arm" ], @@ -4360,9 +4360,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", - "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", "cpu": [ "arm64" ], @@ -4379,9 +4379,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", - "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", "cpu": [ "arm64" ], @@ -4398,9 +4398,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", - "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", "cpu": [ "ppc64" ], @@ -4417,9 +4417,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", - "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", "cpu": [ "s390x" ], @@ -4436,9 +4436,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", - "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", "cpu": [ "x64" ], @@ -4455,9 +4455,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", - "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", "cpu": [ "x64" ], @@ -4474,9 +4474,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", - "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", "cpu": [ "arm64" ], @@ -4490,9 +4490,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", - "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", "cpu": [ "arm64" ], @@ -4506,9 +4506,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", - "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", "cpu": [ "x64" ], @@ -5337,9 +5337,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.0.tgz", - "integrity": "sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==", + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -5411,9 +5411,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.10.tgz", - "integrity": "sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", "dev": true, "license": "MIT", "engines": { @@ -6703,9 +6703,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.26", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz", - "integrity": "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==", + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7513,9 +7513,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.442", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.442.tgz", - "integrity": "sha512-najZYZ3+ZpjN1z3VOsrBiv5ej18vQgfV2lvEmxWzfmKrk4bdm3Owseyud4yGU6DfFn9pyunoAHDDyM4KmP78Ew==", + "version": "1.5.444", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.444.tgz", + "integrity": "sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==", "dev": true, "license": "ISC" }, @@ -8149,9 +8149,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "18.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.4.0.tgz", - "integrity": "sha512-TEm6Nqn9+l7EWfJXOL6pmrEdpiM2ZTgo4fNMRDvBTM70jeQ7x3X3Akcx+sRCIRfpcSVwMypH3ZAA33PLyNKmYw==", + "version": "18.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.4.1.tgz", + "integrity": "sha512-2sBReWOvCbzgkPri9M5SFmD5EbIwZLa1xz6B5RZsrI19WSFKpyx1sOMr+pdk8puCiXuEgCbErEHy1PuaUrc/4w==", "dev": true, "license": "MIT", "dependencies": { @@ -8602,9 +8602,9 @@ } }, "node_modules/exsolve": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", - "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.3.tgz", + "integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==", "dev": true, "license": "MIT" }, @@ -9059,9 +9059,9 @@ } }, "node_modules/globals": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", - "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "version": "17.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz", + "integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==", "dev": true, "license": "MIT", "engines": { @@ -11045,9 +11045,9 @@ "license": "MIT" }, "node_modules/n3": { - "version": "2.7.12", - "resolved": "https://registry.npmjs.org/n3/-/n3-2.7.12.tgz", - "integrity": "sha512-Hy6dfGg9yniLQpSBirvH2E2yO3EG7dSmA3aefRbtO411oqtzG8roD3h1kTc/N065bivCOPfTFIjf0KlwW7KlnQ==", + "version": "2.10.6", + "resolved": "https://registry.npmjs.org/n3/-/n3-2.10.6.tgz", + "integrity": "sha512-LtWkgNd9dtqOTLBIScGei02zo3U0JTh9JtGKpOiEWJ15vEfxXZbIX5XURyjstAoc6YYTElQvJ+8vTvJ8G4pEZw==", "license": "MIT", "dependencies": { "buffer": "^6.0.3", @@ -11948,9 +11948,9 @@ } }, "node_modules/postcss-custom-media": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-12.0.2.tgz", - "integrity": "sha512-qn+su6Ux/TVzBSPYlRxC3Kq5DZNWBZt7y1wj38mnHT+whYnM21IxuYVKJ2E9qahBo0KGvcv/1rAB/F5iY1UiIA==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-12.0.3.tgz", + "integrity": "sha512-pgOICT/unQexoA/Vh4ZvyAUrcwwA/nHh0SJcOwOombpwDEj1x5N6zK+kz97wy9NslhU6UxhE5c5Q/M2gpYfkJA==", "dev": true, "funding": [ { @@ -11964,10 +11964,10 @@ ], "license": "MIT", "dependencies": { - "@csstools/cascade-layer-name-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/cascade-layer-name-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^4.0.2", "@csstools/css-tokenizer": "^4.0.2", - "@csstools/media-query-list-parser": "^5.0.1" + "@csstools/media-query-list-parser": "^5.0.2" }, "engines": { "node": ">=20.19.0" @@ -12372,12 +12372,12 @@ } }, "node_modules/rolldown": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", - "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.151.0", + "@oxc-project/types": "=0.152.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -12387,21 +12387,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.11", - "@rolldown/binding-android-arm64": "1.2.11", - "@rolldown/binding-darwin-arm64": "1.2.11", - "@rolldown/binding-darwin-x64": "1.2.11", - "@rolldown/binding-freebsd-x64": "1.2.11", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", - "@rolldown/binding-linux-arm64-gnu": "1.2.11", - "@rolldown/binding-linux-arm64-musl": "1.2.11", - "@rolldown/binding-linux-ppc64-gnu": "1.2.11", - "@rolldown/binding-linux-s390x-gnu": "1.2.11", - "@rolldown/binding-linux-x64-gnu": "1.2.11", - "@rolldown/binding-linux-x64-musl": "1.2.11", - "@rolldown/binding-openharmony-arm64": "1.2.11", - "@rolldown/binding-win32-arm64-msvc": "1.2.11", - "@rolldown/binding-win32-x64-msvc": "1.2.11" + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, "node_modules/run-applescript": { @@ -12797,9 +12797,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -13230,9 +13230,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", - "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -13692,9 +13692,9 @@ } }, "node_modules/unplugin-dts": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.1.1.tgz", - "integrity": "sha512-Z2VcabC0hjYuzF62oWmZXVoxagkM8bUWgqUy+dotoJTPnooR5mNaxbpv63JCmcaxdGCW2Bb75omK6FOryOYCRg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.1.2.tgz", + "integrity": "sha512-RDqy90XUmFj6rz4U5YoTMdYdaCJe2ZnQZaWIjkWS4952Gv0zBTQyqWtlGgVMPYHa982dURvyoc+sUOFIcDFv+w==", "dev": true, "license": "MIT", "dependencies": { @@ -13886,15 +13886,15 @@ } }, "node_modules/vite": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", - "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", - "rolldown": "~1.2.9", + "rolldown": "~1.2.11", "tinyglobby": "^0.2.17" }, "bin": { @@ -14362,9 +14362,9 @@ } }, "node_modules/vitest/node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "license": "MIT", "engines": { "node": ">=18" diff --git a/src/components/access-control-modal/AccessControlModal.styles.css b/src/components/access-control-modal/AccessControlModal.styles.css new file mode 100644 index 000000000..c904a35ca --- /dev/null +++ b/src/components/access-control-modal/AccessControlModal.styles.css @@ -0,0 +1,251 @@ +:host { + h2 { + color: var(--solid-ui-color-gray-900, #101828); + font-size: var(--solid-ui-font-size-md, 1rem); + font-weight: 600; + } + + .access-grants-form { + display: flex; + padding: 10px 10px; + flex-direction: row; + align-items: flex-start; + gap: 10px; + align-self: stretch; + + --access-role-select-width: 129px; + + solid-ui-input { + flex: 1 1 auto; + min-width: 0; + + --solid-ui-input-border-color: var(--solid-ui-color-slate-200, #E2E8F0); + } + } + + .access-grants-header, + .access-grants-list, + .access-grants-general { + padding: 0 10px; + align-self: stretch; + } + + .access-grants-header { + display: flex; + align-items: center; + gap: 15px; + justify-content: space-between; + + .access-grants-search-input { + flex: 0 0 auto; + width: 12ch; + min-width: 12ch; + --solid-ui-input-border-color: transparent; + } + } + + .access-grants-list { + display: flex; + align-items: flex-start; + gap: 20px; + + ul { + display: flex; + flex-direction: column; + height: 195px; + padding: 15px; + align-items: flex-start; + gap: 15px; + flex: 1 0 0; + border-radius: 10px; + border: 1px solid var(--solid-ui-color-gray-200, #E5E7EB); + } + + li { + display: flex; + align-items: center; + gap: 10px; + flex: 0 0 auto; + width: 100%; + + .access-grants-image { + display: flex; + justify-content: center; + align-items: center; + aspect-ratio: 1/1; + width: 30px; + height: 30px; + flex: 0 0 30px; + border-radius: 999px; + overflow: hidden; + color: var(--solid-ui-color-white, #FFF); + text-align: center; + font-size: var(--solid-ui-font-size-xs, .75rem); + font-weight: 500; + + img { + display: flex; + width: 100%; + height: 100%; + border-radius: 999px; + object-fit: cover; + } + + span { + line-height: 1; + } + } + + .access-grants-image--group { + background: var(--solid-ui-color-blue-900, #083575); + } + + .access-grants-image--agent, + .access-grants-image--agentClass { + background: var(--solid-ui-color-lavender-300, #e6dcff); + } + + .access-grants-image--origin { + background: var(--solid-ui-color-gray-200, #E5E7EB); + } + + .access-grants-image--unknown { + background: var(--solid-ui-color-gray-300, #D1D5DB); + } + + h3 { + flex: 1 1 auto; + min-width: 0; + color: var(--solid-ui-color-gray-800, #1E2939); + font-size: var(--solid-ui-font-size-xs, .75rem); + font-weight: 600; + } + + span { + margin-left: auto; + flex: 0 0 auto; + text-align: right; + } + + .access-grants-role { + margin-left: auto; + flex: 0 0 auto; + font-size: var(--solid-ui-font-size-xs, .75rem); + } + + .access-grants-role--owner { + color: var(--solid-ui-color-gray-400, #99A1AF); + font-size: var(--solid-ui-font-size-xs, .75rem); + } + + .access-grants-role--editable { + width: fit-content; + max-width: 100%; + } + } + } + + .access-grants-general { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; + + --access-role-select-width: 110px; + + solid-ui-combobox, + solid-ui-input { + --solid-ui-input-border-color: var(--solid-ui-color-slate-200, #E2E8F0); + } + + .access-grants-general-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 15px; + align-self: stretch; + } + + .access-grants-general-share { + display: flex; + align-items: center; + gap: 15px; + align-self: stretch; + + .access-grants-general-share-content { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 10px; + flex: 1 0 0; + + .access-grants-general-share-icon { + display: flex; + width: 30px; + height: 30px; + padding: 5px; + justify-content: center; + align-items: center; + aspect-ratio: 1/1; + border-radius: 50px; + background: var(--solid-ui-color-violet-50, #F5F3FF); + } + + .access-grants-general-share-icon-inner { + display: flex; + width: 20px; + height: 20px; + padding: 1.25px; + justify-content: center; + align-items: center; + flex-shrink: 0; + aspect-ratio: 1/1; + + icon-lucide-globe { + width: 17.5px; + height: 17.5px; + flex-shrink: 0; + aspect-ratio: 1/1; + } + } + + .access-grants-general-share-text { + flex: 1 1 auto; + min-width: 0; + + .access-grants-general-share-text-title { + color: var(--solid-ui-color-gray-900, #101828); + font-size: var(--solid-ui-font-size-sm, .875rem); + font-weight: 600; + } + + .access-grants-general-share-text-description { + color: var(--solid-ui-color-gray-700, #364153); + font-size: var(--solid-ui-font-size-sm, .875rem); + } + } + } + } + } + + .access-role-select { + display: block; + max-width: 100%; + margin-top: 25px; + flex: 0 0 auto; + width: var(--access-role-select-width); + } + + .access-control-footer-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 15px; + width: 100%; + + > solid-ui-button { + min-width: 132px; + } + } +} \ No newline at end of file diff --git a/src/components/access-control-modal/AccessControlModal.ts b/src/components/access-control-modal/AccessControlModal.ts new file mode 100644 index 000000000..c9c7d718c --- /dev/null +++ b/src/components/access-control-modal/AccessControlModal.ts @@ -0,0 +1,430 @@ +import { customElement, WebComponent } from '@/lib/components' +import { html, nothing } from 'lit' +import type { PropertyValues } from 'lit' +import { property, query, state } from 'lit/decorators.js' +import { label } from '@/utils/label' +import { findImage } from '@/widgets' +import type Dialog from '@/components/dialog' +import { solidLogicSingleton } from 'solid-logic' +import type { AccessMode, Authorization, SubjectType } from 'solid-logic' +import type { AccessControlBadgeKind, AccessRole, DraftGrant } from './types' +import { sym } from 'rdflib' + +import '~icons/lucide/chevron-down' +import '~icons/lucide/link' +import '~icons/lucide/search' +import '~icons/lucide/globe' +import '@/components/dialog' +import '@/components/dialog-content' +import '@/components/dialog-footer' +import '@/components/button' +import '@/components/input' +import '@/components/combobox' +import '@/components/combobox-option' + + +import styles from './AccessControlModal.styles.css' + + +const ACCESS_ROLE_RULES = [ + { modes: ['Control'], label: 'Owner' }, + { modes: ['Write'], label: 'Editor' }, + { modes: ['Append', 'Read'], label: 'Poster' }, + { modes: ['Append'], label: 'Submitter' }, + { modes: ['Read'], label: 'Viewer' } +] as const + +@customElement('solid-ui-access-control-modal') +export default class AccessControlModal extends WebComponent { + static styles = styles + + @property({ attribute: false }) + accessor subjectUri: string | undefined = undefined + + @property({ attribute: false }) + accessor accessGrants: Authorization[] | undefined = undefined + + @state() + private accessor principleInputValue: string = '' + + @state() + private accessor roleValue: string = 'Viewer' + + @state() + private accessor searchValue: string = '' + + @state() + private accessor failed: boolean = false + + @state() + private accessor submitting: boolean = false + + @state() + private accessor accessGrantRoles: AccessRole[] = [] + + @query('solid-ui-dialog') + private accessor dialog: Dialog | null = null + + connectedCallback () { + super.connectedCallback() + } + + protected willUpdate (changedProperties: PropertyValues) { + super.willUpdate(changedProperties) + + if (changedProperties.has('accessGrants')) { + this.accessGrantRoles = this.accessGrants?.map(item => this.getAuthorizationRole(item)) ?? [] + } + } + + private renderAccessGrants() { + return html` + + ` + } + + private renderAccessGrant (authorization: Authorization, index: number) { + const badge = this.getAuthorizationBadge(authorization) + const role = this.accessGrantRoles[index] ?? this.getAuthorizationRole(authorization) + + return html` +
  • + ${this.renderAuthorizationBadge(badge)} +

    ${this.renderAuthorizationSubjects(authorization)}

    + ${this.renderAuthorizationRole(role, index)} +
  • + ` + } + + private getAuthorizationBadge (authorization: Authorization) { + const agent = authorization.agent[0] + if (agent) { + const image = findImage(sym(agent)) + return { + kind: 'agent' as const, + image, + text: image ? '' : this.getInitials(this.renderAuthorizationSubjects(authorization), 2) + } + } + + const agentGroup = authorization.agentGroup[0] + if (agentGroup) { + return { + kind: 'group' as const, + text: this.getInitials(this.renderAuthorizationSubjects(authorization), 1) + } + } + + const agentClass = authorization.agentClass[0] + if (agentClass) { + const image = findImage(sym(agentClass)) + return { + kind: 'agentClass' as const, + image, + text: image ? '' : this.getInitials(this.renderAuthorizationSubjects(authorization), 2) + } + } + + const origin = authorization.origin[0] + if (origin) { + return { + kind: 'origin' as const, + text: 'O' + } + } + + return { + kind: 'unknown' as const, + text: '?' + } + } + + private renderAuthorizationBadge (badge: { kind: AccessControlBadgeKind, image?: string, text: string }) { + return html` +
    + ${badge.image ? html`` : html``} +
    + ` + } + + private getInitials (label: string, maxWords = 2): string { + const parts = label.split(/\s+/).filter(Boolean).slice(0, maxWords) + const initials = parts.map(part => part[0]).join('') + return (initials || label.slice(0, maxWords)).toUpperCase() + } + + private renderAuthorizationSubjects (authorization: Authorization): string { + const subjects = [ + ...authorization.agent, + ...authorization.agentGroup, + ...authorization.agentClass, + ...authorization.origin + ] + + return subjects.length ? subjects.map(subject => label(sym(subject))).join(', ') : 'Unknown access holder' + } + + private getAuthorizationRole (authorization: Authorization): AccessRole { + const modes = new Set(authorization.mode) + const matchingRule = ACCESS_ROLE_RULES.find(rule => rule.modes.every(mode => modes.has(mode))) + + return matchingRule?.label ?? 'Viewer' + } + + private renderAuthorizationRole (role: AccessRole, index: number) { + if (role === 'Owner') { + return html`${role}` + } + + return html` + this.onAccessGrantRoleInput(index, event)} + > + Editor + Viewer + Poster + Submitter + Remove + + ` + } + + private renderModeSelector () { + + return html` + + Owner + Editor + Viewer + Poster + Submitter + Remove + + ` + } + + private renderAddAccessForm () { + return html` +
    + + ${this.renderModeSelector()} +
    + ` + } + + private renderAccessGrantsSection () { + return html` +
    +

    Share with

    + + + +
    +
    + ${this.renderAccessGrants()} +
    + ` + } + + private renderGeneralAccessSection () { + return html` +
    +
    +

    General Access

    + + + Copy Link + +
    + +
    + ` + } + + private renderGeneralAccessIcon () { + return html` +
    + +
    + ` + } + + private getRoleModes (role: AccessRole): AccessMode[] { + return [...(ACCESS_ROLE_RULES.find(rule => rule.label === role)?.modes ?? [])] + } + + private parsePrincipleInput (): DraftGrant[] | undefined { + const value = this.principleInputValue.trim() + if (!value) return undefined + + const principleList = value.split(',').map(item => item.trim()).filter(item => item) + if (!principleList.length) return undefined + + return principleList.map((principle): DraftGrant => { + const subjectType: SubjectType = solidLogicSingleton.resource.isWebId(principle) ? 'agent' : 'agentGroup' + + return { + subjectType, + subjectValue: principle, + role: this.roleValue as AccessRole + } + }) + } + + private getDialogTitle (): string { + const subject = this.subjectUri ? sym(this.subjectUri) : undefined + const subjectLabel = subject ? label(subject) : '' + + return `Share ${subjectLabel || 'this resource'}` + } + + protected render () { + const dialogTitle = this.getDialogTitle() + + return html` + +
    + + ${this.renderAddAccessForm()} + ${this.renderAccessGrantsSection()} + ${this.renderGeneralAccessSection()} + + + + + +
    +
    + ` + } + + private async onSubmit (e: Event) { + e.preventDefault() + + this.failed = false + + const draftGrants = this.parsePrincipleInput() + if (!draftGrants?.length) { + return + } + + if (!this.subjectUri) { + this.failed = true + return + } + + this.submitting = true + // Basic added to address PR feedback has not been tested, currently only structure implemented + try { + for (const draftGrant of draftGrants) { + const subject = { type: draftGrant.subjectType, iri: draftGrant.subjectValue } + + const plan = this.roleValue === 'Remove' + ? await solidLogicSingleton.acl.planRevoke(this.subjectUri, subject) + : await solidLogicSingleton.acl.planGrant(this.subjectUri, subject, this.getRoleModes(draftGrant.role)) + + await solidLogicSingleton.acl.applyPlan(plan) + } + + this.principleInputValue = '' + this.dialog?.close() + } catch (error) { + this.failed = true + console.error('Failed to save access changes', error) + } finally { + this.submitting = false + } + + } + + private onPrincipleInput (event: Event) { + const target = event.currentTarget as HTMLInputElement | null + this.principleInputValue = target?.value ?? '' + } + + private onSearchInput (event: Event) { + const target = event.currentTarget as HTMLInputElement | null + this.searchValue = target?.value ?? '' + } + + private onRoleInput (event: Event) { + const target = event.currentTarget as { value?: string } | null + this.roleValue = target?.value ?? 'Viewer' + } + + private onAccessGrantRoleInput (index: number, event: Event) { + const target = event.currentTarget as { value?: string } | null + const value = target?.value + + if (!value) return + + this.accessGrantRoles = this.accessGrantRoles.map((role, roleIndex) => { + return roleIndex === index ? value as AccessRole : role + }) + } + + private async onCopyLinkClick (event: Event) { + event.preventDefault() + + if (!this.subjectUri) { + return + } + + try { + await navigator.clipboard.writeText(this.subjectUri) + } catch (error) { + console.error('Failed to copy resource link', error) + } + } +} diff --git a/src/components/access-control-modal/index.ts b/src/components/access-control-modal/index.ts new file mode 100644 index 000000000..b8513f270 --- /dev/null +++ b/src/components/access-control-modal/index.ts @@ -0,0 +1,4 @@ +import AccessControlModal from './AccessControlModal' + +export { AccessControlModal } +export default AccessControlModal \ No newline at end of file diff --git a/src/components/access-control-modal/types.ts b/src/components/access-control-modal/types.ts new file mode 100644 index 000000000..000deff53 --- /dev/null +++ b/src/components/access-control-modal/types.ts @@ -0,0 +1,15 @@ +import type { SubjectType } from 'solid-logic' + +export const ACCESS_ROLES = ['Owner', 'Editor', 'Viewer', 'Poster', 'Submitter', 'Remove'] as const + +export type AccessRole = typeof ACCESS_ROLES[number] + +export type AccessControlBadgeKind = 'agent' | 'group' | 'agentClass' | 'origin' | 'unknown' + +export type DraftGrant = { + id?: string + subjectType: SubjectType + subjectValue: string + role: AccessRole + removed?: boolean +} diff --git a/src/components/combobox/Combobox.stories.ts b/src/components/combobox/Combobox.stories.ts index 7e5a8fc16..6dac7daf1 100644 --- a/src/components/combobox/Combobox.stories.ts +++ b/src/components/combobox/Combobox.stories.ts @@ -37,22 +37,25 @@ const meta = { options: 'Pizza, Ramen, Tacos', asyncJSOptions: false, asyncHtmlOptions: false, + srOnlyLabel: false, }, argTypes: { label: { control: 'text' }, options: { control: 'text' }, asyncJSOptions: { control: 'boolean' }, asyncHtmlOptions: { control: 'boolean' }, + srOnlyLabel: { control: 'boolean' }, }, - render ({ label, options, asyncJSOptions, asyncHtmlOptions }) { + render ({ label, options, asyncJSOptions, asyncHtmlOptions, srOnlyLabel }) { if (asyncJSOptions) { - return html`` + return html`` } if (asyncHtmlOptions) { return html` option.trim()) return html` - + ${parsedOptions.map((option, index) => { const indent = index === 0 ? '' : ' ' diff --git a/src/components/combobox/Combobox.styles.css b/src/components/combobox/Combobox.styles.css index 2308ce043..a09eb8109 100644 --- a/src/components/combobox/Combobox.styles.css +++ b/src/components/combobox/Combobox.styles.css @@ -1,4 +1,6 @@ :host { + --solid-ui-input-border-color: var(--solid-ui-color-gray-400); + display: inline-flex; flex-direction: column; align-items: flex-start; @@ -29,7 +31,7 @@ width: 100%; input { - border: 1px solid var(--solid-ui-color-gray-400); + border: 1px solid var(--solid-ui-input-border-color); border-radius: 5px; padding: 10px; padding-right: 40px; diff --git a/src/components/input/Input.stories.ts b/src/components/input/Input.stories.ts index d07128e39..4ae45e176 100644 --- a/src/components/input/Input.stories.ts +++ b/src/components/input/Input.stories.ts @@ -7,6 +7,7 @@ const args = { value: '', placeholder: 'Enter your name', type: 'text', + srOnlyLabel: false, } const meta = { @@ -16,15 +17,17 @@ const meta = { label: { control: 'text' }, value: { control: 'text' }, placeholder: { control: 'text' }, + srOnlyLabel: { control: 'boolean' }, type: { control: 'select', options: ['text', 'email', 'password', 'search', 'url'], }, }, - render ({ label, value, placeholder, type }: typeof args) { + render ({ label, value, placeholder, type, srOnlyLabel }: typeof args) { return html` + 0) + } + private onKeyDown (e: KeyboardEvent) { if (e.key === 'Enter') { e.preventDefault() diff --git a/src/components/select/Select.stories.ts b/src/components/select/Select.stories.ts index 2d5114409..770fd8c42 100644 --- a/src/components/select/Select.stories.ts +++ b/src/components/select/Select.stories.ts @@ -7,6 +7,7 @@ import './Select' const args = { label: 'What is the best food?', options: 'Pizza, Ramen, Tacos', + srOnlyLabel: false, } const meta = { @@ -15,12 +16,13 @@ const meta = { argTypes: { label: { control: 'text' }, options: { control: 'text' }, + srOnlyLabel: { control: 'boolean' }, }, - render ({ label, options }: typeof args) { + render ({ label, options, srOnlyLabel }: typeof args) { const parsedOptions = options.split(',').map(option => option.trim()) return html` - + ${parsedOptions.map((option, index) => { const indent = index === 0 ? '' : ' ' diff --git a/src/lib/components/form-control-component/FormControlComponent.ts b/src/lib/components/form-control-component/FormControlComponent.ts index 9a5e0b4da..5cacef32d 100644 --- a/src/lib/components/form-control-component/FormControlComponent.ts +++ b/src/lib/components/form-control-component/FormControlComponent.ts @@ -8,6 +8,9 @@ export default abstract class FormControlComponent${this.target.label}` + ? html`` : nothing } diff --git a/src/styles/theme.css b/src/styles/theme.css index 7a8fe5a17..933f25f89 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -8,6 +8,7 @@ --solid-ui-color-primary: #7c4dff; --solid-ui-color-primary-hover: var(--solid-ui-color-lavender-300); --solid-ui-color-primary-selected: var(--solid-ui-color-lavender-400); + --solid-ui-color-blue-900: #083575; --solid-ui-color-tertiary: #083575; --solid-ui-color-white: #ffffff; --solid-ui-color-error: #b00020; @@ -19,6 +20,7 @@ --solid-ui-color-gray-600: #4a5565; --solid-ui-color-gray-700: #364153; --solid-ui-color-gray-800: #101828; + --solid-ui-color-gray-900: #101828; --solid-ui-color-slate-50: #f8fafc; --solid-ui-color-slate-200: #e2e8f0; @@ -28,6 +30,8 @@ --solid-ui-color-lavender-300: #e6dcff; --solid-ui-color-lavender-400: #cbb9ff; + --solid-ui-color-violet-50: #F5F3FF; + --solid-ui-color-red-500: #fb2c36; --solid-ui-color-body-grey: #666; diff --git a/src/types/custom-elements.d.ts b/src/types/custom-elements.d.ts index 62587eafe..71915f251 100644 --- a/src/types/custom-elements.d.ts +++ b/src/types/custom-elements.d.ts @@ -1,3 +1,4 @@ +import type AccessControlModal from '../components/access-control-modal/AccessControlModal' import type Account from '../components/account/Account' import type Avatar from '../components/avatar/Avatar' import type Button from '../components/button/Button' @@ -28,6 +29,7 @@ import type SolidEmblem from '../components/solid-emblem/SolidEmblem' declare global { interface HTMLElementTagNameMap { + 'solid-ui-access-control-modal': AccessControlModal 'solid-ui-account': Account 'solid-ui-avatar': Avatar 'solid-ui-button': Button diff --git a/test/components/access-control-modal.test.ts b/test/components/access-control-modal.test.ts new file mode 100644 index 000000000..50fd12b33 --- /dev/null +++ b/test/components/access-control-modal.test.ts @@ -0,0 +1,59 @@ +// @vitest-environment jsdom + +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const planGrant = vi.fn() +const planRevoke = vi.fn() +const applyPlan = vi.fn() + +vi.mock('solid-logic', async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + solidLogicSingleton: { + ...actual.solidLogicSingleton, + acl: { + ...actual.solidLogicSingleton.acl, + planGrant, + planRevoke, + applyPlan + }, + resource: { + ...actual.solidLogicSingleton.resource, + isWebId: vi.fn(() => true) + } + } + } +}) + +describe('AccessControlModal submit', () => { + beforeEach(async () => { + planGrant.mockReset() + planRevoke.mockReset() + applyPlan.mockReset() + planGrant.mockResolvedValue({ target: 'https://example.com/resource.ttl.acl', deletes: [], inserts: [] }) + applyPlan.mockResolvedValue(new Response('ok', { status: 200 })) + await import('../../src/components/access-control-modal/AccessControlModal') + }) + + it('applies a grant plan and clears the loading state after save', async () => { + const element = document.createElement('solid-ui-access-control-modal') as any + element.subjectUri = 'https://example.com/resource.ttl' + element.principleInputValue = 'https://alice.example.com/profile/card.ttl#me' + element.roleValue = 'Editor' + + await element.onSubmit(new Event('submit')) + + expect(planGrant).toHaveBeenCalledTimes(1) + expect(applyPlan).toHaveBeenCalledTimes(1) + expect(element.submitting).toBe(false) + expect(element.principleInputValue).toBe('') + }) + + it('uses the default share title when no subject uri is present', async () => { + const element = document.createElement('solid-ui-access-control-modal') as any + + expect(element.getDialogTitle()).toBe('Share this resource') + }) +}) \ No newline at end of file