Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
438475a99c | ||
|
|
f967a20edf |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,9 +38,6 @@ 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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -19,26 +19,6 @@
|
||||
"group": "test",
|
||||
"args": ["test"]
|
||||
},
|
||||
{
|
||||
"label": "Run Tests Common Typescript",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"group": "test",
|
||||
"args": ["test"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/common-typescript"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Tests Common React",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"group": "test",
|
||||
"args": ["test"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/common-react"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run DevServer CE",
|
||||
"group": "build",
|
||||
@@ -92,10 +72,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"
|
||||
@@ -127,24 +107,6 @@
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Ui Kit Preview",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/common-react/@dbeaver/ui-kit"
|
||||
},
|
||||
"args": ["docs"]
|
||||
},
|
||||
{
|
||||
"label": "Run Storybook",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/packages/storybook"
|
||||
},
|
||||
"args": ["storybook"]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -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,136 +19,80 @@ 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.1.3 2026-07-20
|
||||
### 25.0.5 2025-05-19
|
||||
- Added the ability to configure the default database or schema in the connection configuration;
|
||||
- A new setting to autosave scripts in the SQL editor appeared in global and users' preferences;
|
||||
- SQLite: added information about a table Strict mode to the table metadata section (thanks to @eusebe-cda);
|
||||
- Vietnamese localization has been added (thanks to @0xhanh).
|
||||
|
||||
- Data Editor:
|
||||
- Fixed copying of large JSON values in the Data Editor - now the full value can be copied and transferred.
|
||||
- Updated the Reference panel to show only relevant rows when a foreign key is null.
|
||||
- General:
|
||||
- Added connection types to visually distinguish database environments using background colors across the Database Navigator and application UI. Users with edit permissions can assign built-in environment types color-coded configurations.
|
||||
- Data Transfer:
|
||||
- Improved memory usage when importing large CSV files.
|
||||
- Fixed CSV export with multi-character quotes and improved quote/delimiter validation
|
||||
- Accessibility:
|
||||
- Enabled the use of the Enter or space keys to toggle checkboxes in the administration tabs and the metadata viewer.
|
||||
- Added the ability to expand and collapse code block elements, including JSON, using the keyboard.
|
||||
- Fixed shortcuts behavior for AZERTY keyboards. Now shortcuts like Ctrl/Cmd + Z work correctly.
|
||||
- Security:
|
||||
- Resolved an authorization vulnerability when testing connections by enforcing strict permission checks. This mitigation prevented unauthenticated users from exploiting JDBC requests to read arbitrary server files.
|
||||
- Resolved an LDAP injection vulnerability by properly escaping user input in the authentication username field, preventing attackers from manipulating search filters, bypassing access restrictions, and enumerating directory users.
|
||||
- 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.
|
||||
### 25.0.4 2025-05-05
|
||||
- Important:
|
||||
- Connection templates were removed from the application. You can use the Connection Editor to create new connections.
|
||||
- Databases and drivers:
|
||||
- The Databend driver was updated to version 0.4.8.
|
||||
- Fixed an issue related to the display of metadata in the navigation tree for DB2, Firebird, and MySQL.
|
||||
- LibSQL: added the ability to use token authentication for connecting;
|
||||
- Clickhouse: driver has been updated to version 0.8.5.
|
||||
|
||||
### 26.1.2 2026-07-06
|
||||
### 25.0.3 2025-04-21
|
||||
- Administration: log records now capture create, update, and delete actions for users, teams, and connections for improved tracking and transparency;
|
||||
- SQL Editor: auto-completion has been improved for aliases and camelCase entities;
|
||||
- Users DDL in Oracle is displayed in metadata editor on corresponding tab for users with Oracle administration permissions;
|
||||
- Different bug fixes and enhancements have been made.
|
||||
|
||||
- Data Editor:
|
||||
- Added the References panel showing related records from other tables connected by foreign keys. It helps to explore table relationships directly in the Data editor.
|
||||
- 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.
|
||||
### 25.0.2 2025-04-07
|
||||
- Data Editor: improved table rendering to scale column width depending on the content.
|
||||
- Improved Navigation tree performance: lost connection does not lead to the application freezing anymore.
|
||||
- Different bug fixes and enhancements have been made.
|
||||
|
||||
### 25.0.1 2025-03-24
|
||||
- Improved performance of the export/import functionality by optimizing disk memory consumption;
|
||||
- Enhanced initial configuration on stage security: if the initial setup time exceeds 1 hour, the server must be restarted.
|
||||
|
||||
### 25.0 2025-03-03
|
||||
### Changes since 24.3.0:
|
||||
- General:
|
||||
- Added the Last Login time to the user form and the user table in the administration part.
|
||||
- Fixed the bug with the inability to select entered text in the Find and Replace panel in editors.
|
||||
- Accessibility:
|
||||
- Added the ability to use the Enter or Space key to turn checkboxes on/off in the connection properties tab.
|
||||
- Security:
|
||||
- Fixed CVE-2026-55449. Resolved a local file disclosure vulnerability by enforcing strict validation of file identifiers and path containment during data updates.
|
||||
- Updated session handling to prevent session fixation vulnerabilities. The application generates new session identifiers upon login.
|
||||
- Fixed the high vulnerability (CVE-2026-12151) in the undici library. The library was updated to version 6.27.0.
|
||||
|
||||
### 26.1.1 2026-06-22
|
||||
|
||||
- General: Added a "Project Info" tab containing the project description to the project context menu.
|
||||
- Security:
|
||||
- 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.
|
||||
- Fixed the critical vulnerability (CVE-2026-9277) in the shell-quote library (updated to version 1.8.4).
|
||||
|
||||
### 26.1.0 2026-06-01
|
||||
|
||||
### Changes since 26.0.0:
|
||||
|
||||
- Added transaction log viewing for connections with manual commit mode. This allows users to see all modifying statements before committing;
|
||||
- Added an ability for all editors to reconnect if a connection has been lost;
|
||||
- Tooltips for metadata objects and SQL Editor tabs now include additional details about catalogs, schemas, and connections;
|
||||
- Added Ctrl + Shift + S (Cmd + Shift + S on Mac) shortcut for the "Save As Script" action;
|
||||
- Updated list of available shortcuts for macOS.
|
||||
- Administration:
|
||||
- Added support for custom startup arguments when running CloudBeaver in a container. Administrators can specify configuration file locations to adapt the application to their environment. (thanks to @MalteHei)
|
||||
- Updated the manifest.webmanifest file to generate dynamically based on the CLOUDBEAVER_ROOT_URI environment variable. (thanks to @houssemexo26)
|
||||
- Authorization:
|
||||
- Added HTTP header authentication support for GraphQL queries. Users can provide API tokens via Authorization or X-Api-Key headers, allowing external data tools to authenticate without modifying request bodies.
|
||||
- Fixed a bug in the authentication form: the existing password is now required for password changes.
|
||||
- SQL Editor:
|
||||
- Updated the SQL Editor to use real tab characters for indentation. The application displayed tabs as four-space gaps with a distinct visual marker.
|
||||
- Added an ability to view and edit scripts without connecting to the database.
|
||||
- Added the ability to open generated queries right from the Generate SQL window.
|
||||
- Added fuzzy search to SQL Editor auto-completion for object names. The feature allows users to find tables and containers even with typos or incorrect letter ordering.
|
||||
- Added the tooltip about quoting rules for the Bind variables parameters dialog.
|
||||
- Fixed keyword autocompletion for partially typed keywords to avoid redundant letters.
|
||||
- Added system information data to the Product Information tab in the Administration panel. This provides administrators with quick access to essential system details, such as the server deployment type (e.g., Docker, Kubernetes), install path, Java version, and Java parameters;
|
||||
- The global access server URL has been added to the console log, making it easier to locate server installation information directly within the logs;
|
||||
- New environment variables were introduced for theme styling, SQL editor, and log viewer settings. You can use them for quick setup during the initial Docker configuration stage.
|
||||
- LDAP authorization:
|
||||
- Added the ability to match users from LDAP configuration with CloudBeaver users;
|
||||
- Added an option to specify a user login as an attribute parameter for LDAP providers;
|
||||
- CloudBeaver now uses EntityID or objectGUID to create new LDAP users in CloudBeaver to ensure uniqueness. This change enhances user identification and prevents conflicts while maintaining backward compatibility for existing users.
|
||||
- Deployment:
|
||||
- CloudBeaver now uses JDK 21 as its base Java image. If you use standard deployment and upgrade scripts, the new Java version will be applied automatically after the product update;
|
||||
- Changed the permission of the Docker volumes directory to enhance security and prevent any insecure actions by the "root" user. All new deployments will use the "dbeaver" user by default. For existing deployments running as the "root" user, this update will automatically adjust file permissions to ensure they are compatible with the new security model. There will be no manual intervention, and the service will continue to run as expected with the updated permissions.
|
||||
- Connections:
|
||||
- Enabled the possibility of switching a database connection to read-only mode. This mode can be activated in the connection dialog. Administrators can set this mode for any connection, while regular users can set it for their private connections only;
|
||||
- For Oracle and SQL Server connections, advanced database settings can now be used in the URL connection mode.
|
||||
- Data Editor:
|
||||
- Added the ability to copy-paste multiple cells at once. Pasted values will be distributed across selected cells.
|
||||
- Added an ability to add, duplicate, delete, or set values to NULL in multiple highlighted rows via the Data Editor context menu.
|
||||
- Added the Find and Replace functionality for the Data Editor with the ability to find data by matching case, whole word, or using regular expressions.
|
||||
- Data Editor started to keep the state of column configurations, such as filters, sorting, and ordering, after the reconnect, page refresh, and re-login.
|
||||
- Added key column mark for tables with unique keys in the data grid.
|
||||
- Added more settings to the Generated SQL window, including the ability to select fully qualified names for query or use compact SQL formatting.
|
||||
- Added a DDL generation option to the Data Editor and Result Set context menu.
|
||||
- Fixed copy and paste behavior for multiline and special characters in the Data Editor. Added visual glyphs to indicate line breaks and tabs in the data grid.
|
||||
- Fixed the ability to copy Boolean values in the clipboard in the Data Editor.
|
||||
- Fixed the ability to calculate row count for non-standard SELECT statements. (thanks to @fdcastel)
|
||||
- Navigator tree:
|
||||
- Reorganized the context menu on the connection level to make it more compact.
|
||||
- Added support for special symbols (pipe, comma, and asterisk) for the search field.
|
||||
- Removed the "Folders" setting from the settings list for the Navigator Tree.
|
||||
- Accessibility:
|
||||
- Added the Skip to content option for quick keyboard access to the Navigator Tree, editors, and shortcuts list tab to improve application accessibility.
|
||||
- Improved keyboard navigation for context menu and buttons for Data Editor, SQL Editor, and Navigator tree.
|
||||
- Added the Ctrl + / keyboard shortcut to open the context menu for selected cells in the Data Editor.
|
||||
- Fixed the ability to switch tabs in the Data Editor using the keyboard.
|
||||
- Fixed contrast for elements across different application parts in the light and dark themes to meet WCAG requirements.
|
||||
- General:
|
||||
- Marked icons for non-saved (temporary) scripts with the hourglass symbol to add visual distinction from the saved ones.
|
||||
- Added the ability to set to NULL any value in the Driver Properties.
|
||||
- Fixed custom driver properties display for the connection page.
|
||||
- Fixed data export in SQL format for values containing single quotes.
|
||||
- Databases and drivers:
|
||||
- ClickHouse:
|
||||
- Fixed the display of Array(JSON) types in the data grid.
|
||||
- Updated driver version to 0.9.7.
|
||||
- Fixed IP address display in the Data Editor by removing the leading slash.
|
||||
- Added support for the "prompt=false" connection property for DB2 for IBM i to turn off interactive prompts and prevent related errors.
|
||||
- DuckDB:
|
||||
- Fixed map rendering for GEOMETRY types with CRS parameters.
|
||||
- Fixed geometry type display for database versions > 1.5.
|
||||
- H2 database:
|
||||
- Added new embedded driver version 2.4.2.
|
||||
- Updated server driver to version 2.4.2.
|
||||
- Firebird: Expanded database-specific SQL dialect coverage for the SQL Editor. Added more keywords, built-in functions, and SQL generators.
|
||||
- MySQL: Fixed query boundaries recognition in the SQL Editor for scripts with the DELIMITER keyword.
|
||||
- PostgreSQL:
|
||||
- Updated driver to version 42.7.11
|
||||
- Fixed missing INOUT parameters in CHECK generation for procedures and functions.
|
||||
- Fixed handling of 24:xx values in time columns.
|
||||
- Fixed an error when connecting via URL if the database name contains a hyphen.
|
||||
- SQL Server:
|
||||
- Fixed DDL generation for external tables.
|
||||
- Fixed the database connectivity issue after the Java update.
|
||||
- Security:
|
||||
- Added an administrative setting to restrict SSH tunneling capabilities. Administrators can now limit tunnel configuration to authorized users, reducing the risk of unauthorized network access.
|
||||
- Fixed the critical vulnerability (CVE-2025-62718) in the axios library. The library was updated to version 1.15.0.
|
||||
- Fixed the high vulnerability (CVE-2026-4800) in the lodash library. The library was updated to version 4.18.0.
|
||||
- Fixed the high vulnerability (CVE-2026-39363) in the vite library. The library was updated to version 7.3.2.
|
||||
- Fixed the high vulnerability (CVE-2026-42035) in the axios library. The library was updated to version 1.15.1.
|
||||
- Fixed the high vulnerability (CVE-2026-3505) in the bcpg-jdk18on library. The library was updated to version 1.84.0.
|
||||
- Fixed the high vulnerability (CVE-2026-33671) in the picomatch library. The library was updated to version 2.3.2.
|
||||
- Fixed the high vulnerability (CVE-2026-33943) in the happy-dom library. The library was updated to version 20.8.8.
|
||||
- Fixed the high vulnerability (CVE-2025-45141) in the tar library. The library was updated to version 7.5.11.
|
||||
- Fixed the high vulnerability (CVE-2026-27148) in the storybook library. The library was updated to version 10.2.10.
|
||||
- Fixed the high vulnerability (CVE-2026-27606) in the rollup library. The library was updated to version 4.59.0.
|
||||
- Fixed the high vulnerability (CVE-2026-27904) in the minimatch library. The library was updated to version 3.1.3.
|
||||
- Fixed the high vulnerability (CVE-2026-27959) in the koa library. The library was updated to version 2.16.4.
|
||||
- Fixed the high vulnerability (CVE-2026-32141) in the flatted library. The library was updated to version 4.4.0.
|
||||
- Fixed the medium vulnerability (CVE-2026-41305) in the postcss library. The library was updated to version 8.5.14.
|
||||
- Auto-suggestions for column names in the Data Editor filter field were introduced to avoid typing mistakes;
|
||||
- Introduced the ability to insert data in tables without unique keys: you can import data from CSV files or insert entire rows directly via UI;
|
||||
- The collapse of the grouping panel doesn't lead to the complete panel cleaning anymore.
|
||||
- Data transfer:
|
||||
- Added the ability to use default spreadsheet names (e.g. Sheet0, Sheet1) for exporting to XLSX;
|
||||
- An option to replace line break characters on any custom value was added when exporting to CSV.
|
||||
- Navigator tree:
|
||||
- An option to show metadata object comments was added. You can enable it in the database navigator settings panel;
|
||||
- Introduced the ability to create connections not only on the initial level but in folders and sub-folders directly;
|
||||
- Fixed the display of objects with long names (more than 32 characters) - now, such names will be displayed entirely and not in a cropped format.
|
||||
- Drivers:
|
||||
- The MariaDB driver has been updated to version 3.5.1;
|
||||
- The SQLite driver has been updated to version 3.48.0.0.
|
||||
|
||||
## 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 24.3.0 was made by:
|
||||
1. [cashlifei](https://github.com/cashlifei)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.13.2</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"
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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`));
|
||||
@@ -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`));
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.5.3-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
|
||||
@@ -0,0 +1,490 @@
|
||||
import { Context } from 'react';
|
||||
import { JSX } from 'react/jsx-runtime';
|
||||
import type { Key } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
declare interface BaseRenderRowProps<TRow, TSummaryRow = unknown> extends Omit_2<React.ComponentProps<'div'>, 'style' | 'children'>, Pick<DataGridProps<TRow, TSummaryRow>, 'onCellClick' | 'onCellDoubleClick' | 'onCellContextMenu'> {
|
||||
viewportColumns: readonly CalculatedColumn<TRow, TSummaryRow>[];
|
||||
rowIdx: number;
|
||||
selectedCellIdx: number | undefined;
|
||||
isRowSelectionDisabled: boolean;
|
||||
isRowSelected: boolean;
|
||||
gridRowStart: number;
|
||||
selectCell: (position: Position, enableEditor?: Maybe<boolean>) => void;
|
||||
}
|
||||
|
||||
export declare interface CalculatedColumn<TRow, TSummaryRow = unknown> extends Column<TRow, TSummaryRow> {
|
||||
readonly parent: CalculatedColumnParent<TRow, TSummaryRow> | undefined;
|
||||
readonly idx: number;
|
||||
readonly level: number;
|
||||
readonly width: number | string;
|
||||
readonly minWidth: number;
|
||||
readonly maxWidth: number | undefined;
|
||||
readonly resizable: boolean;
|
||||
readonly sortable: boolean;
|
||||
readonly draggable: boolean;
|
||||
readonly frozen: boolean;
|
||||
readonly renderCell: (props: RenderCellProps<TRow, TSummaryRow>) => ReactNode;
|
||||
readonly renderHeaderCell: (props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode;
|
||||
}
|
||||
|
||||
export declare type CalculatedColumnOrColumnGroup<R, SR> = CalculatedColumnParent<R, SR> | CalculatedColumn<R, SR>;
|
||||
|
||||
export declare interface CalculatedColumnParent<R, SR> {
|
||||
readonly name: string | ReactElement;
|
||||
readonly parent: CalculatedColumnParent<R, SR> | undefined;
|
||||
readonly idx: number;
|
||||
readonly colSpan: number;
|
||||
readonly level: number;
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
}
|
||||
|
||||
export declare const Cell: <R, SR>(props: CellRendererProps<R, SR>) => React.JSX.Element;
|
||||
|
||||
export declare interface CellClickArgs<TRow, TSummaryRow = unknown> {
|
||||
rowIdx: number;
|
||||
row: TRow;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
selectCell: (enableEditor?: boolean) => void;
|
||||
}
|
||||
|
||||
declare type CellEvent<E extends React.SyntheticEvent<HTMLDivElement>> = E & {
|
||||
preventGridDefault: () => void;
|
||||
isGridDefaultPrevented: () => boolean;
|
||||
};
|
||||
|
||||
export declare type CellKeyboardEvent = CellEvent<React.KeyboardEvent<HTMLDivElement>>;
|
||||
|
||||
export declare type CellKeyDownArgs<TRow, TSummaryRow = unknown> = SelectCellKeyDownArgs<TRow, TSummaryRow> | EditCellKeyDownArgs<TRow, TSummaryRow>;
|
||||
|
||||
export declare type CellMouseEvent = CellEvent<React.MouseEvent<HTMLDivElement>>;
|
||||
|
||||
export declare interface CellRendererProps<TRow, TSummaryRow> extends Pick<RenderRowProps<TRow, TSummaryRow>, 'row' | 'rowIdx' | 'selectCell'>, Omit_2<React.ComponentProps<'div'>, 'children' | 'onClick' | 'onDoubleClick' | 'onContextMenu'> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
colSpan: number | undefined;
|
||||
isCopied: boolean;
|
||||
isDraggedOver: boolean;
|
||||
isCellSelected: boolean;
|
||||
onClick: RenderRowProps<TRow, TSummaryRow>['onCellClick'];
|
||||
onDoubleClick: RenderRowProps<TRow, TSummaryRow>['onCellDoubleClick'];
|
||||
onContextMenu: RenderRowProps<TRow, TSummaryRow>['onCellContextMenu'];
|
||||
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, newRow: TRow) => void;
|
||||
}
|
||||
|
||||
export declare interface CellSelectArgs<TRow, TSummaryRow = unknown> {
|
||||
rowIdx: number;
|
||||
row: TRow | undefined;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
}
|
||||
|
||||
export declare type ColSpanArgs<TRow, TSummaryRow> = {
|
||||
type: 'HEADER';
|
||||
} | {
|
||||
type: 'ROW';
|
||||
row: TRow;
|
||||
} | {
|
||||
type: 'SUMMARY';
|
||||
row: TSummaryRow;
|
||||
};
|
||||
|
||||
export declare interface Column<TRow, TSummaryRow = unknown> {
|
||||
/** The name of the column. By default it will be displayed in the header cell */
|
||||
readonly name: string | ReactElement;
|
||||
/** A unique key to distinguish each column */
|
||||
readonly key: string;
|
||||
/**
|
||||
* Column width. If not specified, it will be determined automatically based on grid width and specified widths of other columns
|
||||
* @default 'auto'
|
||||
*/
|
||||
readonly width?: Maybe<number | string>;
|
||||
/**
|
||||
* Minimum column width in px
|
||||
* @default '50px'
|
||||
*/
|
||||
readonly minWidth?: Maybe<number>;
|
||||
/** Maximum column width in px. */
|
||||
readonly maxWidth?: Maybe<number>;
|
||||
readonly cellClass?: Maybe<string | ((row: TRow) => Maybe<string>)>;
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
readonly summaryCellClass?: Maybe<string | ((row: TSummaryRow) => Maybe<string>)>;
|
||||
/** Render function used to render the content of cells */
|
||||
readonly renderCell?: Maybe<(props: RenderCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function used to render the content of the column's header cell */
|
||||
readonly renderHeaderCell?: Maybe<(props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function used to render the content of summary cells */
|
||||
readonly renderSummaryCell?: Maybe<(props: RenderSummaryCellProps<TSummaryRow, TRow>) => ReactNode>;
|
||||
/** Render function used to render the content of group cells */
|
||||
readonly renderGroupCell?: Maybe<(props: RenderGroupCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function used to render the content of edit cells. When set, the column is automatically set to be editable */
|
||||
readonly renderEditCell?: Maybe<(props: RenderEditCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Enables cell editing. If set and no editor property specified, then a textinput will be used as the cell editor */
|
||||
readonly editable?: Maybe<boolean | ((row: TRow) => boolean)>;
|
||||
readonly colSpan?: Maybe<(args: ColSpanArgs<TRow, TSummaryRow>) => Maybe<number>>;
|
||||
/** Determines whether column is frozen or not */
|
||||
readonly frozen?: Maybe<boolean>;
|
||||
/** Enable resizing of a column */
|
||||
readonly resizable?: Maybe<boolean>;
|
||||
/** Enable sorting of a column */
|
||||
readonly sortable?: Maybe<boolean>;
|
||||
/** Enable dragging of a column */
|
||||
readonly draggable?: Maybe<boolean>;
|
||||
/** Sets the column sort order to be descending instead of ascending the first time the column is sorted */
|
||||
readonly sortDescendingFirst?: Maybe<boolean>;
|
||||
readonly editorOptions?: Maybe<{
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
readonly displayCellContent?: Maybe<boolean>;
|
||||
/** @default true */
|
||||
readonly commitOnOutsideClick?: Maybe<boolean>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export declare interface ColumnGroup<R, SR = unknown> {
|
||||
/** The name of the column group, it will be displayed in the header cell */
|
||||
readonly name: string | ReactElement;
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
readonly children: readonly ColumnOrColumnGroup<R, SR>[];
|
||||
}
|
||||
|
||||
export declare type ColumnOrColumnGroup<R, SR = unknown> = Column<R, SR> | ColumnGroup<R, SR>;
|
||||
|
||||
export declare interface CopyEvent<TRow> {
|
||||
sourceColumnKey: string;
|
||||
sourceRow: TRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main API Component to render a data grid of rows and columns
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* <DataGrid columns={columns} rows={rows} />
|
||||
*/
|
||||
export declare function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridProps<R, SR, K>): JSX.Element;
|
||||
|
||||
export declare const DataGridDefaultRenderersContext: Context<Maybe<Renderers<any, any>>>;
|
||||
|
||||
export declare interface DataGridHandle {
|
||||
element: HTMLDivElement | null;
|
||||
scrollToCell: (position: PartialPosition) => void;
|
||||
selectCell: (position: Position, enableEditor?: Maybe<boolean>) => void;
|
||||
}
|
||||
|
||||
export declare interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivProps {
|
||||
ref?: Maybe<React.Ref<DataGridHandle>>;
|
||||
/**
|
||||
* Grid and data Props
|
||||
*/
|
||||
/** An array of objects representing each column on the grid */
|
||||
columns: readonly ColumnOrColumnGroup<NoInfer<R>, NoInfer<SR>>[];
|
||||
/** A function called for each rendered row that should return a plain key/value pair object */
|
||||
rows: readonly R[];
|
||||
/**
|
||||
* Rows to be pinned at the top of the rows view for summary, the vertical scroll bar will not scroll these rows.
|
||||
*/
|
||||
topSummaryRows?: Maybe<readonly SR[]>;
|
||||
/**
|
||||
* Rows to be pinned at the bottom of the rows view for summary, the vertical scroll bar will not scroll these rows.
|
||||
*/
|
||||
bottomSummaryRows?: Maybe<readonly SR[]>;
|
||||
/** The getter should return a unique key for each row */
|
||||
rowKeyGetter?: Maybe<(row: NoInfer<R>) => K>;
|
||||
onRowsChange?: Maybe<(rows: NoInfer<R>[], data: RowsChangeData<NoInfer<R>, NoInfer<SR>>) => void>;
|
||||
/**
|
||||
* Dimensions props
|
||||
*/
|
||||
/**
|
||||
* The height of each row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
rowHeight?: Maybe<number | ((row: NoInfer<R>) => number)>;
|
||||
/**
|
||||
* The height of the header row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
headerRowHeight?: Maybe<number>;
|
||||
/**
|
||||
* The height of each summary row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
summaryRowHeight?: Maybe<number>;
|
||||
/**
|
||||
* Feature props
|
||||
*/
|
||||
/** Set of selected row keys */
|
||||
selectedRows?: Maybe<ReadonlySet<K>>;
|
||||
/** Determines if row selection is disabled, per row */
|
||||
isRowSelectionDisabled?: Maybe<(row: NoInfer<R>) => boolean>;
|
||||
/** Function called whenever row selection is changed */
|
||||
onSelectedRowsChange?: Maybe<(selectedRows: Set<NoInfer<K>>) => void>;
|
||||
/** Used for multi column sorting */
|
||||
sortColumns?: Maybe<readonly SortColumn[]>;
|
||||
onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>;
|
||||
defaultColumnOptions?: Maybe<DefaultColumnOptions<NoInfer<R>, NoInfer<SR>>>;
|
||||
onFill?: Maybe<(event: FillEvent<NoInfer<R>>) => NoInfer<R>>;
|
||||
onCopy?: Maybe<(event: CopyEvent<NoInfer<R>>) => void>;
|
||||
onPaste?: Maybe<(event: PasteEvent<NoInfer<R>>) => NoInfer<R>>;
|
||||
/**
|
||||
* Event props
|
||||
*/
|
||||
/** Function called whenever a cell is clicked */
|
||||
onCellClick?: Maybe<(args: CellClickArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void>;
|
||||
/** Function called whenever a cell is double clicked */
|
||||
onCellDoubleClick?: Maybe<(args: CellClickArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void>;
|
||||
/** Function called whenever a cell is right clicked */
|
||||
onCellContextMenu?: Maybe<(args: CellClickArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void>;
|
||||
onCellKeyDown?: Maybe<(args: CellKeyDownArgs<NoInfer<R>, NoInfer<SR>>, event: CellKeyboardEvent) => void>;
|
||||
/** Function called whenever cell selection is changed */
|
||||
onSelectedCellChange?: Maybe<(args: CellSelectArgs<NoInfer<R>, NoInfer<SR>>) => void>;
|
||||
/** Called when the grid is scrolled */
|
||||
onScroll?: Maybe<(event: React.UIEvent<HTMLDivElement>) => void>;
|
||||
/** Called when a column is resized */
|
||||
onColumnResize?: Maybe<(column: CalculatedColumn<R, SR>, width: number) => void>;
|
||||
/** Called when a column is reordered */
|
||||
onColumnsReorder?: Maybe<(sourceColumnKey: string, targetColumnKey: string) => void>;
|
||||
/**
|
||||
* Toggles and modes
|
||||
*/
|
||||
/** @default true */
|
||||
enableVirtualization?: Maybe<boolean>;
|
||||
/**
|
||||
* The minimum number of rows to render when virtualization is enabled
|
||||
*/
|
||||
minimumRowsToRender?: Maybe<number>;
|
||||
/**
|
||||
* Miscellaneous
|
||||
*/
|
||||
renderers?: Maybe<Renderers<NoInfer<R>, NoInfer<SR>>>;
|
||||
rowClass?: Maybe<(row: NoInfer<R>, rowIdx: number) => Maybe<string>>;
|
||||
/** @default 'ltr' */
|
||||
direction?: Maybe<Direction>;
|
||||
'data-testid'?: Maybe<string>;
|
||||
'data-cy'?: Maybe<string>;
|
||||
}
|
||||
|
||||
export declare type DefaultColumnOptions<R, SR> = Pick<Column<R, SR>, 'renderCell' | 'renderHeaderCell' | 'width' | 'minWidth' | 'maxWidth' | 'resizable' | 'sortable' | 'draggable'>;
|
||||
|
||||
declare type Direction = 'ltr' | 'rtl';
|
||||
|
||||
declare interface EditCellKeyDownArgs<TRow, TSummaryRow = unknown> {
|
||||
mode: 'EDIT';
|
||||
row: TRow;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
rowIdx: number;
|
||||
navigate: () => void;
|
||||
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
|
||||
}
|
||||
|
||||
export declare interface FillEvent<TRow> {
|
||||
columnKey: string;
|
||||
sourceRow: TRow;
|
||||
targetRow: TRow;
|
||||
}
|
||||
|
||||
declare interface GroupRow<TRow> {
|
||||
readonly childRows: readonly TRow[];
|
||||
readonly id: string;
|
||||
readonly parentId: unknown;
|
||||
readonly groupKey: unknown;
|
||||
readonly isExpanded: boolean;
|
||||
readonly level: number;
|
||||
readonly posInSet: number;
|
||||
readonly setSize: number;
|
||||
readonly startRowIndex: number;
|
||||
}
|
||||
|
||||
declare type Maybe<T> = T | undefined | null;
|
||||
|
||||
declare type Omit_2<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
declare interface PartialPosition {
|
||||
readonly idx?: number | undefined;
|
||||
readonly rowIdx?: number | undefined;
|
||||
}
|
||||
|
||||
export declare interface PasteEvent<TRow> {
|
||||
sourceColumnKey: string;
|
||||
sourceRow: TRow;
|
||||
targetColumnKey: string;
|
||||
targetRow: TRow;
|
||||
}
|
||||
|
||||
declare interface Position {
|
||||
readonly idx: number;
|
||||
readonly rowIdx: number;
|
||||
}
|
||||
|
||||
export declare interface RenderCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
isCellEditable: boolean;
|
||||
tabIndex: number;
|
||||
onRowChange: (row: TRow) => void;
|
||||
}
|
||||
|
||||
export declare function renderCheckbox({ onChange, indeterminate, ...props }: RenderCheckboxProps): JSX.Element;
|
||||
|
||||
export declare interface RenderCheckboxProps extends Pick<React.ComponentProps<'input'>, 'aria-label' | 'aria-labelledby' | 'checked' | 'tabIndex' | 'disabled'> {
|
||||
indeterminate?: boolean | undefined;
|
||||
onChange: (checked: boolean, shift: boolean) => void;
|
||||
}
|
||||
|
||||
export declare interface RenderEditCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
onRowChange: (row: TRow, commitChanges?: boolean) => void;
|
||||
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
|
||||
}
|
||||
|
||||
export declare interface Renderers<TRow, TSummaryRow> {
|
||||
renderCell?: Maybe<(key: Key, props: CellRendererProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>;
|
||||
renderRow?: Maybe<(key: Key, props: RenderRowProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>;
|
||||
noRowsFallback?: Maybe<ReactNode>;
|
||||
}
|
||||
|
||||
export declare interface RenderGroupCellProps<TRow, TSummaryRow = unknown> {
|
||||
groupKey: unknown;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: GroupRow<TRow>;
|
||||
childRows: readonly TRow[];
|
||||
isExpanded: boolean;
|
||||
tabIndex: number;
|
||||
toggleGroup: () => void;
|
||||
}
|
||||
|
||||
export declare function renderHeaderCell<R, SR>({ column, sortDirection, priority }: RenderHeaderCellProps<R, SR>): string | JSX.Element;
|
||||
|
||||
export declare interface RenderHeaderCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
sortDirection: SortDirection | undefined;
|
||||
priority: number | undefined;
|
||||
tabIndex: number;
|
||||
}
|
||||
|
||||
export declare interface RenderRowProps<TRow, TSummaryRow = unknown> extends BaseRenderRowProps<TRow, TSummaryRow> {
|
||||
row: TRow;
|
||||
lastFrozenColumnIndex: number;
|
||||
copiedCellIdx: number | undefined;
|
||||
draggedOverCellIdx: number | undefined;
|
||||
selectedCellEditor: ReactElement<RenderEditCellProps<TRow>> | undefined;
|
||||
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, rowIdx: number, newRow: TRow) => void;
|
||||
rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe<string>>;
|
||||
setDraggedOverRowIdx: ((overRowIdx: number) => void) | undefined;
|
||||
}
|
||||
|
||||
export declare function renderSortIcon({ sortDirection }: RenderSortIconProps): JSX.Element | null;
|
||||
|
||||
export declare interface RenderSortIconProps {
|
||||
sortDirection: SortDirection | undefined;
|
||||
}
|
||||
|
||||
export declare function renderSortPriority({ priority }: RenderSortPriorityProps): number | undefined;
|
||||
|
||||
export declare interface RenderSortPriorityProps {
|
||||
priority: number | undefined;
|
||||
}
|
||||
|
||||
export declare interface RenderSortStatusProps extends RenderSortIconProps, RenderSortPriorityProps {
|
||||
}
|
||||
|
||||
export declare interface RenderSummaryCellProps<TSummaryRow, TRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TSummaryRow;
|
||||
tabIndex: number;
|
||||
}
|
||||
|
||||
export declare function renderToggleGroup<R, SR>(props: RenderGroupCellProps<R, SR>): JSX.Element;
|
||||
|
||||
export declare function renderValue<R, SR>(props: RenderCellProps<R, SR>): ReactNode;
|
||||
|
||||
export declare const Row: <R, SR>(props: RenderRowProps<R, SR>) => React.JSX.Element;
|
||||
|
||||
export declare type RowHeightArgs<TRow> = {
|
||||
type: 'ROW';
|
||||
row: TRow;
|
||||
} | {
|
||||
type: 'GROUP';
|
||||
row: GroupRow<TRow>;
|
||||
};
|
||||
|
||||
export declare interface RowsChangeData<R, SR = unknown> {
|
||||
indexes: number[];
|
||||
column: CalculatedColumn<R, SR>;
|
||||
}
|
||||
|
||||
export declare const SELECT_COLUMN_KEY = "rdg-select-column";
|
||||
|
||||
export declare function SelectCellFormatter({ value, tabIndex, indeterminate, disabled, onChange, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy }: SelectCellFormatterProps): ReactNode;
|
||||
|
||||
declare interface SelectCellFormatterProps extends SharedInputProps {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
declare interface SelectCellKeyDownArgs<TRow, TSummaryRow = unknown> {
|
||||
mode: 'SELECT';
|
||||
row: TRow;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
rowIdx: number;
|
||||
selectCell: (position: Position, enableEditor?: Maybe<boolean>) => void;
|
||||
}
|
||||
|
||||
export declare const SelectColumn: Column<any, any>;
|
||||
|
||||
export declare interface SelectHeaderRowEvent {
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
export declare interface SelectRowEvent<TRow> {
|
||||
row: TRow;
|
||||
checked: boolean;
|
||||
isShiftClick: boolean;
|
||||
}
|
||||
|
||||
declare type SharedDivProps = Pick<React.ComponentProps<'div'>, 'role' | 'aria-label' | 'aria-labelledby' | 'aria-description' | 'aria-describedby' | 'aria-rowcount' | 'className' | 'style'>;
|
||||
|
||||
declare type SharedInputProps = Pick<RenderCheckboxProps, 'disabled' | 'tabIndex' | 'aria-label' | 'aria-labelledby' | 'indeterminate' | 'onChange'>;
|
||||
|
||||
export declare interface SortColumn {
|
||||
readonly columnKey: string;
|
||||
readonly direction: SortDirection;
|
||||
}
|
||||
|
||||
export declare type SortDirection = 'ASC' | 'DESC';
|
||||
|
||||
export declare function textEditor<TRow, TSummaryRow>({ row, column, onRowChange, onClose }: RenderEditCellProps<TRow, TSummaryRow>): JSX.Element;
|
||||
|
||||
export declare function ToggleGroup<R, SR>({ groupKey, isExpanded, tabIndex, toggleGroup }: RenderGroupCellProps<R, SR>): JSX.Element;
|
||||
|
||||
export declare function TreeDataGrid<R, SR = unknown, K extends Key = Key>({ columns: rawColumns, rows: rawRows, rowHeight: rawRowHeight, rowKeyGetter: rawRowKeyGetter, onCellKeyDown: rawOnCellKeyDown, onRowsChange, selectedRows: rawSelectedRows, onSelectedRowsChange: rawOnSelectedRowsChange, renderers, groupBy: rawGroupBy, rowGrouper, expandedGroupIds, onExpandedGroupIdsChange, ...props }: TreeDataGridProps<R, SR, K>): JSX.Element;
|
||||
|
||||
export declare interface TreeDataGridProps<R, SR = unknown, K extends Key = Key> extends Omit_2<DataGridProps<R, SR, K>, 'columns' | 'role' | 'aria-rowcount' | 'rowHeight' | 'onFill' | 'isRowSelectionDisabled'> {
|
||||
columns: readonly Column<NoInfer<R>, NoInfer<SR>>[];
|
||||
rowHeight?: Maybe<number | ((args: RowHeightArgs<NoInfer<R>>) => number)>;
|
||||
groupBy: readonly string[];
|
||||
rowGrouper: (rows: readonly NoInfer<R>[], columnKey: string) => Record<string, readonly NoInfer<R>[]>;
|
||||
expandedGroupIds: ReadonlySet<unknown>;
|
||||
onExpandedGroupIdsChange: (expandedGroupIds: Set<unknown>) => void;
|
||||
}
|
||||
|
||||
export declare function useHeaderRowSelection(): {
|
||||
isIndeterminate: boolean;
|
||||
isRowSelected: boolean;
|
||||
onRowSelectionChange: (selectRowEvent: SelectHeaderRowEvent) => void;
|
||||
};
|
||||
|
||||
export declare function useRowSelection(): {
|
||||
isRowSelectionDisabled: boolean;
|
||||
isRowSelected: boolean;
|
||||
onRowSelectionChange: (selectRowEvent: SelectRowEvent<any>) => void;
|
||||
};
|
||||
|
||||
export { }
|
||||
@@ -15,14 +15,16 @@
|
||||
Root;
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-fa71d63e {
|
||||
.mlln6zg7-0-0-beta-50 {
|
||||
@layer rdg.MeasuringCell {
|
||||
contain: strict;
|
||||
grid-row: 1;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-85c48527 {
|
||||
|
||||
|
||||
.cj343x07-0-0-beta-50 {
|
||||
@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-50 {
|
||||
@layer rdg.Cell {
|
||||
position: sticky;
|
||||
/* Should have a higher value than 0 to show up above unfrozen cells */
|
||||
@@ -64,7 +66,80 @@
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-bfba19bc {
|
||||
|
||||
.c1bn88vv7-0-0-beta-50 {
|
||||
@layer rdg.CheckboxInput {
|
||||
display: block;
|
||||
margin: auto;
|
||||
inline-size: 20px;
|
||||
block-size: 20px;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--rdg-checkbox-focus-color);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.g1s9ylgp7-0-0-beta-50 {
|
||||
@layer rdg.GroupCellContent {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cz54e4y7-0-0-beta-50 {
|
||||
@layer rdg.GroupCellCaret {
|
||||
margin-inline-start: 4px;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5px;
|
||||
fill: transparent;
|
||||
vertical-align: middle;
|
||||
|
||||
> path {
|
||||
transition: d 0.1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.h44jtk67-0-0-beta-50 {
|
||||
@layer rdg.SortableHeaderCell {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.hcgkhxz7-0-0-beta-50 {
|
||||
@layer rdg.SortableHeaderCellName {
|
||||
flex-grow: 1;
|
||||
overflow: clip;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.c6ra8a37-0-0-beta-50 {
|
||||
@layer rdg.Cell {
|
||||
background-color: #ccccff;
|
||||
}
|
||||
}
|
||||
|
||||
.cq910m07-0-0-beta-50 {
|
||||
@layer rdg.Cell {
|
||||
background-color: #ccccff;
|
||||
|
||||
&.c6ra8a37-0-0-beta-50 {
|
||||
background-color: #9999ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.c1w9bbhr7-0-0-beta-50 {
|
||||
@layer rdg.DragHandle {
|
||||
--rdg-drag-handle-size: 8px;
|
||||
z-index: 0;
|
||||
@@ -82,101 +157,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-7abddb3e {
|
||||
.c1creorc7-0-0-beta-50 {
|
||||
@layer rdg.DragHandle {
|
||||
z-index: 1;
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-3b807ead {
|
||||
@layer rdg.CheckboxInput {
|
||||
display: block;
|
||||
margin: auto;
|
||||
inline-size: 20px;
|
||||
block-size: 20px;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--rdg-checkbox-focus-color);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-07919382 {
|
||||
@layer rdg.GroupCellContent {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-02a50147 {
|
||||
@layer rdg.GroupCellCaret {
|
||||
margin-inline-start: 4px;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5px;
|
||||
fill: transparent;
|
||||
vertical-align: middle;
|
||||
|
||||
> path {
|
||||
transition: d 0.1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-56a248e4 {
|
||||
@layer rdg.SortableHeaderCell {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-7fad8c83 {
|
||||
@layer rdg.SortableHeaderCellName {
|
||||
flex-grow: 1;
|
||||
overflow: clip;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-35ccb4c8 {
|
||||
@layer rdg.Cell {
|
||||
background-color: #ccccff;
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-46f9ea88 {
|
||||
.cis5rrm7-0-0-beta-50 {
|
||||
@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-50 {
|
||||
@layer rdg.HeaderCell {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-1893dc0f {
|
||||
.c1kqdw7y7-0-0-beta-50 {
|
||||
@layer rdg.HeaderCell {
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-4e60db91 {
|
||||
.r1y6ywlx7-0-0-beta-50 {
|
||||
@layer rdg.HeaderCell {
|
||||
cursor: col-resize;
|
||||
position: absolute;
|
||||
@@ -187,27 +195,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-3e1a4ad4 {
|
||||
@layer rdg.HeaderCell {
|
||||
background-color: var(--rdg-header-draggable-background-color);
|
||||
}
|
||||
.c1bezg5o7-0-0-beta-50 {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-51abd8b8 {
|
||||
@layer rdg.HeaderCell {
|
||||
background-color: var(--rdg-header-draggable-background-color);
|
||||
}
|
||||
.c1vc96037-0-0-beta-50 {
|
||||
background-color: var(--rdg-header-draggable-background-color);
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-c8d7aa64 {
|
||||
@layer rdg.HeaderCell {
|
||||
border-radius: 4px;
|
||||
width: fit-content;
|
||||
outline: 2px solid hsl(207, 100%, 50%);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-3c083f1b {
|
||||
|
||||
.r1upfr807-0-0-beta-50 {
|
||||
@layer rdg.Row {
|
||||
display: contents;
|
||||
background-color: var(--rdg-background-color);
|
||||
@@ -226,14 +223,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-3fe773c3 {
|
||||
.r190mhd37-0-0-beta-50 {
|
||||
@layer rdg.FocusSink {
|
||||
outline: 2px solid var(--rdg-selection-color);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-97ce3fde {
|
||||
.r139qu9m7-0-0-beta-50 {
|
||||
@layer rdg.FocusSink {
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -245,7 +242,28 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-3d5115f3 {
|
||||
|
||||
|
||||
.h10tskcx7-0-0-beta-50 {
|
||||
@layer rdg.HeaderRow {
|
||||
display: contents;
|
||||
background-color: var(--rdg-header-background-color);
|
||||
font-weight: bold;
|
||||
|
||||
& > .cj343x07-0-0-beta-50 {
|
||||
/* 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-50 {
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.a3ejtar7-0-0-beta-50 {
|
||||
@layer rdg.SortIcon {
|
||||
fill: currentColor;
|
||||
|
||||
@@ -254,7 +272,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-ccd2e5d9 {
|
||||
|
||||
|
||||
.rnvodz57-0-0-beta-50 {
|
||||
@layer rdg.Defaults {
|
||||
*,
|
||||
*::before,
|
||||
@@ -264,30 +284,10 @@
|
||||
}
|
||||
|
||||
@layer rdg.Root {
|
||||
--rdg-selection-width: 2px;
|
||||
--rdg-selection-color: hsl(207, 75%, 66%);
|
||||
--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: #66afe9;
|
||||
--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 +295,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 +318,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-50 {
|
||||
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-50 {
|
||||
border-block-start: 2px solid var(--rdg-summary-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-e9b0e1c9 {
|
||||
.vlqv91k7-0-0-beta-50 {
|
||||
@layer rdg.Root {
|
||||
user-select: none;
|
||||
|
||||
& .rdg-7-0-0-beta-59-3c083f1b {
|
||||
& .r1upfr807-0-0-beta-50 {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-dbb8b3c5 {
|
||||
.f1lsfrzw7-0-0-beta-50 {
|
||||
@layer rdg.FocusSink {
|
||||
grid-column: 1/-1;
|
||||
pointer-events: none;
|
||||
@@ -350,50 +366,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
.rdg-7-0-0-beta-59-e9f55541 {
|
||||
.f1cte0lg7-0-0-beta-50 {
|
||||
@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-50 {
|
||||
@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-50 {
|
||||
@layer rdg.SummaryRow {
|
||||
> .cj343x07-0-0-beta-50 {
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tf8l5ub7-0-0-beta-50 {
|
||||
@layer rdg.SummaryRow {
|
||||
> .cj343x07-0-0-beta-50 {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
> .csofj7r7-0-0-beta-50 {
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.g1yxluv37-0-0-beta-50 {
|
||||
@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-50:not(:last-child, .csofj7r7-0-0-beta-50),
|
||||
> :nth-last-child(n + 2 of .csofj7r7-0-0-beta-50) {
|
||||
border-inline-end: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.rdg-7-0-0-beta-59-2f8db206 {
|
||||
|
||||
|
||||
.t7vyx3i7-0-0-beta-50 {
|
||||
@layer rdg.TextEditor {
|
||||
appearance: none;
|
||||
|
||||
@@ -422,4 +446,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*# sourceMappingURL=styles.css.map*/
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "react-data-grid",
|
||||
"version": "7.0.0-beta.59",
|
||||
"version": "7.0.0-beta.50",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./lib/styles.css": "./lib/styles.css",
|
||||
".": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
"default": "./lib/bundle.js"
|
||||
}
|
||||
},
|
||||
"browser": "./lib/index.js",
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.js",
|
||||
"browser": "./lib/bundle.js",
|
||||
"main": "./lib/bundle.js",
|
||||
"module": "./lib/bundle.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"lib"
|
||||
@@ -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.6.0",
|
||||
"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,24 +10,18 @@ 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?.(
|
||||
props.onClick?.(
|
||||
{
|
||||
rowIdx,
|
||||
row: props.row,
|
||||
column: props.column,
|
||||
selectCell(enableEditor) {
|
||||
props.selectCell(
|
||||
{ rowIdx, idx: virtualColIdx },
|
||||
{
|
||||
enableEditor,
|
||||
},
|
||||
);
|
||||
props.selectCell({ rowIdx, idx: virtualColIdx }, enableEditor);
|
||||
},
|
||||
},
|
||||
createCellMouseEvent(event),
|
||||
@@ -44,13 +29,13 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
|
||||
}
|
||||
|
||||
function handleDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
props.onCellDoubleClick?.(
|
||||
props.onDoubleClick?.(
|
||||
{
|
||||
rowIdx,
|
||||
row: props.row,
|
||||
column: props.column,
|
||||
selectCell(enableEditor) {
|
||||
props.selectCell({ rowIdx, idx: virtualColIdx }, { enableEditor });
|
||||
props.selectCell({ rowIdx, idx: virtualColIdx }, enableEditor);
|
||||
},
|
||||
},
|
||||
createCellMouseEvent(event),
|
||||
@@ -58,13 +43,13 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
|
||||
}
|
||||
|
||||
function handleContextMenu(event: React.MouseEvent<HTMLDivElement>) {
|
||||
props.onCellContextMenu?.(
|
||||
props.onContextMenu?.(
|
||||
{
|
||||
rowIdx,
|
||||
row: props.row,
|
||||
column: props.column,
|
||||
selectCell(enableEditor) {
|
||||
props.selectCell({ rowIdx, idx: virtualColIdx }, { enableEditor });
|
||||
props.selectCell({ rowIdx, idx: virtualColIdx }, enableEditor);
|
||||
},
|
||||
},
|
||||
createCellMouseEvent(event),
|
||||
@@ -72,42 +57,30 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
|
||||
}
|
||||
|
||||
const mappedProps = useMemo(
|
||||
() => ({
|
||||
...props,
|
||||
onCellClick: undefined,
|
||||
onCellContextMenu: undefined,
|
||||
onCellDoubleClick: undefined,
|
||||
isFocused: props.isCellSelected,
|
||||
onClick: handleClick,
|
||||
onDoubleClick: handleDoubleClick,
|
||||
onContextMenu: handleContextMenu,
|
||||
}),
|
||||
() => ({ ...props, isFocused: props.isCellSelected, onClick: handleClick, onDoubleClick: handleDoubleClick, onContextMenu: handleContextMenu }),
|
||||
Object.values(props),
|
||||
);
|
||||
|
||||
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}
|
||||
onClick={(_, event) => onClick?.(event)}
|
||||
onDoubleClick={(_, event) => onDoubleClick?.(event)}
|
||||
onContextMenu={(_, 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,172 @@
|
||||
import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
|
||||
import { DataGrid as DataGridBase, type ColumnOrColumnGroup, type CellSelectArgs, type DataGridHandle } 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,
|
||||
children,
|
||||
className,
|
||||
onCellKeyDown,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const rowsCount = useGridReactiveValue(rowCount);
|
||||
const columnsCount = useGridReactiveValue(columnCount);
|
||||
|
||||
const gridKey = useRef(0);
|
||||
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 }, true);
|
||||
},
|
||||
}));
|
||||
|
||||
if (rowsCountRef.current !== rowsCount) {
|
||||
const previousRowCount = rowsCountRef.current;
|
||||
rowsCountRef.current = rowsCount;
|
||||
|
||||
if (previousRowCount === 0) {
|
||||
// we trigger columns size recalculation when rows are added
|
||||
gridKey.current++;
|
||||
}
|
||||
}
|
||||
|
||||
const 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);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderDnDContext value={dndHeaderContext}>
|
||||
<DataGridRowContext value={{ rowCount, onScrollToBottom }}>
|
||||
<DataGridCellContext value={{ cell, cellText, cellElement, cellTooltip, onCellChange }}>
|
||||
<DataGridCellHeaderContext value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState }}>
|
||||
<DataGridBase
|
||||
key={gridKey.current}
|
||||
ref={innerGridRef}
|
||||
columns={dndHeaderContext.columns}
|
||||
rows={rows}
|
||||
className={className}
|
||||
headerRowHeight={getHeaderHeight?.()}
|
||||
onScroll={onScroll}
|
||||
rowHeight={getRowHeight ? row => getRowHeight(row.idx) : undefined}
|
||||
rowKeyGetter={getRowId ? row => getRowId(row.idx) : undefined}
|
||||
onSelectedCellChange={handleCellFocus}
|
||||
onCellKeyDown={handleCellKeyDown}
|
||||
renderers={{
|
||||
renderRow: rowRenderer,
|
||||
renderCell: cellRenderer,
|
||||
noRowsFallback: children,
|
||||
}}
|
||||
minimumRowsToRender={100}
|
||||
/>
|
||||
</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,7 @@ 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;
|
||||
}
|
||||
|
||||
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,23 +1,14 @@
|
||||
/*
|
||||
* 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;
|
||||
className?: string;
|
||||
focusable?: boolean;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function BooleanFormatter({ value, className, onClick, onKeyDown, focusable }: Props): React.ReactElement {
|
||||
export function BooleanFormatter({ value, className, onClick, onKeyDown }: Props) {
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (event.code === 'Enter' || event.code === 'Space') {
|
||||
event.preventDefault();
|
||||
@@ -26,12 +17,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,24 +1,14 @@
|
||||
/*
|
||||
* 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 { memo, use, useRef } 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;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer({ colIdx, tabIndex }: Props) {
|
||||
const dndHeaderContext = use(HeaderDnDContext);
|
||||
const cellHeaderContext = use(DataGridCellHeaderContext);
|
||||
@@ -26,9 +16,11 @@ export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer
|
||||
const getHeaderText = useGridReactiveValue(headerElement ? undefined : cellHeaderContext?.headerText, colIdx);
|
||||
const isColumnSortable = useGridReactiveValue(cellHeaderContext?.columnSortable, colIdx);
|
||||
const onColumnSort = cellHeaderContext?.onColumnSort;
|
||||
const onHeaderKeyDown = cellHeaderContext?.onHeaderKeyDown;
|
||||
const sortingState = useGridReactiveValue(cellHeaderContext?.columnSortingState, colIdx);
|
||||
|
||||
const orderButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const draggable = dndHeaderContext?.getCanDrag?.(colIdx) ?? false;
|
||||
const drag = useDrag({
|
||||
draggable,
|
||||
@@ -63,38 +55,26 @@ export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer
|
||||
},
|
||||
});
|
||||
|
||||
function handleSort(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) {
|
||||
if (!onColumnSort) {
|
||||
return;
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Tab' && !e.shiftKey && isColumnSortable && onColumnSort && orderButtonRef.current !== document.activeElement) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
containerRef.current?.parentElement?.setAttribute('aria-selected', 'false');
|
||||
orderButtonRef.current?.focus();
|
||||
}
|
||||
|
||||
const nextSortState = sortingState === 'asc' ? 'desc' : sortingState === 'desc' ? null : 'asc';
|
||||
onColumnSort(colIdx, nextSortState, e.ctrlKey || e.metaKey);
|
||||
}
|
||||
|
||||
function onKeyDown(event: DataGridCellKeyboardEvent) {
|
||||
onHeaderKeyDown?.(event);
|
||||
|
||||
if ((event.key === 'Enter' || event.key === ' ') && isColumnSortable && onColumnSort) {
|
||||
event.preventDefault();
|
||||
handleSort(event);
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
ref={containerRef}
|
||||
tabIndex={tabIndex}
|
||||
onKeyDown={handleKeyDown}
|
||||
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:text-ellipsis">{headerElement ?? getHeaderText ?? ''}</span>
|
||||
{isColumnSortable && onColumnSort && <OrderButton ref={orderButtonRef} colIdx={colIdx} sortState={sortingState} onSort={onColumnSort} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { clsx, IconButton } from '@dbeaver/ui-kit';
|
||||
|
||||
interface OrderButtonProps {
|
||||
colIdx: number;
|
||||
sortState?: 'asc' | 'desc' | null;
|
||||
onSort: (attributePosition: number, order: 'asc' | 'desc' | null, isMultiple: boolean) => void;
|
||||
tabIndex?: number;
|
||||
ref: React.Ref<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export function OrderButton({ colIdx, sortState, onSort, tabIndex, ref }: OrderButtonProps) {
|
||||
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
const nextSortState = sortState === 'asc' ? 'desc' : sortState === 'desc' ? null : 'asc';
|
||||
onSort(colIdx, nextSortState, e.ctrlKey || e.metaKey);
|
||||
}
|
||||
|
||||
const svgSortAsc = '#icon-sort-asc';
|
||||
const svgSortDesc = '#icon-sort-desc';
|
||||
const svgSortUnknown = '#icon-sort-unknown';
|
||||
|
||||
const iconSrc = sortState === 'asc' ? svgSortAsc : sortState === 'desc' ? svgSortDesc : svgSortUnknown;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleClick}
|
||||
tabIndex={tabIndex}
|
||||
ref={ref}
|
||||
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',
|
||||
)}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16">
|
||||
<use href={iconSrc} />
|
||||
</svg>
|
||||
</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"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@dbeaver/react-translate",
|
||||
"name": "@dbeaver/react-dnd",
|
||||
"type": "module",
|
||||
"sideEffects": [],
|
||||
"exports": {
|
||||
@@ -23,7 +23,7 @@
|
||||
"!lib/tests",
|
||||
"!.tsbuildinfo"
|
||||
],
|
||||
"packageManager": "yarn@4.14.1",
|
||||
"packageManager": "yarn@4.6.0",
|
||||
"devDependencies": {
|
||||
"@dbeaver/tsconfig": "workspace:^",
|
||||
"@types/react": "^19",
|
||||
@@ -6,7 +6,7 @@ export interface DnDStoreProvider {
|
||||
removeData: () => void;
|
||||
}
|
||||
|
||||
export class DnDStore {
|
||||
class DnDStore {
|
||||
private data: Map<string, Record<string, any>> = new Map();
|
||||
setData(id: string, key: string, value: any) {
|
||||
if (!this.data.has(id)) {
|
||||
@@ -0,0 +1,2 @@
|
||||
import '../src/index.css';
|
||||
import './global.css';
|
||||
|
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 |
|
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 367 B |
|
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 306 B |
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="icon-sort-unknown" viewBox="0 0 24 24" fill="currentColor">
|
||||
<polygon points="3.4 8.29 10.55 8.29 6.98 4 3.4 8.29" />
|
||||
<rect x="6.26" y="5.43" width="1.43" height="15.71" />
|
||||
<polygon points="20.55 16.86 13.4 16.86 16.98 21.14 20.55 16.86" />
|
||||
<rect x="16.26" y="4" width="1.43" height="15.71" />
|
||||
</symbol>
|
||||
<symbol id="icon-sort-desc" viewBox="0 0 24 24"
|
||||
fill="currentColor">
|
||||
<polygon points="9.14 16.71 2 16.71 5.57 21 9.14 16.71" />
|
||||
<rect x="4.86" y="3.86" width="1.43" height="15.71" />
|
||||
<rect x="10.57" y="5.29" width="11.43" height="1.43" />
|
||||
<rect x="10.57" y="11.71" width="8.57" height="1.43" />
|
||||
<rect x="10.57" y="18.14" width="5.71" height="1.43" />
|
||||
</symbol>
|
||||
<symbol id="icon-sort-asc" viewBox="0 0 24 24" fill="currentColor">
|
||||
<polygon points="2 8.14 9.14 8.14 5.57 3.86 2 8.14" />
|
||||
<rect x="4.86" y="5.29" width="1.43" height="15.71" />
|
||||
<rect x="10.57" y="5.29" width="11.43" height="1.43" />
|
||||
<rect x="10.57" y="11.71" width="8.57" height="1.43" />
|
||||
<rect x="10.57" y="18.14" width="5.71" height="1.43" />
|
||||
</symbol>
|
||||
<symbol id="icon-cross" viewBox="0 0 24 24">
|
||||
<path xmlns="http://www.w3.org/2000/svg" fill="currentColor" fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.31579 3.31579C2.89474 3.73684 2.89474 4.42105 3.31579 4.84211L10.4737 12L3.31579 19.1579C2.89474 19.5789 2.89474 20.2632 3.31579 20.6842C3.73684 21.1053 4.42105 21.1053 4.84211 20.6842L12 13.5263L19.1579 20.6842C19.5789 21.1053 20.2632 21.1053 20.6842 20.6842C21.1053 20.2632 21.1053 19.5789 20.6842 19.1579L13.5263 12L20.6842 4.84211C21.1053 4.42105 21.1053 3.73684 20.6842 3.31579C20.2632 2.89474 19.5789 2.89474 19.1579 3.31579L12 10.4737L4.84211 3.31579C4.42105 2.89474 3.73684 2.89474 3.31579 3.31579Z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -15,7 +15,7 @@
|
||||
"docs": "ladle serve",
|
||||
"clean": "rimraf --glob lib"
|
||||
},
|
||||
"packageManager": "yarn@4.14.1",
|
||||
"packageManager": "yarn@4.6.0",
|
||||
"devDependencies": {
|
||||
"@dbeaver/tsconfig": "workspace:^",
|
||||
"@ladle/react": "^5",
|
||||
@@ -23,17 +23,14 @@
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@wroud/vite-plugin-asset-resolver": "^0",
|
||||
"@wroud/vite-plugin-tsc": "^0",
|
||||
"@wroud/vite-plugin-tsc": "^0.11.6",
|
||||
"rimraf": "^6",
|
||||
"tailwindcss": "4.0.7",
|
||||
"typescript": "^5",
|
||||
"vite": "^7",
|
||||
"vite-plugin-svgr": "^4.3.0"
|
||||
"vite": "^6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0",
|
||||
"@dbeaver/js-helpers": "workspace:*",
|
||||
"@dbeaver/react-translate": "workspace:^",
|
||||
"clsx": "^2",
|
||||
"react": "^19",
|
||||
"react-dom": "^19"
|
||||
@@ -136,23 +136,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dbv-kit-button--ghost {
|
||||
--dbv-kit-btn-foreground: var(--dbv-kit-btn-ghost-foreground);
|
||||
--dbv-kit-btn-foreground-hover: var(--dbv-kit-btn-ghost-foreground-hover);
|
||||
--dbv-kit-btn-foreground-active: var(--dbv-kit-btn-ghost-foreground-active);
|
||||
|
||||
--dbv-kit-btn-background: var(--dbv-kit-btn-ghost-background);
|
||||
--dbv-kit-btn-background-hover: var(--dbv-kit-btn-ghost-background-hover);
|
||||
--dbv-kit-btn-background-active: var(--dbv-kit-btn-ghost-background-active);
|
||||
--dbv-kit-btn-border-color: var(--dbv-kit-btn-ghost-border-color);
|
||||
|
||||
--dbv-kit-btn-loader-color: var(--dbv-kit-btn-ghost-foreground);
|
||||
|
||||
&:disabled {
|
||||
color: var(--dbv-kit-control-disabled-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------- */
|
||||
/* Loader */
|
||||
|
||||
@@ -175,10 +158,6 @@
|
||||
/* ---------------------------- */
|
||||
/* Icon */
|
||||
|
||||
.dbv-kit-button:disabled .dbv-kit-button__icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.dbv-kit-button__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1,19 +1,18 @@
|
||||
/*
|
||||
* 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 clsx from 'clsx';
|
||||
import { Button as AriaButton, type ButtonProps as UnstyledButtonProps } from '@ariakit/react';
|
||||
import type { ControlSize } from '../types/controls.js';
|
||||
import { componentProviderWrapper } from '../componentProviderWrapper.js';
|
||||
import { ButtonRoot, type ButtonRootProps } from './ButtonRoot.js';
|
||||
import './Button.css';
|
||||
|
||||
export interface ButtonProps extends ButtonRootProps {
|
||||
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
|
||||
variant?: 'primary' | 'secondary' | 'danger';
|
||||
size?: ControlSize;
|
||||
}
|
||||
|
||||
@@ -38,6 +37,3 @@ export function ButtonBaseIcon({ className, children, placement }: ButtonIconPro
|
||||
|
||||
export const Button = componentProviderWrapper('Button', ButtonBase);
|
||||
export const ButtonIcon = componentProviderWrapper('ButtonIcon', ButtonBaseIcon);
|
||||
|
||||
export const UnstyledButton = AriaButton;
|
||||
export { type UnstyledButtonProps };
|
||||
@@ -19,7 +19,6 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
height: 100%;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
gap: var(--dbv-kit-btn-gap);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--dbv-kit-btn-height: var(--dbv-kit-control-height-medium);
|
||||
--dbv-kit-btn-padding-inline: calc(var(--tw-spacing) * 3);
|
||||
--dbv-kit-btn-padding-inline: calc(var(--tw-spacing) * 2.5);
|
||||
--dbv-kit-btn-gap: calc(var(--dbv-kit-btn-padding-inline) / 2);
|
||||
|
||||
--dbv-kit-btn-outline-offset: var(--dbv-kit-control-outline-offset);
|
||||
@@ -27,15 +27,5 @@
|
||||
--dbv-kit-btn-danger-background-active: var(--tw-color-red-800);
|
||||
--dbv-kit-btn-danger-border-color: transparent;
|
||||
--dbv-kit-btn-danger-color-outline: var(--tw-color-red-300);
|
||||
|
||||
/* Ghost Button */
|
||||
--dbv-kit-btn-ghost-foreground: var(--dbv-kit-color-primary-600);
|
||||
--dbv-kit-btn-ghost-foreground-hover: var(--dbv-kit-color-primary-600);
|
||||
--dbv-kit-btn-ghost-foreground-active: var(--dbv-kit-color-primary-600);
|
||||
--dbv-kit-btn-ghost-background: transparent;
|
||||
--dbv-kit-btn-ghost-background-hover: var(--dbv-kit-color-primary-50);
|
||||
--dbv-kit-btn-ghost-background-active: var(--dbv-kit-color-primary-100);
|
||||
--dbv-kit-btn-ghost-border-color: transparent;
|
||||
--dbv-kit-btn-ghost-border-color-hover: var(--dbv-kit-color-primary-600);
|
||||
}
|
||||
}
|
||||
@@ -29,14 +29,11 @@
|
||||
pointer-events: none;
|
||||
opacity: var(--dbv-kit-control-disabled-opacity);
|
||||
}
|
||||
|
||||
.dbv-kit-checkbox__check {
|
||||
margin-top: calc(var(--dbv-kit-checkbox-height) * (calc(1 - var(--dbv-kit-checkbox-check-size)) / 2));
|
||||
}
|
||||
}
|
||||
|
||||
.dbv-kit-checkbox__check {
|
||||
--check-size: round(calc(var(--dbv-kit-checkbox-height) * var(--dbv-kit-checkbox-check-size)), 2px);
|
||||
margin-top: calc(var(--dbv-kit-checkbox-height) * (calc(1 - var(--dbv-kit-checkbox-check-size)) / 2));
|
||||
height: var(--check-size);
|
||||
line-height: var(--check-size);
|
||||
width: var(--check-size);
|
||||
@@ -50,15 +47,12 @@
|
||||
outline-offset: var(--dbv-kit-control-outline-offset);
|
||||
outline-width: var(--dbv-kit-control-outline-width);
|
||||
outline-color: var(--dbv-kit-control-outline-color);
|
||||
transition-property: stroke-dashoffset;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
stroke-dasharray: 20;
|
||||
stroke-dashoffset: 20;
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition-property: stroke-dashoffset;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
&[data-focus-visible] {
|
||||
outline-style: solid;
|
||||
}
|
||||
@@ -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.
|
||||
@@ -12,7 +12,6 @@ import clsx from 'clsx';
|
||||
import './Checkbox.css';
|
||||
import type { ControlSize } from '../types/controls.js';
|
||||
import { componentProviderWrapper } from '../componentProviderWrapper.js';
|
||||
import { CheckboxIndicator } from './CheckboxIndicator.js';
|
||||
|
||||
export interface CheckboxProps extends Omit<AriaKitCheckboxProps, 'render' | 'size'> {
|
||||
size?: ControlSize;
|
||||
@@ -21,15 +20,7 @@ export interface CheckboxProps extends Omit<AriaKitCheckboxProps, 'render' | 'si
|
||||
indeterminateIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function CheckboxBase({
|
||||
children,
|
||||
className,
|
||||
icon,
|
||||
indeterminate,
|
||||
indeterminateIcon,
|
||||
size = 'medium',
|
||||
...props
|
||||
}: CheckboxProps): React.ReactElement {
|
||||
export function CheckboxBase({ children, className, icon, indeterminate, indeterminateIcon, size = 'medium', ...props }: CheckboxProps) {
|
||||
const [innerChecked, setInnerChecked] = useState(props.defaultChecked ?? false);
|
||||
const checked = props.checked ?? innerChecked;
|
||||
|
||||
@@ -44,7 +35,6 @@ export function CheckboxBase({
|
||||
<VisuallyHidden>
|
||||
<AriaCheckbox
|
||||
{...props}
|
||||
checked={indeterminate ? 'mixed' : checked}
|
||||
className="dbv-kit-checkbox__input"
|
||||
clickOnEnter
|
||||
onFocusVisible={() => setFocusVisible(true)}
|
||||
@@ -56,19 +46,30 @@ export function CheckboxBase({
|
||||
/>
|
||||
</VisuallyHidden>
|
||||
|
||||
<CheckboxIndicator
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
disabled={props.disabled}
|
||||
focusVisible={focusVisible}
|
||||
size={size}
|
||||
icon={icon}
|
||||
indeterminateIcon={indeterminateIcon}
|
||||
/>
|
||||
<div
|
||||
className="dbv-kit-checkbox__check"
|
||||
data-indeterminate={indeterminate}
|
||||
data-checked={checked}
|
||||
data-focus-visible={focusVisible || undefined}
|
||||
>
|
||||
{checked &&
|
||||
(icon ?? (
|
||||
<svg stroke="currentColor" strokeWidth="2" viewBox="0 0 16 16">
|
||||
<polyline fill="none" points="3,7 7,11 13,4" />
|
||||
</svg>
|
||||
))}
|
||||
{indeterminate &&
|
||||
!checked &&
|
||||
(indeterminateIcon ?? (
|
||||
<svg fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 16 16">
|
||||
<line x1="3" y1="8" x2="13" y2="8" />
|
||||
</svg>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<span className="dbv-kit-checkbox__text">{children}</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export const Checkbox = Object.assign(componentProviderWrapper('Checkbox', CheckboxBase), { Indicator: CheckboxIndicator });
|
||||
export const Checkbox = componentProviderWrapper('Checkbox', CheckboxBase);
|
||||
@@ -14,7 +14,7 @@
|
||||
--dbv-kit-checkbox-icon-color: var(--tw-color-white);
|
||||
|
||||
/* Checkbox border */
|
||||
--dbv-kit-checkbox-border-width: var(--dbv-kit-control-border-width);
|
||||
--dbv-kit-checkbox-border-width: calc(var(--dbv-kit-control-border-width) * 2);
|
||||
--dbv-kit-checkbox-border-radius: var(--dbv-kit-control-border-radius);
|
||||
--dbv-kit-checkbox-border-style: solid;
|
||||
--dbv-kit-checkbox-border-color: var(--dbv-kit-color-control-inactive);
|
||||
@@ -1,11 +1,3 @@
|
||||
/*
|
||||
* 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 { createContext } from 'react';
|
||||
import type { ButtonBase, ButtonBaseIcon } from './Button/Button.js';
|
||||
import type { IconButtonBase } from './IconButton/IconButton.js';
|
||||
@@ -15,14 +7,12 @@ import type { InputBase } from './Input/Input.js';
|
||||
import type { RadioGroup } from './Radio/RadioGroup.js';
|
||||
import type { Select } from './Select/Select.js';
|
||||
import type { SelectField } from './Select/SelectField.js';
|
||||
import type { ColorPickerBase } from './ColorPicker/ColorPickerBase.js';
|
||||
|
||||
export interface IComponentProvider {
|
||||
Button?: typeof ButtonBase;
|
||||
ButtonIcon?: typeof ButtonBaseIcon;
|
||||
IconButton?: typeof IconButtonBase;
|
||||
Input?: typeof InputBase;
|
||||
ColorPicker?: typeof ColorPickerBase;
|
||||
Checkbox?: typeof CheckboxBase;
|
||||
Radio?: typeof Radio;
|
||||
RadioGroup?: typeof RadioGroup;
|
||||
@@ -20,7 +20,7 @@
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible,
|
||||
&[data-focus-visible='true'] {
|
||||
&[data-focus-visible] {
|
||||
outline: var(--dbv-kit-icon-btn-outline-width) solid var(--dbv-kit-icon-btn-outline-color);
|
||||
outline-offset: var(--dbv-kit-icon-btn-outline-offset);
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
&:active,
|
||||
&[data-active='true'] {
|
||||
&[data-active] {
|
||||
color: var(--dbv-kit-icon-btn-foreground-active);
|
||||
background-color: var(--dbv-kit-icon-btn-background-active);
|
||||
}
|
||||