Compare commits

..

44 Commits

Author SHA1 Message Date
R Midhun Suresh
6dc1431270 Write tests 2025-10-30 18:33:07 +05:30
R Midhun Suresh
f4e8e79af8 Implement openRoom in NavigationApi 2025-10-30 18:32:53 +05:30
R Midhun Suresh
507eaa02df Use nullish coalescing assignment 2025-10-30 18:02:19 +05:30
R Midhun Suresh
b94d40f166 Write tests 2025-10-30 17:59:12 +05:30
R Midhun Suresh
c2d68f8dc0 Create ClientApi in Api.ts 2025-10-30 17:13:57 +05:30
R Midhun Suresh
3be766d79c Add implementation for ClientApi 2025-10-30 17:13:39 +05:30
R Midhun Suresh
335491eabc Add implementation for Room 2025-10-30 17:13:14 +05:30
R Midhun Suresh
2449557aa8 Add implementation for AccountDataApi 2025-10-30 17:12:32 +05:30
R Midhun Suresh
eebf227cf4 Update license 2025-10-30 16:56:37 +05:30
R Midhun Suresh
ebc9e3ace6 room-id is optional 2025-10-30 16:48:45 +05:30
R Midhun Suresh
61306a1e4a Improve comment 2025-10-30 16:31:59 +05:30
R Midhun Suresh
a9fed64637 Add more tests 2025-10-30 16:29:50 +05:30
R Midhun Suresh
8a875e8c6d Fix import 2025-10-30 16:12:23 +05:30
R Midhun Suresh
620ba9231d Fix circular dependency issue 2025-10-30 16:10:34 +05:30
R Midhun Suresh
f2104b5ec0 Fix import 2025-10-30 16:10:34 +05:30
R Midhun Suresh
1c0738be0f Add tests 2025-10-30 16:10:33 +05:30
R Midhun Suresh
c78461db0b Implement new builtins api 2025-10-30 16:10:31 +05:30
R Midhun Suresh
2b05d51e41 Add RoomContextType 2025-10-30 16:08:08 +05:30
R Midhun Suresh
6f6b3bdd8f No need to pass RVS from LoggedInView 2025-10-30 15:44:07 +05:30
R Midhun Suresh
da11cff6ff Fix test 2025-10-30 15:33:25 +05:30
R Midhun Suresh
302b6567ea Remove RoomViewStore from state
This is now accessed through class field
2025-10-30 15:32:40 +05:30
R Midhun Suresh
b8c79f46ee Add roomId to prop 2025-10-30 15:31:56 +05:30
R Midhun Suresh
0e8a617beb RVS is not needed as prop anymore
Since it's passed through context
2025-10-30 15:25:44 +05:30
David Baker
a94328a125 Update module api 2025-10-29 15:45:41 +00:00
David Baker
4d7d06bfc0 Merge remote-tracking branch 'origin/develop' into dbkr/module_experiments 2025-10-29 15:44:22 +00:00
David Baker
c31d4fea8d Merge branch 'develop' into dbkr/module_experiments 2025-10-21 11:04:34 +01:00
David Baker
a5f3876a38 Add test for builtinsapi 2025-10-17 17:05:45 +01:00
David Baker
206905c2f5 Make room names deterministic
So the tests don't fail if you add other tests or run them individually
2025-10-17 16:51:59 +01:00
David Baker
51499fa106 add test 2025-10-17 16:28:05 +01:00
David Baker
1ebead1c8a Add test for multiroomviewstore 2025-10-17 16:07:02 +01:00
David Baker
738eac9b90 Fairly awful workaround
to actually break the dependency nightmare
2025-10-17 11:22:00 +01:00
David Baker
2dd743dea0 Switch to using module api via .instance 2025-10-16 19:14:34 +01:00
David Baker
ced886aa07 Merge remote-tracking branch 'origin/develop' into dbkr/module_experiments 2025-10-16 11:25:03 +01:00
David Baker
de5a75777f Merge remote-tracking branch 'origin/develop' into dbkr/module_experiments 2025-10-15 11:37:21 +01:00
David Baker
809b41aa59 Merge remote-tracking branch 'origin/develop' into dbkr/module_experiments 2025-10-13 17:00:24 +01:00
David Baker
b6b1658805 Merge remote-tracking branch 'origin/develop' into dbkr/module_experiments 2025-10-02 15:20:40 +01:00
David Baker
afa340eb18 Remove fetchRoomFn from SpaceNotificationStore
which didn't really seem to have any point as it was only called from
one place
2025-09-26 12:09:07 +01:00
David Baker
7ac4a4a2d4 Merge branch 'develop' into dbkr/module_experiments 2025-09-26 09:51:17 +01:00
David Baker
66bf1dd469 Allow space panel items to be updated
and manage which one is selected, allowing module "spaces" to be
considered spaces
2025-09-25 17:37:48 +01:00
David Baker
9ae447f14f Different interface to add space panel items
A bit less flexible but probably simpler and will help keep things
actually consistent rather than just allowing modules to stick any
JSX into the space panel (which means they also have to worry about
styling if they *do* want it to be consistent).
2025-09-25 11:54:54 +01:00
David Baker
a02a5ac849 Make RoomViewStore able to take a roomId prop 2025-09-24 16:35:28 +01:00
David Baker
e4dee7ab63 Add the MultiRoomViewStore 2025-09-24 16:31:57 +01:00
David Baker
9129c35407 Move ResizerNotifier into SDKContext
so we don't have to pass it into RoomView
2025-09-24 10:56:10 +01:00
David Baker
4b701b55b1 Module API experiments 2025-09-23 19:17:19 +01:00
218 changed files with 1427 additions and 5338 deletions

View File

@@ -1,10 +1,3 @@
/*
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.
*/
module.exports = {
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],

View File

@@ -21,14 +21,8 @@ jobs:
cache: "yarn"
node-version-file: ".node-version"
registry-url: "https://registry.npmjs.org"
# Ensure npm 11.5.1 or later is installed
- 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
env:
NODE_AUTH_TOKEN: ${{ secrets.ELEMENT_NPM_TOKEN }}
- name: 🛠️ Setup
# When running `install` it also calls the `prepare` step which generates
@@ -37,4 +31,4 @@ jobs:
- name: 🚀 Publish to npm
working-directory: packages/shared-components
run: npm publish --access public --tag test --provenance
run: npm publish --access public --provenance

View File

@@ -34,6 +34,10 @@ 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
@@ -55,6 +59,12 @@ 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,6 +35,10 @@ 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"
@@ -87,6 +91,10 @@ 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_ew:
name: Jest (Element Web)
jest:
name: Jest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
@@ -93,13 +93,13 @@ jobs:
complete:
name: jest-tests
needs: jest_ew
needs: jest
if: always()
runs-on: ubuntu-24.04
permissions:
statuses: write
steps:
- if: needs.jest_ew.result != 'skipped' && needs.jest_ew.result != 'success'
- if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
run: exit 1
- name: Skip SonarCloud in merge queue
@@ -112,56 +112,3 @@ 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,4 +36,3 @@ storybook-static
/packages/shared-components/node_modules
/packages/shared-components/dist
/packages/shared-components/src/i18nKeys.d.ts

View File

@@ -1,27 +1,3 @@
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

@@ -407,7 +407,7 @@ The VoIP and Jitsi options are:
If you run your own rageshake server to collect bug reports, the following options may be of interest:
1. `bug_report_endpoint_url`: URL for where to submit rageshake logs to. Rageshakes include feedback submissions and bug reports. When
not present in the config, the app will disable all rageshake functionality. Set to `https://rageshakes.element.io/api/submit` to submit
not present in the config, the app will disable all rageshake functionality. Set to `https://element.io/bugreports/submit` to submit
rageshakes to us, or use your own rageshake server.
2. `uisi_autorageshake_app`: If a user has enabled the "automatically send debug logs on decryption errors" flag, this option will be sent
alongside the rageshake so the rageshake server can filter them by app name. By default, this will be `element-auto-uisi`

View File

@@ -8,64 +8,16 @@ There are some exceptions like when using localhost, which is considered a [secu
## Release tarball
The release tarball contains a pre-built, production-ready version of Element Web that you can deploy to any static web server.
1. Download the latest version from <https://github.com/element-hq/element-web/releases>
1. Untar the tarball on your web server
1. Move (or symlink) the `element-x.x.x` directory to an appropriate name
1. Configure the correct caching headers in your webserver (see [README.md](../README.md#caching-requirements))
1. Configure the app by copying `config.sample.json` to `config.json` and
modifying it. See the [configuration docs](config.md) for details.
1. Enter the URL into your browser and log into Element!
### Installation Steps
1. **Download the latest release**
Download from <https://github.com/element-hq/element-web/releases>
Releases are signed using GPG and the OpenPGP standard. You can verify the signature against the public key at <https://packages.element.io/element-release-key.asc>
2. **Extract the tarball**
```bash
tar -xzf element-v*.tar.gz
```
This creates a directory named `element-x.x.x` containing all the static files.
3. **Deploy to your web server**
Move or symlink the directory to your web server's document root:
```bash
# Example: Move to /var/www/element
sudo mv element-x.x.x /var/www/element
# Or create a symlink for easier version management
sudo ln -s /var/www/element-x.x.x /var/www/element
```
4. **Configure Element Web**
Copy the sample configuration and customize it:
```bash
cd /var/www/element
cp config.sample.json config.json
```
Edit `config.json` to configure your homeserver and other settings. See the [configuration docs](config.md) for details.
5. **Configure your web server**
Set up proper caching headers and security settings. See the [web server configuration examples](#web-server-configuration) below.
6. **Access Element Web**
Navigate to your server's URL (e.g., `https://element.example.com`) and log in!
### Web Server Configuration
Element Web requires specific caching headers to work correctly. The following files **must not be cached** to ensure users always get the latest version:
- `/index.html`
- `/version`
- `/config*.json` (including `config.json` and `config.domain.json`)
Additionally, configure `Cache-Control: no-cache` for `/` to force browsers to revalidate on page load.
Releases are signed using gpg and the OpenPGP standard,
and can be checked against the public key located at <https://packages.element.io/element-release-key.asc>.
## Debian package

View File

@@ -57,7 +57,7 @@ Then you can deploy it to your cluster with something like `kubectl apply -f my-
"https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api"
],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"defaultCountryCode": "GB",
"show_labs_settings": false,
"features": { },

View File

@@ -17,7 +17,7 @@
"https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api"
],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": false,
"room_directory": {

View File

@@ -17,7 +17,7 @@
"https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api"
],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": true,
"room_directory": {

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)"],
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)", "<rootDir>/packages/*/src/**/*.test.[t]s?(x)"],
globalSetup: "<rootDir>/test/globalSetup.ts",
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
setupFilesAfterEnv: ["<rootDir>/test/setupTests.ts"],
@@ -40,7 +40,6 @@ const config: Config = {
"^!!raw-loader!.*": "jest-raw-loader",
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
"counterpart": "<rootDir>/node_modules/counterpart",
},
transformIgnorePatterns: [
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs)).+$",

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.12.3",
"version": "1.12.2",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -29,7 +29,7 @@
"UserFriendlyError"
],
"scripts": {
"i18n": "matrix-gen-i18n src res packages/shared-components/src && yarn i18n:sort && yarn i18n:lint",
"i18n": "matrix-gen-i18n src res packages/shared-components && yarn i18n:sort && yarn i18n:lint",
"i18n:sort": "jq --sort-keys '.' src/i18n/strings/en_EN.json > src/i18n/strings/en_EN.json.tmp && mv src/i18n/strings/en_EN.json.tmp src/i18n/strings/en_EN.json",
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null",
"i18n:diff": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && yarn i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
@@ -72,7 +72,6 @@
"@playwright/test": "1.56.1",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@types/serve-static": "1.15.10",
"oidc-client-ts": "3.3.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001751",
@@ -83,7 +82,6 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@element-hq/element-web-module-api": "1.5.0",
"@element-hq/web-shared-components": "file:packages/shared-components",
"@fontsource/inconsolata": "^5",
"@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7",
@@ -105,6 +103,7 @@
"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,12 +1,4 @@
/*
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.
*/
module.exports = {
root: true,
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: [
"plugin:matrix-org/babel",

View File

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

View File

@@ -1,21 +0,0 @@
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

@@ -1,58 +0,0 @@
/*
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.7",
"version": "0.0.0-test.6",
"description": "Shared components for Element",
"author": "New Vector Ltd.",
"repository": {
@@ -19,10 +19,6 @@
"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",
@@ -34,8 +30,8 @@
"package.json"
],
"scripts": {
"test": "jest",
"prepare": "patch-package && yarn --cwd ../.. build:res && ts-node scripts/gatherTranslationKeys.ts && vite build",
"postinstall": "patch-package",
"prepare": "vite build",
"storybook": "storybook dev -p 6007",
"build-storybook": "storybook build",
"lint": "yarn lint:types && yarn lint:js",
@@ -43,41 +39,27 @@
"lint:types": "tsc --noEmit --jsx react",
"test:storybook": "test-storybook --url http://localhost:6007/",
"test:storybook:ci": "concurrently -k -s first -n \"SB,TEST\" \"yarn storybook --no-open\" \"wait-on tcp:6007 && yarn test-storybook --url http://localhost:6007/ --ci --maxWorkers=2\"",
"test:storybook:update": "playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
"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",
"react-merge-refs": "^3.0.2",
"temporal-polyfill": "^0.3.0"
"counterpart": "^0.18.6"
},
"devDependencies": {
"@element-hq/element-web-playwright-common": "^2.0.0",
"@playwright/test": "^1.50.1",
"@storybook/addon-a11y": "^9.1.10",
"@storybook/addon-designs": "^10.0.2",
"@storybook/addon-docs": "^9.1.10",
"@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",
@@ -86,9 +68,5 @@
"engines": {
"node": ">=20.0.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"peerDependencies": {
"@vector-im/compound-design-tokens": "^6.0.0",
"@vector-im/compound-web": "^8.2.5"
}
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -1,61 +0,0 @@
/*
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) !important;
padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x);
}
.mediaInfo {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
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, type TextualEventViewSnapshot } from "./TextualEventView";
export { TextualEventView } from "./TextualEventView";

View File

@@ -21,17 +21,12 @@ export * from "./utils/Box";
export * from "./utils/Flex";
// Utils
export * from "./utils/i18n";
export { setLanguage } 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) !important;
border-radius: var(--cpd-space-2x);
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://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MediaBody renders the media body 1`] = `
<div>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +0,0 @@
/*
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

@@ -1,47 +0,0 @@
/*
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

@@ -1,46 +0,0 @@
/*
* 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 { KEY_SEPARATOR } from "matrix-web-i18n";
import { type TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n";
import counterpart from "counterpart";
import type { TranslationKey } from "../index";
import type Translations from "../../../../src/i18n/strings/en_EN.json";
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
import webpackLangJsonUrl from "$webapp/i18n/languages.json";
@@ -45,23 +45,16 @@ counterpart.setSeparator(KEY_SEPARATOR);
const FALLBACK_LOCALE = "en";
counterpart.setFallbackLocale(FALLBACK_LOCALE);
// 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);
}
/**
* 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>;
// 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": ["./src/test/utils/jest-matrix-react"],
"jest-matrix-react": ["../../test/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", "@vector-im/compound-design-tokens", "@vector-im/compound-web"],
external: ["react", "react-dom"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
@@ -43,12 +43,5 @@ export default defineConfig({
$webapp: resolve(__dirname, "..", "..", "webapp"),
},
},
plugins: [
dts({
rollupTypes: true,
include: ["src/**/*.{ts,tsx}"],
exclude: ["src/**/*.test.{ts,tsx}"],
copyDtsFiles: true,
}),
],
plugins: [dts({ rollupTypes: true, include: ["src/**/*.{ts,tsx}"], copyDtsFiles: true })],
});

File diff suppressed because it is too large Load Diff

View File

@@ -76,57 +76,6 @@ test.describe("Composer", () => {
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
});
test.describe("render emoji picker with larger viewport height", async () => {
test.use({ viewport: { width: 1280, height: 720 } });
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker.png");
});
});
test.describe("render emoji picker with small viewport height", async () => {
test.use({ viewport: { width: 1280, height: 360 } });
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker-small.png");
});
});
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);

View File

@@ -56,85 +56,4 @@ test.describe("History sharing", function () {
});
},
);
test("Messages sent when we believed the room history was unshared should not be visible", async ({
labsFlags,
browser,
page: alicePage,
user: aliceCredentials,
app: aliceElementApp,
homeserver,
}, testInfo) => {
test.setTimeout(60000);
// In this test:
// 1. Alice creates an encrypted room with Bob.
// 2. She sets the history visibility to "shared", but Bob doesn't receive the memo
// 3. Bob sends a message
// 4. Alice invites Charlie
// 5. Charlie can't see the message.
await aliceElementApp.client.bootstrapCrossSigning(aliceCredentials);
await createRoom(alicePage, "TestRoom", true);
// Register a second user, and open it in a second instance of the app
const bobCredentials = await homeserver.registerUser(`user_${testInfo.testId}_bob`, "password", "Bob");
const bobPage = await createNewInstance(browser, bobCredentials, {}, labsFlags);
const bobElementApp = new ElementAppPage(bobPage);
await bobElementApp.client.bootstrapCrossSigning(bobCredentials);
// ... and a third
const charlieCredentials = await homeserver.registerUser(
`user_${testInfo.testId}_charlie`,
"password",
"Charlie",
);
const charliePage = await createNewInstance(browser, charlieCredentials, {}, labsFlags);
const charlieElementApp = new ElementAppPage(charliePage);
await charlieElementApp.client.bootstrapCrossSigning(charlieCredentials);
// Alice invites Bob, and Bob accepts
const roomId = await aliceElementApp.getCurrentRoomIdFromUrl();
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
await bobPage.getByRole("option", { name: "TestRoom" }).click();
await bobPage.getByRole("button", { name: "Accept" }).click();
// Bob sends a message with "shared" visibility
await sendMessageInCurrentRoom(bobPage, "Message1: 'shared' visibility");
await expect(alicePage.getByText("Message1")).toBeVisible();
// Alice sets the history visibility to "joined"
await aliceElementApp.client.sendStateEvent(roomId, "m.room.history_visibility", {
history_visibility: "joined",
});
await expect(
bobPage.getByText(
"Alice made future room history visible to all room members, from the point they joined.",
),
).toBeVisible();
// Bob stops syncing, and sends a message with "joined" visibility.
// (Stopping syncing *before* sending the message means that the active sync will be flushed by sending the
// message, so that Alice's change to the history viz below won't be seen by Bob.)
await bobPage.route(`**/sync*`, (route) => route.fulfill({}));
await sendMessageInCurrentRoom(bobPage, "Message2: 'joined' visibility");
await expect(alicePage.getByText("Message2")).toBeVisible();
// Alice changes the history viz, but Bob doesn't receive the memo
await aliceElementApp.client.sendStateEvent(roomId, "m.room.history_visibility", {
history_visibility: "shared",
});
await sendMessageInCurrentRoom(bobPage, "Message3: 'shared' visibility, but Bob thinks it is still 'joined'");
// Alice now invites Charlie
await aliceElementApp.inviteUserToCurrentRoom(charlieCredentials.userId);
await charliePage.getByRole("option", { name: "TestRoom" }).click();
await charliePage.getByRole("button", { name: "Accept" }).click();
// Message1 should be visible
// Message2 should be invisible
// Message3 should be undecryptable
await expect(charliePage.getByText("Message1")).toBeVisible();
await expect(charliePage.getByText("You don't have access to this message")).toBeVisible();
});
});

View File

@@ -30,42 +30,33 @@ test.describe("Location sharing", { tag: "@no-firefox" }, () => {
});
});
test(
"sends and displays pin drop location message successfully",
{ tag: "@screenshot" },
async ({ page, user, app }) => {
const roomId = await app.client.createRoom({});
await page.goto(`/#/room/${roomId}`);
test("sends and displays pin drop location message successfully", async ({ page, user, app }) => {
const roomId = await app.client.createRoom({});
await page.goto(`/#/room/${roomId}`);
const composerOptions = await app.openMessageComposerOptions();
await composerOptions.getByRole("menuitem", { name: "Location", exact: true }).click();
const composerOptions = await app.openMessageComposerOptions();
await composerOptions.getByRole("menuitem", { name: "Location", exact: true }).click();
await selectLocationShareTypeOption(page, "Pin").click();
await selectLocationShareTypeOption(page, "Pin").click();
await page.locator("#mx_LocationPicker_map").click();
await page.locator("#mx_LocationPicker_map").click();
await submitShareLocation(page);
await submitShareLocation(page);
await page.locator(".mx_RoomView_body .mx_EventTile .mx_MLocationBody").click({
position: {
x: 225,
y: 150,
},
});
await page.locator(".mx_RoomView_body .mx_EventTile .mx_MLocationBody").click({
position: {
x: 225,
y: 150,
},
});
// Wait for map to load
await expect(page.getByRole("region", { name: "Map" })).toMatchScreenshot(
"location-pin-drop-message-map.png",
);
// clicking location tile opens maximised map
await expect(page.getByRole("dialog")).toBeVisible();
// clicking location tile opens maximised map
await expect(page.getByRole("dialog")).toBeVisible();
await app.closeDialog();
await app.closeDialog();
await expect(page.locator(".mx_Marker")).toBeVisible();
},
);
await expect(page.locator(".mx_Marker")).toBeVisible();
});
test(
"is prompted for and can consent to live location sharing",

View File

@@ -199,21 +199,6 @@ export class ElementAppPage {
return this.page.locator(".mx_RightPanel");
}
/**
* Opens the room info panel if it is not already open.
*
* TODO: fix this so that it works correctly if, say, the member list was open instead of the room info panel.
*
* @returns locator to the right panel
*/
public async openRoomInfoPanel(): Promise<Locator> {
const locator = this.page.getByTestId("right-panel");
if (!(await locator.isVisible())) {
await this.page.getByRole("button", { name: "Room info" }).first().click();
}
return locator;
}
/**
* Opens/closes the memberlist panel
* @returns locator to the memberlist panel
@@ -232,8 +217,8 @@ export class ElementAppPage {
* @param userId - The user to invite to the room.
*/
public async inviteUserToCurrentRoom(userId: string): Promise<void> {
const rightPanel = await this.openRoomInfoPanel();
await rightPanel.getByRole("menuitem", { name: "Invite" }).click();
await this.toggleRoomInfoPanel(); // TODO skip this if the room info panel is already open
await this.page.getByTestId("right-panel").getByRole("menuitem", { name: "Invite" }).click();
const input = this.page.getByRole("dialog").getByTestId("invite-dialog-input");
await input.fill(userId);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.3 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -10,7 +10,7 @@ import {
type StartedPostgreSqlContainer,
} from "@element-hq/element-web-playwright-common/lib/testcontainers";
const TAG = "main@sha256:5957b8a5377c9f767b844fa7f3c800fdb1f7d3d95d3d218fe000fdd2d8f0f2a6";
const TAG = "main@sha256:3d139198268dd06c42fe0fc7ffea5df0f2924704867f68e0a8eb3e542f91b9e8";
/**
* MatrixAuthenticationServiceContainer which freezes the docker digest to

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:6ed2ef07dae854e8004d62dd507a535b9c2408e5def06cf2de4c7c467b30e903";
const TAG = "develop@sha256:5a505af08294414dd832517ee0eafadc1faa33a675e577ca073c6fa731c7e5a8";
/**
* SynapseContainer which freezes the docker digest to stabilise tests,

View File

@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
.mx_SearchBox {
flex: 1 1 0;
min-width: 0;
min-height: 36px; /* to avoid jumping when the X to clear shows/hides */
&.mx_SearchBox_blurred:not(:hover) {
background-color: transparent;

View File

@@ -61,11 +61,6 @@ Please see LICENSE files in the repository root for full details.
/* To match the space around the title */
margin: 0 0 15px 0;
flex-grow: 0;
&:not(:focus-within) + .mx_ForwardList_content {
/* Inhibit the styling if focus is not within the input which handles keyboard accessibility */
--mx_ForwardList_entry_selectedBgColor: transparent;
}
}
.mx_ForwardList_content {
@@ -95,7 +90,7 @@ Please see LICENSE files in the repository root for full details.
&:hover,
&.mx_ForwardList_entry_active {
background-color: var(--mx_ForwardList_entry_selectedBgColor, $spacePanel-bg-color);
background-color: $spacePanel-bg-color;
}
.mx_ForwardList_roomButton {

View File

@@ -150,16 +150,6 @@ Please see LICENSE files in the repository root for full details.
height: unset;
margin-left: $spacing-16;
}
&:not(:focus-within) + #mx_SpotlightDialog_content {
/* Inhibit the styling if focus is not within the input which handles keyboard accessibility */
--mx_SpotlightDialog_option_selectedBgColor: transparent;
/* Hide the enter prompt as in this state pressing enter would not actuate that option */
.mx_SpotlightDialog_enterPrompt {
visibility: hidden;
}
}
}
#mx_SpotlightDialog_content {
@@ -203,17 +193,6 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_SpotlightDialog_option {
border-radius: 8px;
overflow: hidden;
text-overflow: ellipsis;
&:hover,
&[aria-selected="true"] {
background-color: var(--mx_SpotlightDialog_option_selectedBgColor, $quinary-content);
}
}
.mx_SpotlightDialog_recentlyViewed {
> div {
display: flex;
@@ -223,6 +202,7 @@ Please see LICENSE files in the repository root for full details.
}
.mx_SpotlightDialog_option {
border-radius: 8px;
padding: $spacing-4;
color: $primary-content;
font-size: $font-12px;
@@ -233,6 +213,8 @@ Please see LICENSE files in the repository root for full details.
min-width: 58px;
box-sizing: border-box;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
.mx_DecoratedRoomAvatar {
margin: 0 9px $spacing-4; /* maintain centering */
@@ -241,6 +223,11 @@ Please see LICENSE files in the repository root for full details.
& + .mx_SpotlightDialog_option {
margin-left: $spacing-16;
}
&:hover,
&[aria-selected="true"] {
background-color: $quinary-content;
}
}
}
@@ -249,9 +236,8 @@ Please see LICENSE files in the repository root for full details.
.mx_SpotlightDialog_otherSearches,
.mx_SpotlightDialog_hiddenResults {
.mx_SpotlightDialog_option {
--mx_SpotlightDialog_option_selectedBgColor: $system;
padding: 6px $spacing-4;
border-radius: 8px;
font-size: $font-15px;
line-height: $font-24px;
color: $primary-content;
@@ -259,6 +245,8 @@ Please see LICENSE files in the repository root for full details.
display: flex;
align-items: center;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.mx_SpotlightDialog_option--endAdornment {
display: inline-flex;
@@ -362,7 +350,7 @@ Please see LICENSE files in the repository root for full details.
}
&:hover::before,
&:focus-visible::before {
&[aria-selected="true"]::before {
background-color: $secondary-content;
}
}
@@ -373,6 +361,8 @@ Please see LICENSE files in the repository root for full details.
&:hover,
&[aria-selected="true"] {
background-color: $system;
.mx_SpotlightDialog_option--menu,
.mx_SpotlightDialog_option--notifications {
display: block;

View File

@@ -8,8 +8,7 @@ Please see LICENSE files in the repository root for full details.
.mx_EmojiPicker {
width: 340px;
height: min(450px, 80vh);
max-height: 80vh;
height: 450px;
border-radius: 4px;
@@ -182,7 +181,7 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_EmojiPicker_body_showHighlight .mx_EmojiPicker_item_wrapper [tabindex="0"] .mx_EmojiPicker_item {
.mx_EmojiPicker_body .mx_EmojiPicker_item_wrapper[tabindex="0"] .mx_EmojiPicker_item {
background-color: $focus-bg-color;
}

View File

@@ -8,18 +8,13 @@ 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 */
legend {
h2 {
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;
@@ -28,7 +23,7 @@ Please see LICENSE files in the repository root for full details.
}
}
legend::before {
h2::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 !important;
padding-right: 48px;
}
.mx_MImageBody {

View File

@@ -17,10 +17,4 @@ 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,11 +44,3 @@ 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.*,packages/*/src/test/**/*
sonar.test.inclusions=test/*,playwright/*,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,packages/shared-components/coverage/lcov.info
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.coverage.exclusions=\
test/**/*,\
playwright/**/*,\
@@ -21,6 +21,5 @@ sonar.coverage.exclusions=\
src/components/views/dialogs/devtools/**/*,\
src/utils/SessionLock.ts,\
src/**/*.d.ts,\
src/vector/mobile_guide/**/* \
packages/shared-components/src/test/**/*
src/vector/mobile_guide/**/*
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 "@element-hq/web-shared-components";
export { formatSeconds } from "../packages/shared-components/src/utils/DateUtils";
export const MINUTE_MS = 60000;
export const HOUR_MS = MINUTE_MS * 60;

View File

@@ -9,8 +9,7 @@ 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 "@element-hq/web-shared-components";
import { _td, type TranslationKey } from "../../packages/shared-components/src/utils/i18n";
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";
@@ -202,7 +202,6 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
private onPlaybackEnd = async (): Promise<void> => {
await this.context.suspend();
this.emit(PlaybackState.Stopped);
this.clock.flagStop();
};
public async play(): Promise<void> {
@@ -249,8 +248,9 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
this.emit(PlaybackState.Paused);
}
public stop(): Promise<void> {
return this.onPlaybackEnd();
public async stop(): Promise<void> {
await this.onPlaybackEnd();
this.clock.flagStop();
}
public async toggle(): Promise<void> {

View File

@@ -1,14 +1,16 @@
/*
Copyright 2024 New Vector Ltd.
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 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,7 +11,6 @@ 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";
@@ -20,6 +19,7 @@ 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

@@ -12,7 +12,6 @@ import React, { type JSX, type CSSProperties, type RefObject, type SyntheticEven
import ReactDOM from "react-dom";
import classNames from "classnames";
import FocusLock from "react-focus-lock";
import { TooltipProvider } from "@vector-im/compound-web";
import { type Writeable } from "../../@types/common";
import UIStore from "../../stores/UIStore";
@@ -405,7 +404,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
);
if (focusLock) {
body = <FocusLock returnFocus>{body}</FocusLock>;
body = <FocusLock>{body}</FocusLock>;
}
// filter props that are invalid for DOM elements
@@ -426,17 +425,15 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
onContextMenu={this.onContextMenuPreventBubbling}
>
{background}
<TooltipProvider>
<div
className={menuClasses}
style={menuStyle}
ref={this.collectContextMenuRect}
role={managed ? "menu" : undefined}
{...divProps}
>
{body}
</div>
</TooltipProvider>
<div
className={menuClasses}
style={menuStyle}
ref={this.collectContextMenuRect}
role={managed ? "menu" : undefined}
{...divProps}
>
{body}
</div>
</div>
)}
</RovingTabIndexProvider>

View File

@@ -191,28 +191,12 @@ class LoggedInView extends React.Component<IProps, IState> {
SettingsStore.watchSetting("userTimezone", null, this.onTimezoneUpdate),
];
this.loadResizer();
OwnProfileStore.instance.on(UPDATE_EVENT, this.refreshBackgroundImage);
this.refreshBackgroundImage();
}
/**
* Load or reload the resizer for the left panel
*/
private loadResizer(): void {
// If the resizer already exists, detach it first
this.resizer?.detach();
this.resizer = this.createResizer();
this.resizer.attach();
this.loadResizerPreferences();
}
public componentDidUpdate(nextProps: Readonly<IProps>, nextState: Readonly<IState>, nextContext: any): void {
if (nextProps.page_type !== this.props.page_type) {
this.loadResizer();
}
OwnProfileStore.instance.on(UPDATE_EVENT, this.refreshBackgroundImage);
this.loadResizerPreferences();
this.refreshBackgroundImage();
}
private onTimezoneUpdate = async (): Promise<void> => {

View File

@@ -1924,8 +1924,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
userId: userId,
subAction: params?.action,
});
} else if (ModuleApi.instance.navigation.locationRenderers.get(screen)) {
this.setState({ page_type: screen });
} else {
if (ModuleApi.instance.navigation.locationRenderers.get(screen)) {
this.setState({ page_type: screen });
}
}
}

View File

@@ -165,16 +165,6 @@ interface IRoomProps extends RoomViewProps {
* Omitting this will mean that RoomView renders for the room held in SDKContext.RoomViewStore.
*/
roomId?: string;
/*
* If true, hide the header
*/
hideHeader?: boolean;
/*
* If true, hide the composer
*/
hideComposer?: boolean;
}
export { MainSplitContentType };
@@ -2465,7 +2455,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
let messageComposer;
const showComposer =
!this.props.hideComposer &&
!isRoomEncryptionLoading &&
// joined and not showing search results
myMembership === KnownMembership.Join &&
@@ -2676,12 +2665,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
ref={this.roomViewBody}
data-layout={this.state.layout}
>
{!this.props.hideHeader && (
<RoomHeader
room={this.state.room}
additionalButtons={this.state.viewRoomOpts.buttons}
/>
)}
<RoomHeader
room={this.state.room}
additionalButtons={this.state.viewRoomOpts.buttons}
/>
{mainSplitBody}
</div>
</MainSplit>

View File

@@ -329,8 +329,8 @@ const SpaceSetupFirstRooms: React.FC<{
return createRoom(space.client, {
createOpts: {
preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
name,
},
name,
spinner: false,
encryption: false,
andView: false,
@@ -423,7 +423,7 @@ const SpaceSetupPublicShare: React.FC<ISpaceSetupPublicShareProps> = ({
<div className="mx_SpaceRoomView_publicShare">
<h1>
{_t("create_space|share_heading", {
name: justCreatedOpts?.name || space.name,
name: justCreatedOpts?.createOpts?.name || space.name,
})}
</h1>
<div className="mx_SpaceRoomView_description">{_t("create_space|share_description")}</div>
@@ -449,7 +449,7 @@ const SpaceSetupPrivateScope: React.FC<{
<h1>{_t("create_space|private_personal_heading")}</h1>
<div className="mx_SpaceRoomView_description">
{_t("create_space|private_personal_description", {
name: justCreatedOpts?.name || space.name,
name: justCreatedOpts?.createOpts?.name || space.name,
})}
</div>
@@ -686,7 +686,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
<SpaceSetupFirstRooms
space={this.props.space}
title={_t("create_space|setup_rooms_community_heading", {
spaceName: this.props.justCreatedOpts?.name || this.props.space.name,
spaceName: this.props.justCreatedOpts?.createOpts?.name || this.props.space.name,
})}
description={
<>

View File

@@ -42,7 +42,7 @@ export interface IListViewProps<Item, Context>
index: number,
item: Item,
context: ListContext<Context>,
onFocus: (item: Item, e: React.FocusEvent) => void,
onFocus: (e: React.FocusEvent) => void,
) => JSX.Element;
/**
@@ -230,26 +230,19 @@ export function ListView<Item, Context = any>(props: IListViewProps<Item, Contex
virtuosoDomRef.current = element;
}, []);
/**
* Focus handler passed to each item component.
* Don't declare inside getItemComponent to avoid re-creating on each render.
*/
const onFocusForGetItemComponent = useCallback(
(item: Item, e: React.FocusEvent) => {
// If one of the item components has been focused directly, set the focused and tabIndex state
// and stop propagation so the ListViews onFocus doesn't also handle it.
const key = getItemKey(item);
setIsFocused(true);
setTabIndexKey(key);
e.stopPropagation();
},
[getItemKey],
);
const getItemComponentInternal = useCallback(
(index: number, item: Item, context: ListContext<Context>): JSX.Element =>
getItemComponent(index, item, context, onFocusForGetItemComponent),
[getItemComponent, onFocusForGetItemComponent],
(index: number, item: Item, context: ListContext<Context>): JSX.Element => {
const onFocus = (e: React.FocusEvent): void => {
// If one of the item components has been focused directly, set the focused and tabIndex state
// and stop propagation so the ListViews onFocus doesn't also handle it.
const key = getItemKey(item);
setIsFocused(true);
setTabIndexKey(key);
e.stopPropagation();
};
return getItemComponent(index, item, context, onFocus);
},
[getItemComponent, getItemKey],
);
/**
* Handles focus events on the list.

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,7 +8,6 @@ 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";
@@ -19,6 +18,7 @@ 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} focusLock>
<ContextMenu {...toRightOf(buttonRect)} onFinished={this.closeMenu} managed={false}>
<ReactionPicker mxEvent={mxEvent} onFinished={this.onCloseReactionPicker} reactions={reactions} />
</ContextMenu>
);

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