Compare commits

...

23 Commits

Author SHA1 Message Date
David Baker
7182b1129f Merge branch 'develop' into dbkr/remove_duplicate_step 2025-11-04 14:37:12 +00:00
RiotRobot
53dc281a24 Reset matrix-js-sdk back to develop branch 2025-11-04 14:13:04 +00:00
RiotRobot
efc5b0260b Merge branch 'master' into develop 2025-11-04 14:12:52 +00:00
RiotRobot
d4c22d43f3 v1.12.3 2025-11-04 14:09:39 +00:00
RiotRobot
926bb56723 Upgrade dependency to matrix-js-sdk@39.1.2 2025-11-04 14:03:39 +00:00
David Baker
f11d90cfd3 Remove duplicate test step
As the comment indicated, this apparently was necessary to get stuff
built the first time, but does not appear to be necessary anymore and
looks to be causing occasional flakiness, so let's remove it.
2025-11-04 12:04:21 +00:00
David Baker
3dca2da5d6 Add test tag while we're publishing test versions 2025-11-04 11:29:03 +00:00
David Baker
71604c9e19 Fx shared component publish workflow (#31161) 2025-11-04 11:06:42 +00:00
David Langley
4e57b80556 Change polls to use fieldset/legend markup (#31160)
* Change polls to use fieldset/legend markup

* Update MPollEndBody-test.tsx.snap

* Fix letter spacing in legend

* Update PollHistory-test.tsx.snap
2025-11-04 09:55:35 +00:00
ElementRobot
c14d072cb7 [create-pull-request] automated change (#31163)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-11-04 09:25:04 +00:00
David Langley
dcf3e536ab Don't allow pin or edit of messages with a send status (#31158)
* Don't allow pin or edit of messages with a send status

* lint and improve comments.
2025-11-03 17:33:18 +00:00
David Baker
18a5565b70 Bump shared components version 2025-11-03 17:25:06 +00:00
David Baker
b0cdbf5eff Make shared component build work in isolation (#31066)
* Make shared component build work in isolation

 * Add deps that were missing because they were getting picked up
   from element-web main but shared-components needs itself
 * Exclude test files from dts generation
 * Bump version

* Change all the shared-component import to be the built artifact

* Don't randomly inhale eslint configs in parent dirs please

* maybe we don't need this anymore?

* maybe fix build

* Maybe fix docker build

* More build faff

 * build:res on the parent as part of shared component prepare
 * link shared component repo inn docker build

* 💅

* 💅x2

* Try converting the translation keys to a .d.ts file manually

so it gets bundled rather than left as a relative import to the json
file

* add the script

* Add this back for 2nd time now I think

* Shouldn't need this anymore

* patch-package on prepare

because we're patching a dev dependency so it won't be there if we're
installed as a dependency

* Unused import

* Prettier compliance

* Only use counterpart from shared components

as per comment

* Import shared components CSS

* Prettier

* Call the one from shared components

rather than recurse infinitely

* Hopefully make tests work

* wake up, comment goes before import

* Fix lint errors

* Fix dupe TranslationKey export

* Update compound-web to fix type error

An update to @types.react adds the 'hint' value to the enum of the
'popover' attribute and this version of compound-web uses the maching
verson of @types/react so they don't conflict.

* Maybe, hopefully, get the types working?

Please?

* Add copyright header to i18nkeys

as eslint complains otherwise since it's now in src

* prettier

* stop running shared-component tests in EW

* update snapshots

because flex is now from an external stylesheet I guess

* More snapshots

* Manual class update

* Avoid bundling compound bits

Because a) it's silly and b) it means we end up bundling a copy of
floating-ui too which causes absolute madness with its useDelayGroup
contexts.

* ignore test util files for coverage

* Add !important

because the styles are being applied in a different order now

* Another !important because css order has changed

* Try adding it here to make the test files ignored

* More !important

* commit yarn lock change

* Add shared components coverage file

* Update snapshots

Because the line height was being overridden to 22.5px somehow by
something I can't find, and now isn't: surely the normal 1.5rem is
more sensible.

* Update snapshots, attempt 2

* Another !important

* More snapshot updates

* Add test for i18n wrappers

& add test script

* lint

* Prettier

* Hopefully run shared component tests

* don't need this bit for non-matrix

* install ew deps

* rigfht coverage location

* Rename job here too

* Try different coverage filename

* Fix copyrights & comment

* Typo

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-11-03 16:26:47 +00:00
Will Hunt
486d4d59bc Use compound Button styles for Jitsi button (#31159)
* Use compound styles for jitsi button

* lint
2025-11-03 16:02:21 +00:00
Will Hunt
54f967efd5 Hide room header buttons if the room hasn't been created yet. (#31092)
* Hide room header buttons if the room isn't ready

* update test

* update unit tests

* Remove on-click actions
2025-11-03 13:25:08 +00:00
ElementRobot
24fc018845 [create-pull-request] automated change (#31150)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-11-01 06:18:47 +00:00
David Langley
36ccc1ae9a Fix screen readers not indicating the emoji picker search field is focused. (#31128)
* Only set active descendant when the user starts typing.

* Fix jest tests.

* Remove aria-hidden

It was failing code quality checks and it actually wasn't addressing the issue.

* Only show highlight on arrow key navigation or updating the search query.

* Update screenshots

* Enter should not select an emoji if it is not highlighted.

* On clearing a query and using arrow kets again the highlighted emoji should be reset to the first.

* Update selector in picker tests
2025-10-31 17:10:02 +00:00
Michael Telatynski
017aee9a8f Fix emoji picker highlight missing when not active element (#31148)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-10-31 15:20:57 +00:00
David Langley
e0a94a05ea Add FocusLock to emoji picker (#31146)
* Add focus lock to emoji picker and e2e test.

* Remove direct use of FocusLock in favour of the ContextMenu prop.

* Apply returnFocus for ContextMenu focusLocks

* Remove import
2025-10-31 13:54:26 +00:00
renovate[bot]
23f372ca08 Update dependency eslint-plugin-react-hooks to v7.0.1 (#31103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 12:52:40 +00:00
RiotRobot
1dfd92c1eb Upgrade dependency to matrix-js-sdk@39.1.0-rc.1 2025-10-30 09:04:27 +00:00
RiotRobot
68e6b0f845 v1.12.3-rc.0 2025-10-28 15:07:21 +00:00
RiotRobot
d426063a22 Upgrade dependency to matrix-js-sdk@39.1.0-rc.0 2025-10-28 14:05:15 +00:00
158 changed files with 2193 additions and 911 deletions

View File

@@ -26,6 +26,10 @@ jobs:
- name: Update npm
run: npm install -g npm@latest
# Need to setup element web too as it needs the translations
- name: 🛠️ Setup EW
run: yarn install --pure-lockfile
- name: 🛠️ Setup
# When running `install` it also calls the `prepare` step which generates
# a build
@@ -33,4 +37,4 @@ jobs:
- name: 🚀 Publish to npm
working-directory: packages/shared-components
run: npm publish --access public --provenance
run: npm publish --access public --tag test --provenance

View File

@@ -34,10 +34,6 @@ jobs:
- name: Install element web dependencies
run: yarn install --frozen-lockfile
- name: Build Element Web resources
# Needed to prepare language files
run: "yarn build:res"
- name: Install dependencies
working-directory: packages/shared-components
run: yarn install --frozen-lockfile
@@ -59,12 +55,6 @@ jobs:
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: "yarn playwright install --with-deps --only-shell"
- name: Build storybook dependencies
# When the first test is ran, it will fail because the dependencies are not yet built.
# This step is to ensure that the dependencies are built before running the tests.
run: "yarn --cwd packages/shared-components test:storybook:ci"
continue-on-error: true
- name: Run Visual tests
run: "yarn --cwd packages/shared-components test:storybook:ci"

View File

@@ -35,10 +35,6 @@ jobs:
- name: Typecheck
run: "yarn run lint:types"
- name: Build Element Web resources
# Needed to prepare language files for shared components
run: "yarn build:res"
- name: Install Shared Component Dependencies
run: "yarn --cwd packages/shared-components install"
@@ -91,10 +87,6 @@ jobs:
- name: Run Linter
run: "yarn run lint:js"
- name: Build Element Web resources
# Needed to prepare language files for shared components
run: "yarn build:res"
- name: Install Shared Component Deps
run: "yarn --cwd packages/shared-components install --frozen-lockfile"

View File

@@ -29,8 +29,8 @@ env:
permissions: {}
jobs:
jest:
name: Jest
jest_ew:
name: Jest (Element Web)
runs-on: ubuntu-24.04
strategy:
fail-fast: false
@@ -93,13 +93,13 @@ jobs:
complete:
name: jest-tests
needs: jest
needs: jest_ew
if: always()
runs-on: ubuntu-24.04
permissions:
statuses: write
steps:
- if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
- if: needs.jest_ew.result != 'skipped' && needs.jest_ew.result != 'success'
run: exit 1
- name: Skip SonarCloud in merge queue
@@ -112,3 +112,56 @@ jobs:
context: SonarCloud Code Analysis
sha: ${{ github.sha }}
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
jest_sc:
name: Jest (Shared Components)
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
- name: Yarn cache
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version: "lts/*"
cache: "yarn"
- name: Install EW Deps
run: "yarn install"
- name: Install Shared Component Deps
working-directory: "packages/shared-components"
run: "yarn install"
- name: Jest Cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: /tmp/jest_cache
key: ${{ hashFiles('**/yarn.lock') }}
- name: Get number of CPU cores
id: cpu-cores
uses: SimenB/github-actions-cpu-cores@97ba232459a8e02ff6121db9362b09661c875ab8 # v2
- name: Run tests
working-directory: "packages/shared-components"
run: |
yarn test \
--coverage=${{ env.ENABLE_COVERAGE }} \
--ci \
--max-workers ${{ steps.cpu-cores.outputs.count }} \
--cacheDirectory /tmp/jest_cache
env:
# tell jest to use coloured output
FORCE_COLOR: true
- name: Upload Artifact
if: env.ENABLE_COVERAGE == 'true'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: coverage-sharedcomponents
path: |
packages/shared-components/coverage
!packages/shared-components/coverage/lcov-report

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@ storybook-static
/packages/shared-components/node_modules
/packages/shared-components/dist
/packages/shared-components/src/i18nKeys.d.ts

View File

@@ -1,3 +1,27 @@
Changes in [1.12.3](https://github.com/element-hq/element-web/releases/tag/v1.12.3) (2025-11-04)
================================================================================================
## 🦖 Deprecations
* Remove allowVoipWithNoMedia feature flag ([#31087](https://github.com/element-hq/element-web/pull/31087)). Contributed by @Half-Shot.
## ✨ Features
* Change module API to be an instance getter ([#31025](https://github.com/element-hq/element-web/pull/31025)). Contributed by @dbkr.
## 🐛 Bug Fixes
* Show hover elements when keyboard focus is within an event tile ([#31078](https://github.com/element-hq/element-web/pull/31078)). Contributed by @t3chguy.
* Ensure toolbar navigation pattern works in MessageActionBar ([#31080](https://github.com/element-hq/element-web/pull/31080)). Contributed by @t3chguy.
* Ensure sent markers are hidden when showing thread summary. ([#31076](https://github.com/element-hq/element-web/pull/31076)). Contributed by @Half-Shot.
* Fix translation in dev mode ([#31045](https://github.com/element-hq/element-web/pull/31045)). Contributed by @florianduros.
* Fix sort order in space hierarchy ([#30975](https://github.com/element-hq/element-web/pull/30975)). Contributed by @t3chguy.
* New Room list: don't display message preview of thread ([#31043](https://github.com/element-hq/element-web/pull/31043)). Contributed by @florianduros.
* Revert "A11y: move focus to right panel when opened" ([#30999](https://github.com/element-hq/element-web/pull/30999)). Contributed by @florianduros.
* Fix highlights in messages (or search results) breaking links ([#30264](https://github.com/element-hq/element-web/pull/30264)). Contributed by @bojidar-bg.
* Add prepare script ([#31030](https://github.com/element-hq/element-web/pull/31030)). Contributed by @dbkr.
* Fix html exports by adding SDKContext ([#30987](https://github.com/element-hq/element-web/pull/30987)). Contributed by @t3chguy.
Changes in [1.12.2](https://github.com/element-hq/element-web/releases/tag/v1.12.2) (2025-10-21)
================================================================================================
## ✨ Features

View File

@@ -17,7 +17,7 @@ const config: Config = {
// This is needed to be able to load dual CJS/ESM WASM packages e.g. rust crypto & matrix-wywiwyg
customExportConditions: ["browser", "node"],
},
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)", "<rootDir>/packages/*/src/**/*.test.[t]s?(x)"],
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)"],
globalSetup: "<rootDir>/test/globalSetup.ts",
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
setupFilesAfterEnv: ["<rootDir>/test/setupTests.ts"],

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.12.2",
"version": "1.12.3",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -83,6 +83,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@element-hq/element-web-module-api": "1.4.1",
"@element-hq/web-shared-components": "file:packages/shared-components",
"@fontsource/inconsolata": "^5",
"@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7",
@@ -104,7 +105,6 @@
"browserslist": "^4.23.2",
"classnames": "^2.2.6",
"commonmark": "^0.31.0",
"counterpart": "^0.18.6",
"css-tree": "^3.0.0",
"diff-dom": "^5.0.0",
"diff-match-patch": "^1.0.5",

View File

@@ -1,4 +1,5 @@
module.exports = {
root: true,
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: [
"plugin:matrix-org/babel",

View File

@@ -1 +1,2 @@
dist/
i18n/i18nKeys.d.ts

View File

@@ -0,0 +1,21 @@
module.exports = {
sourceMaps: true,
presets: [
[
"@babel/preset-env",
{
include: ["@babel/plugin-transform-class-properties"],
},
],
["@babel/preset-typescript", { allowDeclareFields: true }],
"@babel/preset-react",
],
plugins: [
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-transform-numeric-separator",
"@babel/plugin-transform-object-rest-spread",
"@babel/plugin-transform-optional-chaining",
"@babel/plugin-transform-nullish-coalescing-operator",
"@babel/plugin-transform-runtime",
],
};

View File

@@ -0,0 +1,58 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { env } from "process";
import type { Config } from "jest";
const config: Config = {
testEnvironment: "jsdom",
testEnvironmentOptions: {
url: "http://localhost/",
},
testMatch: ["<rootDir>/src/**/*.test.[tj]s?(x)"],
setupFilesAfterEnv: ["<rootDir>/src/test/setupTests.ts"],
moduleNameMapper: {
// Support CSS module
"\\.(module.css)$": "identity-obj-proxy",
"\\.(css|scss|pcss)$": "<rootDir>/__mocks__/cssMock.js",
"\\.(gif|png|ttf|woff2)$": "<rootDir>/__mocks__/imageMock.js",
"\\.svg$": "<rootDir>/__mocks__/svg.js",
"\\$webapp/i18n/languages.json": "<rootDir>/../../__mocks__/languages.json",
"^react$": "<rootDir>/node_modules/react",
"^react-dom$": "<rootDir>/node_modules/react-dom",
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"context-filter-polyfill": "<rootDir>/__mocks__/empty.js",
"workers/(.+)Factory": "<rootDir>/__mocks__/workerFactoryMock.js",
},
transformIgnorePatterns: [
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs)).+$",
],
collectCoverageFrom: [
"<rootDir>/src/**/*.{js,ts,tsx}",
"<rootDir>/packages/**/*.{js,ts,tsx}",
// Coverage chokes on type definition files
"!<rootDir>/src/**/*.d.ts",
],
coverageReporters: ["text-summary", "lcov"],
testResultsProcessor: "@casualbot/jest-sonar-reporter",
prettierPath: null,
moduleDirectories: ["node_modules", "./src/test/utils"],
};
// if we're running under GHA, enable the GHA reporter
if (env["GITHUB_ACTIONS"] !== undefined) {
const reporters: Config["reporters"] = [["github-actions", { silent: false }], "summary"];
// if we're running against the develop branch, also enable the slow test reporter
if (env["GITHUB_REF"] == "refs/heads/develop") {
reporters.push("<rootDir>/test/slowReporter.cjs");
}
config.reporters = reporters;
}
export default config;

View File

@@ -1,6 +1,6 @@
{
"name": "@element-hq/web-shared-components",
"version": "0.0.0-test.6",
"version": "0.0.0-test.7",
"description": "Shared components for Element",
"author": "New Vector Ltd.",
"repository": {
@@ -19,6 +19,10 @@
"types": "./dist/element-web-shared-components.d.ts",
"default": "./dist/element-web-shared-components.mjs"
}
},
"./dist/element-web-shared-components.css": {
"require": "./dist/element-web-shared-components.css",
"import": "./dist/element-web-shared-components.css"
}
},
"types": "dist/element-web-shared-components.d.ts",
@@ -30,8 +34,8 @@
"package.json"
],
"scripts": {
"postinstall": "patch-package",
"prepare": "vite build",
"test": "jest",
"prepare": "patch-package && yarn --cwd ../.. build:res && ts-node scripts/gatherTranslationKeys.ts && vite build",
"storybook": "storybook dev -p 6007",
"build-storybook": "storybook build",
"lint": "yarn lint:types && yarn lint:js",
@@ -42,9 +46,13 @@
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
},
"dependencies": {
"classnames": "^2.5.1",
"counterpart": "^0.18.6",
"lodash": "^4.17.21",
"matrix-web-i18n": "^3.4.0",
"patch-package": "^8.0.1",
"counterpart": "^0.18.6"
"react-merge-refs": "^3.0.2",
"temporal-polyfill": "^0.3.0"
},
"devDependencies": {
"@storybook/addon-a11y": "^9.1.10",
@@ -53,13 +61,21 @@
"@storybook/icons": "^1.6.0",
"@storybook/react-vite": "^9.1.10",
"@storybook/test-runner": "^0.23.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.0",
"@types/counterpart": "^0.18.4",
"@types/lodash": "^4.17.20",
"@types/react": "^19.2.2",
"concurrently": "^9.2.1",
"eslint": "8",
"eslint-plugin-matrix-org": "^3.0.0",
"eslint-plugin-storybook": "^10.0.0",
"jest": "^30.2.0",
"jest-image-snapshot": "^6.5.1",
"patch-package": "^8.0.1",
"prettier": "^3.6.2",
"storybook": "^9.1.10",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"vite": "^7.1.9",
"vite-plugin-dts": "^4.5.4",
@@ -68,5 +84,9 @@
"engines": {
"node": ">=20.0.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"peerDependencies": {
"@vector-im/compound-design-tokens": "^6.0.0",
"@vector-im/compound-web": "^8.2.5"
}
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// Gathers all the translation keys from element-web's en_EN.json into a TypeScript type definition file
// that exports a type `TranslationKey` which is a union of all supported translation keys.
// This prevents having to import the json file and make typescript do the work as this results in vite-dts
// generating an import to the json file in the .d.ts which doesn't work at runtime: this way, the type
// gets put into the bundle.
// XXX: It should *not* be in the 'src' directory, being a generated file, but if it isn't then the type
// bundler won't bundle the types and will leave the file as a relative import, which will break.
import * as fs from "fs";
import * as path from "path";
const i18nStringsPath = path.resolve(__dirname, "../../../src/i18n/strings/en_EN.json");
const outPath = path.resolve(__dirname, "../src/i18nKeys.d.ts");
function gatherKeys(obj: any, prefix: string[] = []): string[] {
if (typeof obj !== "object" || obj === null) return [];
let keys: string[] = [];
for (const key of Object.keys(obj)) {
const value = obj[key];
// add the path (for both leaves and intermediates as then we include plurals)
keys.push([...prefix, key].join("|"));
if (typeof value === "object" && value !== null) {
// If the value is an object, recurse
keys = keys.concat(gatherKeys(value, [...prefix, key]));
}
}
return keys;
}
function main() {
const json = JSON.parse(fs.readFileSync(i18nStringsPath, "utf8"));
const keys = gatherKeys(json);
const typeDef =
"/*\n" +
" * Copyright 2025 Element Creations Ltd.\n" +
" *\n" +
" * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial\n" +
" * Please see LICENSE files in the repository root for full details.\n" +
" */\n" +
"\n" +
"// This file is auto-generated by gatherTranslationKeys.ts\n" +
"// Do not edit manually.\n\n" +
"export type TranslationKey =\n" +
keys.map((k) => ` | \"${k}\"`).join("\n") +
";\n";
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, typeDef, "utf8");
console.log(`Wrote ${keys.length} keys to ${outPath}`);
}
if (require.main === module) {
main();
}

View File

@@ -6,7 +6,7 @@
*/
.audioPlayer {
padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x);
padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x) !important;
}
.mediaInfo {

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`AudioPlayerView renders the audio player in default state 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Clock renders the clock 1`] = `
<div>

View File

@@ -6,6 +6,6 @@
*/
.button {
border-radius: 32px;
background-color: var(--cpd-color-bg-subtle-primary);
border-radius: 32px !important;
background-color: var(--cpd-color-bg-subtle-primary) !important;
}

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`PlayPauseButton renders the button in default state 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Seekbar renders the clock 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`AvatarWithDetails renders a textual event 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`TextualEventView renders a textual event 1`] = `
<div>

View File

@@ -5,4 +5,4 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
export { TextualEventView } from "./TextualEventView";
export { TextualEventView, type TextualEventViewSnapshot } from "./TextualEventView";

View File

@@ -21,12 +21,17 @@ export * from "./utils/Box";
export * from "./utils/Flex";
// Utils
export { setLanguage } from "./utils/i18n";
export * from "./utils/i18n";
export * from "./utils/humanize";
export * from "./utils/DateUtils";
export * from "./utils/numbers";
export * from "./utils/FormattingUtils";
// MVVM
export * from "./viewmodel";
export * from "./useMockedViewModel";
export * from "./useViewModel";
// i18n (we must export this directly in order to not confuse the type bundler, it seems,
// otherwise it will leave it as a relative import rather than bundling it)
export type * from "./i18nKeys.d.ts";

View File

@@ -7,7 +7,7 @@
.mediaBody {
background-color: var(--cpd-color-bg-subtle-secondary);
border-radius: var(--cpd-space-2x);
border-radius: var(--cpd-space-2x) !important;
max-width: 243px; /* use max-width instead of width so it fits within right panels */
font: var(--cpd-font-body-md-regular);

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`MediaBody renders the media body 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Pill renders the pill 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`PillInput renders only the input without children 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`RichItem renders the item in default state 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`RichItem renders the list 1`] = `
<div>

View File

@@ -0,0 +1,22 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import fetchMock from "fetch-mock-jest";
import { setLanguage } from "../../src/utils/i18n";
import en from "../../../../src/i18n/strings/en_EN.json";
export function setupLanguageMock(): void {
fetchMock
.get("/i18n/languages.json", {
en: "en_EN.json",
})
.get("end:en_EN.json", en);
}
setupLanguageMock();
setLanguage("en");

View File

@@ -0,0 +1,47 @@
/*
Copyright 2025 Element Creations Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// Copied from element-web/test/test-utils because, seemingly, if we
// set that as the modules directory to use it directly, it fails to
// actually put the right thing in the context somehow.
import React, { type ReactElement } from "react";
// eslint-disable-next-line no-restricted-imports
import { render, type RenderOptions } from "@testing-library/react";
import { TooltipProvider } from "@vector-im/compound-web";
const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
return ({ children }: { children: React.ReactNode }) => {
if (Wrapper) {
return (
<Wrapper>
<TooltipProvider>{children}</TooltipProvider>
</Wrapper>
);
} else {
return <TooltipProvider>{children}</TooltipProvider>;
}
};
};
const customRender = (ui: ReactElement, options: RenderOptions = {}): ReturnType<typeof render> => {
return render(ui, {
...options,
wrapper: wrapWithTooltipProvider(options?.wrapper) as RenderOptions["wrapper"],
}) as ReturnType<typeof render>;
};
// eslint-disable-next-line no-restricted-imports
export * from "@testing-library/react";
/**
* This custom render function wraps your component with a TooltipProvider.
* See https://testing-library.com/docs/react-testing-library/setup/#custom-render
*/
export { customRender as render };

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import counterpart from "counterpart";
import { registerTranslations, setMissingEntryGenerator, getLocale, setLocale } from "./i18n";
describe("i18n utils", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should wrap registerTranslations", () => {
jest.spyOn(counterpart, "registerTranslations");
registerTranslations("en", { test: "This is a test" });
expect(counterpart.registerTranslations).toHaveBeenCalledWith("en", { test: "This is a test" });
});
it("should wrap setMissingEntryGenerator", () => {
jest.spyOn(counterpart, "setMissingEntryGenerator");
const dummyFn = jest.fn();
setMissingEntryGenerator(dummyFn);
expect(counterpart.setMissingEntryGenerator).toHaveBeenCalledWith(dummyFn);
});
it("should wrap getLocale", () => {
jest.spyOn(counterpart, "getLocale");
getLocale();
expect(counterpart.getLocale).toHaveBeenCalled();
});
it("should wrap setLocale", () => {
jest.spyOn(counterpart, "setLocale");
setLocale("en");
expect(counterpart.setLocale).toHaveBeenCalledWith("en");
});
});

View File

@@ -22,10 +22,10 @@
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/
import React from "react";
import { type TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n";
import { KEY_SEPARATOR } from "matrix-web-i18n";
import counterpart from "counterpart";
import type Translations from "../../../../src/i18n/strings/en_EN.json";
import type { TranslationKey } from "../index";
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
import webpackLangJsonUrl from "$webapp/i18n/languages.json";
@@ -45,16 +45,23 @@ counterpart.setSeparator(KEY_SEPARATOR);
const FALLBACK_LOCALE = "en";
counterpart.setFallbackLocale(FALLBACK_LOCALE);
/**
* A type representing the union of possible keys into the translation file using `|` delimiter to access nested fields.
* @example `common|error` to access `error` within the `common` sub-object.
* {
* "common": {
* "error": "Error"
* }
* }
*/
export type TranslationKey = _TranslationKey<typeof Translations>;
// export wrappers around these functions because if we used counterpart directly from
// element-web, it operates on a different instance of counterpart
export function registerTranslations(locale: string, data: object): void {
counterpart.registerTranslations(locale, data);
}
export function setMissingEntryGenerator(callback: (value: string) => void): void {
counterpart.setMissingEntryGenerator(callback);
}
export function getLocale(): string {
return counterpart.getLocale();
}
export function setLocale(value: string): string {
return counterpart.setLocale(value);
}
// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings

View File

@@ -17,7 +17,7 @@
"lib": ["es2022", "es2024.promise", "dom", "dom.iterable"],
"strict": true,
"paths": {
"jest-matrix-react": ["../../test/test-utils/jest-matrix-react"],
"jest-matrix-react": ["./src/test/utils/jest-matrix-react"],
"rollup/parseAst": ["./node_modules/rollup/dist/parseAst"]
}
},

View File

@@ -26,7 +26,7 @@ export default defineConfig({
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ["react", "react-dom"],
external: ["react", "react-dom", "@vector-im/compound-design-tokens", "@vector-im/compound-web"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
@@ -43,5 +43,12 @@ export default defineConfig({
$webapp: resolve(__dirname, "..", "..", "webapp"),
},
},
plugins: [dts({ rollupTypes: true, include: ["src/**/*.{ts,tsx}"], copyDtsFiles: true })],
plugins: [
dts({
rollupTypes: true,
include: ["src/**/*.{ts,tsx}"],
exclude: ["src/**/*.test.{ts,tsx}"],
copyDtsFiles: true,
}),
],
});

File diff suppressed because it is too large Load Diff

View File

@@ -92,6 +92,41 @@ test.describe("Composer", () => {
});
});
test("should have focus lock in emoji picker", async ({ page, app }) => {
const emojiButton = app.getComposer(false).getByRole("button", { name: "Emoji" });
// Open emoji picker by clicking the button
await emojiButton.click();
// Wait for emoji picker to be visible
const emojiPicker = page.getByTestId("mx_EmojiPicker");
await expect(emojiPicker).toBeVisible();
// Get initial focused element (should be search input)
const searchInput = emojiPicker.getByRole("textbox", { name: "Search" });
await expect(searchInput).toBeFocused();
// Try to tab multiple times - focus should stay within emoji picker
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
// Verify we're still within the emoji picker (not back to composer)
const focusedElement = await page.evaluate(() => document.activeElement?.closest(".mx_EmojiPicker"));
expect(focusedElement).not.toBeNull();
// Close with Escape key
await page.keyboard.press("Escape");
// Verify emoji picker is closed
await expect(emojiPicker).not.toBeVisible();
// Verify focus returns to emoji button
await expect(emojiButton).toBeFocused();
});
test.describe("when Control+Enter is required to send", () => {
test.beforeEach(async ({ app }) => {
await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
const TAG = "develop@sha256:8bae6225b998cbf0c09260b8dd66f320109a3d6d156ad289d2c3dbcb03f5fce9";
const TAG = "develop@sha256:450b176d61e75b73d1acbbfab1de5ec3ebe08ba0f08ec00da872f7b5aaa23e54";
/**
* SynapseContainer which freezes the docker digest to stabilise tests,

View File

@@ -182,7 +182,7 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_EmojiPicker_body .mx_EmojiPicker_item_wrapper[tabindex="0"] .mx_EmojiPicker_item {
.mx_EmojiPicker_body_showHighlight .mx_EmojiPicker_item_wrapper [tabindex="0"] .mx_EmojiPicker_item {
background-color: $focus-bg-color;
}

View File

@@ -8,13 +8,18 @@ Please see LICENSE files in the repository root for full details.
.mx_MPollBody {
margin-top: 8px;
min-width: 0; /* Override fieldset default min-width: min-content */
width: 100%; /* Ensure fieldset takes full available width */
border: none; /* Remove default fieldset border */
padding: 0; /* Remove default fieldset padding */
h2 {
legend {
font-weight: var(--cpd-font-weight-semibold);
font-size: $font-15px;
line-height: $font-24px;
margin-top: 0;
margin-bottom: 8px;
letter-spacing: var(--cpd-font-letter-spacing-heading-lg);
.mx_MPollBody_edited {
color: $roomtopic-color;
@@ -23,7 +28,7 @@ Please see LICENSE files in the repository root for full details.
}
}
h2::before {
legend::before {
content: "";
position: relative;
display: inline-block;

View File

@@ -153,7 +153,7 @@ Please see LICENSE files in the repository root for full details.
.mx_MediaBody {
/* leave space for the timestamp */
padding-right: 48px;
padding-right: 48px !important;
}
.mx_MImageBody {

View File

@@ -17,4 +17,10 @@ else
fi
DIST_VERSION=$("$DIR"/normalize-version.sh "$DIST_VERSION")
yarn --cwd packages/shared-components install
yarn --cwd packages/shared-components link
yarn link @element-hq/web-shared-components
VERSION=$DIST_VERSION yarn build

View File

@@ -44,3 +44,11 @@ fi
yarn link matrix-js-sdk
[ -d matrix-analytics-events ] && yarn link @matrix-org/analytics-events
yarn install --frozen-lockfile $@
# Link shared components
pushd packages/shared-components
yarn link
yarn install --frozen-lockfile
popd
yarn link @element-hq/web-shared-components

View File

@@ -6,12 +6,12 @@ sonar.organization=element-hq
sonar.sources=src,res,packages/shared-components/src
sonar.tests=test,playwright,src,packages
sonar.test.inclusions=test/*,playwright/*,src/**/*.test.*,packages/*/src/**/*.test.*
sonar.test.inclusions=test/*,playwright/*,src/**/*.test.*,packages/*/src/**/*.test.*,packages/*/src/test/**/*
sonar.exclusions=__mocks__,docs,element.io,nginx
sonar.cpd.exclusions=src/i18n/strings/*.json
sonar.typescript.tsconfigPath=./tsconfig.json
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.javascript.lcov.reportPaths=coverage/lcov.info,packages/shared-components/coverage/lcov.info
sonar.coverage.exclusions=\
test/**/*,\
playwright/**/*,\
@@ -21,5 +21,6 @@ sonar.coverage.exclusions=\
src/components/views/dialogs/devtools/**/*,\
src/utils/SessionLock.ts,\
src/**/*.d.ts,\
src/vector/mobile_guide/**/*
src/vector/mobile_guide/**/* \
packages/shared-components/src/test/**/*
sonar.testExecutionReportPaths=coverage/jest-sonar-report.xml

View File

@@ -13,7 +13,7 @@ import { type Optional } from "matrix-events-sdk";
import { _t, getUserLanguage } from "./languageHandler";
import { getUserTimezone } from "./TimezoneHandler";
export { formatSeconds } from "../packages/shared-components/src/utils/DateUtils";
export { formatSeconds } from "@element-hq/web-shared-components";
export const MINUTE_MS = 60000;
export const HOUR_MS = MINUTE_MS * 60;

View File

@@ -9,7 +9,8 @@ Please see LICENSE files in the repository root for full details.
*/
// Import i18n.tsx instead of languageHandler to avoid circular deps
import { _td, type TranslationKey } from "../../packages/shared-components/src/utils/i18n";
import { _td, type TranslationKey } from "@element-hq/web-shared-components";
import { IS_MAC, IS_ELECTRON, Key } from "../Keyboard";
import { type IBaseSetting } from "../settings/Settings";
import { type KeyCombo } from "../KeyBindingsManager";

View File

@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type ReactNode } from "react";
import { Text, Heading, Button, Separator } from "@vector-im/compound-web";
import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out";
import { Flex } from "@element-hq/web-shared-components";
import SdkConfig from "../../SdkConfig";
import { Flex } from "../../../packages/shared-components/src/utils/Flex";
import { _t } from "../../languageHandler";
import { Icon as AppleIcon } from "../../../res/themes/element/img/compound/apple.svg";
import { Icon as MicrosoftIcon } from "../../../res/themes/element/img/compound/microsoft.svg";

View File

@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
import EventEmitter from "events";
import { SimpleObservable } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
import { clamp } from "@element-hq/web-shared-components";
import { UPDATE_EVENT } from "../stores/AsyncStore";
import { arrayFastResample } from "../utils/arrays";
import { type IDestroyable } from "../utils/IDestroyable";
import { PlaybackClock } from "./PlaybackClock";
import { createAudioContext, decodeOgg } from "./compat";
import { clamp } from "../../packages/shared-components/src/utils/numbers";
import { DEFAULT_WAVEFORM, PLAYBACK_WAVEFORM_SAMPLES } from "./consts";
import { PlaybackEncoder } from "../PlaybackEncoder";

View File

@@ -1,16 +1,14 @@
/*
Copyrimport { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
import { percentageOf } from "../../packages/shared-components/src/utils/numbers";
// from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope" 2024 New Vector Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { percentageOf } from "@element-hq/web-shared-components";
import { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
import { percentageOf } from "../../packages/shared-components/src/utils/numbers";
// from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope
declare const currentTime: number;

View File

@@ -11,6 +11,7 @@ import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
import { SimpleObservable } from "matrix-widget-api";
import EventEmitter from "events";
import { logger } from "matrix-js-sdk/src/logger";
import { clamp } from "@element-hq/web-shared-components";
import MediaDeviceHandler from "../MediaDeviceHandler";
import { type IDestroyable } from "../utils/IDestroyable";
@@ -19,7 +20,6 @@ import { PayloadEvent, WORKLET_NAME } from "./consts";
import { UPDATE_EVENT } from "../stores/AsyncStore";
import { createAudioContext } from "./compat";
import { FixedRollingArray } from "../utils/FixedRollingArray";
import { clamp } from "../../packages/shared-components/src/utils/numbers";
import recorderWorkletFactory from "./recorderWorkletFactory";
const CHANNELS = 1; // stereo isn't important

View File

@@ -405,7 +405,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
);
if (focusLock) {
body = <FocusLock>{body}</FocusLock>;
body = <FocusLock returnFocus>{body}</FocusLock>;
}
// filter props that are invalid for DOM elements

View File

@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type ChangeEvent, type CSSProperties, type ReactNode } from "react";
import { percentageOf } from "@element-hq/web-shared-components";
import { type PlaybackInterface } from "../../../audio/Playback";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import { percentageOf } from "../../../../packages/shared-components/src/utils/numbers";
import { _t } from "../../../languageHandler";
interface IProps {

View File

@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { Clock } from "@element-hq/web-shared-components";
import { type IRecordingUpdate } from "../../../audio/VoiceRecording";
import { Clock } from "../../../../packages/shared-components/src/audio/Clock";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";

View File

@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { Clock } from "@element-hq/web-shared-components";
import { Clock } from "../../../../packages/shared-components/src/audio/Clock";
import { type Playback, PlaybackState } from "../../../audio/Playback";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";

View File

@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { percentageOf } from "@element-hq/web-shared-components";
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
import Waveform from "./Waveform";
import { type Playback } from "../../../audio/Playback";
import { percentageOf } from "../../../../packages/shared-components/src/utils/numbers";
import { PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/consts";
interface IProps {

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type HTMLProps, useContext } from "react";
import { type Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
import { humanizeTime } from "@element-hq/web-shared-components";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
@@ -18,7 +19,6 @@ import BeaconStatus from "./BeaconStatus";
import { BeaconDisplayStatus } from "./displayStatus";
import StyledLiveBeaconIcon from "./StyledLiveBeaconIcon";
import ShareLatestLocation from "./ShareLatestLocation";
import { humanizeTime } from "../../../../packages/shared-components/src/utils/humanize";
interface Props {
beacon: Beacon;

View File

@@ -735,7 +735,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (this.state.reactionPickerDisplayed) {
const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect();
reactionPicker = (
<ContextMenu {...toRightOf(buttonRect)} onFinished={this.closeMenu} managed={false}>
<ContextMenu {...toRightOf(buttonRect)} onFinished={this.closeMenu} managed={false} focusLock>
<ReactionPicker mxEvent={mxEvent} onFinished={this.onCloseReactionPicker} reactions={reactions} />
</ContextMenu>
);

View File

@@ -12,6 +12,7 @@ import { KnownMembership } from "matrix-js-sdk/src/types";
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import { logger } from "matrix-js-sdk/src/logger";
import { uniqBy } from "lodash";
import { RichList, RichItem, PillInput, Pill } from "@element-hq/web-shared-components";
import { Icon as EmailPillAvatarIcon } from "../../../../res/img/icon-email-pill-avatar.svg";
import { _t, _td } from "../../../languageHandler";
@@ -63,10 +64,6 @@ import AskInviteAnywayDialog, { type UnknownProfiles } from "./AskInviteAnywayDi
import { SdkContextClass } from "../../../contexts/SDKContext";
import { type UserProfilesStore } from "../../../stores/UserProfilesStore";
import InviteProgressBody from "./InviteProgressBody.tsx";
import { RichList } from "../../../../packages/shared-components/src/rich-list/RichList";
import { RichItem } from "../../../../packages/shared-components/src/rich-list/RichItem";
import { PillInput } from "../../../../packages/shared-components/src/pill-input/PillInput";
import { Pill } from "../../../../packages/shared-components/src/pill-input/Pill";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */

View File

@@ -12,8 +12,8 @@ import { debounce } from "lodash";
import classNames from "classnames";
import React, { type ChangeEvent, type FormEvent } from "react";
import { type SecretStorage } from "matrix-js-sdk/src/matrix";
import { Flex } from "@element-hq/web-shared-components";
import { Flex } from "../../../../../packages/shared-components/src/utils/Flex";
import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "../../settings/encryption/EncryptionCard";
import { EncryptionCardButtons } from "../../settings/encryption/EncryptionCardButtons";

View File

@@ -6,13 +6,13 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, useCallback, useId, useState } from "react";
import { _t } from "@element-hq/web-shared-components";
import SettingsStore from "../../../settings/SettingsStore";
import { type SettingLevel } from "../../../settings/SettingLevel";
import { SETTINGS, type StringSettingKey } from "../../../settings/Settings";
import { useSettingValueAt } from "../../../hooks/useSettings.ts";
import Dropdown, { type DropdownProps } from "./Dropdown.tsx";
import { _t } from "../../../../packages/shared-components/src/utils/i18n.tsx";
interface Props {
settingKey: StringSettingKey;

View File

@@ -9,6 +9,8 @@ Please see LICENSE files in the repository root for full details.
import React, { type Dispatch } from "react";
import { DATA_BY_CATEGORY, getEmojiFromUnicode, type Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
import { clamp } from "@element-hq/web-shared-components";
import classNames from "classnames";
import { _t } from "../../../languageHandler";
import * as recent from "../../../emojipicker/recent";
@@ -26,7 +28,6 @@ import {
Type,
} from "../../../accessibility/RovingTabIndex";
import { Key } from "../../../Keyboard";
import { clamp } from "../../../../packages/shared-components/src/utils/numbers";
import { type ButtonEvent } from "../elements/AccessibleButton";
export const CATEGORY_HEADER_HEIGHT = 20;
@@ -50,6 +51,8 @@ interface IState {
// should be enough to never have blank rows of emojis as
// 3 rows of overflow are also rendered. The actual value is updated on scroll.
viewportHeight: number;
// Track if user has interacted with arrow keys or search
showHighlight: boolean;
}
class EmojiPicker extends React.Component<IProps, IState> {
@@ -66,6 +69,7 @@ class EmojiPicker extends React.Component<IProps, IState> {
filter: "",
scrollTop: 0,
viewportHeight: 280,
showHighlight: false,
};
// Convert recent emoji characters to emoji data, removing unknowns and duplicates
@@ -233,6 +237,20 @@ class EmojiPicker extends React.Component<IProps, IState> {
private onKeyDown = (ev: React.KeyboardEvent, state: RovingState, dispatch: Dispatch<RovingAction>): void => {
if (state.activeNode && [Key.ARROW_DOWN, Key.ARROW_RIGHT, Key.ARROW_LEFT, Key.ARROW_UP].includes(ev.key)) {
// If highlight is not shown yet, show it and reset to first emoji
if (!this.state.showHighlight) {
this.setState({ showHighlight: true });
// Reset to first emoji when showing highlight for the first time (or after it was hidden)
if (state.nodes.length > 0) {
dispatch({
type: Type.SetFocus,
payload: { node: state.nodes[0] },
});
}
ev.preventDefault();
ev.stopPropagation();
return;
}
this.keyboardNavigation(ev, state, dispatch);
}
};
@@ -274,6 +292,15 @@ class EmojiPicker extends React.Component<IProps, IState> {
private onChangeFilter = (filter: string): void => {
const lcFilter = filter.toLowerCase().trim(); // filter is case insensitive
// User has typed a query, show highlight
// If filter is cleared, hide highlight again
if (lcFilter && !this.state.showHighlight) {
this.setState({ showHighlight: true });
} else if (!lcFilter && this.state.showHighlight) {
this.setState({ showHighlight: false });
}
for (const cat of this.categories) {
let emojis: IEmoji[];
// If the new filter string includes the old filter string, we don't have to re-filter the whole dataset.
@@ -335,6 +362,9 @@ class EmojiPicker extends React.Component<IProps, IState> {
};
private onEnterFilter = (): void => {
// Only select emoji if highlight is shown
if (!this.state.showHighlight) return;
const btn = this.scrollRef.current?.containerRef.current?.querySelector<HTMLButtonElement>(
'.mx_EmojiPicker_item_wrapper [tabindex="0"]',
);
@@ -391,7 +421,9 @@ class EmojiPicker extends React.Component<IProps, IState> {
/>
<AutoHideScrollbar
id="mx_EmojiPicker_body"
className="mx_EmojiPicker_body"
className={classNames("mx_EmojiPicker_body", {
mx_EmojiPicker_body_showHighlight: this.state.showHighlight,
})}
ref={this.scrollRef}
onScroll={this.onScroll}
>

View File

@@ -71,7 +71,9 @@ class Search extends React.PureComponent<IProps> {
onChange={(ev) => this.props.onChange(ev.target.value)}
onKeyDown={this.onKeyDown}
ref={this.inputRef}
aria-activedescendant={this.context.state.activeNode?.id}
// Setting aria-activedescendant on the input allows screen readers to identify the active emoji.
// Setting it when there is not a query causes screen readers to read out the first emoji when focusing the input, and it continually tells you you are in the table vs the input.
aria-activedescendant={this.props.query ? this.context.state.activeNode?.id : undefined}
aria-controls="mx_EmojiPicker_body"
aria-haspopup="grid"
aria-autocomplete="list"

View File

@@ -10,6 +10,7 @@ import React, { type JSX, createRef } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { CallErrorCode, CallState } from "matrix-js-sdk/src/webrtc/call";
import classNames from "classnames";
import { Clock } from "@element-hq/web-shared-components";
import { _t } from "../../../languageHandler";
import MemberAvatar from "../avatars/MemberAvatar";
@@ -18,7 +19,6 @@ import { LegacyCallEventGrouperEvent } from "../../structures/LegacyCallEventGro
import AccessibleButton from "../elements/AccessibleButton";
import InfoTooltip, { InfoTooltipKind } from "../elements/InfoTooltip";
import { formatPreciseDuration } from "../../../DateUtils";
import { Clock } from "../../../../packages/shared-components/src/audio/Clock";
const MAX_NON_NARROW_WIDTH = (450 / 70) * 100;

View File

@@ -10,6 +10,7 @@ import React, { type JSX, useEffect, useMemo } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { type IContent } from "matrix-js-sdk/src/matrix";
import { type MediaEventContent } from "matrix-js-sdk/src/types";
import { AudioPlayerView } from "@element-hq/web-shared-components";
import { type Playback } from "../../../audio/Playback";
import InlineSpinner from "../elements/InlineSpinner";
@@ -20,7 +21,6 @@ import { PlaybackManager } from "../../../audio/PlaybackManager";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import MediaProcessingError from "./shared/MediaProcessingError";
import { AudioPlayerViewModel } from "../../../viewmodels/audio/AudioPlayerViewModel";
import { AudioPlayerView } from "../../../../packages/shared-components/src/audio/AudioPlayerView";
interface IState {
error?: boolean;

View File

@@ -325,11 +325,11 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
) : null;
return (
<div className="mx_MPollBody">
<h2 data-testid="pollQuestion">
<fieldset className="mx_MPollBody">
<legend data-testid="pollQuestion">
{pollEvent.question.text}
{editedSpan}
</h2>
</legend>
<div className="mx_MPollBody_allOptions">
{pollEvent.answers.map((answer: PollAnswerSubevent) => {
let answerVotes = 0;
@@ -360,7 +360,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
{totalText}
{isFetchingResponses && <Spinner w={16} h={16} />}
</div>
</div>
</fieldset>
);
}
}

View File

@@ -156,7 +156,7 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
if (menuDisplayed && buttonRef.current) {
const buttonRect = buttonRef.current.getBoundingClientRect();
contextMenu = (
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false} focusLock>
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
</ContextMenu>
);

View File

@@ -34,7 +34,7 @@ const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
if (menuDisplayed && button.current) {
const buttonRect = button.current.getBoundingClientRect();
contextMenu = (
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false} focusLock>
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
</ContextMenu>
);

View File

@@ -8,8 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type ComponentType } from "react";
import { Text } from "@vector-im/compound-web";
import { Flex } from "../../../../packages/shared-components/src/utils/Flex";
import { Flex } from "@element-hq/web-shared-components";
interface Props {
Icon: ComponentType<React.SVGAttributes<SVGElement>>;

View File

@@ -39,6 +39,7 @@ import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error"
import ErrorSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { Box, Flex } from "@element-hq/web-shared-components";
import BaseCard from "./BaseCard.tsx";
import { _t } from "../../../languageHandler.tsx";
@@ -46,9 +47,7 @@ import RoomAvatar from "../avatars/RoomAvatar.tsx";
import { E2EStatus } from "../../../utils/ShieldUtils.ts";
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks.ts";
import RoomName from "../elements/RoomName.tsx";
import { Flex } from "../../../../packages/shared-components/src/utils/Flex";
import { Linkify, topicToHtml } from "../../../HtmlUtils.tsx";
import { Box } from "../../../../packages/shared-components/src/utils/Box";
import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx";
import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";

View File

@@ -9,10 +9,10 @@ import React from "react";
import { type User, type RoomMember } from "matrix-js-sdk/src/matrix";
import { Text, Button, InlineSpinner, Badge } from "@vector-im/compound-web";
import { VerifiedIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Flex } from "@element-hq/web-shared-components";
import { useUserInfoVerificationViewModel } from "../../../viewmodels/right_panel/user_info/UserInfoHeaderVerificationViewModel";
import { type IDevice } from "../UserInfo";
import { Flex } from "../../../../../packages/shared-components/src/utils/Flex";
import { _t } from "../../../../languageHandler";
export const UserInfoHeaderVerificationView: React.FC<{

View File

@@ -8,11 +8,11 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react";
import { type User, type RoomMember } from "matrix-js-sdk/src/matrix";
import { Heading, Tooltip, Text } from "@vector-im/compound-web";
import { Flex } from "@element-hq/web-shared-components";
import { useUserfoHeaderViewModel } from "../../../viewmodels/right_panel/user_info/UserInfoHeaderViewModel";
import MemberAvatar from "../../avatars/MemberAvatar";
import { Container, type Member, type IDevice } from "../UserInfo";
import { Flex } from "../../../../../packages/shared-components/src/utils/Flex";
import PresenceLabel from "../../rooms/PresenceLabel";
import CopyableText from "../../elements/CopyableText";
import { UserInfoHeaderVerificationView } from "./UserInfoHeaderVerificationView";

View File

@@ -11,6 +11,7 @@ import classNames from "classnames";
import { Resizable, type Size } from "re-resizable";
import { type Room } from "matrix-js-sdk/src/matrix";
import { type IWidget } from "matrix-widget-api";
import { clamp, percentageOf, percentageWithin } from "@element-hq/web-shared-components";
import AppTile from "../elements/AppTile";
import dis from "../../../dispatcher/dispatcher";
@@ -22,7 +23,6 @@ import ResizeHandle from "../elements/ResizeHandle";
import Resizer, { type IConfig } from "../../../resizer/resizer";
import PercentageDistributor from "../../../resizer/distributors/percentage";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import { clamp, percentageOf, percentageWithin } from "../../../../packages/shared-components/src/utils/numbers";
import UIStore from "../../../stores/UIStore";
import { type ActionPayload } from "../../../dispatcher/payloads";
import Spinner from "../elements/Spinner";

View File

@@ -34,7 +34,7 @@ export function EmojiButton({ addEmoji, menuPosition, className }: IEmojiButtonP
};
contextMenu = (
<ContextMenu {...position} onFinished={onFinished} managed={false}>
<ContextMenu {...position} onFinished={onFinished} managed={false} focusLock>
<EmojiPicker onChoose={addEmoji} onFinished={onFinished} />
</ContextMenu>
);

View File

@@ -9,8 +9,8 @@ import { Search, Text, Button, Tooltip, InlineSpinner } from "@vector-im/compoun
import React from "react";
import InviteIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
import { UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Flex } from "@element-hq/web-shared-components";
import { Flex } from "../../../../../packages/shared-components/src/utils/Flex";
import { type MemberListViewState } from "../../../viewmodels/memberlist/MemberListViewModel";
import { _t } from "../../../../languageHandler";

View File

@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
import { Form } from "@vector-im/compound-web";
import React, { type JSX, useCallback } from "react";
import { Flex } from "@element-hq/web-shared-components";
import { Flex } from "../../../../../packages/shared-components/src/utils/Flex";
import {
type MemberWithSeparator,
SEPARATOR,

View File

@@ -8,8 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react";
import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid";
import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add-solid";
import { Flex } from "../../../../../../../packages/shared-components/src/utils/Flex";
import { Flex } from "@element-hq/web-shared-components";
interface Props {
isThreePid: boolean;

View File

@@ -12,8 +12,8 @@ import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/ic
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid";
import { UnreadCounter, Unread } from "@vector-im/compound-web";
import { Flex } from "@element-hq/web-shared-components";
import { Flex } from "../../../../packages/shared-components/src/utils/Flex";
import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState";
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
import { NotificationStateEvents } from "../../../stores/notifications/NotificationState";

Some files were not shown because too many files have changed in this diff Show More