Compare commits

..
3614 changed files with 45699 additions and 91993 deletions
+23 -4
View File
@@ -46,17 +46,24 @@ jobs:
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: "24.13.1"
node-version: "lts/*"
cache: "yarn"
cache-dependency-path: |
cloudbeaver/common-*/yarn.lock
cloudbeaver/webapp/yarn.lock
- name: restore cloudbeaver-common typescript cache
uses: actions/cache@v4
with:
path: "cloudbeaver/common-*/**/*/lib"
key: ${{ runner.os }}-common-dist-${{ hashFiles('cloudbeaver/common-*/yarn.lock') }}
restore-keys: |
${{ runner.os }}-common-dist-
- name: restore typescript cache
uses: actions/cache@v4
with:
path: |
"cloudbeaver/webapp/common-*/**/*/lib"
"cloudbeaver/webapp/**/packages/*/dist"
path: "cloudbeaver/webapp/**/packages/*/dist"
key: ${{ runner.os }}-dist-${{ hashFiles('cloudbeaver/webapp/yarn.lock') }}
restore-keys: |
${{ runner.os }}-dist-
@@ -64,11 +71,23 @@ jobs:
- if: inputs.clean-cache
run: yarn clear
- run: yarn install --immutable
working-directory: ./cloudbeaver/common-typescript
- run: yarn install --immutable
working-directory: ./cloudbeaver/common-react
- run: yarn install --immutable
- run: yarn bundle
working-directory: ./cloudbeaver/webapp/packages/product-default
- run: yarn test
working-directory: ./cloudbeaver/common-typescript
- run: yarn test
working-directory: ./cloudbeaver/common-react
- run: yarn test
# - name: Archive build artifacts
+3 -3
View File
@@ -25,15 +25,15 @@ jobs:
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: "24.13.1"
node-version: "lts/*"
cache: "yarn"
cache-dependency-path: "./cloudbeaver/webapp/yarn.lock"
# - run: yarn install --immutable
# working-directory: ./cloudbeaver/webapp/common-typescript
# working-directory: ./cloudbeaver/common-typescript
# - run: yarn install --immutable
# working-directory: ./cloudbeaver/webapp/common-react
# working-directory: ./cloudbeaver/common-react
- run: |
yarn install --immutable
+5 -22
View File
@@ -6,7 +6,6 @@ on:
- opened
- synchronize
- reopened
- ready_for_review
push:
branches: [devel]
@@ -25,6 +24,11 @@ jobs:
project-deps: ./cloudbeaver/project.deps
timeout-minutes: 5
# build-server:
# name: Server
# uses: ./.github/workflows/backend-build.yml
# secrets: inherit
build-frontend:
name: Frontend
uses: ./.github/workflows/frontend-build.yml
@@ -34,33 +38,12 @@ jobs:
name: Server
uses: dbeaver/dbeaver-common/.github/workflows/java-checkstyle.yml@devel
secrets: inherit
permissions:
contents: read
pull-requests: read
lint-frontend:
name: Frontend
uses: ./.github/workflows/frontend-lint.yml
secrets: inherit
required-builds:
name: Required Builds
if: ${{ always() && github.event.pull_request.draft != true }}
needs: [build-server, build-frontend]
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Verify required builds
env:
SERVER_RESULT: ${{ needs.build-server.result }}
FRONTEND_RESULT: ${{ needs.build-frontend.result }}
run: |
if [[ "$SERVER_RESULT" != "success" || "$FRONTEND_RESULT" != "success" ]]; then
echo "Server build: $SERVER_RESULT"
echo "Frontend build: $FRONTEND_RESULT"
exit 1
fi
# call-frontend-tests:
# name: Frontend Unit Tests
# needs: call-frontend-build
+2 -1
View File
@@ -14,7 +14,8 @@
"redhat.vscode-yaml",
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"github.copilot-chat",
"syler.sass-indented",
"VisualStudioExptTeam.vscodeintellicode",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
"mrmlnc.vscode-json5",
+2 -2
View File
@@ -48,8 +48,7 @@
"-Declipse.pde.launch=true",
"-XstartOnFirstThread",
"-Dfile.encoding=UTF-8",
"--enable-native-access=ALL-UNNAMED",
"--add-modules=ALL-DEFAULT",
"--add-modules=ALL-SYSTEM",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
@@ -65,6 +64,7 @@
"--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED",
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
"--add-opens=java.base/sun.security.ssl=ALL-UNNAMED",
"--add-opens=java.base/sun.security.action=ALL-UNNAMED",
"--add-opens=java.base/sun.security.util=ALL-UNNAMED",
"--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED",
"--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
+5 -14
View File
@@ -26,7 +26,7 @@
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/webapp/common-typescript"
"cwd": "${workspaceFolder}/common-typescript"
}
},
{
@@ -36,7 +36,7 @@
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/webapp/common-react"
"cwd": "${workspaceFolder}/common-react"
}
},
{
@@ -92,10 +92,10 @@
"label": "Run Backend CE",
"type": "shell",
"windows": {
"command": "./run-cloudbeaver-server.bat"
"command": "./run-server.bat"
},
"osx": {
"command": "./run-cloudbeaver-server.sh"
"command": "./run-server.sh"
},
"options": {
"cwd": "${workspaceFolder}/deploy/cloudbeaver"
@@ -133,18 +133,9 @@
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/webapp/common-react/@dbeaver/ui-kit"
"cwd": "${workspaceFolder}/common-react/@dbeaver/ui-kit"
},
"args": ["docs"]
},
{
"label": "Run Storybook",
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/webapp/packages/storybook"
},
"args": ["storybook"]
}
],
"inputs": [
-96
View File
@@ -1,96 +0,0 @@
# CloudBeaver
## What is CloudBeaver?
CloudBeaver is an open-source, web-based database management application. Its modular frontend provides the database navigator, SQL editor, data editor, administration, and related features, and communicates with the CloudBeaver server through GraphQL.
## Frontend
### Scope
- These instructions apply to the frontend under `webapp/` in CloudBeaver Community Edition and to any other CloudBeaver edition or product that shares this frontend architecture.
- Do not modify `server/`, `deploy/`, or backend configuration unless the task explicitly requires it. Backend GraphQL schemas are an input to frontend code generation, not part of the default edit scope.
- Treat `package.json`, `.yarnrc.yml`, and the frontend CI workflows as the source of truth for tool and runtime versions; do not duplicate their version numbers in documentation.
### Project map
- `webapp/package.json`: Yarn Plug'n'Play workspace root and repository-wide frontend scripts.
- `webapp/packages/core-*`: shared application foundations, services, UI primitives, state, and infrastructure.
- `webapp/packages/plugin-*`: feature modules. A package normally registers services and bootstraps in `src/module.ts` and exposes its public API from `src/index.ts`.
- `webapp/packages/product-*`: CE product composition and Vite entry points. `product-default` is the runnable/bundled frontend; `product-default-impl` selects the CE plugin set.
- `webapp/packages/core-sdk`: GraphQL operations and generated client types.
- `webapp/common-typescript/@dbeaver/*` and `webapp/common-react/@dbeaver/*`: shared DBeaver utilities and React components used by the workspace.
- Tests are colocated with source as `*.test.ts` or `*.test.tsx`; styles are usually colocated CSS modules.
### Stack
TypeScript ES modules, React, MobX, the project DI/module registry, GraphQL Code Generator, Vite, Yarn workspaces with PnP, Vitest and Testing Library, ESLint, Prettier, CSS Modules, and Tailwind-based theming.
### Setup and common commands
Use the Node version configured by frontend CI and the Yarn version pinned in `webapp/package.json`. Run frontend commands from `webapp/` unless noted.
```sh
corepack enable
cd webapp
yarn install --immutable
```
```sh
# Start the frontend dev server; a compatible CloudBeaver API must run separately.
(cd packages/product-default && yarn dev)
# From webapp/: repository-wide checks.
yarn lint
yarn test
yarn validate-dependencies
# Production frontend build.
(cd packages/product-default && yarn bundle)
```
For a focused check, use a package's existing script, for example `yarn workspace @cloudbeaver/core-utils test` or `yarn workspace @cloudbeaver/plugin-sql-editor lint`. Do not invent a package script that is absent from its `package.json`.
### Working conventions
- Put changes in the smallest package that owns the behavior. Prefer existing `core-*` abstractions and UI primitives before adding cross-package helpers.
- Keep dependencies directed from plugins to core: `core-*` packages must never import or depend on `plugin-*` packages. Plugins may use public APIs from core packages and from other plugins, subject to the administration boundary and the no-cycles rule below.
- Put administration-only behavior in a separate package whose name ends with `-administration`. Public/non-administration plugins must not import or depend on administration packages.
- Keep the workspace package dependency graph acyclic; do not introduce direct or indirect circular dependencies.
- Import workspace packages through their public `@cloudbeaver/*` or `@dbeaver/*` exports; never reach into another package's `src/` directory.
- When adding a workspace dependency, update the package's `package.json` through Yarn. The configured `ts-project-linker` synchronizes `tsconfig.json` project references during a non-immutable install; edit references manually only if automatic linking cannot complete. Run `yarn validate-dependencies` afterward.
- Register services and bootstraps through the package's `src/module.ts`. Change product composition only when enabling or disabling a package for the CE product.
- Put user-visible text in the package localization files and follow the neighboring `LocaleService` pattern; do not hardcode UI copy in components.
- Add or change GraphQL operations as `.gql` files in `core-sdk`. Regenerate through `yarn workspace @cloudbeaver/core-sdk gql:gen`; never edit the ignored generated `src/sdk.ts` manually.
- Preserve valid license headers on existing source files. When modifying an existing source file, update the ending copyright year to the current four-digit calendar year while keeping its original starting year. For new source files, use the template below and replace `<CURRENT_YEAR>` with the current year; never leave the placeholder in committed code. Use the repository ESLint and Prettier configuration rather than manual formatting rules.
- Do not edit or commit generated/install artifacts such as `node_modules/`, `.pnp.*`, `lib/`, `dist/`, `coverage/`, or `allure-results/`.
- Add focused tests for behavior changes. Before handoff, run lint and tests for affected packages; run the repository-wide checks and product bundle for cross-cutting or product-composition changes.
### License header for new source files
```text
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-<CURRENT_YEAR> DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
```
## Backend
- Before backend work, read and follow `../dbeaver/AGENTS.md` for inherited Java and Tycho conventions.
- Cloudbeaver does not use SWT or Eclipse RCP, and its backend is headless.
- Configuration file is generated using `apps/config-generator`. For making changes there, update `config/template/cloudbeaver-base.conf` or use patches (for specific product parameters).
- The backend must support at least the following databases: PostgreSQL, MySQL, Oracle, and SQL Server. Support for additional databases is optional.
### GraphQL API
- Use consistent names. IDs must use type `ID`; ID arguments and inputs must end with `Id`, for example `projectId`.
- Top-level methods follow `{pluginId}{methodName}`, for example `authLogin`, `rmListProjects`, and `navNodeChildren`. Mark new public API with `@since`.
- Preserve released APIs: deprecate instead of renaming or removing, include the deprecation version, and remove only after more than one year. EA-only APIs may change before public release.
- New arguments and input fields added to a released API must be optional.
### License header
- Please use header from ../dbeaver/docs/license_header.txt for backend source files.
+56 -77
View File
@@ -1,20 +1,17 @@
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" alt="CloudBeaver logo" align="right" width="250"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" align="right" width="250"/>
# CloudBeaver Community
Cloud Database Manager - Community Edition.
CloudBeaver is a web server that provides a rich web interface. The server itself is a Java application, and the web part is written in TypeScript and React.
It is free to use and open-source (licensed under [Apache 2](https://github.com/dbeaver/cloudbeaver/blob/devel/LICENSE) license).
See our [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details.
<a><img src="https://github.com/dbeaver/cloudbeaver/wiki/images/connection-creation-demo.png" width="400"/></a>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/gis-demo.png" width="400"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/data-transfer-demo.png" width="400"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/sql-editor-demo.png" width="400"/>
## Documentation
- [GitHub WIKI](https://github.com/dbeaver/cloudbeaver/wiki)
- [Official documentation](https://dbeaver.com/docs/cloudbeaver/)
## Run in Docker
- [Official Docker repository](https://hub.docker.com/r/dbeaver/cloudbeaver)
@@ -22,85 +19,67 @@ It is free to use and open-source (licensed under [Apache 2](https://github.com/
## Demo server
You can see a live demo of CloudBeaver server here: https://demo.cloudbeaver.io
You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
[Database access instructions](https://github.com/dbeaver/cloudbeaver/wiki/Demo-Server)
## Changelog
### 26.2.0 2026-08-31
### 25.1.4 2025-08-04
- Added an ability to review the script before execution when users edit tables without primary keys in the Data Editor;
- DuckDB: driver has been updated to version 1.3.2;
- Databend: driver has been updated to version 0.3.9.
### Changes since 26.1.0
### 25.1.3 2025-07-21
- A password confirmation field has been added for administrators in the Easy Config section to help prevent accidental misconfigurations;
- Added column descriptions in the Data Editor to provide more metadata context. You can disable this in Preferences > Data Viewer;
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences -> SQL Editor;
- Clickhouse: fixed the presentation of tuples and map data types in the data editor.
- AI Assistant:
- Added AI Chat to help users generate and fix queries and explore data more easily. It’s integrated with the SQL Editor to run generated queries instantly. All AI features can be turned off in the Server configuration.
- Added the ability to use multiple AI configurations for working with the AI chat. Administrators can define tokens, engines and models for OpenAI and Copilot in the Administration panel for each AI profile separately. After that, users can switch between these profiles while using AI features.
- Added the ability to cancel a response in the AI Chat to stop response generation when needed.
- The "Endpoint" setting was renamed to "API Base URL" for the OpenAI provider.
- Added AI engine icons to AI configuration profiles, making it easier to identify AI providers in the Administration panel and AI Chat configuration.
### 25.1.2 2025-07-07
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
- Added a button “Clear” to the Output panel;
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
- PostgresSQL: fixed misplaced comment for table DDL generation.
### 25.1.1 2025-06-23
### Changes since 25.1.0:
- Added a search panel for SQL Editor and Value panel: press Ctrl+F to open a panel that allows searching and replacing by keyword or regular expression.
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL editor or documentation.
- Databend database support has been added.
### 25.1.0 2025-06-02
### Changes since 25.0.0:
- Important:
- Connection templates were removed from the application. You can use the Connection Editor to create new connections.
- Administration:
- Added the database version and the driver version to diagnostic logs.
- Fixed the issue of fetching groups during LDAP authorization. CloudBeaver now provides all matched groups regardless of fetching errors.
- Added the Last Login time to the user form and the user table in the administration part.
- Authorization:
- Added the option to upload files for certificates and keys in the SSL authorization configuration.
- Fixed an issue causing the application to lose connection to the database after extended uptime. Access is kept automatically during long-running sessions.
- Log records now capture create, update, and delete actions for users, teams, and connections for improved tracking and transparency.
- LDAP authorization:
- Added support for secure [LDAP authentication over SSL](https://github.com/dbeaver/cloudbeaver/wiki/LDAP-Authentication);
- Brute force protection is now supported for LDAP authentication. All authentication methods on the application level have this security mechanism.
- SQL Editor:
- A new setting to autosave scripts in the SQL editor appeared in global and users' preferences;
- Auto-completion has been improved for aliases and camelCase entities.
- Data Editor:
- Added the References panel to the Data Editor. The panel displays related records from connected tables. Users can explore table relationships directly.
- Added the ability to export generated SQL to a .sql file from the Generate SQL dialog.
- Fixed copying of large JSON values in the Data Editor - now the full value can be copied.
- Resolved an issue where the Data Editor ignored the text preview's maximum length quota. Increasing this quota now enables the ability to edit large values.
- Data Transfer:
- Added advanced data import settings, including database-specific replace methods, transaction support, bulk loading, and the option to use a separate connection for data import.
- Improved memory usage when importing large CSV files to improve the application performance.
- Accessibility:
- Improved accessibility for the search on the Users section in the Administration panel. The application allows focusing on the search field with the Tab key.
- Added a loader to the Save button in the Administration panel to improve indication of the long-performing operations.
- Improved drag-and-drop in the Navigator Tree for objects by enlarging the auto-scroll trigger areas.
- Added the ability to expand and collapse code block elements, including JSON, using the keyboard in the Data Editor.
- Fixed shortcuts behavior for AZERTY keyboards. Now shortcuts like Ctrl/Cmd + Z work correctly.
- Added the ability to use the Enter or space keys to turn checkboxes on/off in the connection properties tab.
- Improved table rendering to scale column width depending on the content;
- Accessibility: improved keyboard navigation by sorting buttons in column headers.
- General:
- Added connection types to visually distinguish database connections using background colors across the Database Navigator and application UI.
- Improved SQL autocomplete suggestion ordering for object names. Now suggestions are sorted alphabetically.
- Added a Project Info tab containing the project description to the project context menu.
- Fixed synchronization between devices. Users see updated connections and folders in other devices without manual refreshes.
- Databases:
- ClickHouse
- The driver was updated to version 0.10.0
- Improved JSON display in the Value panel
- Added support for the Map data type visualization for the Data Editor
- The Databend driver was updated to version 0.4.8.
- LibSQL: Added support for database views.
- MySQL: Fixed support for comments starting with '#' symbol in the SQL Editor.
- PostgreSQL
- Updated the driver to version 42.7.13.
- Added a Show full DDL option for schemas and tables to generate complete DDL, including objects within a schema and object comments and privileges.
- Fixed an error that occurred when applying filters to database nodes
- Fixed generated calls for procedures with OUT parameters
- Security:
- Removed the ability to rename SQL scripts that start with dots to prevent them from being moved outside the Scripts directory into the root Tree.
- Added a new "Auto-create users" setting for the reverse proxy provider, allowing new user creation on login. It is enabled by default.
- Fixed a security flaw allowing unauthorized users to test database connections. The application now requires proper permissions for this action, preventing malicious requests and protecting server files.
- Fixed a security flaw in the LDAP authentication process. The application validates the username input to prevent unauthorized access and protect directory data.
- Fixed a cross-site scripting vulnerability caused by unsanitized user input in the web interface.
- Resolved a security flaw related to user-controlled file paths by adding strict validation to prevent unauthorized resource access.
- Updated session handling to prevent session fixation vulnerabilities. The application generates new session identifiers upon login.
- Updated the PostgreSQL JDBC driver to version 42.7.13, including a fix for a high-severity security vulnerability (CVE-2026-54291).
- Fixed the critical vulnerability (CVE-2026-59873) in the tar library. The library was updated to version 7.5.19.
- Fixed the critical vulnerability (CVE-2026-9277) in the shell-quote library (updated to version 1.8.4)
- Fixed the high vulnerability (CVE-2026-10050) in the jetty-security library. The library was updated to version 12.1.11.
- Fixed the high vulnerability (CVE-2026-14257) in the brace-expansion library. The library was updated to version 5.0.8.
- Fixed the high vulnerability (GHSA-r28c-9q8g-f849) in the postcss library. The library was updated to version 8.5.18.
- Fixed the high vulnerability (GHSA-pm4m-ph32-ghv5) in the js-yaml library. The library was updated to version 5.2.2.
- Fixed the high vulnerability (CVE-2026-59880) in the immutable library. The library was updated to version 5.1.8.
- Fixed the high vulnerability (CVE-2026-59879) in the fast-uri library. The library was updated to version 3.1.3.
- Fixed the high vulnerability (CVE-2026-13311) in the shell-quote library. The library was updated to version 1.9.0.
- Fixed the high vulnerability (GHSA-gcfj-64vw-6mp9) in the axios library. The library was updated to version 1.18.0.
- Fixed the high vulnerability (CVE-2026-67213) in the nanoid library. The library was updated to version 3.3.17.
- Fixed the high vulnerability (CVE-2025-71329) in the less library. The library was updated to version 4.7.0.
- Fixed the high vulnerability (CVE-2026-13697) in the undici library. The library was updated to version 8.9.0.
- Fixed the high vulnerability (CVE-2026-18446) in the fast-uri library. The library was updated to version 4.1.2.
- Fixed the high vulnerability (CVE-2026-69152) in the brace-expansion library. The library was updated to version 5.0.9.
- Fixed the high vulnerability (GHSA-gv7w-rqvm-qjhr) in the esbuild library. The library was updated to version 0.28.1.
- Fixed the high vulnerability (CVE-2026-12143) in the form-data library. The library was updated to version 4.0.6.
- Added the ability to configure the default database or schema in the connection configuration;
- Enhanced initial configuration stage security: a server needs to be restarted if the initial setup time exceeds 1 hour;
- Improved Navigation tree performance: lost connection does not lead to the application freezing anymore;
- Data transfer: Improved export/import functionality performance by optimizing disk memory consumption;
- Vietnamese localization has been added (thanks to [0xhanh](https://github.com/0xhanh)).
- Databases and drivers:
- Clickhouse: driver has been updated to version 0.8.5;
- DuckDB: expanded the list of system objects that can be hidden in the Navigation tree;
- LibSQL: added the ability to connect using token authentication;
- Oracle: users' DDL in Oracle is displayed in the metadata editor on the corresponding tab for users with Oracle administration permissions;
- SQLite: added information about a table's Strict mode to the table metadata section (thanks to [eusebe-cda](https://github.com/eusebe-cda)).
## Contribution
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
The most significant contribution to our code for the major release 25.1.0 was made by:
1. [0xhanh](https://github.com/0xhanh) - for Vietnamese localization.
+13 -1
View File
@@ -1,6 +1,18 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | --------- |
| 22.x | yes |
| 23.x | yes |
| 24.x | yes |
## Reporting a Vulnerability
Please report (suspected) security vulnerabilities to <security@dbeaver.com>.
Please report (suspected) security vulnerabilities to devops@dbeaver.com.
You will receive a response from us within 48 hours.
If the issue is confirmed, we will release a patch as soon as possible, depending on complexity, but historically, within a few days.
-61
View File
@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.cloudbeaver</groupId>
<artifactId>config-generator</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>io.cloudbeaver.ConfigGenerator</mainClass>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-external-config</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/config</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/../../config/template</directory>
<includes>
<include>cloudbeaver-base.conf</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,235 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import java.io.Reader;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class ConfigGenerator {
private static final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.create();
private static final String EMPTY_STRING = "''";
public static void main(String[] args) throws Exception {
String baseConfigPath = System.getProperty("config.base");
if (baseConfigPath == null || baseConfigPath.isEmpty()) {
URL resource = ConfigGenerator.class.getClassLoader().getResource("config/cloudbeaver-base.conf");
if (resource == null) {
System.out.println("Base config path is required");
return;
}
baseConfigPath = resource.toURI().getPath();
if (System.getProperty("os.name").toLowerCase().contains("win")) {
// Fix for Windows in dev mode. Resource URI starts with / (e.g. /C:/some/folder)
// And this breaks Java NIO
if (baseConfigPath.startsWith("/")) {
baseConfigPath = baseConfigPath.substring(1);
}
}
}
String outputPath = System.getProperty("config.output");
if (outputPath == null || outputPath.isEmpty()) {
System.out.println("Output config path is required");
return;
}
String configPatches = System.getProperty("config.patches");
List<String> overrideConfigs = configPatches == null ? List.of() : Arrays.asList(configPatches.split(","));
Map<String, Object> config = readConfigurationFile(baseConfigPath);
System.out.println("Base config loaded");
for (String overrideConfig : overrideConfigs) {
if (Files.exists(Path.of(overrideConfig))) {
System.out.println("Applying override config: " + overrideConfig);
} else {
System.out.println("Override config not found, skipping: " + overrideConfig);
continue;
}
System.out.println("Processing override config: " + overrideConfig);
Map<String, Object> overrideConfigMap = readConfigurationFile(overrideConfig);
System.out.println("Override config loaded: " + overrideConfig);
applyProductConfig(config, overrideConfigMap);
System.out.println("Override config applied: " + overrideConfig);
}
Map<String, Object> finalMap = convertToOutputFormat(config);
System.out.println("Config converted to output format");
writeConfigurationFile(finalMap, outputPath);
System.out.println("Config written to: " + outputPath);
}
private static Map<String, Object> readConfigurationFile(String filePath) throws Exception {
Path path = Path.of(filePath);
if (!Files.exists(path)) {
throw new IllegalArgumentException("Configuration file not found: " + filePath);
}
try (Reader reader = Files.newBufferedReader(path)) {
return gson.fromJson(reader, TypeToken.getParameterized(Map.class, String.class, Object.class).getType());
}
}
private static void applyProductConfig(Map<String, Object> baseConfig, Map<String, Object> productConfig) {
if (productConfig.containsKey("add")) {
Map<String, Object> add = (Map<String, Object>) productConfig.get("add");
deepMergeMap(baseConfig, add);
}
if (productConfig.containsKey("remove")) {
Map<String, Object> remove = (Map<String, Object>) productConfig.get("remove");
deepRemoveMap(baseConfig, remove);
}
}
private static void deepMergeMap(Map<String, Object> target, Map<String, Object> source) {
for (Map.Entry<String, Object> entry : source.entrySet()) {
Object sourceValue = entry.getValue();
Object targetValue = target.get(entry.getKey());
if (sourceValue instanceof Map && targetValue instanceof Map) {
deepMergeMap((Map<String, Object>) targetValue, (Map<String, Object>) sourceValue);
} else {
target.put(entry.getKey(), sourceValue);
}
}
}
private static void deepRemoveMap(Map<String, Object> target, Map<String, Object> source) {
for (Map.Entry<String, Object> entry : source.entrySet()) {
Object sourceValue = entry.getValue();
Object targetValue = target.get(entry.getKey());
if (sourceValue instanceof Map && targetValue instanceof Map) {
deepRemoveMap((Map<String, Object>) targetValue, (Map<String, Object>) sourceValue);
} else {
target.remove(entry.getKey());
}
}
}
private static void writeConfigurationFile(Map<String, Object> map, String filePath) throws Exception {
JsonElement tree = gson.toJsonTree(map);
StringBuilder sb = new StringBuilder();
writeElement(tree, sb, 0);
Files.write(Path.of(filePath), sb.toString().getBytes());
}
private static void writeElement(JsonElement elem, StringBuilder sb, int count) {
String indent = " ";
if (elem.isJsonObject()) {
sb.append("{\n");
JsonObject obj = elem.getAsJsonObject();
var iterator = obj.entrySet().iterator();
while (iterator.hasNext()) {
var entry = iterator.next();
sb.repeat(indent, count + 1).append(entry.getKey()).append(": ");
writeElement(entry.getValue(), sb, count + 1);
if (iterator.hasNext()) {
sb.append(",");
}
sb.append("\n");
}
sb.repeat(indent, count).append("}");
} else if (elem.isJsonArray()) {
sb.append("[\n");
JsonArray array = elem.getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
sb.repeat(indent, count + 1);
writeElement(array.get(i), sb, count + 1);
if (i < array.size() - 1) {
sb.append(",");
}
sb.append("\n");
}
sb.repeat(indent, count).append("]");
} else {
String valueStr = gson.toJson(elem).replace("\n", "\n" + indent);
sb.append(valueStr);
}
}
private static Map<String, Object> convertToOutputFormat(Map<String, Object> map) {
Map<String, Object> result = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
result.put(entry.getKey(), convertValue(entry.getValue()));
}
return result;
}
private static Object convertValue(Object value) {
if (value instanceof Map) {
Map<String, Object> map = (Map<String, Object>) value;
if (!map.isEmpty() && map.containsKey("value")) {
if (map.containsKey("env")) {
String env = (String) map.get("env");
Object defValue = map.get("value");
return "${" + env + ":" + formatDefaultValue(defValue) + "}";
} else {
return convertValue(map.get("value"));
}
}
Map<String, Object> result = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
result.put(entry.getKey(), convertValue(entry.getValue()));
}
return result;
} else if (value instanceof List) {
List<Object> list = (List<Object>) value;
List<Object> result = new ArrayList<>();
for (Object item : list) {
result.add(convertValue(item));
}
return result;
}
return value;
}
private static String formatDefaultValue(Object value) {
return switch (value) {
case null -> EMPTY_STRING;
case String str -> str.isEmpty() ? EMPTY_STRING : str;
case Boolean b -> value.toString();
case Number number -> {
if (number instanceof Double || number instanceof Float) {
long longVal = number.longValue();
if (longVal == number.doubleValue()) {
yield String.valueOf(longVal);
}
}
yield number.toString();
}
// TODO: handle list elements if needed
default -> value.toString();
};
}
}
@@ -1,7 +1,6 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
+9
View File
@@ -0,0 +1,9 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
}
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
+7
View File
@@ -0,0 +1,7 @@
{
"name": "prettier",
"version": "3.6.2-sdk",
"main": "./index.cjs",
"type": "commonjs",
"bin": "./bin/prettier.cjs"
}
@@ -1,6 +1,9 @@
enableTelemetry: false
preferReuse: true
# pnpIgnorePatterns:
# - "**/cloudbeaver/common-typescript/**"
plugins:
- checksum: 50414773926286d8c0fc32e9936a799ad23296281046f03674bc33517b597e1ff7856092c3fcf8d5460a29549ef8b3dca3f59a1732b650a02bf523b255d726e7
path: .yarn/plugins/@yarnpkg/plugin-ts-project-linker.cjs
@@ -0,0 +1 @@
!lib
@@ -1,6 +1,14 @@
import * as react0 from "react";
import * as react2 from "react";
import * as react13 from "react";
import * as react10 from "react";
import { Key, ReactElement, ReactNode } from "react";
import * as react_jsx_runtime1 from "react/jsx-runtime";
import * as react_jsx_runtime0 from "react/jsx-runtime";
import * as react_jsx_runtime9 from "react/jsx-runtime";
import * as react_jsx_runtime11 from "react/jsx-runtime";
import * as react_jsx_runtime4 from "react/jsx-runtime";
import * as react_jsx_runtime5 from "react/jsx-runtime";
import * as react_jsx_runtime7 from "react/jsx-runtime";
//#region src/types.d.ts
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@@ -22,11 +30,11 @@ interface Column<TRow, TSummaryRow = unknown> {
readonly minWidth?: Maybe<number>;
/** Maximum column width in pixels */
readonly maxWidth?: Maybe<number>;
/** Class name(s) for cells */
/** Class name(s) for the cell */
readonly cellClass?: Maybe<string | ((row: TRow) => Maybe<string>)>;
/** Class name(s) for the header cell */
readonly headerCellClass?: Maybe<string>;
/** Class name(s) for summary cells */
/** Class name(s) for the summary cell */
readonly summaryCellClass?: Maybe<string | ((row: TSummaryRow) => Maybe<string>)>;
/** Render function to render the content of cells */
readonly renderCell?: Maybe<(props: RenderCellProps<TRow, TSummaryRow>) => ReactNode>;
@@ -54,7 +62,7 @@ interface Column<TRow, TSummaryRow = unknown> {
/** Options for cell editing */
readonly editorOptions?: Maybe<{
/**
* Render the cell content in addition to the edit cell content.
* Render the cell content in addition to the edit cell.
* Enable this option when the editor is rendered outside the grid, like a modal for example.
* By default, the cell content is not rendered when the edit cell is open.
* @default false
@@ -66,7 +74,7 @@ interface Column<TRow, TSummaryRow = unknown> {
*/
readonly commitOnOutsideClick?: Maybe<boolean>;
/**
* Close the editor when the row value changes externally
* Close the editor when the row changes externally
* @default true
*/
readonly closeOnExternalRowChange?: Maybe<boolean>;
@@ -106,10 +114,6 @@ interface Position {
readonly idx: number;
readonly rowIdx: number;
}
interface PositionByKey {
readonly columnKey: string;
readonly rowIdx: number;
}
interface RenderCellProps<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
@@ -300,16 +304,14 @@ interface PartialPosition {
//#endregion
//#region src/DataGrid.d.ts
type DefaultColumnOptions<R, SR> = Pick<Column<R, SR>, 'renderCell' | 'renderHeaderCell' | 'width' | 'minWidth' | 'maxWidth' | 'resizable' | 'sortable' | 'draggable'>;
interface DataGridHandle<R, SR = unknown> {
interface DataGridHandle {
element: HTMLDivElement | null;
scrollToCell: (position: PartialPosition) => void;
selectCell: (position: Position, options?: SelectCellOptions) => void;
selectCellByKey: (position: PositionByKey, options?: SelectCellOptions) => void;
getColumnsOrdered: () => readonly CalculatedColumn<R, SR>[];
}
type SharedDivProps = Pick<React.ComponentProps<'div'>, 'role' | 'aria-label' | 'aria-labelledby' | 'aria-description' | 'aria-describedby' | 'aria-rowcount' | 'className' | 'style'>;
interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivProps {
ref?: Maybe<React.Ref<DataGridHandle<R, SR>>>;
ref?: Maybe<React.Ref<DataGridHandle>>;
/**
* Grid and data Props
*/
@@ -405,7 +407,7 @@ interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivP
/**
* Text direction of the grid ('ltr' or 'rtl')
* @default 'ltr'
*/
* */
direction?: Maybe<Direction>;
'data-testid'?: Maybe<string>;
'data-cy'?: Maybe<string>;
@@ -417,7 +419,7 @@ interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivP
*
* <DataGrid columns={columns} rows={rows} />
*/
declare function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridProps<R, SR, K>): react_jsx_runtime0.JSX.Element;
declare function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridProps<R, SR, K>): react_jsx_runtime1.JSX.Element;
//#endregion
//#region src/TreeDataGrid.d.ts
interface TreeDataGridProps<R, SR = unknown, K extends Key = Key> extends Omit<DataGridProps<R, SR, K>, 'columns' | 'role' | 'aria-rowcount' | 'rowHeight' | 'onFill' | 'isRowSelectionDisabled'> {
@@ -450,7 +452,7 @@ declare function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
}: TreeDataGridProps<R, SR, K>): react_jsx_runtime0.JSX.Element;
//#endregion
//#region src/DataGridDefaultRenderersContext.d.ts
declare const DataGridDefaultRenderersContext: react0.Context<Maybe<Renderers<any, any>>>;
declare const DataGridDefaultRenderersContext: react2.Context<Maybe<Renderers<any, any>>>;
//#endregion
//#region src/Row.d.ts
declare const RowComponent: <R, SR>(props: RenderRowProps<R, SR>) => React.JSX.Element;
@@ -467,19 +469,19 @@ declare function renderCheckbox({
onChange,
indeterminate,
...props
}: RenderCheckboxProps): react_jsx_runtime0.JSX.Element;
}: RenderCheckboxProps): react_jsx_runtime9.JSX.Element;
//#endregion
//#region src/cellRenderers/renderToggleGroup.d.ts
declare function renderToggleGroup<R, SR>(props: RenderGroupCellProps<R, SR>): react_jsx_runtime0.JSX.Element;
declare function renderToggleGroup<R, SR>(props: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
declare function ToggleGroup<R, SR>({
groupKey,
isExpanded,
tabIndex,
toggleGroup
}: RenderGroupCellProps<R, SR>): react_jsx_runtime0.JSX.Element;
}: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
//#endregion
//#region src/cellRenderers/renderValue.d.ts
declare function renderValue<R, SR>(props: RenderCellProps<R, SR>): react0.ReactNode;
declare function renderValue<R, SR>(props: RenderCellProps<R, SR>): react13.ReactNode;
//#endregion
//#region src/cellRenderers/SelectCellFormatter.d.ts
type SharedInputProps = Pick<RenderCheckboxProps, 'disabled' | 'tabIndex' | 'aria-label' | 'aria-labelledby' | 'indeterminate' | 'onChange'>;
@@ -494,27 +496,27 @@ declare function SelectCellFormatter({
onChange,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy
}: SelectCellFormatterProps): react0.ReactNode;
}: SelectCellFormatterProps): react10.ReactNode;
//#endregion
//#region src/editors/renderTextEditor.d.ts
declare function textTextEditor<TRow, TSummaryRow>({
//#region src/editors/textEditor.d.ts
declare function textEditor<TRow, TSummaryRow>({
row,
column,
onRowChange,
onClose
}: RenderEditCellProps<TRow, TSummaryRow>): react_jsx_runtime0.JSX.Element;
}: RenderEditCellProps<TRow, TSummaryRow>): react_jsx_runtime4.JSX.Element;
//#endregion
//#region src/renderHeaderCell.d.ts
declare function renderHeaderCell<R, SR>({
column,
sortDirection,
priority
}: RenderHeaderCellProps<R, SR>): string | react_jsx_runtime0.JSX.Element;
}: RenderHeaderCellProps<R, SR>): string | react_jsx_runtime5.JSX.Element;
//#endregion
//#region src/sortStatus.d.ts
declare function renderSortIcon({
sortDirection
}: RenderSortIconProps): react_jsx_runtime0.JSX.Element | null;
}: RenderSortIconProps): react_jsx_runtime7.JSX.Element | null;
declare function renderSortPriority({
priority
}: RenderSortPriorityProps): number | undefined;
@@ -531,5 +533,5 @@ declare function useHeaderRowSelection(): {
onRowSelectionChange: (selectRowEvent: SelectHeaderRowEvent) => void;
};
//#endregion
export { type CalculatedColumn, type CalculatedColumnOrColumnGroup, type CalculatedColumnParent, CellComponent as Cell, type CellCopyArgs, type CellKeyDownArgs, type CellKeyboardEvent, type CellMouseArgs, type CellMouseEvent, type CellPasteArgs, type CellRendererProps, type CellSelectArgs, type ColSpanArgs, type Column, type ColumnGroup, type ColumnOrColumnGroup, type ColumnWidth, type ColumnWidths, DataGrid, DataGridDefaultRenderersContext, type DataGridHandle, type DataGridProps, type DefaultColumnOptions, type Direction, type FillEvent, type RenderCellProps, type RenderCheckboxProps, type RenderEditCellProps, type RenderGroupCellProps, type RenderHeaderCellProps, type RenderRowProps, type RenderSortIconProps, type RenderSortPriorityProps, type RenderSortStatusProps, type RenderSummaryCellProps, type Renderers, RowComponent as Row, type RowHeightArgs, type RowsChangeData, SELECT_COLUMN_KEY, SelectCellFormatter, type SelectCellOptions, SelectColumn, type SelectHeaderRowEvent, type SelectRowEvent, type SortColumn, type SortDirection, ToggleGroup, TreeDataGrid, type TreeDataGridProps, renderCheckbox, renderHeaderCell, renderSortIcon, renderSortPriority, textTextEditor as renderTextEditor, renderToggleGroup, renderValue, useHeaderRowSelection, useRowSelection };
export { CalculatedColumn, CalculatedColumnOrColumnGroup, CalculatedColumnParent, CellComponent as Cell, CellCopyArgs, CellKeyDownArgs, CellKeyboardEvent, CellMouseArgs, CellMouseEvent, CellPasteArgs, CellRendererProps, CellSelectArgs, ColSpanArgs, Column, ColumnGroup, ColumnOrColumnGroup, ColumnWidth, ColumnWidths, DataGrid, DataGridDefaultRenderersContext, DataGridHandle, DataGridProps, DefaultColumnOptions, FillEvent, RenderCellProps, RenderCheckboxProps, RenderEditCellProps, RenderGroupCellProps, RenderHeaderCellProps, RenderRowProps, RenderSortIconProps, RenderSortPriorityProps, RenderSortStatusProps, RenderSummaryCellProps, Renderers, RowComponent as Row, RowHeightArgs, RowsChangeData, SELECT_COLUMN_KEY, SelectCellFormatter, SelectCellOptions, SelectColumn, SelectHeaderRowEvent, SelectRowEvent, SortColumn, SortDirection, ToggleGroup, TreeDataGrid, TreeDataGridProps, renderCheckbox, renderHeaderCell, renderSortIcon, renderSortPriority, renderToggleGroup, renderValue, textEditor, useHeaderRowSelection, useRowSelection };
//# sourceMappingURL=index.d.ts.map
@@ -1,11 +1,13 @@
import { createContext, memo, useCallback, useContext, useEffectEvent, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createContext, memo, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
import { flushSync } from "react-dom";
import clsx from "clsx";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
//#region src/utils/colSpanUtils.ts
function getColSpan(column, lastFrozenColumnIndex, args) {
const colSpan = typeof column.colSpan === "function" ? column.colSpan(args) : 1;
if (Number.isInteger(colSpan) && colSpan > 1 && (!column.frozen || column.idx + colSpan - 1 <= lastFrozenColumnIndex)) return colSpan;
return void 0;
}
//#endregion
@@ -107,8 +109,8 @@ function getLeftRightKey(direction) {
//#endregion
//#region src/utils/renderMeasuringCells.tsx
const measuringCellClassname = "rdg-7-0-0-beta-59-fa71d63e";
function renderMeasuringCells(viewportColumns) {
const measuringCellClassname = "mlln6zg7-0-0-beta-55";
function renderMeasuringCells(viewportColumns, columnsToMeasure) {
return viewportColumns.map(({ key, idx, minWidth, maxWidth }) => /* @__PURE__ */ jsx("div", {
className: measuringCellClassname,
style: {
@@ -116,7 +118,8 @@ function renderMeasuringCells(viewportColumns) {
minWidth,
maxWidth
},
"data-measuring-cell-key": key
"data-measuring-cell-key": key,
"data-measuring-cell": columnsToMeasure.has(key) || void 0
}, key));
}
@@ -148,6 +151,7 @@ function getSelectedCellColSpan({ rows, topSummaryRows, bottomSummaryRows, rowId
type: "SUMMARY",
row: bottomSummaryRows[rowIdx - rows.length]
});
return void 0;
}
function getNextSelectedCellPosition({ moveUp, moveNext, cellNavigationMode, columns, colSpanColumns, rows, topSummaryRows, bottomSummaryRows, minRowIdx, mainHeaderRowIdx, maxRowIdx, currentPosition: { idx: currentIdx, rowIdx: currentRowIdx }, nextPosition, lastFrozenColumnIndex, isCellWithinBounds }) {
let { idx: nextIdx, rowIdx: nextRowIdx } = nextPosition;
@@ -176,7 +180,8 @@ function getNextSelectedCellPosition({ moveUp, moveNext, cellNavigationMode, col
};
const setHeaderGroupColAndRowSpan = () => {
if (moveNext) {
let parent = columns[nextIdx].parent;
const nextColumn = columns[nextIdx];
let parent = nextColumn.parent;
while (parent !== void 0) {
const parentRowIdx = getParentRowIdx(parent);
if (nextRowIdx === parentRowIdx) {
@@ -186,7 +191,8 @@ function getNextSelectedCellPosition({ moveUp, moveNext, cellNavigationMode, col
parent = parent.parent;
}
} else if (moveUp) {
let parent = columns[nextIdx].parent;
const nextColumn = columns[nextIdx];
let parent = nextColumn.parent;
let found = false;
while (parent !== void 0) {
const parentRowIdx = getParentRowIdx(parent);
@@ -212,12 +218,14 @@ function getNextSelectedCellPosition({ moveUp, moveNext, cellNavigationMode, col
const isAfterLastColumn = nextIdx === columnsCount;
const isBeforeFirstColumn = nextIdx === -1;
if (isAfterLastColumn) {
if (!(nextRowIdx === maxRowIdx)) {
const isLastRow = nextRowIdx === maxRowIdx;
if (!isLastRow) {
nextIdx = 0;
nextRowIdx += 1;
}
} else if (isBeforeFirstColumn) {
if (!(nextRowIdx === minRowIdx)) {
const isFirstRow = nextRowIdx === minRowIdx;
if (!isFirstRow) {
nextRowIdx -= 1;
nextIdx = columnsCount - 1;
}
@@ -225,7 +233,8 @@ function getNextSelectedCellPosition({ moveUp, moveNext, cellNavigationMode, col
}
}
if (nextRowIdx < mainHeaderRowIdx && nextIdx > -1 && nextIdx < columnsCount) {
let parent = columns[nextIdx].parent;
const nextColumn = columns[nextIdx];
let parent = nextColumn.parent;
const nextParentRowIdx = nextRowIdx;
nextRowIdx = mainHeaderRowIdx;
while (parent !== void 0) {
@@ -243,17 +252,21 @@ function getNextSelectedCellPosition({ moveUp, moveNext, cellNavigationMode, col
};
}
function canExitGrid({ maxColIdx, minRowIdx, maxRowIdx, selectedPosition: { rowIdx, idx }, shiftKey }) {
return shiftKey ? idx === 0 && rowIdx === minRowIdx : idx === maxColIdx && rowIdx === maxRowIdx;
const atLastCellInRow = idx === maxColIdx;
const atFirstCellInRow = idx === 0;
const atLastRow = rowIdx === maxRowIdx;
const atFirstRow = rowIdx === minRowIdx;
return shiftKey ? atFirstCellInRow && atFirstRow : atLastCellInRow && atLastRow;
}
//#endregion
//#region src/style/cell.ts
const cell = "rdg-7-0-0-beta-59-85c48527";
const cell = "cj343x07-0-0-beta-55";
const cellClassname = `rdg-cell ${cell}`;
const cellFrozen = "rdg-7-0-0-beta-59-17a9a6d4";
const cellFrozen = "csofj7r7-0-0-beta-55";
const cellFrozenClassname = `rdg-cell-frozen ${cellFrozen}`;
const cellDragHandle = "rdg-7-0-0-beta-59-bfba19bc";
const cellDragHandleFrozenClassname = "rdg-7-0-0-beta-59-7abddb3e";
const cellDragHandle = "ch2wcw87-0-0-beta-55";
const cellDragHandleFrozenClassname = "c1wvphzh7-0-0-beta-55";
const cellDragHandleClassname = `rdg-cell-drag-handle ${cellDragHandle}`;
//#endregion
@@ -285,18 +298,8 @@ function getCellStyle(column, colSpan = 1) {
insetInlineStart: column.frozen ? `var(--rdg-frozen-left-${column.idx})` : void 0
};
}
function classnames(...args) {
let classname = "";
for (const arg of args) if (arg) {
if (typeof arg === "string") classname += ` ${arg}`;
else if (typeof arg === "object") {
for (const key in arg) if (arg[key]) classname += ` ${key}`;
}
}
return classname.trimStart();
}
function getCellClassname(column, ...extraClasses) {
return classnames(cellClassname, { [cellFrozenClassname]: column.frozen }, ...extraClasses);
return clsx(cellClassname, { [cellFrozenClassname]: column.frozen }, ...extraClasses);
}
//#endregion
@@ -316,8 +319,9 @@ function getHeaderCellRowSpan(column, rowIdx) {
//#endregion
//#region src/cellRenderers/renderCheckbox.tsx
const checkboxClassname = `rdg-checkbox-input rdg-7-0-0-beta-59-3b807ead`;
function renderCheckbox({ onChange, indeterminate, ...props }) {
const checkbox = "c1bn88vv7-0-0-beta-55";
const checkboxClassname = `rdg-checkbox-input ${checkbox}`;
function renderCheckbox({ onChange, indeterminate,...props }) {
function handleChange(e) {
onChange(e.target.checked, e.nativeEvent.shiftKey);
}
@@ -334,8 +338,10 @@ function renderCheckbox({ onChange, indeterminate, ...props }) {
//#endregion
//#region src/cellRenderers/renderToggleGroup.tsx
const groupCellContentClassname = `rdg-group-cell-content rdg-7-0-0-beta-59-07919382`;
const caretClassname = `rdg-caret rdg-7-0-0-beta-59-02a50147`;
const groupCellContent = "g1s9ylgp7-0-0-beta-55";
const groupCellContentClassname = `rdg-group-cell-content ${groupCellContent}`;
const caret = "cz54e4y7-0-0-beta-55";
const caretClassname = `rdg-caret ${caret}`;
function renderToggleGroup(props) {
return /* @__PURE__ */ jsx(ToggleGroup, { ...props });
}
@@ -486,8 +492,9 @@ const SelectColumn = {
//#endregion
//#region src/renderHeaderCell.tsx
const headerSortCellClassname = "rdg-7-0-0-beta-59-56a248e4";
const headerSortNameClassname = `rdg-header-sort-name rdg-7-0-0-beta-59-7fad8c83`;
const headerSortCellClassname = "h44jtk67-0-0-beta-55";
const headerSortName = "hcgkhxz7-0-0-beta-55";
const headerSortNameClassname = `rdg-header-sort-name ${headerSortName}`;
function renderHeaderCell({ column, sortDirection, priority }) {
if (!column.sortable) return column.name;
return /* @__PURE__ */ jsx(SortableHeaderCell, {
@@ -651,7 +658,9 @@ function useCalculatedColumns({ rawColumns, defaultColumnOptions, getColumnWidth
if (left + width >= viewportRight) break;
colVisibleEndIdx++;
}
return [max(firstUnfrozenColumnIdx, colVisibleStartIdx - 1), min(lastColIdx, colVisibleEndIdx + 1)];
const colOverscanStartIdx$1 = max(firstUnfrozenColumnIdx, colVisibleStartIdx - 1);
const colOverscanEndIdx$1 = min(lastColIdx, colVisibleEndIdx + 1);
return [colOverscanStartIdx$1, colOverscanEndIdx$1];
}, [
columnMetrics,
columns,
@@ -692,22 +701,26 @@ function useColumnWidths(columns, viewportColumns, templateColumns, gridRef, gri
const columnsCanFlex = columns.length === viewportColumns.length;
const ignorePreviouslyMeasuredColumnsOnGridWidthChange = columnsCanFlex && gridWidth !== prevGridWidth;
const newTemplateColumns = [...templateColumns];
const columnsToMeasure = [];
const columnsToMeasure = /* @__PURE__ */ new Set();
for (const { key, idx, width } of viewportColumns) {
const columnWidth = columnWidths.get(key);
if (key === columnToAutoResize?.key) {
newTemplateColumns[idx] = columnToAutoResize.width === "max-content" ? columnToAutoResize.width : `${columnToAutoResize.width}px`;
columnsToMeasure.push(key);
} else if (typeof width === "string" && columnWidth?.type !== "resized" && (ignorePreviouslyMeasuredColumnsOnGridWidthChange || columnsToMeasureOnResize?.has(key) === true || columnWidth === void 0)) {
if (key === columnToAutoResize?.key) if (typeof columnToAutoResize.width === "number") {
newTemplateColumns[idx] = `${columnToAutoResize.width}px`;
columnsToMeasure.delete(key);
} else {
newTemplateColumns[idx] = columnToAutoResize.width;
columnsToMeasure.add(key);
}
else if (typeof width === "string" && columnWidth?.type !== "resized" && (ignorePreviouslyMeasuredColumnsOnGridWidthChange || columnsToMeasureOnResize?.has(key) === true || columnWidth === void 0)) {
newTemplateColumns[idx] = width;
columnsToMeasure.push(key);
columnsToMeasure.add(key);
}
}
const gridTemplateColumns = newTemplateColumns.join(" ");
useLayoutEffect(updateMeasuredAndResizedWidths);
function updateMeasuredAndResizedWidths() {
setPreviousGridWidth(gridWidth);
if (columnsToMeasure.length === 0) return;
if (columnsToMeasure.size === 0) return;
const newColumnWidths = new Map(columnWidths);
let hasChanges = false;
for (const key of columnsToMeasure) {
@@ -757,12 +770,14 @@ function useColumnWidths(columns, viewportColumns, templateColumns, gridRef, gri
}
return {
gridTemplateColumns,
columnsToMeasure,
handleColumnResize
};
}
function measureColumnWidth(gridRef, key) {
const selector = `[data-measuring-cell-key="${CSS.escape(key)}"]`;
return (gridRef.current?.querySelector(selector))?.getBoundingClientRect().width;
const measuringCell = gridRef.current?.querySelector(selector);
return measuringCell?.getBoundingClientRect().width;
}
//#endregion
@@ -809,7 +824,7 @@ function useGridDimensions() {
//#region src/hooks/useLatestFunc.ts
function useLatestFunc(fn) {
const ref = useRef(fn);
useLayoutEffect(() => {
useEffect(() => {
ref.current = fn;
});
const callbackFn = useCallback((...args) => {
@@ -824,16 +839,15 @@ function useRovingTabIndex(isSelected) {
const [isChildFocused, setIsChildFocused] = useState(false);
if (isChildFocused && !isSelected) setIsChildFocused(false);
function onFocus(event) {
if (event.target === event.currentTarget) {
const elementToFocus = event.currentTarget.querySelector("[tabindex=\"0\"]");
if (elementToFocus !== null) {
elementToFocus.focus({ preventScroll: true });
setIsChildFocused(true);
} else setIsChildFocused(false);
} else setIsChildFocused(true);
const elementToFocus = event.currentTarget.querySelector("[tabindex=\"0\"]");
if (elementToFocus !== null) {
elementToFocus.focus({ preventScroll: true });
setIsChildFocused(true);
}
}
const isFocusable = isSelected && !isChildFocused;
return {
tabIndex: isSelected && !isChildFocused ? 0 : -1,
tabIndex: isFocusable ? 0 : -1,
childTabIndex: isSelected ? 0 : -1,
onFocus: isSelected ? onFocus : void 0
};
@@ -914,28 +928,15 @@ function useViewportRows({ rows, rowHeight, clientHeight, scrollTop, enableVirtu
findRowIdx: (offset) => floor(offset / rowHeight)
};
let totalRowHeight$1 = 0;
let gridTemplateRows$1 = "";
let currentHeight = null;
let repeatCount = 0;
const rowPositions = rows.map((row$1, index) => {
let gridTemplateRows$1 = " ";
const rowPositions = rows.map((row$1) => {
const currentRowHeight = rowHeight(row$1);
const position = {
top: totalRowHeight$1,
height: currentRowHeight
};
gridTemplateRows$1 += `${currentRowHeight}px `;
totalRowHeight$1 += currentRowHeight;
if (currentHeight === null) {
currentHeight = currentRowHeight;
repeatCount = 1;
} else if (currentHeight === currentRowHeight) repeatCount++;
else {
if (repeatCount > 1) gridTemplateRows$1 += `repeat(${repeatCount}, ${currentHeight}px) `;
else gridTemplateRows$1 += `${currentHeight}px `;
currentHeight = currentRowHeight;
repeatCount = 1;
}
if (index === rows.length - 1) if (repeatCount > 1) gridTemplateRows$1 += `repeat(${repeatCount}, ${currentHeight}px)`;
else gridTemplateRows$1 += `${currentHeight}px`;
return position;
});
const validateRowIdx = (rowIdx) => {
@@ -983,8 +984,9 @@ function useViewportRows({ rows, rowHeight, clientHeight, scrollTop, enableVirtu
//#endregion
//#region src/Cell.tsx
const cellDraggedOverClassname = `rdg-cell-dragged-over rdg-7-0-0-beta-59-35ccb4c8`;
function Cell({ column, colSpan, isCellSelected, isDraggedOver, row: row$1, rowIdx, className, onMouseDown, onCellMouseDown, onClick, onCellClick, onDoubleClick, onCellDoubleClick, onContextMenu, onCellContextMenu, onRowChange, selectCell, style, ...props }) {
const cellDraggedOver = "c6ra8a37-0-0-beta-55";
const cellDraggedOverClassname = `rdg-cell-dragged-over ${cellDraggedOver}`;
function Cell({ column, colSpan, isCellSelected, isDraggedOver, row: row$1, rowIdx, className, onMouseDown, onCellMouseDown, onClick, onCellClick, onDoubleClick, onCellDoubleClick, onContextMenu, onCellContextMenu, onRowChange, selectCell, style,...props }) {
const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);
const { cellClass } = column;
className = getCellClassname(column, { [cellDraggedOverClassname]: isDraggedOver }, typeof cellClass === "function" ? cellClass(row$1) : cellClass, className);
@@ -1064,51 +1066,26 @@ function defaultRenderCell(key, props) {
//#endregion
//#region src/EditCell.tsx
const canUsePostTask = typeof scheduler === "object" && typeof scheduler.postTask === "function";
const cellEditing = "rdg-7-0-0-beta-59-46f9ea88";
const cellEditing = "cis5rrm7-0-0-beta-55";
function EditCell({ column, colSpan, row: row$1, rowIdx, onRowChange, closeEditor, onKeyDown, navigate }) {
const captureEventRef = useRef(void 0);
const abortControllerRef = useRef(void 0);
const frameRequestRef = useRef(void 0);
const commitOnOutsideClick = column.editorOptions?.commitOnOutsideClick ?? true;
const commitOnOutsideMouseDown = useEffectEvent(() => {
const commitOnOutsideMouseDown = useLatestFunc(() => {
onClose(true, false);
});
useLayoutEffect(() => {
useEffect(() => {
if (!commitOnOutsideClick) return;
function onWindowCaptureMouseDown(event) {
captureEventRef.current = event;
if (canUsePostTask) {
const abortController = new AbortController();
const { signal } = abortController;
abortControllerRef.current = abortController;
scheduler.postTask(commitOnOutsideMouseDown, {
priority: "user-blocking",
signal
}).catch(() => {});
} else frameRequestRef.current = requestAnimationFrame(commitOnOutsideMouseDown);
}
function onWindowMouseDown(event) {
if (captureEventRef.current === event) commitOnOutsideMouseDown();
function onWindowCaptureMouseDown() {
frameRequestRef.current = requestAnimationFrame(commitOnOutsideMouseDown);
}
addEventListener("mousedown", onWindowCaptureMouseDown, { capture: true });
addEventListener("mousedown", onWindowMouseDown);
return () => {
removeEventListener("mousedown", onWindowCaptureMouseDown, { capture: true });
removeEventListener("mousedown", onWindowMouseDown);
cancelTask();
cancelFrameRequest();
};
}, [commitOnOutsideClick]);
function cancelTask() {
captureEventRef.current = void 0;
if (abortControllerRef.current !== void 0) {
abortControllerRef.current.abort();
abortControllerRef.current = void 0;
}
if (frameRequestRef.current !== void 0) {
cancelAnimationFrame(frameRequestRef.current);
frameRequestRef.current = void 0;
}
}, [commitOnOutsideClick, commitOnOutsideMouseDown]);
function cancelFrameRequest() {
cancelAnimationFrame(frameRequestRef.current);
}
function handleKeyDown(event) {
if (onKeyDown) {
@@ -1146,7 +1123,7 @@ function EditCell({ column, colSpan, row: row$1, rowIdx, onRowChange, closeEdito
className,
style: getCellStyle(column, colSpan),
onKeyDown: handleKeyDown,
onMouseDownCapture: cancelTask,
onMouseDownCapture: cancelFrameRequest,
children: column.renderEditCell != null && /* @__PURE__ */ jsxs(Fragment, { children: [column.renderEditCell({
column,
row: row$1,
@@ -1184,7 +1161,7 @@ function GroupedColumnHeaderCell({ column, rowIdx, isCellSelected, selectCell })
"aria-rowspan": rowSpan,
"aria-selected": isCellSelected,
tabIndex,
className: classnames(cellClassname, column.headerCellClass),
className: clsx(cellClassname, column.headerCellClass),
style: {
...getHeaderCellStyle(column, rowIdx, rowSpan),
gridColumnStart: index,
@@ -1198,19 +1175,22 @@ function GroupedColumnHeaderCell({ column, rowIdx, isCellSelected, selectCell })
//#endregion
//#region src/HeaderCell.tsx
const cellSortableClassname = "rdg-7-0-0-beta-59-2a7e240d";
const cellResizableClassname = `rdg-cell-resizable rdg-7-0-0-beta-59-1893dc0f`;
const resizeHandleClassname = `rdg-resize-handle rdg-7-0-0-beta-59-4e60db91`;
const cellSortableClassname = "c6l2wv17-0-0-beta-55";
const cellResizable = "c1kqdw7y7-0-0-beta-55";
const cellResizableClassname = `rdg-cell-resizable ${cellResizable}`;
const resizeHandleClassname = "r1y6ywlx7-0-0-beta-55";
const cellDraggableClassname = "rdg-cell-draggable";
const cellDraggingClassname = `rdg-cell-dragging rdg-7-0-0-beta-59-3e1a4ad4`;
const cellOverClassname = `rdg-cell-drag-over rdg-7-0-0-beta-59-51abd8b8`;
const dragImageClassname = "rdg-7-0-0-beta-59-c8d7aa64";
function HeaderCell({ column, colSpan, rowIdx, isCellSelected, onColumnResize, onColumnResizeEnd, onColumnsReorder, sortColumns, onSortColumnsChange, selectCell, shouldFocusGrid, direction, draggedColumnKey, setDraggedColumnKey }) {
const cellDragging = "c1bezg5o7-0-0-beta-55";
const cellDraggingClassname = `rdg-cell-dragging ${cellDragging}`;
const cellOver = "c1vc96037-0-0-beta-55";
const cellOverClassname = `rdg-cell-drag-over ${cellOver}`;
const dragImageClassname = "d8rwc9w7-0-0-beta-55";
function HeaderCell({ column, colSpan, rowIdx, isCellSelected, onColumnResize, onColumnResizeEnd, onColumnsReorder, sortColumns, onSortColumnsChange, selectCell, direction, draggedColumnKey, setDraggedColumnKey }) {
const [isOver, setIsOver] = useState(false);
const dragImageRef = useRef(null);
const isDragging = draggedColumnKey === column.key;
const rowSpan = getHeaderCellRowSpan(column, rowIdx);
const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(shouldFocusGrid || isCellSelected);
const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);
const sortIndex = sortColumns?.findIndex((sort) => sort.columnKey === column.key);
const sortColumn = sortIndex !== void 0 && sortIndex > -1 ? sortColumns[sortIndex] : void 0;
const sortDirection = sortColumn?.direction;
@@ -1247,13 +1227,6 @@ function HeaderCell({ column, colSpan, rowIdx, isCellSelected, onColumnResize, o
} else onSortColumnsChange(nextSortColumn ? [nextSortColumn] : []);
}
}
function handleFocus(event) {
onFocus?.(event);
if (shouldFocusGrid) selectCell({
idx: 0,
rowIdx
});
}
function onMouseDown() {
selectCell({
idx: column.idx,
@@ -1272,7 +1245,8 @@ function HeaderCell({ column, colSpan, rowIdx, isCellSelected, onColumnResize, o
event.stopPropagation();
const { width } = event.currentTarget.getBoundingClientRect();
const { leftKey } = getLeftRightKey(direction);
const newWidth = clampColumnWidth(width + (key === leftKey ? -10 : 10), column);
const offset = key === leftKey ? -10 : 10;
const newWidth = clampColumnWidth(width + offset, column);
if (newWidth !== width) onColumnResize(column, newWidth);
}
}
@@ -1342,7 +1316,7 @@ function HeaderCell({ column, colSpan, rowIdx, isCellSelected, onColumnResize, o
className,
style,
onMouseDown,
onFocus: handleFocus,
onFocus,
onClick,
onKeyDown,
...dragTargetProps,
@@ -1363,7 +1337,8 @@ function ResizeHandle({ direction, column, onColumnResize, onColumnResizeEnd })
event.preventDefault();
const { currentTarget, pointerId } = event;
currentTarget.setPointerCapture(pointerId);
const { right, left } = currentTarget.parentElement.getBoundingClientRect();
const headerCell = currentTarget.parentElement;
const { right, left } = headerCell.getBoundingClientRect();
resizingOffsetRef.current = isRtl ? event.clientX - left : right - event.clientX;
}
function onPointerMove(event) {
@@ -1382,7 +1357,6 @@ function ResizeHandle({ direction, column, onColumnResize, onColumnResizeEnd })
onColumnResize(column, "max-content");
}
return /* @__PURE__ */ jsx("div", {
"aria-hidden": true,
className: resizeHandleClassname,
onClick: stopPropagation,
onPointerDown,
@@ -1398,19 +1372,19 @@ function isEventPertinent(event) {
//#endregion
//#region src/style/row.ts
const row = "rdg-7-0-0-beta-59-3c083f1b";
const row = "r1upfr807-0-0-beta-55";
const rowClassname = `rdg-row ${row}`;
const rowSelected = "rdg-7-0-0-beta-59-3fe773c3";
const rowSelected = "r190mhd37-0-0-beta-55";
const rowSelectedClassname = "rdg-row-selected";
const rowSelectedWithFrozenCell = "rdg-7-0-0-beta-59-97ce3fde";
const rowSelectedWithFrozenCell = "r139qu9m7-0-0-beta-55";
const topSummaryRowClassname = "rdg-top-summary-row";
const bottomSummaryRowClassname = "rdg-bottom-summary-row";
//#endregion
//#region src/HeaderRow.tsx
const headerRow = "rdg-7-0-0-beta-59-0dbd5994";
const headerRow = "h10tskcx7-0-0-beta-55";
const headerRowClassname = `rdg-header-row ${headerRow}`;
function HeaderRow({ headerRowClass, rowIdx, columns, onColumnResize, onColumnResizeEnd, onColumnsReorder, sortColumns, onSortColumnsChange, lastFrozenColumnIndex, selectedCellIdx, selectCell, shouldFocusGrid, direction }) {
function HeaderRow({ headerRowClass, rowIdx, columns, onColumnResize, onColumnResizeEnd, onColumnsReorder, sortColumns, onSortColumnsChange, lastFrozenColumnIndex, selectedCellIdx, selectCell, direction }) {
const [draggedColumnKey, setDraggedColumnKey] = useState();
const cells = [];
for (let index = 0; index < columns.length; index++) {
@@ -1428,7 +1402,6 @@ function HeaderRow({ headerRowClass, rowIdx, columns, onColumnResize, onColumnRe
onSortColumnsChange,
sortColumns,
selectCell,
shouldFocusGrid: shouldFocusGrid && index === 0,
direction,
draggedColumnKey,
setDraggedColumnKey
@@ -1437,7 +1410,7 @@ function HeaderRow({ headerRowClass, rowIdx, columns, onColumnResize, onColumnRe
return /* @__PURE__ */ jsx("div", {
role: "row",
"aria-rowindex": rowIdx,
className: classnames(headerRowClassname, { [rowSelectedClassname]: selectedCellIdx === -1 }, headerRowClass),
className: clsx(headerRowClassname, { [rowSelectedClassname]: selectedCellIdx === -1 }, headerRowClass),
children: cells
});
}
@@ -1477,12 +1450,12 @@ var GroupedColumnHeaderRow_default = memo(GroupedColumnHeaderRow);
//#endregion
//#region src/Row.tsx
function Row({ className, rowIdx, gridRowStart, selectedCellIdx, isRowSelectionDisabled, isRowSelected, draggedOverCellIdx, lastFrozenColumnIndex, row: row$1, viewportColumns, selectedCellEditor, onCellMouseDown, onCellClick, onCellDoubleClick, onCellContextMenu, rowClass, onRowChange, selectCell, style, ...props }) {
function Row({ className, rowIdx, gridRowStart, selectedCellIdx, isRowSelectionDisabled, isRowSelected, draggedOverCellIdx, lastFrozenColumnIndex, row: row$1, viewportColumns, selectedCellEditor, onCellMouseDown, onCellClick, onCellDoubleClick, onCellContextMenu, rowClass, onRowChange, selectCell, style,...props }) {
const renderCell = useDefaultRenderers().renderCell;
const handleRowChange = useLatestFunc((column, newRow) => {
onRowChange(column, rowIdx, newRow);
});
className = classnames(rowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, { [rowSelectedClassname]: selectedCellIdx === -1 }, rowClass?.(row$1, rowIdx), className);
className = clsx(rowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, { [rowSelectedClassname]: selectedCellIdx === -1 }, rowClass?.(row$1, rowIdx), className);
const cells = [];
for (let index = 0; index < viewportColumns.length; index++) {
const column = viewportColumns[index];
@@ -1509,11 +1482,12 @@ function Row({ className, rowIdx, gridRowStart, selectedCellIdx, isRowSelectionD
selectCell
}));
}
const selectionValue = useMemo(() => ({
isRowSelected,
isRowSelectionDisabled
}), [isRowSelectionDisabled, isRowSelected]);
return /* @__PURE__ */ jsx(RowSelectionContext, {
value: useMemo(() => ({
isRowSelected,
isRowSelectionDisabled
}), [isRowSelectionDisabled, isRowSelected]),
value: selectionValue,
children: /* @__PURE__ */ jsx("div", {
role: "row",
className,
@@ -1563,7 +1537,8 @@ function ScrollToCell({ scrollToPosition: { idx, rowIdx }, gridRef, setScrollToC
//#endregion
//#region src/sortStatus.tsx
const arrowClassname = `rdg-sort-arrow rdg-7-0-0-beta-59-3d5115f3`;
const arrow = "a3ejtar7-0-0-beta-55";
const arrowClassname = `rdg-sort-arrow ${arrow}`;
function renderSortStatus({ sortDirection, priority }) {
return /* @__PURE__ */ jsxs(Fragment, { children: [renderSortIcon({ sortDirection }), renderSortPriority({ priority })] });
}
@@ -1584,16 +1559,16 @@ function renderSortPriority({ priority }) {
//#endregion
//#region src/style/core.ts
const root = "rdg-7-0-0-beta-59-ccd2e5d9";
const root = "rnvodz57-0-0-beta-55";
const rootClassname = `rdg ${root}`;
const viewportDragging = "rdg-7-0-0-beta-59-e9b0e1c9";
const viewportDragging = "vlqv91k7-0-0-beta-55";
const viewportDraggingClassname = `rdg-viewport-dragging ${viewportDragging}`;
const focusSinkClassname = "rdg-7-0-0-beta-59-dbb8b3c5";
const focusSinkHeaderAndSummaryClassname = "rdg-7-0-0-beta-59-e9f55541";
const focusSinkClassname = "f1lsfrzw7-0-0-beta-55";
const focusSinkHeaderAndSummaryClassname = "f1cte0lg7-0-0-beta-55";
//#endregion
//#region src/SummaryCell.tsx
const summaryCellClassname = "rdg-7-0-0-beta-59-d907aa87";
const summaryCellClassname = "s8wc6fl7-0-0-beta-55";
function SummaryCell({ column, colSpan, row: row$1, rowIdx, isCellSelected, selectCell }) {
const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);
const { summaryCellClass } = column;
@@ -1625,8 +1600,8 @@ var SummaryCell_default = memo(SummaryCell);
//#endregion
//#region src/SummaryRow.tsx
const summaryRow = "rdg-7-0-0-beta-59-0b90c82c";
const topSummaryRow = "rdg-7-0-0-beta-59-d0520eab";
const summaryRow = "skuhp557-0-0-beta-55";
const topSummaryRow = "tf8l5ub7-0-0-beta-55";
const summaryRowClassname = `rdg-summary-row ${summaryRow}`;
function SummaryRow({ rowIdx, gridRowStart, row: row$1, viewportColumns, top, bottom, lastFrozenColumnIndex, selectedCellIdx, isTop, selectCell, "aria-rowindex": ariaRowIndex }) {
const cells = [];
@@ -1650,7 +1625,7 @@ function SummaryRow({ rowIdx, gridRowStart, row: row$1, viewportColumns, top, bo
return /* @__PURE__ */ jsx("div", {
role: "row",
"aria-rowindex": ariaRowIndex,
className: classnames(rowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, summaryRowClassname, {
className: clsx(rowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, summaryRowClassname, {
[rowSelectedClassname]: selectedCellIdx === -1,
[`${topSummaryRowClassname} ${topSummaryRow}`]: isTop,
[bottomSummaryRowClassname]: !isTop
@@ -1793,12 +1768,13 @@ function DataGrid(props) {
topSummaryRows,
bottomSummaryRows
});
const { gridTemplateColumns, handleColumnResize } = useColumnWidths(columns, viewportColumns, templateColumns, gridRef, gridWidth, columnWidths, onColumnWidthsChange, onColumnResize, setColumnResizing);
const { gridTemplateColumns, columnsToMeasure, handleColumnResize } = useColumnWidths(columns, viewportColumns, templateColumns, gridRef, gridWidth, columnWidths, onColumnWidthsChange, onColumnResize, setColumnResizing);
const minColIdx = isTreeGrid ? -1 : 0;
const maxColIdx = columns.length - 1;
const selectedCellIsWithinSelectionBounds = isCellWithinSelectionBounds(selectedPosition);
const selectedCellIsWithinViewportBounds = isCellWithinViewportBounds(selectedPosition);
const scrollHeight = headerRowHeight + totalRowHeight + summaryRowsHeight + horizontalScrollbarHeight;
const shouldFocusGrid = !selectedCellIsWithinSelectionBounds;
/**
* The identity of the wrapper function is stable so it won't break memoization
*/
@@ -1850,9 +1826,7 @@ function DataGrid(props) {
rowIdx: scrollToRowIdx
});
},
selectCell,
selectCellByKey,
getColumnsOrdered
selectCell
}));
/**
* event handlers
@@ -1927,6 +1901,12 @@ function DataGrid(props) {
break;
}
}
function handleFocus(event) {
if (event.target === event.currentTarget) selectHeaderCell({
idx: minColIdx,
rowIdx: headerRowsCount
}, { shouldFocusCell: true });
}
function handleScroll(event) {
const { scrollTop: scrollTop$1, scrollLeft: scrollLeft$1 } = event.currentTarget;
flushSync(() => {
@@ -1938,7 +1918,8 @@ function DataGrid(props) {
function updateRow(column, rowIdx, row$1) {
if (typeof onRowsChange !== "function") return;
if (row$1 === rows[rowIdx]) return;
onRowsChange(rows.with(rowIdx, row$1), {
const updatedRows = rows.with(rowIdx, row$1);
onRowsChange(updatedRows, {
indexes: [rowIdx],
column
});
@@ -1959,10 +1940,11 @@ function DataGrid(props) {
if (!onCellPaste || !onRowsChange || !isCellEditable(selectedPosition)) return;
const { idx, rowIdx } = selectedPosition;
const column = columns[idx];
updateRow(column, rowIdx, onCellPaste({
const updatedRow = onCellPaste({
row: rows[rowIdx],
column
}, event));
}, event);
updateRow(column, rowIdx, updatedRow);
}
function handleCellInput(event) {
if (!selectedCellIsWithinViewportBounds) return;
@@ -2001,10 +1983,13 @@ function DataGrid(props) {
}
function handleDragHandlePointerMove(event) {
const gridEl = gridRef.current;
const overRowIdx = findRowIdx(scrollTop - (headerRowsHeight + topSummaryRowsCount * summaryRowHeight) + event.clientY - gridEl.getBoundingClientRect().top);
const headerAndTopSummaryRowsHeight = headerRowsHeight + topSummaryRowsCount * summaryRowHeight;
const offset = scrollTop - headerAndTopSummaryRowsHeight + event.clientY - gridEl.getBoundingClientRect().top;
const overRowIdx = findRowIdx(offset);
setDraggedOverRowIdx(overRowIdx);
const ariaRowIndex = headerAndTopSummaryRowsCount + overRowIdx + 1;
scrollIntoView(gridEl.querySelector(`:scope > [aria-rowindex="${ariaRowIndex}"] > [aria-colindex="${selectedPosition.idx + 1}"]`));
const el = gridEl.querySelector(`:scope > [aria-rowindex="${ariaRowIndex}"] > [aria-colindex="${selectedPosition.idx + 1}"]`);
scrollIntoView(el);
}
function handleDragHandleLostPointerCapture() {
setDragging(false);
@@ -2098,22 +2083,11 @@ function DataGrid(props) {
column: columns[position.idx]
});
}
function getColumnsOrdered() {
return columns;
}
function selectCellByKey(position, options) {
const columnIdx = columns.findIndex((col) => col.key === position.columnKey);
if (columnIdx === -1) throw new Error(`Column with key "${position.columnKey}" not found`);
selectCell({
idx: columnIdx,
rowIdx: position.rowIdx
}, options);
}
function selectHeaderCell({ idx, rowIdx }) {
function selectHeaderCell({ idx, rowIdx }, options) {
selectCell({
rowIdx: minRowIdx + rowIdx - 1,
idx
});
}, options);
}
function getNextPosition(key, ctrlKey, shiftKey) {
const { idx, rowIdx } = selectedPosition;
@@ -2193,9 +2167,10 @@ function DataGrid(props) {
cellNavigationMode = "CHANGE_ROW";
}
event.preventDefault();
const nextPosition = getNextPosition(key, isCtrlKeyHeldDown(event), shiftKey);
const ctrlKey = isCtrlKeyHeldDown(event);
const nextPosition = getNextPosition(key, ctrlKey, shiftKey);
if (isSamePosition(selectedPosition, nextPosition)) return;
selectCell(getNextSelectedCellPosition({
const nextSelectedCellPosition = getNextSelectedCellPosition({
moveUp: key === "ArrowUp",
moveNext: key === rightKey || key === "Tab" && !shiftKey,
columns,
@@ -2211,12 +2186,14 @@ function DataGrid(props) {
currentPosition: selectedPosition,
nextPosition,
isCellWithinBounds: isCellWithinSelectionBounds
}), { shouldFocusCell: true });
});
selectCell(nextSelectedCellPosition, { shouldFocusCell: true });
}
function getDraggedOverCellIdx(currentRowIdx) {
if (draggedOverRowIdx === void 0) return;
const { rowIdx } = selectedPosition;
return (rowIdx < draggedOverRowIdx ? rowIdx < currentRowIdx && currentRowIdx <= draggedOverRowIdx : rowIdx > currentRowIdx && currentRowIdx >= draggedOverRowIdx) ? selectedPosition.idx : void 0;
const isDraggedOver = rowIdx < draggedOverRowIdx ? rowIdx < currentRowIdx && currentRowIdx <= draggedOverRowIdx : rowIdx > currentRowIdx && currentRowIdx >= draggedOverRowIdx;
return isDraggedOver ? selectedPosition.idx : void 0;
}
function getDragHandle() {
if (onFill == null || selectedPosition.mode === "EDIT" || !isCellWithinViewportBounds(selectedPosition)) return;
@@ -2229,18 +2206,19 @@ function DataGrid(props) {
type: "ROW",
row: rows[rowIdx]
}) ?? 1;
const { insetInlineStart, ...style$1 } = getCellStyle(column, colSpan);
const { insetInlineStart,...style$1 } = getCellStyle(column, colSpan);
const marginEnd = "calc(var(--rdg-drag-handle-size) * -0.5 + 1px)";
const isLastColumn = column.idx + colSpan - 1 === maxColIdx;
const dragHandleStyle = {
...style$1,
gridRowStart: headerAndTopSummaryRowsCount + rowIdx + 1,
marginInlineEnd: isLastColumn ? void 0 : marginEnd,
marginBlockEnd: isLastRow ? void 0 : marginEnd,
insetInlineStart: insetInlineStart ? `calc(${insetInlineStart} + ${columnWidth}px + var(--rdg-drag-handle-size) * -0.5 - 1px)` : void 0
};
return /* @__PURE__ */ jsx("div", {
style: {
...style$1,
gridRowStart: headerAndTopSummaryRowsCount + rowIdx + 1,
marginInlineEnd: isLastColumn ? void 0 : marginEnd,
marginBlockEnd: isLastRow ? void 0 : marginEnd,
insetInlineStart: insetInlineStart ? `calc(${insetInlineStart} + ${columnWidth}px + var(--rdg-drag-handle-size) * -0.5 - 1px)` : void 0
},
className: classnames(cellDragHandleClassname, column.frozen && cellDragHandleFrozenClassname),
style: dragHandleStyle,
className: clsx(cellDragHandleClassname, column.frozen && cellDragHandleFrozenClassname),
onPointerDown: handleDragHandlePointerDown,
onPointerMove: isDragging ? handleDragHandlePointerMove : void 0,
onLostPointerCapture: isDragging ? handleDragHandleLostPointerCapture : void 0,
@@ -2249,7 +2227,7 @@ function DataGrid(props) {
});
}
function getCellEditor(rowIdx) {
if (!isCellWithinViewportBounds(selectedPosition) || selectedPosition.rowIdx !== rowIdx || selectedPosition.mode === "SELECT") return;
if (selectedPosition.rowIdx !== rowIdx || selectedPosition.mode === "SELECT") return;
const { idx, row: row$1 } = selectedPosition;
const column = columns[idx];
const colSpan = getColSpan(column, lastFrozenColumnIndex, {
@@ -2363,8 +2341,8 @@ function DataGrid(props) {
"aria-multiselectable": isSelectable ? true : void 0,
"aria-colcount": columns.length,
"aria-rowcount": ariaRowCount,
tabIndex: -1,
className: classnames(rootClassname, { [viewportDraggingClassname]: isDragging }, className),
tabIndex: shouldFocusGrid ? 0 : -1,
className: clsx(rootClassname, { [viewportDraggingClassname]: isDragging }, className),
style: {
...style,
scrollPaddingInlineStart: selectedPosition.idx > lastFrozenColumnIndex || scrollToPosition?.idx !== void 0 ? `${totalFrozenColumnWidth}px` : void 0,
@@ -2377,6 +2355,7 @@ function DataGrid(props) {
},
dir: direction,
ref: gridRef,
onFocus: shouldFocusGrid ? handleFocus : void 0,
onScroll: handleScroll,
onKeyDown: handleKeyDown,
onCopy: handleCellCopy,
@@ -2408,7 +2387,6 @@ function DataGrid(props) {
lastFrozenColumnIndex,
selectedCellIdx: selectedPosition.rowIdx === mainHeaderRowIdx ? selectedPosition.idx : void 0,
selectCell: selectHeaderCellLatest,
shouldFocusGrid: !selectedCellIsWithinSelectionBounds,
direction
})]
})
@@ -2459,11 +2437,11 @@ function DataGrid(props) {
] })]
}),
getDragHandle(),
renderMeasuringCells(viewportColumns),
renderMeasuringCells(viewportColumns, columnsToMeasure),
isTreeGrid && /* @__PURE__ */ jsx("div", {
ref: focusSinkRef,
tabIndex: isGroupRowFocused ? 0 : -1,
className: classnames(focusSinkClassname, {
className: clsx(focusSinkClassname, {
[focusSinkHeaderAndSummaryClassname]: !isRowIdxWithinViewportBounds(selectedPosition.rowIdx),
[rowSelected]: isGroupRowFocused,
[rowSelectedWithFrozenCell]: isGroupRowFocused && lastFrozenColumnIndex !== -1
@@ -2523,8 +2501,9 @@ var GroupCell_default = memo(GroupCell);
//#endregion
//#region src/GroupRow.tsx
const groupRowClassname = `rdg-group-row rdg-7-0-0-beta-59-e74a2be3`;
function GroupedRow({ className, row: row$1, rowIdx, viewportColumns, selectedCellIdx, isRowSelected, selectCell, gridRowStart, groupBy, toggleGroup, isRowSelectionDisabled, ...props }) {
const groupRow = "g1yxluv37-0-0-beta-55";
const groupRowClassname = `rdg-group-row ${groupRow}`;
function GroupedRow({ className, row: row$1, rowIdx, viewportColumns, selectedCellIdx, isRowSelected, selectCell, gridRowStart, groupBy, toggleGroup, isRowSelectionDisabled,...props }) {
const idx = viewportColumns[0].key === SELECT_COLUMN_KEY ? row$1.level + 1 : row$1.level;
function handleSelectGroup() {
selectCell({
@@ -2532,18 +2511,19 @@ function GroupedRow({ className, row: row$1, rowIdx, viewportColumns, selectedCe
idx: -1
}, { shouldFocusCell: true });
}
const selectionValue = useMemo(() => ({
isRowSelectionDisabled: false,
isRowSelected
}), [isRowSelected]);
return /* @__PURE__ */ jsx(RowSelectionContext, {
value: useMemo(() => ({
isRowSelectionDisabled: false,
isRowSelected
}), [isRowSelected]),
value: selectionValue,
children: /* @__PURE__ */ jsx("div", {
role: "row",
"aria-level": row$1.level + 1,
"aria-setsize": row$1.setSize,
"aria-posinset": row$1.posInSet + 1,
"aria-expanded": row$1.isExpanded,
className: classnames(rowClassname, groupRowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, selectedCellIdx === -1 && rowSelectedClassname, className),
className: clsx(rowClassname, groupRowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, selectedCellIdx === -1 && rowSelectedClassname, className),
onMouseDown: handleSelectGroup,
style: getRowStyle(gridRowStart),
...props,
@@ -2566,7 +2546,7 @@ var GroupRow_default = memo(GroupedRow);
//#endregion
//#region src/TreeDataGrid.tsx
function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHeight, rowKeyGetter: rawRowKeyGetter, onCellKeyDown: rawOnCellKeyDown, onCellCopy: rawOnCellCopy, onCellPaste: rawOnCellPaste, onRowsChange, selectedRows: rawSelectedRows, onSelectedRowsChange: rawOnSelectedRowsChange, renderers, groupBy: rawGroupBy, rowGrouper, expandedGroupIds, onExpandedGroupIdsChange, groupIdGetter: rawGroupIdGetter, ...props }) {
function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHeight, rowKeyGetter: rawRowKeyGetter, onCellKeyDown: rawOnCellKeyDown, onCellCopy: rawOnCellCopy, onCellPaste: rawOnCellPaste, onRowsChange, selectedRows: rawSelectedRows, onSelectedRowsChange: rawOnSelectedRowsChange, renderers, groupBy: rawGroupBy, rowGrouper, expandedGroupIds, onExpandedGroupIdsChange, groupIdGetter: rawGroupIdGetter,...props }) {
const defaultRenderers = useDefaultRenderers();
const rawRenderRow = renderers?.renderRow ?? defaultRenderers?.renderRow ?? defaultRenderRow;
const headerAndTopSummaryRowsCount = 1 + (props.topSummaryRows?.length ?? 0);
@@ -2635,7 +2615,7 @@ function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHei
const id = groupIdGetter(groupKey, parentId);
const isExpanded = expandedGroupIds.has(id);
const { childRows, childGroups, startRowIndex } = rows$1[groupKey];
const groupRow = {
const groupRow$1 = {
id,
parentId,
groupKey,
@@ -2646,8 +2626,8 @@ function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHei
startRowIndex,
setSize: keys.length
};
flattenedRows.push(groupRow);
allGroupRows.add(groupRow);
flattenedRows.push(groupRow$1);
allGroupRows.add(groupRow$1);
if (isExpanded) expandGroup(childGroups, id, level + 1);
});
};
@@ -2681,6 +2661,7 @@ function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHei
const parentRow = rows[i];
if (isGroupRow(parentRow) && (!isGroupRow(row$1) || row$1.parentId === parentRow.id)) return [parentRow, i];
}
return void 0;
}, [isGroupRow, rows]);
const rowKeyGetter = useCallback((row$1) => {
if (isGroupRow(row$1)) return row$1.id;
@@ -2688,7 +2669,8 @@ function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHei
const parentRowAndIndex = getParentRowAndIndex(row$1);
if (parentRowAndIndex !== void 0) {
const { startRowIndex, childRows } = parentRowAndIndex[0];
return startRowIndex + childRows.indexOf(row$1) + 1;
const groupIndex = childRows.indexOf(row$1);
return startRowIndex + groupIndex + 1;
}
return rows.indexOf(row$1);
}, [
@@ -2702,7 +2684,8 @@ function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHei
assertIsValidKeyGetter(rawRowKeyGetter);
const selectedRows$1 = new Set(rawSelectedRows);
for (const row$1 of rows) if (isGroupRow(row$1)) {
if (row$1.childRows.every((cr) => rawSelectedRows.has(rawRowKeyGetter(cr)))) selectedRows$1.add(row$1.id);
const isGroupRowSelected = row$1.childRows.every((cr) => rawSelectedRows.has(rawRowKeyGetter(cr)));
if (isGroupRowSelected) selectedRows$1.add(row$1.id);
}
return selectedRows$1;
}, [
@@ -2780,7 +2763,7 @@ function TreeDataGrid({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHei
else newExpandedGroupIds.add(groupId);
onExpandedGroupIdsChange(newExpandedGroupIds);
}
function renderRow(key, { row: row$1, rowClass, onCellMouseDown, onCellClick, onCellDoubleClick, onCellContextMenu, onRowChange, lastFrozenColumnIndex, draggedOverCellIdx, selectedCellEditor, ...rowProps }) {
function renderRow(key, { row: row$1, rowClass, onCellMouseDown, onCellClick, onCellDoubleClick, onCellContextMenu, onRowChange, lastFrozenColumnIndex, draggedOverCellIdx, selectedCellEditor,...rowProps }) {
if (isGroupRow(row$1)) {
const { startRowIndex } = row$1;
return /* @__PURE__ */ jsx(GroupRow_default, {
@@ -2841,14 +2824,14 @@ function isReadonlyArray(arr) {
}
//#endregion
//#region src/editors/renderTextEditor.tsx
const textEditorInternalClassname = "rdg-7-0-0-beta-59-2f8db206";
//#region src/editors/textEditor.tsx
const textEditorInternalClassname = "t7vyx3i7-0-0-beta-55";
const textEditorClassname = `rdg-text-editor ${textEditorInternalClassname}`;
function autoFocusAndSelect(input) {
input?.focus();
input?.select();
}
function textTextEditor({ row: row$1, column, onRowChange, onClose }) {
function textEditor({ row: row$1, column, onRowChange, onClose }) {
return /* @__PURE__ */ jsx("input", {
className: textEditorClassname,
ref: autoFocusAndSelect,
@@ -2862,5 +2845,5 @@ function textTextEditor({ row: row$1, column, onRowChange, onClose }) {
}
//#endregion
export { Cell_default as Cell, DataGrid, DataGridDefaultRenderersContext, Row_default as Row, SELECT_COLUMN_KEY, SelectCellFormatter, SelectColumn, ToggleGroup, TreeDataGrid, renderCheckbox, renderHeaderCell, renderSortIcon, renderSortPriority, textTextEditor as renderTextEditor, renderToggleGroup, renderValue, useHeaderRowSelection, useRowSelection };
export { Cell_default as Cell, DataGrid, DataGridDefaultRenderersContext, Row_default as Row, SELECT_COLUMN_KEY, SelectCellFormatter, SelectColumn, ToggleGroup, TreeDataGrid, renderCheckbox, renderHeaderCell, renderSortIcon, renderSortPriority, renderToggleGroup, renderValue, textEditor, useHeaderRowSelection, useRowSelection };
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -15,14 +15,16 @@
Root;
}
.rdg-7-0-0-beta-59-fa71d63e {
.mlln6zg7-0-0-beta-55 {
@layer rdg.MeasuringCell {
contain: strict;
grid-row: 1;
visibility: hidden;
}
}
.rdg-7-0-0-beta-59-85c48527 {
.cj343x07-0-0-beta-55 {
@layer rdg.Cell {
/* max-content does not work with size containment
* dynamically switching between different containment styles incurs a heavy relayout penalty
@@ -33,8 +35,8 @@
position: relative; /* needed for absolute positioning to work */
padding-block: 0;
padding-inline: 8px;
border-inline-end: var(--rdg-border-width) solid var(--rdg-border-color);
border-block-end: var(--rdg-border-width) solid var(--rdg-border-color);
border-inline-end: 1px solid var(--rdg-border-color);
border-block-end: 1px solid var(--rdg-border-color);
grid-row-start: var(--rdg-grid-row-start);
align-content: center;
background-color: inherit;
@@ -45,13 +47,13 @@
outline: none;
&[aria-selected='true'] {
outline: var(--rdg-selection-width) solid var(--rdg-selection-color);
outline-offset: calc(var(--rdg-selection-width) * -1);
outline: 2px solid var(--rdg-selection-color);
outline-offset: -2px;
}
}
}
.rdg-7-0-0-beta-59-17a9a6d4 {
.csofj7r7-0-0-beta-55 {
@layer rdg.Cell {
position: sticky;
/* Should have a higher value than 0 to show up above unfrozen cells */
@@ -64,7 +66,7 @@
}
}
.rdg-7-0-0-beta-59-bfba19bc {
.ch2wcw87-0-0-beta-55 {
@layer rdg.DragHandle {
--rdg-drag-handle-size: 8px;
z-index: 0;
@@ -82,13 +84,15 @@
}
}
.rdg-7-0-0-beta-59-7abddb3e {
.c1wvphzh7-0-0-beta-55 {
@layer rdg.DragHandle {
z-index: 1;
position: sticky;
}
}
.rdg-7-0-0-beta-59-3b807ead {
.c1bn88vv7-0-0-beta-55 {
@layer rdg.CheckboxInput {
display: block;
margin: auto;
@@ -105,13 +109,15 @@
}
}
}
.rdg-7-0-0-beta-59-07919382 {
.g1s9ylgp7-0-0-beta-55 {
@layer rdg.GroupCellContent {
outline: none;
}
}
.rdg-7-0-0-beta-59-02a50147 {
.cz54e4y7-0-0-beta-55 {
@layer rdg.GroupCellCaret {
margin-inline-start: 4px;
stroke: currentColor;
@@ -124,59 +130,50 @@
}
}
}
.rdg-7-0-0-beta-59-56a248e4 {
.h44jtk67-0-0-beta-55 {
@layer rdg.SortableHeaderCell {
display: flex;
}
}
.rdg-7-0-0-beta-59-7fad8c83 {
.hcgkhxz7-0-0-beta-55 {
@layer rdg.SortableHeaderCellName {
flex-grow: 1;
overflow: clip;
text-overflow: ellipsis;
}
}
.rdg-7-0-0-beta-59-35ccb4c8 {
.c6ra8a37-0-0-beta-55 {
@layer rdg.Cell {
background-color: #ccccff;
}
}
.rdg-7-0-0-beta-59-46f9ea88 {
.cis5rrm7-0-0-beta-55 {
@layer rdg.EditCell {
padding: 0;
}
}
.rdg-7-0-0-beta-59-0dbd5994 {
@layer rdg.HeaderRow {
display: contents;
background-color: var(--rdg-header-background-color);
font-weight: bold;
& > .rdg-7-0-0-beta-59-85c48527 {
/* Should have a higher value than 1 to show up above regular cells and the focus sink */
z-index: 2;
position: sticky;
}
& > .rdg-7-0-0-beta-59-17a9a6d4 {
z-index: 3;
}
}
}
.rdg-7-0-0-beta-59-2a7e240d {
.c6l2wv17-0-0-beta-55 {
@layer rdg.HeaderCell {
cursor: pointer;
}
}
.rdg-7-0-0-beta-59-1893dc0f {
.c1kqdw7y7-0-0-beta-55 {
@layer rdg.HeaderCell {
touch-action: none;
}
}
.rdg-7-0-0-beta-59-4e60db91 {
.r1y6ywlx7-0-0-beta-55 {
@layer rdg.HeaderCell {
cursor: col-resize;
position: absolute;
@@ -187,19 +184,19 @@
}
}
.rdg-7-0-0-beta-59-3e1a4ad4 {
.c1bezg5o7-0-0-beta-55 {
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
}
.rdg-7-0-0-beta-59-51abd8b8 {
.c1vc96037-0-0-beta-55 {
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
}
.rdg-7-0-0-beta-59-c8d7aa64 {
.d8rwc9w7-0-0-beta-55 {
@layer rdg.HeaderCell {
border-radius: 4px;
width: fit-content;
@@ -207,7 +204,9 @@
outline-offset: -2px;
}
}
.rdg-7-0-0-beta-59-3c083f1b {
.r1upfr807-0-0-beta-55 {
@layer rdg.Row {
display: contents;
background-color: var(--rdg-background-color);
@@ -226,14 +225,14 @@
}
}
.rdg-7-0-0-beta-59-3fe773c3 {
.r190mhd37-0-0-beta-55 {
@layer rdg.FocusSink {
outline: 2px solid var(--rdg-selection-color);
outline-offset: -2px;
}
}
.rdg-7-0-0-beta-59-97ce3fde {
.r139qu9m7-0-0-beta-55 {
@layer rdg.FocusSink {
&::before {
content: '';
@@ -245,7 +244,28 @@
}
}
}
.rdg-7-0-0-beta-59-3d5115f3 {
.h10tskcx7-0-0-beta-55 {
@layer rdg.HeaderRow {
display: contents;
background-color: var(--rdg-header-background-color);
font-weight: bold;
& > .cj343x07-0-0-beta-55 {
/* Should have a higher value than 1 to show up above regular cells and the focus sink */
z-index: 2;
position: sticky;
}
& > .csofj7r7-0-0-beta-55 {
z-index: 3;
}
}
}
.a3ejtar7-0-0-beta-55 {
@layer rdg.SortIcon {
fill: currentColor;
@@ -254,7 +274,9 @@
}
}
}
.rdg-7-0-0-beta-59-ccd2e5d9 {
.rnvodz57-0-0-beta-55 {
@layer rdg.Defaults {
*,
*::before,
@@ -264,30 +286,10 @@
}
@layer rdg.Root {
--rdg-selection-width: 2px;
--rdg-color: #000; --rdg-border-color: #ddd; --rdg-summary-border-color: #aaa; --rdg-background-color: hsl(0deg 0% 100%); --rdg-header-background-color: hsl(0deg 0% 97.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 90.5%); --rdg-row-hover-background-color: hsl(0deg 0% 96%); --rdg-row-selected-background-color: hsl(207deg 76% 92%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 88%); --rdg-checkbox-focus-color: hsl(207deg 100% 69%);
--rdg-selection-color: hsl(207, 75%, 66%);
--rdg-font-size: 14px;
--rdg-cell-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);
--rdg-border-width: 1px;
--rdg-summary-border-width: calc(var(--rdg-border-width) * 2);
--rdg-color: light-dark(#000, #ddd);
--rdg-border-color: light-dark(#ddd, #444);
--rdg-summary-border-color: light-dark(#aaa, #555);
--rdg-background-color: light-dark(hsl(0deg 0% 100%), hsl(0deg 0% 13%));
--rdg-header-background-color: light-dark(hsl(0deg 0% 97.5%), hsl(0deg 0% 10.5%));
--rdg-header-draggable-background-color: light-dark(hsl(0deg 0% 90.5%), hsl(0deg 0% 17.5%));
--rdg-row-hover-background-color: light-dark(hsl(0deg 0% 96%), hsl(0deg 0% 9%));
--rdg-row-selected-background-color: light-dark(hsl(207deg 76% 92%), hsl(207deg 76% 42%));
--rdg-row-selected-hover-background-color: light-dark(hsl(207deg 76% 88%), hsl(207deg 76% 38%));
--rdg-checkbox-focus-color: hsl(207deg 100% 69%);
&.rdg-dark {
color-scheme: dark;
}
&.rdg-light {
color-scheme: light;
}
&:dir(rtl) {
--rdg-cell-frozen-box-shadow: -2px 0 5px -2px rgba(136, 136, 136, 0.3);
@@ -295,6 +297,7 @@
display: grid;
color-scheme: var(--rdg-color-scheme, light dark);
accent-color: light-dark(hsl(207deg 100% 29%), hsl(207deg 100% 79%));
/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context */
@@ -317,31 +320,46 @@
grid-row: 1/-1;
}
&.rdg-dark {
--rdg-color-scheme: dark;
--rdg-color: #ddd; --rdg-border-color: #444; --rdg-summary-border-color: #555; --rdg-background-color: hsl(0deg 0% 13%); --rdg-header-background-color: hsl(0deg 0% 10.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 17.5%); --rdg-row-hover-background-color: hsl(0deg 0% 9%); --rdg-row-selected-background-color: hsl(207deg 76% 42%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 38%); --rdg-checkbox-focus-color: hsl(207deg 100% 89%);
}
&.rdg-light {
--rdg-color-scheme: light;
}
@media (prefers-color-scheme: dark) {
&:not(.rdg-light) {
--rdg-color: #ddd; --rdg-border-color: #444; --rdg-summary-border-color: #555; --rdg-background-color: hsl(0deg 0% 13%); --rdg-header-background-color: hsl(0deg 0% 10.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 17.5%); --rdg-row-hover-background-color: hsl(0deg 0% 9%); --rdg-row-selected-background-color: hsl(207deg 76% 42%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 38%); --rdg-checkbox-focus-color: hsl(207deg 100% 89%);
}
}
> :nth-last-child(1 of .rdg-top-summary-row) {
> .rdg-7-0-0-beta-59-85c48527 {
border-block-end: var(--rdg-summary-border-width) solid var(--rdg-summary-border-color);
> .cj343x07-0-0-beta-55 {
border-block-end: 2px solid var(--rdg-summary-border-color);
}
}
> :nth-child(1 of .rdg-bottom-summary-row) {
> .rdg-7-0-0-beta-59-85c48527 {
border-block-start: var(--rdg-summary-border-width) solid var(--rdg-summary-border-color);
> .cj343x07-0-0-beta-55 {
border-block-start: 2px solid var(--rdg-summary-border-color);
}
}
}
}
.rdg-7-0-0-beta-59-e9b0e1c9 {
.vlqv91k7-0-0-beta-55 {
@layer rdg.Root {
user-select: none;
& .rdg-7-0-0-beta-59-3c083f1b {
& .r1upfr807-0-0-beta-55 {
cursor: move;
}
}
}
.rdg-7-0-0-beta-59-dbb8b3c5 {
.f1lsfrzw7-0-0-beta-55 {
@layer rdg.FocusSink {
grid-column: 1/-1;
pointer-events: none;
@@ -350,50 +368,58 @@
}
}
.rdg-7-0-0-beta-59-e9f55541 {
.f1cte0lg7-0-0-beta-55 {
@layer rdg.FocusSink {
/* Should have a higher value than 3 to show up above header and summary rows */
z-index: 3;
}
}
.rdg-7-0-0-beta-59-0b90c82c {
@layer rdg.SummaryRow {
> .rdg-7-0-0-beta-59-85c48527 {
position: sticky;
}
}
}
.rdg-7-0-0-beta-59-d0520eab {
@layer rdg.SummaryRow {
> .rdg-7-0-0-beta-59-85c48527 {
z-index: 2;
}
> .rdg-7-0-0-beta-59-17a9a6d4 {
z-index: 3;
}
}
}
.rdg-7-0-0-beta-59-d907aa87 {
.s8wc6fl7-0-0-beta-55 {
@layer rdg.SummaryCell {
inset-block-start: var(--rdg-summary-row-top);
inset-block-end: var(--rdg-summary-row-bottom);
}
}
.rdg-7-0-0-beta-59-e74a2be3 {
.skuhp557-0-0-beta-55 {
@layer rdg.SummaryRow {
> .cj343x07-0-0-beta-55 {
position: sticky;
}
}
}
.tf8l5ub7-0-0-beta-55 {
@layer rdg.SummaryRow {
> .cj343x07-0-0-beta-55 {
z-index: 2;
}
> .csofj7r7-0-0-beta-55 {
z-index: 3;
}
}
}
.g1yxluv37-0-0-beta-55 {
@layer rdg.GroupedRow {
&:not([aria-selected='true']) {
background-color: var(--rdg-header-background-color);
}
> .rdg-7-0-0-beta-59-85c48527:not(:last-child, .rdg-7-0-0-beta-59-17a9a6d4),
> :nth-last-child(n + 2 of .rdg-7-0-0-beta-59-17a9a6d4) {
> .cj343x07-0-0-beta-55:not(:last-child, .csofj7r7-0-0-beta-55),
> :nth-last-child(n + 2 of .csofj7r7-0-0-beta-55) {
border-inline-end: none;
}
}
}
.rdg-7-0-0-beta-59-2f8db206 {
.t7vyx3i7-0-0-beta-55 {
@layer rdg.TextEditor {
appearance: none;
@@ -422,4 +448,6 @@
}
}
/*# sourceMappingURL=styles.css.map*/
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
{
"name": "react-data-grid",
"version": "7.0.0-beta.59",
"version": "7.0.0-beta.55",
"type": "module",
"exports": {
"./lib/styles.css": "./lib/styles.css",
@@ -19,11 +19,11 @@
"sideEffects": [
"**/*.css"
],
"peerDependencies": {
"react": "^19.2",
"react-dom": "^19.2"
"dependencies": {
"clsx": "^2.0.0"
},
"overrides": {
"vite": "$vite"
"peerDependencies": {
"react": "^19.0",
"react-dom": "^19.0"
}
}
@@ -8,7 +8,6 @@
"./*": "./lib/*.js"
},
"scripts": {
"build": "tsc -b",
"clear": "rimraf lib"
},
"files": [
@@ -25,7 +24,7 @@
"!lib/tests",
"!.tsbuildinfo"
],
"packageManager": "yarn@4.14.1",
"packageManager": "yarn@4.9.2",
"devDependencies": {
"@dbeaver/tsconfig": "workspace:^",
"@types/react": "^19",
@@ -34,12 +33,10 @@
"typescript": "^5"
},
"dependencies": {
"@dbeaver/js-helpers": "workspace:*",
"@dbeaver/react-dnd": "workspace:^",
"@dbeaver/react-translate": "workspace:^",
"@dbeaver/ui-kit": "workspace:^",
"react": "^19.2",
"react": "^19",
"react-data-grid": "file:./artifacts",
"react-dom": "^19.2"
"react-dom": "^19"
}
}
@@ -1,14 +1,5 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { memo, use, useMemo } from 'react';
import { Cell, type CellRendererProps } from 'react-data-grid';
import { clsx } from '@dbeaver/ui-kit';
import { DataGridCellContext, type IDataGridCellRenderer } from './DataGridCellContext.js';
import { createCellMouseEvent } from './eventUtils.js';
import { DataGridCellInnerContext, type IDataGridCellInnerContext } from './DataGridCellInnerContext.js';
@@ -19,10 +10,9 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
const cellContext = use(DataGridCellContext);
const dndContext = use(HeaderDnDContext)!;
const virtualColIdx = props.column.idx;
const dataColIdx = dndContext.getDataColIdxByKey(props.column.key);
const dataColIdx = dndContext.getDataColIdx(props.column.idx);
const rowIdx = props.rowIdx;
const tooltip = useGridReactiveValue(cellContext?.cellTooltip, rowIdx, dataColIdx);
const cellClassName = useGridReactiveValue(cellContext?.getCellClassName, rowIdx, dataColIdx);
function handleClick(event: React.MouseEvent<HTMLDivElement>) {
props.onCellClick?.(
@@ -87,27 +77,24 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
const renderDefaultCell = useMemo<IDataGridCellRenderer>(
() =>
({ onClick, onDoubleClick, onContextMenu, className, ...rest }) => (
<Cell
{...props}
title={tooltip}
className={clsx(cellClassName, className)}
onCellClick={(_, event) => onClick?.(event)}
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
onCellContextMenu={(_, event) => onContextMenu?.(event)}
{...rest}
isCellSelected={props.isCellSelected || rest.isFocused || false}
/>
),
[...Object.values(props), tooltip, cellClassName],
({ onClick, onDoubleClick, onContextMenu, ...rest }) => {
return (
<Cell
{...props}
title={tooltip}
onCellClick={(_, event) => onClick?.(event)}
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
onCellContextMenu={(_, event) => onContextMenu?.(event)}
{...rest}
isCellSelected={props.isCellSelected || rest.isFocused || false}
/>
);
},
[...Object.values(props), tooltip],
);
const cellElement = useGridReactiveValue(cellContext?.cellElement, rowIdx, dataColIdx, mappedProps, renderDefaultCell);
const innerCellContext = useMemo<IDataGridCellInnerContext>(() => ({ isFocused: props.isCellSelected }), [props.isCellSelected]);
return (
<DataGridCellInnerContext value={innerCellContext}>
{cellElement ?? <Cell title={tooltip} className={cellClassName} {...props} />}
</DataGridCellInnerContext>
);
return <DataGridCellInnerContext value={innerCellContext}>{cellElement ?? <Cell title={tooltip} {...props} />}</DataGridCellInnerContext>;
}) as <TRow, TSummaryRow>(props: CellRendererProps<TRow, TSummaryRow>) => React.ReactNode;
@@ -0,0 +1,16 @@
import { Row, type RenderRowProps } from 'react-data-grid';
import { DataGridRowContext } from './DataGridRowContext.js';
import { memo, use, useEffect } from 'react';
import { useGridReactiveValue } from './useGridReactiveValue.js';
export const BaseRow = memo(function BaseRow<TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) {
const rowContext = use(DataGridRowContext);
const rowsCount = useGridReactiveValue(rowContext?.onScrollToBottom ? rowContext?.rowCount : undefined);
useEffect(() => {
if (rowsCount === props.rowIdx + 1) {
rowContext?.onScrollToBottom?.();
}
}, [rowsCount]);
return <Row {...props} />;
}) as <TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) => React.ReactNode;
@@ -1,25 +1,9 @@
@import url('@dbeaver/ui-kit/index.css');
@import url('react-data-grid/lib/styles.css') layer(components);
@layer components.rdg.Search {
.rdg-search-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.rdg-search-panel {
padding-bottom: 0 !important;
}
.rdg-cell-search-match {
background-color: #ffff0054;
}
.rdg-cell-search-active {
background-color: #ff6a0054;
@layer components.rdg.MeasuringCell {
[data-measuring-cell] {
max-width: var(--rdg-max-auto-size, 350px);
}
}
@@ -0,0 +1,181 @@
import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { DataGrid as DataGridBase, type ColumnOrColumnGroup, type CellSelectArgs, type DataGridHandle, type ColumnWidth, type ColumnWidths } from 'react-data-grid';
import { rowRenderer } from './renderers/rowRenderer.js';
import { cellRenderer } from './renderers/cellRenderer.js';
import { DataGridCellHeaderContext, type IDataGridHeaderCellContext } from './DataGridHeaderCellContext.js';
import { DataGridCellContext, type IDataGridCellContext } from './DataGridCellContext.js';
import type { IInnerRow } from './IInnerRow.js';
import type { IGridReactiveValue } from './IGridReactiveValue.js';
import { useGridReactiveValue } from './useGridReactiveValue.js';
import { mapCellContentRenderer } from './mapCellContentRenderer.js';
import { mapRenderHeaderCell } from './mapRenderHeaderCell.js';
import { mapEditCellRenderer } from './mapEditCellRenderer.js';
import { DataGridRowContext, type IDataGridRowContext } from './DataGridRowContext.js';
import './DataGrid.css';
import { HeaderDnDContext, useHeaderDnD } from './useHeaderDnD.js';
export interface ICellPosition {
rowIdx: number;
colIdx: number;
}
export interface DataGridCellKeyboardEvent extends React.KeyboardEvent<HTMLDivElement> {
preventGridDefault: () => void;
isGridDefaultPrevented: () => boolean;
}
export interface DataGridProps extends IDataGridCellContext, IDataGridRowContext, IDataGridHeaderCellContext, React.PropsWithChildren {
getRowHeight?: (rowIdx: number) => number;
getRowId?: (rowIdx: number) => React.Key;
columnCount: IGridReactiveValue<number, []>;
getColumnKey?: (colIdx: number) => string;
onScroll?: (event: React.UIEvent<HTMLDivElement>) => void;
onFocus?: (position: ICellPosition) => void;
onEditorOpen?: (position: ICellPosition) => void;
onCellKeyDown?: (position: ICellPosition, event: DataGridCellKeyboardEvent) => void;
className?: string;
}
export interface DataGridRef {
selectCell: (position: ICellPosition) => void;
scrollToCell: (position: Partial<ICellPosition>) => void;
openEditor: (position: ICellPosition) => void;
}
export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid(
{
headerElement,
getHeaderWidth,
headerText,
getHeaderOrder,
getHeaderResizable,
columnSortable,
getHeaderHeight,
getHeaderPinned,
columnSortingState,
getHeaderDnD,
cell,
cellText,
cellElement,
cellTooltip,
getCellEditable,
columnCount,
getColumnKey,
rowCount,
getRowId,
getRowHeight,
onHeaderReorder,
onScroll,
onScrollToBottom,
onFocus,
onCellChange,
onColumnSort,
onHeaderKeyDown,
children,
className,
onCellKeyDown,
},
ref,
) {
const [columnWidths, setColumnWidths] = useState<ColumnWidths>(() => new Map<string, ColumnWidth>());
const rowsCount = useGridReactiveValue(rowCount);
const columnsCount = useGridReactiveValue(columnCount);
const rowsCountRef = useRef(rowsCount);
const innerGridRef = useRef<DataGridHandle>(null);
const columns = new Array<ColumnOrColumnGroup<IInnerRow, unknown>>(columnsCount)
.fill(null as any)
.map((_, i): ColumnOrColumnGroup<IInnerRow, unknown> => {
const width = getHeaderWidth?.(i) ?? 'max-content';
return {
key: getColumnKey?.(i) ?? String(i),
name: '',
resizable: getHeaderResizable?.(i) ?? true,
width,
minWidth: 26,
editable: row => getCellEditable?.(row.idx, i) ?? false,
frozen: getHeaderPinned?.(i),
renderHeaderCell: mapRenderHeaderCell(i),
renderCell: mapCellContentRenderer(i),
renderEditCell: mapEditCellRenderer(i),
};
});
const dndHeaderContext = useHeaderDnD({ columns, onReorder: onHeaderReorder, getCanDrag: getHeaderDnD, getHeaderOrder });
useImperativeHandle(ref, () => ({
selectCell: (position: ICellPosition) => {
innerGridRef.current?.selectCell({ idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx });
},
scrollToCell: (position: Partial<ICellPosition>) => {
innerGridRef.current?.scrollToCell({ idx: position.colIdx && dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx });
},
openEditor: (position: ICellPosition) => {
innerGridRef.current?.selectCell({ idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx }, {
enableEditor: true,
});
},
}));
if (rowsCountRef.current !== rowsCount) {
const previousRowCount = rowsCountRef.current;
rowsCountRef.current = rowsCount;
if (previousRowCount === 0) {
setColumnWidths(new Map<string, ColumnWidth>());
}
}
let rows = useMemo(
() =>
new Array<IInnerRow>(rowsCount).fill({ idx: 0 }).map((_, i) => ({
idx: i,
})),
[rowsCount],
);
function handleCellFocus(args: CellSelectArgs<IInnerRow, unknown>) {
onFocus?.({ colIdx: dndHeaderContext.getDataColIdx(args.column.idx), rowIdx: args.rowIdx });
}
function handleCellKeyDown(args: CellSelectArgs<IInnerRow, unknown>, event: DataGridCellKeyboardEvent) {
onCellKeyDown?.({ colIdx: dndHeaderContext.getDataColIdx(args.column.idx), rowIdx: args.rowIdx }, event);
}
const isMeasurementRender = columnWidths.size === 0 && columnCount.get() > 0;
if (isMeasurementRender) {
rows = rows.slice(0, 100);
}
return (
<HeaderDnDContext value={dndHeaderContext}>
<DataGridRowContext value={{ rowCount, onScrollToBottom }}>
<DataGridCellContext value={{ cell, cellText, cellElement, cellTooltip, onCellChange }}>
<DataGridCellHeaderContext value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState, onHeaderKeyDown }}>
<DataGridBase
ref={innerGridRef}
columns={dndHeaderContext.columns}
enableVirtualization={!isMeasurementRender}
rows={rows}
className={className}
headerRowHeight={getHeaderHeight?.()}
rowHeight={getRowHeight ? row => getRowHeight(row.idx) : undefined}
rowKeyGetter={getRowId ? row => getRowId(row.idx) : undefined}
columnWidths={columnWidths}
renderers={{
renderRow: rowRenderer,
renderCell: cellRenderer,
noRowsFallback: children,
}}
onScroll={onScroll}
onSelectedCellChange={handleCellFocus}
onCellKeyDown={handleCellKeyDown}
onColumnWidthsChange={setColumnWidths}
/>
</DataGridCellHeaderContext>
</DataGridCellContext>
</DataGridRowContext>
</HeaderDnDContext>
);
});
@@ -1,11 +1,3 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createContext } from 'react';
import type { IGridReactiveValue } from './IGridReactiveValue.js';
@@ -13,16 +5,10 @@ export interface IDataGridCellProps extends React.DetailedHTMLProps<React.HTMLAt
isFocused: boolean;
}
export interface IDataGridCellRenderer {
export interface IDataGridCellRenderer {
(propsOverride: Partial<IDataGridCellProps> & { [key: string]: any }): React.ReactNode;
}
export interface ICellChange {
rowIdx: number;
colIdx: number;
value: any;
}
export interface IDataGridCellContext {
cellElement?: IGridReactiveValue<
React.ReactNode,
@@ -31,10 +17,8 @@ export interface IDataGridCellContext {
cell?: IGridReactiveValue<React.ReactNode, [rowIdx: number, colIdx: number]>;
cellText?: IGridReactiveValue<string, [rowIdx: number, colIdx: number]>;
cellTooltip?: IGridReactiveValue<string, [rowIdx: number, colIdx: number]>;
getCellClassName?: IGridReactiveValue<string | undefined, [rowIdx: number, colIdx: number]>;
getCellEditable?: (rowIdx: number, colIdx: number) => boolean;
onCellChange?: (rowIdx: number, colIdx: number, value: any) => void;
onCellChangeBatch?: (changes: ICellChange[]) => void;
}
export const DataGridCellContext = createContext<IDataGridCellContext | null>(null);
@@ -0,0 +1,7 @@
import { createContext } from 'react';
export interface IDataGridCellInnerContext {
isFocused: boolean;
}
export const DataGridCellInnerContext = createContext<IDataGridCellInnerContext | null>(null);
@@ -1,14 +1,5 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createContext } from 'react';
import type { IGridReactiveValue } from './IGridReactiveValue.js';
import type { DataGridCellKeyboardEvent } from './DataGrid.js';
export interface IDataGridHeaderCellContext {
headerElement?: IGridReactiveValue<React.ReactNode, [colIdx: number]>;
@@ -22,9 +13,8 @@ export interface IDataGridHeaderCellContext {
onHeaderReorder?: (from: number, to: number) => void;
columnSortingState?: IGridReactiveValue<'asc' | 'desc' | undefined | null, [colIdx: number]>;
columnSortable?: IGridReactiveValue<boolean, [colIdx: number]>;
columnSortingMultiple?: boolean;
onColumnSort?: (colIdx: number, order: 'asc' | 'desc' | null, isMultiple: boolean) => void;
onHeaderKeyDown?: (event: DataGridCellKeyboardEvent) => void;
onHeaderKeyDown?: (event: React.KeyboardEvent) => void;
}
export const DataGridCellHeaderContext = createContext<IDataGridHeaderCellContext | null>(null);
@@ -0,0 +1,9 @@
import { createContext } from 'react';
import type { IGridReactiveValue } from './IGridReactiveValue.js';
export interface IDataGridRowContext {
rowCount: IGridReactiveValue<number, []>;
onScrollToBottom?: () => void;
}
export const DataGridRowContext = createContext<IDataGridRowContext | null>(null);
@@ -1,13 +1,5 @@
:global(.rdg-cell) .editor {
appearance: none;
resize: none;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
box-sizing: border-box;
min-height: initial;
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -11,22 +11,9 @@ import classes from './TextEditor.module.css';
import { DataGridCellContext } from '../DataGridCellContext.js';
import { useGridReactiveValue } from '../useGridReactiveValue.js';
function autoFocusAndSelect(input: HTMLTextAreaElement | null) {
if (!input) {
return;
}
input.focus();
input.select();
input.addEventListener(
'beforeinput',
e => {
if (e.inputType === 'insertLineBreak') {
e.preventDefault();
}
},
{ once: true },
);
function autoFocusAndSelect(input: HTMLInputElement | null) {
input?.focus();
input?.select();
}
export interface IProps {
@@ -41,11 +28,9 @@ export function TextEditor({ rowIdx, colIdx, onClose }: IProps) {
const value = useGridReactiveValue(cellContext?.cellText, rowIdx, colIdx) ?? '';
return (
<textarea
ref={autoFocusAndSelect}
<input
className={classes['editor']}
rows={1}
wrap="off"
ref={autoFocusAndSelect}
value={value}
onChange={event => cellContext?.onCellChange?.(rowIdx, colIdx, event.target.value)}
onBlur={() => onClose()}
@@ -1,13 +1,5 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { NullFormatter } from './NullFormatter.js';
import { CheckboxIndicator, clsx, Focusable } from '@dbeaver/ui-kit';
import { Checkbox, clsx, Focusable } from '@dbeaver/ui-kit';
interface Props {
value: boolean | null;
@@ -17,7 +9,7 @@ interface Props {
onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
}
export function BooleanFormatter({ value, className, onClick, onKeyDown, focusable }: Props): React.ReactElement {
export function BooleanFormatter({ value, className, onClick, onKeyDown, focusable }: Props) {
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
if (event.code === 'Enter' || event.code === 'Space') {
event.preventDefault();
@@ -27,11 +19,11 @@ export function BooleanFormatter({ value, className, onClick, onKeyDown, focusab
return (
<Focusable
focusable={focusable}
className={clsx('tw:flex tw:items-center tw:outline-none! tw:hover:cursor-pointer', className)}
className={clsx('tw:flex tw:items-center tw:outline-none tw:hover:cursor-pointer', className)}
onKeyDown={handleKeyDown}
onClick={onClick}
>
{value === null ? <NullFormatter /> : <CheckboxIndicator size="small" checked={value} />}
{value === null ? <NullFormatter /> : <Checkbox className="tw:data-disabled:opacity-100!" disabled size="small" checked={value} />}
</Focusable>
);
}
@@ -0,0 +1,14 @@
import { clsx } from '@dbeaver/ui-kit';
interface Props {
className?: string;
onClick?: React.MouseEventHandler<HTMLElement>;
}
export function NullFormatter({ className, onClick }: Props) {
return (
<span className={clsx('tw:uppercase', 'tw:opacity-65', className)} onClick={onClick}>
[NULL]
</span>
);
}
@@ -1,23 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export * from './DataGrid.js';
export * from './DataGridRowContext.js';
export * from './DataGridCellContext.js';
export * from './DataGridCellInnerContext.js';
export * from './IGridReactiveValue.js';
export * from './useCreateGridReactiveValue.js';
export * from './useGridReactiveValue.js';
export * from './reorderArray.js';
export * from './formatters/NullFormatter.js';
export * from './formatters/BooleanFormatter.js';
export * from './formatters/NumberFormatter.js';
export * from './formatters/DateFormatter.js';
export * from './formatters/BlobFormatter.js';
export * from './search/index.js';
@@ -1,18 +1,9 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { memo, use } from 'react';
import { useDrag, useDrop, type DnDStoreProvider } from '@dbeaver/react-dnd';
import { DataGridCellHeaderContext } from '../DataGridHeaderCellContext.js';
import { useGridReactiveValue } from '../useGridReactiveValue.js';
import { HeaderDnDContext } from '../useHeaderDnD.js';
import { OrderButton } from './OrderButton.js';
import type { DataGridCellKeyboardEvent } from '../DataGrid.js';
interface Props {
colIdx: number;
@@ -64,15 +55,13 @@ export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer
});
function handleSort(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) {
if (!onColumnSort) {
return;
}
if (!onColumnSort) return;
const nextSortState = sortingState === 'asc' ? 'desc' : sortingState === 'desc' ? null : 'asc';
onColumnSort(colIdx, nextSortState, e.ctrlKey || e.metaKey);
}
function onKeyDown(event: DataGridCellKeyboardEvent) {
function onKeyDown(event: React.KeyboardEvent<HTMLElement>) {
onHeaderKeyDown?.(event);
if ((event.key === 'Enter' || event.key === ' ') && isColumnSortable && onColumnSort) {
@@ -81,20 +70,16 @@ export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer
}
}
const ownsFocus = isColumnSortable || !headerElement;
return (
<div
tabIndex={ownsFocus ? tabIndex : -1}
className="tw:w-full tw:h-full tw:content-center tw:flex tw:items-center tw:justify-between tw:gap-1 tw:outline-none! tw:group"
onKeyDown={onKeyDown}
tabIndex={tabIndex}
className="tw:w-full tw:h-full tw:content-center tw:flex tw:items-center tw:justify-between tw:gap-1 tw:outline-none tw:group"
{...drag.props}
{...drop.props}
onKeyDown={onKeyDown}
>
<span className="tw:overflow-hidden tw:text-ellipsis tw:w-full">{headerElement ?? getHeaderText ?? ''}</span>
{isColumnSortable && onColumnSort && (
<OrderButton sortState={sortingState} columnSortingMultiple={cellHeaderContext.columnSortingMultiple} onClick={handleSort} />
)}
<span className="tw:overflow-hidden tw:h-full tw:text-ellipsis">{headerElement ?? getHeaderText ?? ''}</span>
{isColumnSortable && onColumnSort && <OrderButton sortState={sortingState} onClick={handleSort} />}
</div>
);
});
@@ -1,43 +1,30 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { useTranslate } from '@dbeaver/react-translate';
import { clsx, IconButton, Icon } from '@dbeaver/ui-kit';
interface OrderButtonProps {
sortState?: 'asc' | 'desc' | null;
onClick: (e: React.MouseEvent<HTMLElement>) => void;
tabIndex?: number;
columnSortingMultiple?: boolean;
}
export function OrderButton({ sortState, onClick, tabIndex, columnSortingMultiple }: OrderButtonProps) {
export function OrderButton({ sortState, onClick, tabIndex }: OrderButtonProps) {
const svgSortAsc = 'sort-asc';
const svgSortDesc = 'sort-desc';
const svgSortUnknown = 'sort-unknown';
const translate = useTranslate();
const iconSrc = sortState === 'asc' ? svgSortAsc : sortState === 'desc' ? svgSortDesc : svgSortUnknown;
const titleToken = columnSortingMultiple ? 'react_data_grid_order_button_multiple' : 'react_data_grid_order_button';
const titleDefault = columnSortingMultiple ? 'Click to sort. Hold CTRL/CMD + click for multi-sort' : 'Sort By Column';
return (
<IconButton
variant="secondary"
size="small"
onClick={onClick}
tabIndex={tabIndex}
title={translate(titleToken, titleDefault)}
aria-label={translate(titleToken, titleDefault)}
title="Sort by column"
aria-label="Sort by column"
className={clsx(
'tw:opacity-0 tw:group-focus:opacity-100 tw:focus:opacity-100 tw:group-hover:opacity-100 tw:hover:opacity-100 tw:outline-offset-0',
sortState && 'tw:opacity-100',
)}
onClick={onClick}
>
<Icon name={iconSrc} />
</IconButton>
@@ -0,0 +1,11 @@
export function reorderArray<T>(array: T[], from: number, to: number): T[] {
const newArray = [...array];
if (from < to) {
newArray.splice(to, 0, array[from]!);
newArray.splice(from, 1);
} else {
newArray.splice(from, 1);
newArray.splice(to, 0, array[from]!);
}
return newArray;
}
@@ -1,15 +1,7 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { Column, ColumnOrColumnGroup } from 'react-data-grid';
import type { ColumnOrColumnGroup } from 'react-data-grid';
import type { IInnerRow } from './IInnerRow.js';
import { createContext, useId, useMemo, useState } from 'react';
import { reorderArray } from '@dbeaver/js-helpers';
import { reorderArray } from './reorderArray.js';
interface IHeaderDnDOptions {
columns: Array<ColumnOrColumnGroup<IInnerRow, unknown>>;
@@ -23,7 +15,6 @@ interface IHeaderDnD {
columns: Array<ColumnOrColumnGroup<IInnerRow, unknown>>;
getDataColIdx: (virtualColIdx: number) => number;
getVirtualColIdx: (dataColIdx: number) => number;
getDataColIdxByKey: (key: string) => number;
getCanDrag?: (colIdx: number) => boolean;
onDragOver: (dragColIdx: number, dropColIdx: number, isAfter: boolean) => void;
onDragEnd: (colIdx: number) => void;
@@ -31,25 +22,11 @@ interface IHeaderDnD {
export const HeaderDnDContext = createContext<IHeaderDnD | null>(null);
export function isColumn(column: ColumnOrColumnGroup<IInnerRow, unknown>): column is Column<IInnerRow, unknown> {
return (column as Column<IInnerRow, unknown>).key !== undefined;
}
export function useHeaderDnD({ columns, getCanDrag, onReorder, getHeaderOrder }: IHeaderDnDOptions): IHeaderDnD {
const id = useId();
const [activeDnDElement, setActiveDnDElement] = useState<number | null>(null);
const [dragOverElement, setDragOverElement] = useState<[number, boolean] | null>(null);
function getDataColIdxByKey(key: string) {
const columnIndex = columns.findIndex(col => isColumn(col) && col.key === key);
if (columnIndex === -1) {
throw new Error(`Column with key '${key}' not found`);
}
return getDataColIdx(columnIndex);
}
function getDataColIdx(virtualColIdx: number) {
// Given a visible (ordered) column index, return the original data column index
const order = getHeaderOrder?.();
@@ -109,7 +86,6 @@ export function useHeaderDnD({ columns, getCanDrag, onReorder, getHeaderOrder }:
id,
columns,
getDataColIdx,
getDataColIdxByKey,
getVirtualColIdx,
getCanDrag,
onDragOver,
@@ -14,15 +14,9 @@
"src"
],
"references": [
{
"path": "../../../common-typescript/@dbeaver/js-helpers"
},
{
"path": "../react-dnd"
},
{
"path": "../react-translate"
},
{
"path": "../ui-kit"
}
@@ -23,13 +23,13 @@
"!lib/tests",
"!.tsbuildinfo"
],
"packageManager": "yarn@4.14.1",
"packageManager": "yarn@4.9.2",
"devDependencies": {
"@dbeaver/tsconfig": "workspace:^",
"@types/react": "^19",
"rimraf": "^6",
"typescript": "^5",
"vitest": "^4"
"vitest": "^3"
},
"dependencies": {
"react": "^19"
@@ -23,7 +23,7 @@
"!lib/tests",
"!.tsbuildinfo"
],
"packageManager": "yarn@4.14.1",
"packageManager": "yarn@4.9.2",
"devDependencies": {
"@dbeaver/cli": "workspace:^",
"@dbeaver/tsconfig": "workspace:^",
@@ -31,10 +31,8 @@
"@testing-library/jest-dom": "^6",
"@testing-library/react": "^16",
"@types/node": "^22",
"@types/ws": "^8",
"rimraf": "^6",
"typescript": "^5",
"vitest": "^4",
"ws": "^8"
"vitest": "^3"
}
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@ export default mergeConfig(
setupFiles: [posix.resolve('./vitest.setup.js')],
alias: [
{
find: /.*.(css)$/,
find: /.*.(css|scss|less)$/,
replacement: import.meta.resolve('./__mocks__/styleMock.js'),
},
],
@@ -1,14 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { WebSocket } from 'ws';
import '@testing-library/jest-dom/vitest';
// different machine has its own timezones and some tests can fail because of it
process.env['TZ'] = 'UTC';
globalThis.WebSocket = WebSocket as any;
process.env.TZ = 'UTC';
@@ -1,6 +1,6 @@
import '../src/index.css';
import './global.css';
import Icons from '../assets/icons/preload/icons.svg?react';
import Icons from '../assets/icons/icons.svg?react';
import type { GlobalProvider } from '@ladle/react';

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 540 B

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 546 B

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 535 B

Before

Width:  |  Height:  |  Size: 532 B

After

Width:  |  Height:  |  Size: 532 B

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 546 B

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 530 B

Before

Width:  |  Height:  |  Size: 856 B

After

Width:  |  Height:  |  Size: 856 B

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 832 B

Before

Width:  |  Height:  |  Size: 828 B

After

Width:  |  Height:  |  Size: 828 B

Before

Width:  |  Height:  |  Size: 353 B

After

Width:  |  Height:  |  Size: 353 B

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 346 B

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 355 B

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 418 B

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 350 B

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

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