From 538e36f94904e19ccc2635799a3c59c33bed39bc Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 11:37:06 -0400 Subject: [PATCH 01/14] chore(a11y): add eslint-plugin-jsx-a11y and fix violations --- widget/eslint.config.js | 8 + widget/package.json | 9 +- widget/pnpm-lock.yaml | 1042 +++++++++++++++++ .../components/__tests__/ChatMessage.test.tsx | 12 + 4 files changed, 1067 insertions(+), 4 deletions(-) diff --git a/widget/eslint.config.js b/widget/eslint.config.js index f5bd391..0009b7a 100644 --- a/widget/eslint.config.js +++ b/widget/eslint.config.js @@ -2,6 +2,7 @@ import js from "@eslint/js"; import tseslint from "typescript-eslint"; import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; +import jsxA11y from "eslint-plugin-jsx-a11y"; import prettier from "eslint-config-prettier"; export default tseslint.config( @@ -12,13 +13,20 @@ export default tseslint.config( plugins: { "react-hooks": reactHooks, "react-refresh": reactRefresh, + "jsx-a11y": jsxA11y, }, rules: { ...reactHooks.configs.recommended.rules, + ...jsxA11y.flatConfigs.recommended.rules, "react-refresh/only-export-components": [ "warn", { allowConstantExport: true }, ], + "jsx-a11y/click-events-have-key-events": "error", + "jsx-a11y/no-static-element-interactions": "error", + "jsx-a11y/label-has-associated-control": "error", + "jsx-a11y/aria-role": "error", + "jsx-a11y/role-supports-aria-props": "error", }, }, prettier, diff --git a/widget/package.json b/widget/package.json index 40c546f..44679fc 100644 --- a/widget/package.json +++ b/widget/package.json @@ -39,6 +39,7 @@ "react-dom": "^18.3.0" }, "devDependencies": { + "@eslint/js": "^9.24.0", "@testing-library/jest-dom": "^6.6.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.0", @@ -47,16 +48,16 @@ "@vitejs/plugin-react": "^4.4.0", "autoprefixer": "^10.4.0", "eslint": "^9.24.0", - "@eslint/js": "^9.24.0", - "typescript-eslint": "^8.30.0", + "eslint-config-prettier": "^10.1.0", + "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", - "prettier": "^3.5.0", - "eslint-config-prettier": "^10.1.0", "jsdom": "^26.0.0", "postcss": "^8.5.0", + "prettier": "^3.5.0", "tailwindcss": "^3.4.0", "typescript": "^5.8.0", + "typescript-eslint": "^8.30.0", "vite": "^6.2.0", "vitest": "^4.1.0" } diff --git a/widget/pnpm-lock.yaml b/widget/pnpm-lock.yaml index 3b94fd8..de5c8eb 100644 --- a/widget/pnpm-lock.yaml +++ b/widget/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: eslint-config-prettier: specifier: ^10.1.0 version: 10.1.8(eslint@9.39.4(jiti@1.21.7)) + eslint-plugin-jsx-a11y: + specifier: ^6.10.2 + version: 6.10.2(eslint@9.39.4(jiti@1.21.7)) eslint-plugin-react-hooks: specifier: ^5.2.0 version: 5.2.0(eslint@9.39.4(jiti@1.21.7)) @@ -782,10 +785,37 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + autoprefixer@10.4.27: resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} engines: {node: ^10 || ^12 || >=14} @@ -793,6 +823,18 @@ packages: peerDependencies: postcss: ^8.1.0 + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.3: + resolution: {integrity: sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -825,6 +867,18 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -884,10 +938,25 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -903,6 +972,14 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -919,16 +996,51 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + electron-to-chromium@1.5.321: resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -948,6 +1060,12 @@ packages: peerDependencies: eslint: '>=7.0.0' + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + eslint-plugin-react-hooks@5.2.0: resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} engines: {node: '>=10'} @@ -1056,6 +1174,10 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -1067,10 +1189,33 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1083,10 +1228,37 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1127,22 +1299,74 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1150,6 +1374,45 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1192,9 +1455,20 @@ packages: engines: {node: '>=6'} hasBin: true + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1230,6 +1504,10 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1281,6 +1559,26 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -1288,6 +1586,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1336,6 +1638,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -1430,6 +1736,14 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1454,6 +1768,18 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1473,6 +1799,18 @@ packages: engines: {node: '>=10'} hasBin: true + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1481,6 +1819,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1494,6 +1848,26 @@ packages: std-env@4.0.0: resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -1577,6 +1951,22 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + typescript-eslint@8.57.1: resolution: {integrity: sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1589,6 +1979,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -1697,6 +2091,22 @@ packages: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2393,8 +2803,52 @@ snapshots: aria-query@5.3.2: {} + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} + + async-function@1.0.0: {} + autoprefixer@10.4.27(postcss@8.5.8): dependencies: browserslist: 4.28.1 @@ -2404,6 +2858,14 @@ snapshots: postcss: 8.5.8 postcss-value-parser: 4.2.0 + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.3: {} + + axobject-query@4.1.0: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -2433,6 +2895,23 @@ snapshots: node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -2487,11 +2966,31 @@ snapshots: csstype@3.2.3: {} + damerau-levenshtein@1.0.8: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2500,6 +2999,18 @@ snapshots: deep-is@0.1.4: {} + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + dequal@2.0.3: {} didyoumean@1.2.2: {} @@ -2510,12 +3021,102 @@ snapshots: dom-accessibility-api@0.6.3: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + electron-to-chromium@1.5.321: {} + emoji-regex@9.2.2: {} + entities@6.0.1: {} + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -2553,6 +3154,25 @@ snapshots: dependencies: eslint: 9.39.4(jiti@1.21.7) + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@1.21.7)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.3 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.4(jiti@1.21.7) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + eslint-plugin-react-hooks@5.2.0(eslint@9.39.4(jiti@1.21.7)): dependencies: eslint: 9.39.4(jiti@1.21.7) @@ -2679,6 +3299,10 @@ snapshots: flatted@3.4.2: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + fraction.js@5.3.4: {} fsevents@2.3.3: @@ -2686,8 +3310,45 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2698,8 +3359,31 @@ snapshots: globals@14.0.0: {} + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + has-bigints@1.1.0: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -2739,24 +3423,128 @@ snapshots: indent-string@4.0.0: {} + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + isexe@2.0.0: {} jiti@1.21.7: {} @@ -2804,10 +3592,23 @@ snapshots: json5@2.2.3: {} + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -2839,6 +3640,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + math-intrinsics@1.1.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -2878,6 +3681,33 @@ snapshots: object-hash@3.0.0: {} + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + obug@2.1.1: {} optionator@0.9.4: @@ -2889,6 +3719,12 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -2923,6 +3759,8 @@ snapshots: pirates@4.0.7: {} + possible-typed-array-names@1.1.0: {} + postcss-import@15.1.0(postcss@8.5.8): dependencies: postcss: 8.5.8 @@ -3001,6 +3839,26 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + resolve-from@4.0.0: {} resolve@1.22.11: @@ -3048,6 +3906,25 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + safer-buffer@2.1.2: {} saxes@6.0.0: @@ -3062,12 +3939,62 @@ snapshots: semver@7.7.4: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} source-map-js@1.2.1: {} @@ -3076,6 +4003,40 @@ snapshots: std-env@4.0.0: {} + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -3175,6 +4136,39 @@ snapshots: dependencies: prelude-ls: 1.2.1 + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + typescript-eslint@8.57.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) @@ -3188,6 +4182,13 @@ snapshots: typescript@5.9.3: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -3256,6 +4257,47 @@ snapshots: tr46: 5.1.1 webidl-conversions: 7.0.0 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/widget/src/components/__tests__/ChatMessage.test.tsx b/widget/src/components/__tests__/ChatMessage.test.tsx index 8879e0b..3fce333 100644 --- a/widget/src/components/__tests__/ChatMessage.test.tsx +++ b/widget/src/components/__tests__/ChatMessage.test.tsx @@ -11,6 +11,7 @@ const mockSources: Source[] = [ describe("ChatMessage", () => { it("renders user message with correct styling", () => { + // eslint-disable-next-line jsx-a11y/aria-role -- `role` here is a ChatMessage component prop, not an ARIA role attribute render(); const bubble = screen.getByText("Hello!"); expect(bubble).toBeInTheDocument(); @@ -20,6 +21,7 @@ describe("ChatMessage", () => { }); it("renders assistant message with correct styling", () => { + // eslint-disable-next-line jsx-a11y/aria-role -- `role` here is a ChatMessage component prop, not an ARIA role attribute render(); const bubble = screen.getByText("How can I help?"); expect(bubble).toBeInTheDocument(); @@ -31,6 +33,7 @@ describe("ChatMessage", () => { it("renders links as clickable anchors", () => { render( @@ -44,6 +47,7 @@ describe("ChatMessage", () => { it("renders source icon for assistant messages with sources", () => { render( { it("does not render source icon for user messages", () => { render( { it("does not render source icon when no sources", () => { render( + // eslint-disable-next-line jsx-a11y/aria-role -- `role` here is a ChatMessage component prop, not an ARIA role attribute ); expect(screen.queryByRole("button", { name: /view sources/i })).not.toBeInTheDocument(); @@ -80,6 +86,7 @@ describe("ChatMessage", () => { const onSourceClick = vi.fn(); render( { it("renders script tags as plain text", () => { render( @@ -106,6 +114,7 @@ describe("ChatMessage", () => { it("renders HTML tags as plain text", () => { render( @@ -116,6 +125,7 @@ describe("ChatMessage", () => { it("does not create links from javascript: URLs", () => { render( @@ -127,6 +137,7 @@ describe("ChatMessage", () => { it("safely handles URL-like text with malicious schemes", () => { render( @@ -138,6 +149,7 @@ describe("ChatMessage", () => { it("renders safe https URLs as clickable links", () => { render( From 422dc408f15423feb2c154a8ae32ee7185e48645 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 11:43:35 -0400 Subject: [PATCH 02/14] chore(a11y): use ignoreNonDOM on aria-role instead of line-level disables --- widget/eslint.config.js | 2 +- widget/src/components/__tests__/ChatMessage.test.tsx | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/widget/eslint.config.js b/widget/eslint.config.js index 0009b7a..c03d7e7 100644 --- a/widget/eslint.config.js +++ b/widget/eslint.config.js @@ -25,7 +25,7 @@ export default tseslint.config( "jsx-a11y/click-events-have-key-events": "error", "jsx-a11y/no-static-element-interactions": "error", "jsx-a11y/label-has-associated-control": "error", - "jsx-a11y/aria-role": "error", + "jsx-a11y/aria-role": ["error", { ignoreNonDOM: true }], "jsx-a11y/role-supports-aria-props": "error", }, }, diff --git a/widget/src/components/__tests__/ChatMessage.test.tsx b/widget/src/components/__tests__/ChatMessage.test.tsx index 3fce333..8879e0b 100644 --- a/widget/src/components/__tests__/ChatMessage.test.tsx +++ b/widget/src/components/__tests__/ChatMessage.test.tsx @@ -11,7 +11,6 @@ const mockSources: Source[] = [ describe("ChatMessage", () => { it("renders user message with correct styling", () => { - // eslint-disable-next-line jsx-a11y/aria-role -- `role` here is a ChatMessage component prop, not an ARIA role attribute render(); const bubble = screen.getByText("Hello!"); expect(bubble).toBeInTheDocument(); @@ -21,7 +20,6 @@ describe("ChatMessage", () => { }); it("renders assistant message with correct styling", () => { - // eslint-disable-next-line jsx-a11y/aria-role -- `role` here is a ChatMessage component prop, not an ARIA role attribute render(); const bubble = screen.getByText("How can I help?"); expect(bubble).toBeInTheDocument(); @@ -33,7 +31,6 @@ describe("ChatMessage", () => { it("renders links as clickable anchors", () => { render( @@ -47,7 +44,6 @@ describe("ChatMessage", () => { it("renders source icon for assistant messages with sources", () => { render( { it("does not render source icon for user messages", () => { render( { it("does not render source icon when no sources", () => { render( - // eslint-disable-next-line jsx-a11y/aria-role -- `role` here is a ChatMessage component prop, not an ARIA role attribute ); expect(screen.queryByRole("button", { name: /view sources/i })).not.toBeInTheDocument(); @@ -86,7 +80,6 @@ describe("ChatMessage", () => { const onSourceClick = vi.fn(); render( { it("renders script tags as plain text", () => { render( @@ -114,7 +106,6 @@ describe("ChatMessage", () => { it("renders HTML tags as plain text", () => { render( @@ -125,7 +116,6 @@ describe("ChatMessage", () => { it("does not create links from javascript: URLs", () => { render( @@ -137,7 +127,6 @@ describe("ChatMessage", () => { it("safely handles URL-like text with malicious schemes", () => { render( @@ -149,7 +138,6 @@ describe("ChatMessage", () => { it("renders safe https URLs as clickable links", () => { render( From 6ec6de737bf1553b3ce9fb39f6f0fa87e2393576 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 11:47:15 -0400 Subject: [PATCH 03/14] feat(a11y): add dialogLabel and newMessageAnnouncement translations --- widget/src/__tests__/i18n.test.ts | 18 ++++++++++++++++++ widget/src/i18n.ts | 8 ++++++++ 2 files changed, 26 insertions(+) create mode 100644 widget/src/__tests__/i18n.test.ts diff --git a/widget/src/__tests__/i18n.test.ts b/widget/src/__tests__/i18n.test.ts new file mode 100644 index 0000000..21170fd --- /dev/null +++ b/widget/src/__tests__/i18n.test.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from "vitest"; +import { defaultTranslations, createTranslations } from "../i18n"; + +describe("i18n", () => { + it("includes dialogLabel and newMessageAnnouncement defaults", () => { + expect(defaultTranslations.dialogLabel).toBe("Chat dialog"); + expect(defaultTranslations.newMessageAnnouncement).toBe("New message from assistant"); + }); + + it("allows overriding the new strings", () => { + const t = createTranslations({ + dialogLabel: "Support chat", + newMessageAnnouncement: "Reply received", + }); + expect(t.dialogLabel).toBe("Support chat"); + expect(t.newMessageAnnouncement).toBe("Reply received"); + }); +}); diff --git a/widget/src/i18n.ts b/widget/src/i18n.ts index ef13640..0941967 100644 --- a/widget/src/i18n.ts +++ b/widget/src/i18n.ts @@ -19,6 +19,10 @@ export interface ClaudiusTranslations { errorConnection: string; errorRateLimitMinute: string; errorRateLimitHour: string; + + // Dialog / a11y + dialogLabel: string; + newMessageAnnouncement: string; } export const defaultTranslations: ClaudiusTranslations = { @@ -42,6 +46,10 @@ export const defaultTranslations: ClaudiusTranslations = { errorConnection: "Failed to connect. Please try again.", errorRateLimitMinute: "Too many requests. Please wait a minute.", errorRateLimitHour: "Hourly limit reached. Please try again later.", + + // Dialog / a11y + dialogLabel: "Chat dialog", + newMessageAnnouncement: "New message from assistant", }; export function createTranslations( From 610ad1c304044dc52423e08ad2dce8bc680b5a78 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 11:52:18 -0400 Subject: [PATCH 04/14] feat(a11y): make ChatWindow a labelled dialog --- widget/src/components/ChatWindow.tsx | 8 ++++++-- .../components/__tests__/ChatWindow.test.tsx | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index 3d7348c..cd15a2c 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useId, useRef, useState } from "react"; import { ChatMessage } from "./ChatMessage"; import { ChatInput } from "./ChatInput"; import { ChatSources } from "./ChatSources"; @@ -55,6 +55,7 @@ export function ChatWindow({ translations, isMobile = false, }: ChatWindowProps) { + const titleId = useId(); const messagesContainerRef = useRef(null); const [activeSources, setActiveSources] = useState<{ messageId: string; sources: Source[] } | null>(null); @@ -77,6 +78,9 @@ export function ChatWindow({ return (
-

+

{title}

{subtitle}

diff --git a/widget/src/components/__tests__/ChatWindow.test.tsx b/widget/src/components/__tests__/ChatWindow.test.tsx index 647fd7b..64035ba 100644 --- a/widget/src/components/__tests__/ChatWindow.test.tsx +++ b/widget/src/components/__tests__/ChatWindow.test.tsx @@ -193,3 +193,20 @@ describe("ChatWindow - mobile bottom sheet", () => { expect(handle).toBeInTheDocument(); }); }); + +describe("ChatWindow - dialog semantics", () => { + it("has role=dialog and aria-modal=true", () => { + render( + + ); + const dialog = screen.getByRole("dialog"); + expect(dialog).toHaveAttribute("aria-modal", "true"); + }); + + it("is labelled by the title heading via aria-labelledby", () => { + render( + + ); + expect(screen.getByRole("dialog", { name: "Support" })).toBeInTheDocument(); + }); +}); From 8b29e609488bf2d43cced3cd4a744865c1338cab Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 11:57:06 -0400 Subject: [PATCH 05/14] fix(a11y): make aria-modal conditional on isMobile (honest claim) --- widget/src/components/ChatWindow.tsx | 2 +- .../src/components/__tests__/ChatWindow.test.tsx | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index cd15a2c..5b33b81 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -79,7 +79,7 @@ export function ChatWindow({ return (
{ }); describe("ChatWindow - dialog semantics", () => { - it("has role=dialog and aria-modal=true", () => { + it("sets aria-modal=true on mobile (scrim blocks background)", () => { render( - + ); - const dialog = screen.getByRole("dialog"); - expect(dialog).toHaveAttribute("aria-modal", "true"); + expect(screen.getByRole("dialog")).toHaveAttribute("aria-modal", "true"); + }); + + it("omits aria-modal on desktop (background remains interactive)", () => { + render( + + ); + expect(screen.getByRole("dialog")).not.toHaveAttribute("aria-modal"); }); it("is labelled by the title heading via aria-labelledby", () => { From d63eb4b7fd76a35dd17162f845b0c9a1c5c14c39 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:01:01 -0400 Subject: [PATCH 06/14] feat(a11y): close chat on Escape key --- widget/src/components/ChatWindow.tsx | 11 ++++++++++ .../components/__tests__/ChatWindow.test.tsx | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index 5b33b81..5f7baf5 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -73,6 +73,17 @@ export function ChatWindow({ } }, [messages, isLoading]); + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.stopPropagation(); + onClose(); + } + }; + document.addEventListener("keydown", handler); + return () => document.removeEventListener("keydown", handler); + }, [onClose]); + const closeLabel = translations?.closeChat ?? "Close chat"; const messagesLabel = translations?.chatMessages ?? "Chat messages"; diff --git a/widget/src/components/__tests__/ChatWindow.test.tsx b/widget/src/components/__tests__/ChatWindow.test.tsx index 5012a90..09e7133 100644 --- a/widget/src/components/__tests__/ChatWindow.test.tsx +++ b/widget/src/components/__tests__/ChatWindow.test.tsx @@ -216,3 +216,25 @@ describe("ChatWindow - dialog semantics", () => { expect(screen.getByRole("dialog", { name: "Support" })).toBeInTheDocument(); }); }); + +describe("ChatWindow - keyboard", () => { + it("calls onClose when Escape is pressed", async () => { + const onClose = vi.fn(); + const user = userEvent.setup(); + render( + + ); + await user.keyboard("{Escape}"); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it("does not call onClose for other keys", async () => { + const onClose = vi.fn(); + const user = userEvent.setup(); + render( + + ); + await user.keyboard("a"); + expect(onClose).not.toHaveBeenCalled(); + }); +}); From aa54c7d92022a6cf0507193dca7b0948c8090941 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:06:05 -0400 Subject: [PATCH 07/14] fix(a11y): ignore Escape during IME composition; drop unused stopPropagation --- widget/src/components/ChatWindow.tsx | 3 +-- widget/src/components/__tests__/ChatWindow.test.tsx | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index 5f7baf5..be1d14d 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -75,8 +75,7 @@ export function ChatWindow({ useEffect(() => { const handler = (e: KeyboardEvent) => { - if (e.key === "Escape") { - e.stopPropagation(); + if (e.key === "Escape" && !e.isComposing) { onClose(); } }; diff --git a/widget/src/components/__tests__/ChatWindow.test.tsx b/widget/src/components/__tests__/ChatWindow.test.tsx index 09e7133..f12c27f 100644 --- a/widget/src/components/__tests__/ChatWindow.test.tsx +++ b/widget/src/components/__tests__/ChatWindow.test.tsx @@ -237,4 +237,16 @@ describe("ChatWindow - keyboard", () => { await user.keyboard("a"); expect(onClose).not.toHaveBeenCalled(); }); + + it("does not call onClose when Escape is pressed during IME composition", () => { + const onClose = vi.fn(); + render( + + ); + // Dispatch a keydown with isComposing: true — user is mid-IME, Escape should cancel the composition, not close the widget + const event = new KeyboardEvent("keydown", { key: "Escape", bubbles: true }); + Object.defineProperty(event, "isComposing", { value: true }); + document.dispatchEvent(event); + expect(onClose).not.toHaveBeenCalled(); + }); }); From 1320ed26b6e9d40c0c7d5a5af5fa5ccb5e564ad5 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:10:14 -0400 Subject: [PATCH 08/14] feat(a11y): add useFocusTrap hook --- .../src/hooks/__tests__/useFocusTrap.test.tsx | 47 +++++++++++++++++++ widget/src/hooks/useFocusTrap.ts | 37 +++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 widget/src/hooks/__tests__/useFocusTrap.test.tsx create mode 100644 widget/src/hooks/useFocusTrap.ts diff --git a/widget/src/hooks/__tests__/useFocusTrap.test.tsx b/widget/src/hooks/__tests__/useFocusTrap.test.tsx new file mode 100644 index 0000000..47bed03 --- /dev/null +++ b/widget/src/hooks/__tests__/useFocusTrap.test.tsx @@ -0,0 +1,47 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, it, expect } from "vitest"; +import { useRef } from "react"; +import { useFocusTrap } from "../useFocusTrap"; + +function Harness({ active }: { active: boolean }) { + const ref = useRef(null); + useFocusTrap(ref, active); + return ( + <> + +
+ + + +
+ + + ); +} + +describe("useFocusTrap", () => { + it("cycles forward from last to first on Tab", async () => { + const user = userEvent.setup(); + render(); + screen.getByText("last").focus(); + await user.tab(); + expect(screen.getByText("first")).toHaveFocus(); + }); + + it("cycles backward from first to last on Shift+Tab", async () => { + const user = userEvent.setup(); + render(); + screen.getByText("first").focus(); + await user.tab({ shift: true }); + expect(screen.getByText("last")).toHaveFocus(); + }); + + it("does nothing when inactive", async () => { + const user = userEvent.setup(); + render(); + screen.getByText("last").focus(); + await user.tab(); + expect(screen.getByText("outside-after")).toHaveFocus(); + }); +}); diff --git a/widget/src/hooks/useFocusTrap.ts b/widget/src/hooks/useFocusTrap.ts new file mode 100644 index 0000000..152dd71 --- /dev/null +++ b/widget/src/hooks/useFocusTrap.ts @@ -0,0 +1,37 @@ +import { useEffect, type RefObject } from "react"; + +const FOCUSABLE = + 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'; + +export function useFocusTrap( + containerRef: RefObject, + active: boolean +) { + useEffect(() => { + if (!active) return; + const container = containerRef.current; + if (!container) return; + + const handler = (e: KeyboardEvent) => { + if (e.key !== "Tab") return; + const focusables = Array.from( + container.querySelectorAll(FOCUSABLE) + ).filter((el) => !el.hasAttribute("aria-hidden")); + if (focusables.length === 0) return; + const first = focusables[0]; + const last = focusables[focusables.length - 1]; + const activeEl = document.activeElement as HTMLElement | null; + + if (e.shiftKey && activeEl === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && activeEl === last) { + e.preventDefault(); + first.focus(); + } + }; + + container.addEventListener("keydown", handler); + return () => container.removeEventListener("keydown", handler); + }, [containerRef, active]); +} From 47b155883d65192a4b4645cc5f55e75329a4fdcd Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:15:40 -0400 Subject: [PATCH 09/14] feat(a11y): trap focus and focus input on open --- widget/src/components/ChatWindow.tsx | 12 ++++++++++ .../components/__tests__/ChatWindow.test.tsx | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index be1d14d..8cefc88 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -3,6 +3,7 @@ import { ChatMessage } from "./ChatMessage"; import { ChatInput } from "./ChatInput"; import { ChatSources } from "./ChatSources"; import { useSwipeToDismiss } from "../hooks/useSwipeToDismiss"; +import { useFocusTrap } from "../hooks/useFocusTrap"; import type { WidgetPosition } from "./ChatWidget"; import type { ClaudiusTranslations } from "../i18n"; import type { ChatMessage as ChatMessageData, Source } from "../api/types"; @@ -56,9 +57,19 @@ export function ChatWindow({ isMobile = false, }: ChatWindowProps) { const titleId = useId(); + const dialogRef = useRef(null); const messagesContainerRef = useRef(null); const [activeSources, setActiveSources] = useState<{ messageId: string; sources: Source[] } | null>(null); + useFocusTrap(dialogRef, true); + + useEffect(() => { + const input = dialogRef.current?.querySelector( + 'input[type="text"]' + ); + input?.focus(); + }, []); + const { offsetY } = useSwipeToDismiss(messagesContainerRef, onClose, isMobile); const isDragging = offsetY !== 0; const reducedMotion = @@ -88,6 +99,7 @@ export function ChatWindow({ return (
{ document.dispatchEvent(event); expect(onClose).not.toHaveBeenCalled(); }); + + it("focuses the message input on mount", async () => { + render( + + ); + expect(await screen.findByLabelText(/type your message/i)).toHaveFocus(); + }); + + it("traps tab within the dialog", async () => { + const user = userEvent.setup(); + render( + <> + + + + + ); + // Tab several times and confirm focus stays inside the dialog + for (let i = 0; i < 8; i++) await user.tab(); + const dialog = screen.getByRole("dialog"); + expect(dialog.contains(document.activeElement)).toBe(true); + }); }); From 37abd8b801fe516ec5e97e9fd7e8fc241f6456d7 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:20:25 -0400 Subject: [PATCH 10/14] refactor(a11y): remove redundant mount-focus effect (ChatInput already handles it) --- widget/src/components/ChatWindow.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index 8cefc88..218cf5f 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -63,13 +63,6 @@ export function ChatWindow({ useFocusTrap(dialogRef, true); - useEffect(() => { - const input = dialogRef.current?.querySelector( - 'input[type="text"]' - ); - input?.focus(); - }, []); - const { offsetY } = useSwipeToDismiss(messagesContainerRef, onClose, isMobile); const isDragging = offsetY !== 0; const reducedMotion = From 4bcac15c2d9291ea6a37c15f561934d2e6f68a63 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:25:26 -0400 Subject: [PATCH 11/14] feat(a11y): announce new assistant messages via dedicated live region --- widget/src/components/ChatWindow.tsx | 12 ++++++++- .../components/__tests__/ChatWidget.test.tsx | 12 +++++---- .../components/__tests__/ChatWindow.test.tsx | 26 ++++++++++++++++--- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index 218cf5f..254e755 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -89,6 +89,7 @@ export function ChatWindow({ const closeLabel = translations?.closeChat ?? "Close chat"; const messagesLabel = translations?.chatMessages ?? "Chat messages"; + const lastAssistantMessage = [...messages].reverse().find((m) => m.role === "assistant"); return (
@@ -206,6 +206,16 @@ export function ChatWindow({
+ {/* Dedicated live region for new assistant messages */} +
+ {lastAssistantMessage?.content ?? ""} +
+ {/* Input */} { const input = screen.getByPlaceholderText(/type your message/i); await user.type(input, "Hi{enter}"); - // Wait for reply - expect(await screen.findByText("Hello!")).toBeInTheDocument(); + // Wait for reply (scope to log to avoid matching sr-only live region) + await screen.findByRole("log"); + expect(await within(screen.getByRole("log")).findByText("Hello!")).toBeInTheDocument(); // Close via header button and reopen const closeButtons = screen.getAllByRole("button", { name: /close chat/i }); @@ -59,8 +60,9 @@ describe("ChatWidget", () => { await user.click(screen.getByRole("button", { name: /open chat/i })); // Messages should still be there - expect(screen.getByText("Hi")).toBeInTheDocument(); - expect(screen.getByText("Hello!")).toBeInTheDocument(); + const log = screen.getByRole("log"); + expect(within(log).getByText("Hi")).toBeInTheDocument(); + expect(within(log).getByText("Hello!")).toBeInTheDocument(); }); }); diff --git a/widget/src/components/__tests__/ChatWindow.test.tsx b/widget/src/components/__tests__/ChatWindow.test.tsx index ddbdabb..4f693a4 100644 --- a/widget/src/components/__tests__/ChatWindow.test.tsx +++ b/widget/src/components/__tests__/ChatWindow.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, it, expect, vi } from "vitest"; import { ChatWindow } from "../ChatWindow"; @@ -29,8 +29,9 @@ describe("ChatWindow", () => { onSend={vi.fn()} onClose={vi.fn()} /> ); - expect(screen.getByText("What are your prices?")).toBeInTheDocument(); - expect(screen.getByText(/Prices start at \$1,000/)).toBeInTheDocument(); + const log = screen.getByRole("log"); + expect(within(log).getByText("What are your prices?")).toBeInTheDocument(); + expect(within(log).getByText(/Prices start at \$1,000/)).toBeInTheDocument(); }); it("shows typing indicator when loading", () => { @@ -146,6 +147,25 @@ describe("ChatWindow", () => { ); expect(screen.getByText("Chat")).toBeInTheDocument(); }); + + it("announces the latest assistant message via a polite live region", async () => { + const { rerender } = render( + + ); + rerender( + + ); + const liveRegion = document.querySelector('[data-claudius-live="assistant"]'); + expect(liveRegion).toHaveAttribute("aria-live", "polite"); + expect(liveRegion?.textContent).toContain("Hello there!"); + }); }); describe("ChatWindow - mobile bottom sheet", () => { From dafdc640de3c7a6e144b446108dd9c098c95ce34 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:32:26 -0400 Subject: [PATCH 12/14] fix(a11y): strip markdown and collapse URLs in live-region announcements --- widget/src/components/ChatWindow.tsx | 9 +++++-- .../components/__tests__/ChatWindow.test.tsx | 14 +++++++++++ .../stripAnnouncementFormatting.test.ts | 25 +++++++++++++++++++ .../src/utils/stripAnnouncementFormatting.ts | 21 ++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 widget/src/utils/__tests__/stripAnnouncementFormatting.test.ts create mode 100644 widget/src/utils/stripAnnouncementFormatting.ts diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index 254e755..08bf894 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -4,6 +4,7 @@ import { ChatInput } from "./ChatInput"; import { ChatSources } from "./ChatSources"; import { useSwipeToDismiss } from "../hooks/useSwipeToDismiss"; import { useFocusTrap } from "../hooks/useFocusTrap"; +import { stripAnnouncementFormatting } from "../utils/stripAnnouncementFormatting"; import type { WidgetPosition } from "./ChatWidget"; import type { ClaudiusTranslations } from "../i18n"; import type { ChatMessage as ChatMessageData, Source } from "../api/types"; @@ -206,14 +207,18 @@ export function ChatWindow({
- {/* Dedicated live region for new assistant messages */} + {/* Dedicated live region for new assistant messages. + Outside `role="log"` so typing indicator / sources panel mutations + don't trigger redundant announcements. aria-atomic forces the full + reply to be read; stripAnnouncementFormatting removes markdown + markers and collapses URLs to hostnames for SR-friendliness. */}
- {lastAssistantMessage?.content ?? ""} + {lastAssistantMessage ? stripAnnouncementFormatting(lastAssistantMessage.content) : ""}
{/* Input */} diff --git a/widget/src/components/__tests__/ChatWindow.test.tsx b/widget/src/components/__tests__/ChatWindow.test.tsx index 4f693a4..071ca7c 100644 --- a/widget/src/components/__tests__/ChatWindow.test.tsx +++ b/widget/src/components/__tests__/ChatWindow.test.tsx @@ -166,6 +166,20 @@ describe("ChatWindow", () => { expect(liveRegion).toHaveAttribute("aria-live", "polite"); expect(liveRegion?.textContent).toContain("Hello there!"); }); + + it("strips markdown markers from live-region announcements", () => { + const { rerender } = render( + + ); + rerender( + + ); + const liveRegion = document.querySelector('[data-claudius-live="assistant"]'); + expect(liveRegion?.textContent).toBe("Visit pmds at pmds.info today"); + }); }); describe("ChatWindow - mobile bottom sheet", () => { diff --git a/widget/src/utils/__tests__/stripAnnouncementFormatting.test.ts b/widget/src/utils/__tests__/stripAnnouncementFormatting.test.ts new file mode 100644 index 0000000..da414a8 --- /dev/null +++ b/widget/src/utils/__tests__/stripAnnouncementFormatting.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from "vitest"; +import { stripAnnouncementFormatting } from "../stripAnnouncementFormatting"; + +describe("stripAnnouncementFormatting", () => { + it("removes bold markers", () => { + expect(stripAnnouncementFormatting("Visit **pmds** today")).toBe("Visit pmds today"); + }); + it("removes italic markers", () => { + expect(stripAnnouncementFormatting("See *more details* here")).toBe("See more details here"); + }); + it("strips bold and italic in the same string", () => { + expect(stripAnnouncementFormatting("**Bold** and *italic*")).toBe("Bold and italic"); + }); + it("replaces URLs with the hostname", () => { + expect(stripAnnouncementFormatting("Read https://pmds.info/blog/seo-tips for more.")).toBe( + "Read pmds.info for more." + ); + }); + it("leaves plain text unchanged", () => { + expect(stripAnnouncementFormatting("Hello there!")).toBe("Hello there!"); + }); + it("returns empty string for empty input", () => { + expect(stripAnnouncementFormatting("")).toBe(""); + }); +}); diff --git a/widget/src/utils/stripAnnouncementFormatting.ts b/widget/src/utils/stripAnnouncementFormatting.ts new file mode 100644 index 0000000..a24c09b --- /dev/null +++ b/widget/src/utils/stripAnnouncementFormatting.ts @@ -0,0 +1,21 @@ +/** + * Strips markdown-style formatting from message content so screen readers + * don't announce literal asterisks or long URLs. Used only for live-region + * announcements; visual rendering keeps the markers for formatting. + */ +const BOLD = /\*\*([^*]+)\*\*/g; +const ITALIC = /\*([^*]+)\*/g; +const URL_PATTERN = /https?:\/\/[^\s)]+/g; + +export function stripAnnouncementFormatting(content: string): string { + return content + .replace(BOLD, "$1") + .replace(ITALIC, "$1") + .replace(URL_PATTERN, (match) => { + try { + return new URL(match).hostname; + } catch { + return match; + } + }); +} From 44ad06189570c3f07780116d5cdd77576f9e3972 Mon Sep 17 00:00:00 2001 From: Paul Mulligan Date: Thu, 16 Apr 2026 12:37:32 -0400 Subject: [PATCH 13/14] fix(a11y): bump subtitle to full white for WCAG AA contrast (was 4.49:1) --- widget/src/components/ChatWindow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/components/ChatWindow.tsx b/widget/src/components/ChatWindow.tsx index 08bf894..57f4490 100644 --- a/widget/src/components/ChatWindow.tsx +++ b/widget/src/components/ChatWindow.tsx @@ -128,7 +128,7 @@ export function ChatWindow({

{title}

-

{subtitle}

+

{subtitle}