diff --git a/package-lock.json b/package-lock.json index 99a042392..a989475bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,10 @@ "./plugins/*/src/js/" ], "devDependencies": { - "@deephaven/babel-preset": "^0.40.0", - "@deephaven/eslint-config": "^0.40.0", - "@deephaven/prettier-config": "^0.40.0", - "@deephaven/tsconfig": "^0.40.0", + "@deephaven/babel-preset": "^0.72.0", + "@deephaven/eslint-config": "^0.72.0", + "@deephaven/prettier-config": "^0.72.0", + "@deephaven/tsconfig": "^0.72.0", "@playwright/test": "^1.41.2", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12.1.3", @@ -34,7 +34,7 @@ "lerna": "^6.6.1", "npm-run-all": "^4.1.5", "nx": "15.9.2", - "prettier": "^2.8.7", + "prettier": "3.0.0", "vite": "~4.1.4" } }, @@ -2300,9 +2300,9 @@ } }, "node_modules/@deephaven/babel-preset": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/babel-preset/-/babel-preset-0.40.0.tgz", - "integrity": "sha512-5ZoFXB1SJTJwhg5phupC+wWNR18Jir8uibILsQiKuMLcmZdfR1uRsJNzmMnz/MV/C+ifhip54waJQjEEljMrbQ==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/babel-preset/-/babel-preset-0.72.0.tgz", + "integrity": "sha512-flk1Pqq9YrwYtxUKDYZV1RYrnEbzdZ/52cxcU+P7c+zgM79bgzEfCRsab50y1m3PeWls9qlVi+iNlUGNz6z5GQ==", "dev": true, "dependencies": { "@babel/core": "^7.20.0", @@ -3526,9 +3526,9 @@ } }, "node_modules/@deephaven/eslint-config": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/eslint-config/-/eslint-config-0.40.0.tgz", - "integrity": "sha512-227bATcyanUNJ8jybJt/wZyoR7Hun/ZYI8EmRXKbO+0SMBG1LiyzEYqTnjB/VKzPF44OAk8ouo5LdJ1H3TIbnw==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/eslint-config/-/eslint-config-0.72.0.tgz", + "integrity": "sha512-8cDs2K1VxByED5L3U8wHrTe7Yoj/EOW4yUW2lOV5QlLmjTOgRf5pevNrBm2GnplBMefdyB49T5dEyhRBIPwthw==", "dev": true, "dependencies": { "eslint-config-airbnb": "^19.0.4", @@ -3541,7 +3541,7 @@ "eslint": "^8.29.0", "eslint-import-resolver-typescript": "^3.5.0", "eslint-plugin-es": "^4.1.0", - "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react-refresh": "0.3.4" } }, @@ -4170,12 +4170,12 @@ } }, "node_modules/@deephaven/prettier-config": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/prettier-config/-/prettier-config-0.40.0.tgz", - "integrity": "sha512-jnzyj7AFRtTVGflPJzbNvdVL2lSb0ah5NPwHZ9sNyfwHoB0mUK8Jy0JNv2DqHD96LpFQLdQS+2QK3ouTFEZHXg==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/prettier-config/-/prettier-config-0.72.0.tgz", + "integrity": "sha512-edYejgDJnIspoUXFC9NWWuwBUTwy898y8vcjKhfRRolovY8ihDDuGLwHwbnCaOYNlNukukhuHJKK1ychASQ8bQ==", "dev": true, "peerDependencies": { - "prettier": "^2.2.1" + "prettier": "^3.0.0" } }, "node_modules/@deephaven/react-hooks": { @@ -4291,9 +4291,9 @@ } }, "node_modules/@deephaven/tsconfig": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/tsconfig/-/tsconfig-0.40.0.tgz", - "integrity": "sha512-2VA+rSmvTLTfLy0Z61pPu+aZ+ljnOgTMVZaVnXbeZzKdbQgyM9a8W5OmsQ/Vq8K3JQ+qGJ7ghYYmZmJva+BKrw==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/tsconfig/-/tsconfig-0.72.0.tgz", + "integrity": "sha512-ER4+KsrTBO8rhd4YA6SY5dRBZjUajrNKb2yQijSXNZTbWbQCet/522Yui2YCgWFBRbM5GvYGDoUcc/07tZeLZQ==", "dev": true }, "node_modules/@deephaven/utils": { @@ -7876,6 +7876,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@playwright/test": { "version": "1.41.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", @@ -16182,22 +16195,31 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "peer": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" }, "engines": { - "node": ">=6.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } @@ -29042,15 +29064,15 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -31950,6 +31972,23 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "peer": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/table": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", @@ -35397,7 +35436,7 @@ }, "plugins/ui/src/js": { "name": "@deephaven/js-plugin-ui", - "version": "0.11.0", + "version": "0.13.0", "license": "Apache-2.0", "dependencies": { "@adobe/react-spectrum": "^3.34.1", @@ -37542,9 +37581,9 @@ } }, "@deephaven/babel-preset": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/babel-preset/-/babel-preset-0.40.0.tgz", - "integrity": "sha512-5ZoFXB1SJTJwhg5phupC+wWNR18Jir8uibILsQiKuMLcmZdfR1uRsJNzmMnz/MV/C+ifhip54waJQjEEljMrbQ==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/babel-preset/-/babel-preset-0.72.0.tgz", + "integrity": "sha512-flk1Pqq9YrwYtxUKDYZV1RYrnEbzdZ/52cxcU+P7c+zgM79bgzEfCRsab50y1m3PeWls9qlVi+iNlUGNz6z5GQ==", "dev": true, "requires": { "@babel/core": "^7.20.0", @@ -38475,9 +38514,9 @@ } }, "@deephaven/eslint-config": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/eslint-config/-/eslint-config-0.40.0.tgz", - "integrity": "sha512-227bATcyanUNJ8jybJt/wZyoR7Hun/ZYI8EmRXKbO+0SMBG1LiyzEYqTnjB/VKzPF44OAk8ouo5LdJ1H3TIbnw==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/eslint-config/-/eslint-config-0.72.0.tgz", + "integrity": "sha512-8cDs2K1VxByED5L3U8wHrTe7Yoj/EOW4yUW2lOV5QlLmjTOgRf5pevNrBm2GnplBMefdyB49T5dEyhRBIPwthw==", "dev": true, "requires": { "eslint-config-airbnb": "^19.0.4", @@ -40445,9 +40484,9 @@ } }, "@deephaven/prettier-config": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/prettier-config/-/prettier-config-0.40.0.tgz", - "integrity": "sha512-jnzyj7AFRtTVGflPJzbNvdVL2lSb0ah5NPwHZ9sNyfwHoB0mUK8Jy0JNv2DqHD96LpFQLdQS+2QK3ouTFEZHXg==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/prettier-config/-/prettier-config-0.72.0.tgz", + "integrity": "sha512-edYejgDJnIspoUXFC9NWWuwBUTwy898y8vcjKhfRRolovY8ihDDuGLwHwbnCaOYNlNukukhuHJKK1ychASQ8bQ==", "dev": true, "requires": {} }, @@ -40529,9 +40568,9 @@ } }, "@deephaven/tsconfig": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@deephaven/tsconfig/-/tsconfig-0.40.0.tgz", - "integrity": "sha512-2VA+rSmvTLTfLy0Z61pPu+aZ+ljnOgTMVZaVnXbeZzKdbQgyM9a8W5OmsQ/Vq8K3JQ+qGJ7ghYYmZmJva+BKrw==", + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@deephaven/tsconfig/-/tsconfig-0.72.0.tgz", + "integrity": "sha512-ER4+KsrTBO8rhd4YA6SY5dRBZjUajrNKb2yQijSXNZTbWbQCet/522Yui2YCgWFBRbM5GvYGDoUcc/07tZeLZQ==", "dev": true }, "@deephaven/utils": { @@ -43124,6 +43163,13 @@ "dev": true, "optional": true }, + "@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "peer": true + }, "@playwright/test": { "version": "1.41.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", @@ -49626,13 +49672,14 @@ } }, "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "peer": true, "requires": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" } }, "eslint-plugin-react": { @@ -58805,9 +58852,9 @@ "dev": true }, "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true }, "prettier-linter-helpers": { @@ -61046,6 +61093,17 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "peer": true, + "requires": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + } + }, "table": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", diff --git a/package.json b/package.json index 6e62cd333..64acaf276 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "update-dh-packages": "lerna run --concurrency 1 update-dh-packages" }, "devDependencies": { - "@deephaven/babel-preset": "^0.40.0", - "@deephaven/eslint-config": "^0.40.0", - "@deephaven/prettier-config": "^0.40.0", - "@deephaven/tsconfig": "^0.40.0", + "@deephaven/babel-preset": "^0.72.0", + "@deephaven/eslint-config": "^0.72.0", + "@deephaven/prettier-config": "^0.72.0", + "@deephaven/tsconfig": "^0.72.0", "@playwright/test": "^1.41.2", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12.1.3", @@ -47,7 +47,7 @@ "lerna": "^6.6.1", "npm-run-all": "^4.1.5", "nx": "15.9.2", - "prettier": "^2.8.7", + "prettier": "3.0.0", "vite": "~4.1.4" }, "prettier": "@deephaven/prettier-config" diff --git a/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts b/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts index 244f7991c..e2a86a8f0 100644 --- a/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts +++ b/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts @@ -126,7 +126,7 @@ export class PlotlyExpressChartModel extends ChartModel { return this.layout; } - override close() { + override close(): void { super.close(); this.widget?.close(); this.widget = undefined; @@ -180,7 +180,7 @@ export class PlotlyExpressChartModel extends ChartModel { this.widget = undefined; } - updateLayout(data: PlotlyChartWidgetData) { + updateLayout(data: PlotlyChartWidgetData): void { const { figure } = data; const { plotly } = figure; const { layout: plotlyLayout = {} } = plotly; @@ -255,7 +255,7 @@ export class PlotlyExpressChartModel extends ChartModel { this.fireUpdate(this.getData()); } - addTable(id: number, table: Table) { + addTable(id: number, table: Table): void { if (this.tableReferenceMap.has(id)) { return; } @@ -267,7 +267,7 @@ export class PlotlyExpressChartModel extends ChartModel { } } - subscribeTable(id: number) { + subscribeTable(id: number): void { const table = this.tableReferenceMap.get(id); const columnReplacements = this.tableColumnReplacementMap.get(id); @@ -292,7 +292,7 @@ export class PlotlyExpressChartModel extends ChartModel { } } - removeTable(id: number) { + removeTable(id: number): void { this.subscriptionCleanupMap.get(id)?.(); this.tableSubscriptionMap.get(id)?.close(); diff --git a/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx b/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx index db2cd42ad..64887bd1e 100644 --- a/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx +++ b/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx @@ -7,7 +7,9 @@ import { useApi } from '@deephaven/jsapi-bootstrap'; import PlotlyExpressChartModel from './PlotlyExpressChartModel.js'; import { useHandleSceneTicks } from './useHandleSceneTicks.js'; -export function PlotlyExpressChartPanel(props: WidgetPanelProps) { +export function PlotlyExpressChartPanel( + props: WidgetPanelProps +): JSX.Element { const dh = useApi(); const { fetch, metadata = {}, ...rest } = props; const containerRef = useRef(null); diff --git a/plugins/plotly-express/src/js/src/PlotlyExpressChartUtils.ts b/plugins/plotly-express/src/js/src/PlotlyExpressChartUtils.ts index c91c66f82..48d78c35a 100644 --- a/plugins/plotly-express/src/js/src/PlotlyExpressChartUtils.ts +++ b/plugins/plotly-express/src/js/src/PlotlyExpressChartUtils.ts @@ -2,12 +2,12 @@ import type { Data, PlotlyDataLayoutConfig } from 'plotly.js'; import type { Table, Widget } from '@deephaven/jsapi-types'; export interface PlotlyChartWidget { - getDataAsBase64(): string; - exportedObjects: { fetch(): Promise }[]; - addEventListener( + getDataAsBase64: () => string; + exportedObjects: { fetch: () => Promise
}[]; + addEventListener: ( type: string, fn: (event: CustomEvent) => () => void - ): void; + ) => void; } export interface PlotlyChartWidgetData { diff --git a/plugins/plotly-express/src/js/src/useHandleSceneTicks.ts b/plugins/plotly-express/src/js/src/useHandleSceneTicks.ts index c40bb726c..1c1fb2a8b 100644 --- a/plugins/plotly-express/src/js/src/useHandleSceneTicks.ts +++ b/plugins/plotly-express/src/js/src/useHandleSceneTicks.ts @@ -4,7 +4,7 @@ import PlotlyExpressChartModel from './PlotlyExpressChartModel.js'; export function useHandleSceneTicks( model: PlotlyExpressChartModel | undefined, container: HTMLDivElement | null -) { +): void { useEffect(() => { // Plotly scenes and geo views reset when our data ticks // Pause rendering data updates when the user is manipulating a scene diff --git a/plugins/ui/CHANGELOG.md b/plugins/ui/CHANGELOG.md index 525bff8b8..8bb7cd8c3 100644 --- a/plugins/ui/CHANGELOG.md +++ b/plugins/ui/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines. - - - +## ui-v0.13.0 - 2024-04-18 +#### Bug Fixes +- Fix conditional use_effect in use_table_listener (#422) - (5f4f238) - Joe +- add on_change to toggle_button (#426) - (6ead733) - Joe +- buttons not working due to extra prop (#423) - (b10f67c) - Don +#### Features +- python date picker implementation (#409) - (5ed66a7) - Joe + +- - - + +## ui-v0.12.0 - 2024-04-17 +#### Documentation +- Combo box spec (#392) - (da1076a) - Joe +- toggle button (#402) - (702ad2a) - ethanalvizo +- date picker spec (#388) - (e1a135d) - Joe +#### Features +- improve default dh.ui layouts (#411) - (67f82e3) - Don +- Picker - format settings (#394) - (f9a0e34) - bmingles +#### Tests +- bump ts, eslint and prettier configs (#416) - (a4761cc) - Don + +- - - + ## ui-v0.11.0 - 2024-04-03 #### Bug Fixes - Re-opening widgets after re-hydrated (#379) - (42242a5) - mofojed diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index 560a1e485..07b2abc75 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -1411,7 +1411,7 @@ ui.date_picker( granularity: Granularity | None = None, on_change: Callable[[Date], None] | None = None, **props: Any -) -> ListViewElement +) -> DatePickerElement ``` ###### Parameters diff --git a/plugins/ui/setup.cfg b/plugins/ui/setup.cfg index a4a8753e5..a2553ad12 100644 --- a/plugins/ui/setup.cfg +++ b/plugins/ui/setup.cfg @@ -3,7 +3,7 @@ name = deephaven-plugin-ui description = deephaven.ui plugin long_description = file: README.md long_description_content_type = text/markdown -version = 0.11.0.dev0 +version = 0.13.0.dev0 url = https://github.com/deephaven/deephaven-plugins project_urls = Source Code = https://github.com/deephaven/deephaven-plugins diff --git a/plugins/ui/src/deephaven/ui/_internal/utils.py b/plugins/ui/src/deephaven/ui/_internal/utils.py index 27ea3d7d2..158da5148 100644 --- a/plugins/ui/src/deephaven/ui/_internal/utils.py +++ b/plugins/ui/src/deephaven/ui/_internal/utils.py @@ -4,11 +4,20 @@ from inspect import signature import sys from functools import partial +from deephaven.time import to_j_instant, to_j_zdt, to_j_local_date + +from ..types import Date, JavaDate _UNSAFE_PREFIX = "UNSAFE_" _ARIA_PREFIX = "aria_" _ARIA_PREFIX_REPLACEMENT = "aria-" +_CONVERTERS = { + "java.time.Instant": to_j_instant, + "java.time.ZonedDateTime": to_j_zdt, + "java.time.LocalDate": to_j_local_date, +} + def get_component_name(component: Any) -> str: """ @@ -194,11 +203,195 @@ def create_props(args: dict[str, Any]) -> tuple[tuple[Any, ...], dict[str, Any]] Returns: A tuple of children and props """ - children, props = args.pop("children"), args.pop("props") + children, props = args.pop("children", tuple()), args.pop("props", {}) props.update(args) return children, props +def _convert_to_java_date( + date: Date, +) -> JavaDate: + """ + Convert a Date to a Java date type. + In order of preference, tries to convert to Instant, ZonedDateTime, and LocalDate. + If none of these work, raises a TypeError. + + Args: + date: The date to convert to a Java date type. + + Returns: + The Java date type. + """ + try: + return to_j_instant(date) # type: ignore + except Exception: + # ignore, try next + pass + + try: + return to_j_zdt(date) # type: ignore + except Exception: + # ignore, try next + pass + + try: + return to_j_local_date(date) # type: ignore + except Exception: + raise TypeError( + f"Could not convert {date} to one of Instant, ZonedDateTime, or LocalDate." + ) + + +def get_jclass_name(value: Any) -> str: + """ + Get the name of the Java class of the value. + + Args: + value: The value to get the Java class name of. + + Returns: + The name of the Java class of the value. + """ + return str(value.jclass)[6:] + + +def _jclass_converter( + value: JavaDate, +) -> Callable[[Date], Any]: + """ + Get the converter for the Java date type. + + Args: + value: The Java date type to get the converter for. + + Returns: + The converter for the Java date type. + """ + return _CONVERTERS[get_jclass_name(value)] + + +def _wrap_date_callable( + date_callable: Callable[[Date], None], + converter: Callable[[Date], Any], +) -> Callable[[Date], None]: + """ + Wrap a callable to convert the Date argument to a Java date type. + This maintains the original callable signature so that the Date argument can be dropped. + + Args: + date_callable: The callable to wrap. + converter: The date converter to use. + + Returns: + The wrapped callable. + """ + return lambda date: wrap_callable(date_callable)(converter(date)) + + +def _get_first_set_key(props: dict[str, Any], sequence: Sequence[str]) -> str | None: + """ + Of the keys in sequence, get the first key that has a non-None value in props. + If none of the keys have a non-None value, return None. + + Args: + props: The props to check for non-None values. + sequence: The sequence to check. + + Returns: + The first non-None prop, or None if all props are None. + """ + for key in sequence: + if props.get(key) is not None: + return key + return None + + +def _prioritized_callable_converter( + props: dict[str, Any], + priority: Sequence[str], + default_converter: Callable[[Date], Any], +) -> Callable[[Date], Any]: + """ + Get a callable date converter based on the type of the first non-None prop set. + Checks the props in the order provided by the `priority` sequence. + All the props in `priority` should be Java date types already. + We do this so conversion so that the type returned on callbacks matches the type passed in by the user. + If none of the props in `priority` are present, returns the default converter. + + Args: + props: The props passed to the component. + priority: The priority of the props to check. + default_converter: The default converter to use if none of the priority props are present. + + Returns: + The callable date converter. + """ + + first_set_key = _get_first_set_key(props, priority) + return ( + _jclass_converter(props[first_set_key]) + if first_set_key is not None + else default_converter + ) + + +def convert_list_prop( + key: str, + value: list[Date] | None, +) -> list[JavaDate] | None: + """ + Convert a list of Dates to Java date types. + + Args: + key: The key of the prop. + value: A list of Dates to convert to Java date types. + + Returns: + The list of Java date types. + """ + if value is None: + return None + + if not isinstance(value, list): + raise TypeError(f"{key} must be a list of Dates") + return [_convert_to_java_date(date) for date in value] + + +def convert_date_props( + props: dict[str, Any], + simple_date_props: set[str], + callable_date_props: set[str], + priority: Sequence[str], + default_converter: Callable[[Date], Any] = to_j_instant, +) -> None: + """ + Convert date props to Java date types in place. + + Args: + props: The props passed to the component. + simple_date_props: A set of simple date keys to convert. The prop value should be a single Date. + callable_date_props: A set of callable date keys to convert. + The prop value should be a callable that takes a Date. + priority: The priority of the props to check. + default_converter: The default converter to use if none of the priority props are present. + + Returns: + The converted props. + """ + for key in simple_date_props: + if props.get(key) is not None: + props[key] = _convert_to_java_date(props[key]) + + # the simple props must be converted before this to simplify the callable conversion + converter = _prioritized_callable_converter(props, priority, default_converter) + + for key in callable_date_props: + if props.get(key) is not None: + if not callable(props[key]): + raise TypeError(f"{key} must be a callable") + props[key] = _wrap_date_callable(props[key], converter) + + def unpack_item_table_source( children: tuple[Any, ...], props: dict[str, Any], diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index 09efe6596..a1acce803 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -15,6 +15,7 @@ from .list_action_group import list_action_group from .list_action_menu import list_action_menu from .item_table_source import item_table_source +from .date_picker import date_picker from . import html @@ -29,6 +30,7 @@ "content", "contextual_help", "dashboard", + "date_picker", "flex", "form", "fragment", diff --git a/plugins/ui/src/deephaven/ui/components/date_picker.py b/plugins/ui/src/deephaven/ui/components/date_picker.py new file mode 100644 index 000000000..98160cf42 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/date_picker.py @@ -0,0 +1,273 @@ +from __future__ import annotations + +from typing import Any, Sequence, Callable + +from .spectrum import ( + FocusEventCallable, + KeyboardEventCallable, + LayoutFlex, + Number, + DimensionValue, + AlignSelf, + JustifySelf, + Position, + AriaPressed, + CSSProperties, + LabelPosition, + Alignment, + ValidationBehavior, + NecessityIndicator, + ValidationState, + PageBehavior, + HourCycle, +) +from ..hooks import use_memo +from ..elements import Element, BaseElement +from .._internal.utils import ( + create_props, + convert_date_props, + convert_list_prop, +) +from ..types import Date, Granularity + +DatePickerElement = Element + +# All the props that can be date types +_SIMPLE_DATE_PROPS = { + "placeholder_value", + "value", + "default_value", + "min_value", + "max_value", +} +_LIST_DATE_PROPS = {"unavailable_values"} +_CALLABLE_DATE_PROPS = {"on_change"} + +# The priority of the date props to determine the format of the date passed to the callable date props +_DATE_PROPS_PRIORITY = ["value", "default_value", "placeholder_value"] + + +def _convert_date_picker_props( + props: dict[str, Any], +) -> dict[str, Any]: + """ + Convert date picker props to Java date types. + + Args: + props: The props passed to the date picker. + + Returns: + The converted props. + """ + + convert_date_props( + props, + _SIMPLE_DATE_PROPS, + _CALLABLE_DATE_PROPS, + _DATE_PROPS_PRIORITY, + ) + + return props + + +def date_picker( + placeholder_value: Date | None = None, + value: Date | None = None, + default_value: Date | None = None, + min_value: Date | None = None, + max_value: Date | None = None, + unavailable_values: Sequence[Date] | None = None, + granularity: Granularity | None = None, + page_behavior: PageBehavior | None = None, + hour_cycle: HourCycle | None = None, + hide_time_zone: bool = False, + should_force_leading_zeros: bool | None = None, + is_disabled: bool | None = None, + is_read_only: bool | None = None, + is_required: bool | None = None, + validation_behavior: ValidationBehavior | None = None, + auto_focus: bool | None = None, + label: Element | None = None, + description: Element | None = None, + error_message: Element | None = None, + is_open: bool | None = None, + default_open: bool | None = None, + name: str | None = None, + max_visible_months: int | None = None, + should_flip: bool | None = None, + is_quiet: bool | None = None, + show_format_help_text: bool | None = None, + label_position: LabelPosition | None = None, + label_align: Alignment | None = None, + necessity_indicator: NecessityIndicator | None = None, + contextual_help: Element | None = None, + validation_state: ValidationState | None = None, + on_focus: FocusEventCallable | None = None, + on_blur: FocusEventCallable | None = None, + on_focus_change: Callable[[bool], None] | None = None, + on_key_down: KeyboardEventCallable | None = None, + on_key_up: KeyboardEventCallable | None = None, + on_open_change: Callable[[bool], None] | None = None, + on_change: Callable[[Date], None] | None = None, + flex: LayoutFlex | None = None, + flex_grow: Number | None = None, + flex_shrink: Number | None = None, + flex_basis: DimensionValue | None = None, + align_self: AlignSelf | None = None, + justify_self: JustifySelf | None = None, + order: Number | None = None, + grid_area: str | None = None, + grid_row: str | None = None, + grid_row_start: str | None = None, + grid_row_end: str | None = None, + grid_column: str | None = None, + grid_column_start: str | None = None, + grid_column_end: str | None = None, + margin: DimensionValue | None = None, + margin_top: DimensionValue | None = None, + margin_bottom: DimensionValue | None = None, + margin_start: DimensionValue | None = None, + margin_end: DimensionValue | None = None, + margin_x: DimensionValue | None = None, + margin_y: DimensionValue | None = None, + width: DimensionValue | None = None, + height: DimensionValue | None = None, + min_width: DimensionValue | None = None, + min_height: DimensionValue | None = None, + max_width: DimensionValue | None = None, + max_height: DimensionValue | None = None, + position: Position | None = None, + top: DimensionValue | None = None, + bottom: DimensionValue | None = None, + start: DimensionValue | None = None, + end: DimensionValue | None = None, + left: DimensionValue | None = None, + right: DimensionValue | None = None, + z_index: Number | None = None, + is_hidden: bool | None = None, + id: str | None = None, + aria_label: str | None = None, + aria_labelledby: str | None = None, + aria_describedby: str | None = None, + aria_pressed: AriaPressed | None = None, + aria_details: str | None = None, + UNSAFE_class_name: str | None = None, + UNSAFE_style: CSSProperties | None = None, +) -> DatePickerElement: + """ + A date picker allows the user to select a date. + + + Args: + placeholder_value: A placeholder date that influences the format of the + placeholder shown when no value is selected. + Defaults to today at midnight in the user's timezone. + value: The current value (controlled). + default_value: The default value (uncontrolled). + min_value: The minimum allowed date that a user may select. + max_value: The maximum allowed date that a user may select. + unavailable_values: A list of dates that cannot be selected. + granularity: Determines the smallest unit that is displayed in the date picker. + By default, this is `"DAY"` for `LocalDate`, and `"SECOND"` otherwise. + page_behavior: Controls the behavior of paging. Pagination either works by + advancing the visible page by visibleDuration (default) + or one unit of visibleDuration. + hour_cycle: Whether to display the time in 12 or 24 hour format. + By default, this is determined by the user's locale. + hide_time_zone: Whether to hide the time zone abbreviation. + should_force_leading_zeros: Whether to always show leading zeros in the + month, day, and hour fields. + By default, this is determined by the user's locale. + is_disabled: Whether the input is disabled. + is_read_only: Whether the input can be selected but not changed by the user. + is_required: Whether user input is required on the input before form submission. + validation_behavior: Whether to use native HTML form validation to prevent form + submission when the value is missing or invalid, + or mark the field as required or invalid via ARIA. + auto_focus: Whether the element should receive focus on render. + label: The content to display as the label. + description: A description for the field. + Provides a hint such as specific requirements for what to choose. + error_message: An error message for the field. + is_open: Whether the overlay is open by default (controlled). + default_open: Whether the overlay is open by default (uncontrolled). + name: The name of the input element, used when submitting an HTML form. + max_visible_months: The maximum number of months to display at + once in the calendar popover, if screen space permits. + should_flip: Whether the calendar popover should automatically flip direction + when space is limited. + is_quiet: Whether the date picker should be displayed with a quiet style. + show_format_help_text: Whether to show the localized date format as help + text below the field. + label_position: The label's overall position relative to the element it is labeling. + label_align: The label's horizontal alignment relative to the element it is labeling. + necessity_indicator: Whether the required state should be shown as an icon or text. + contextual_help: A ContextualHelp element to place next to the label. + validation_state: Whether the input should display its "valid" or "invalid" visual styling. + on_focus: Function called when the button receives focus. + on_blur: Function called when the button loses focus. + on_focus_change: Function called when the focus state changes. + on_key_down: Function called when a key is pressed. + on_key_up: Function called when a key is released. + on_open_change: Handler that is called when the overlay's open state changes. + on_change: Handler that is called when the value changes. + The exact `Date` type will be the same as the type passed to + `value`, `default_value` or `placeholder_value`, in that order of precedence. + flex: When used in a flex layout, specifies how the element will grow or shrink to fit the space available. + flex_grow: When used in a flex layout, specifies how much the element will grow to fit the space available. + flex_shrink: When used in a flex layout, specifies how much the element will shrink to fit the space available. + flex_basis: When used in a flex layout, specifies the initial size of the element. + align_self: Overrides the align_items property of a flex or grid container. + justify_self: Specifies how the element is justified inside a flex or grid container. + order: The layout order for the element within a flex or grid container. + grid_area: The name of the grid area to place the element in. + grid_row: The name of the grid row to place the element in. + grid_row_start: The name of the grid row to start the element in. + grid_row_end: The name of the grid row to end the element in. + grid_column: The name of the grid column to place the element in. + grid_column_start: The name of the grid column to start the element in. + grid_column_end: The name of the grid column to end the element in. + margin: The margin to apply around the element. + margin_top: The margin to apply above the element. + margin_bottom: The margin to apply below the element. + margin_start: The margin to apply before the element. + margin_end: The margin to apply after the element. + margin_x: The margin to apply to the left and right of the element. + margin_y: The margin to apply to the top and bottom of the element. + width: The width of the element. + height: The height of the element. + min_width: The minimum width of the element. + min_height: The minimum height of the element. + max_width: The maximum width of the element. + max_height: The maximum height of the element. + position: Specifies how the element is positioned. + top: The distance from the top of the containing element. + bottom: The distance from the bottom of the containing element. + start: The distance from the start of the containing element. + end: The distance from the end of the containing element. + left: The distance from the left of the containing element. + right: The distance from the right of the containing element. + z_index: The stack order of the element. + is_hidden: Whether the element is hidden. + id: A unique identifier for the element. + aria_label: The label for the element. + aria_labelledby: The id of the element that labels the element. + aria_describedby: The id of the element that describes the element. + aria_pressed: Whether the element is pressed. + aria_details: The details for the element. + UNSAFE_class_name: A CSS class to apply to the element. + UNSAFE_style: A CSS style to apply to the element. + + Returns: + The date picker element. + """ + _, props = create_props(locals()) + + _convert_date_picker_props(props) + + props["unavailable_values"] = use_memo( + lambda: convert_list_prop("unavailable_values", props["unavailable_values"]), + [unavailable_values], + ) + + return BaseElement("deephaven.ui.components.DatePicker", **props) diff --git a/plugins/ui/src/deephaven/ui/components/panel.py b/plugins/ui/src/deephaven/ui/components/panel.py index 2f446c683..82da15bec 100644 --- a/plugins/ui/src/deephaven/ui/components/panel.py +++ b/plugins/ui/src/deephaven/ui/components/panel.py @@ -2,16 +2,63 @@ from typing import Any from ..elements import BaseElement +from .._internal.utils import create_props +from .spectrum.layout import ( + Direction, + Wrap, + JustifyContent, + AlignContent, + AlignItems, + DimensionValue, +) -def panel(*children: Any, title: str | None = None, **kwargs: Any): +def panel( + *children: Any, + title: str | None = None, + direction: Direction | None = "column", + wrap: Wrap | None = None, + justify_content: JustifyContent | None = None, + align_content: AlignContent | None = None, + align_items: AlignItems | None = None, + gap: DimensionValue | None = "size-100", + column_gap: DimensionValue | None = None, + row_gap: DimensionValue | None = None, + padding: DimensionValue | None = "size-100", + padding_top: DimensionValue | None = None, + padding_bottom: DimensionValue | None = None, + padding_start: DimensionValue | None = None, + padding_end: DimensionValue | None = None, + padding_x: DimensionValue | None = None, + padding_y: DimensionValue | None = None, + **props: Any, +): """ A panel is a container that can be used to group elements. Args: children: Elements to render in the panel. title: Title of the panel. + direction: The direction in which to layout children. + wrap: Whether children should wrap when they exceed the panel's width. + justify_content: The distribution of space around items along the main axis. + align_content: The distribution of space between and around items along the cross axis. + align_items: The alignment of children within their container. + gap: The space to display between both rows and columns of children. + column_gap: The space to display between columns of children. + row_gap: The space to display between rows of children. + padding: The padding to apply around the element. + padding_top: The padding to apply above the element. + padding_bottom: The padding to apply below the element. + padding_start: The padding to apply before the element. + padding_end: The padding to apply after the element. + padding_x: The padding to apply to the left and right of the element. + padding_y: The padding to apply to the top and bottom of the element. """ + + children, props = create_props(locals()) return BaseElement( - "deephaven.ui.components.Panel", *children, title=title, **kwargs + "deephaven.ui.components.Panel", + *children, + **props, ) diff --git a/plugins/ui/src/deephaven/ui/components/spectrum/__init__.py b/plugins/ui/src/deephaven/ui/components/spectrum/__init__.py index 3f5ce97a6..999367be5 100644 --- a/plugins/ui/src/deephaven/ui/components/spectrum/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/spectrum/__init__.py @@ -5,3 +5,4 @@ from .text_field import * from .toggle_button import * from .flex import * +from .date_picker import * diff --git a/plugins/ui/src/deephaven/ui/components/spectrum/date_picker.py b/plugins/ui/src/deephaven/ui/components/spectrum/date_picker.py new file mode 100644 index 000000000..3c8be378d --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/spectrum/date_picker.py @@ -0,0 +1,8 @@ +from typing import Literal + +PageBehavior = Literal["single", "visible"] +HourCycle = Literal[12, 24] +ValidationBehavior = Literal["aria", "native"] +Alignment = Literal["start", "end"] +NecessityIndicator = Literal["label", "icon"] +ValidationState = Literal["valid", "invalid"] diff --git a/plugins/ui/src/deephaven/ui/components/spectrum/flex.py b/plugins/ui/src/deephaven/ui/components/spectrum/flex.py index 91f5f2f69..0eb365e03 100644 --- a/plugins/ui/src/deephaven/ui/components/spectrum/flex.py +++ b/plugins/ui/src/deephaven/ui/components/spectrum/flex.py @@ -2,6 +2,7 @@ from typing import Any from .basic import spectrum_element from .layout import ( + LayoutFlex, Direction, Wrap, JustifyContent, @@ -13,6 +14,7 @@ def flex( *children: Any, + flex: LayoutFlex | None = "auto", direction: Direction | None = None, wrap: Wrap | None = None, justify_content: JustifyContent | None = None, @@ -24,12 +26,24 @@ def flex( **props: Any, ): """ - Python implementation for the Adobe React Spectrum Flex component. - https://react-spectrum.adobe.com/react-spectrum/Flex.html + Base Flex component for laying out children in a flexbox. + + Args: + children: Elements to render in the flexbox. + flex: The flex property of the flexbox. + direction: The direction in which to layout children. + wrap: Whether children should wrap when they exceed the panel's width. + justify_content: The distribution of space around items along the main axis. + align_content: The distribution of space between and around items along the cross axis. + align_items: The alignment of children within their container. + gap: The space to display between both rows and columns of children. + column_gap: The space to display between columns of children. + row_gap: The space to display between rows of children. """ return spectrum_element( "Flex", *children, + flex=flex, direction=direction, wrap=wrap, justify_content=justify_content, diff --git a/plugins/ui/src/deephaven/ui/components/spectrum/toggle_button.py b/plugins/ui/src/deephaven/ui/components/spectrum/toggle_button.py index e7ce1c445..9b058c3cd 100644 --- a/plugins/ui/src/deephaven/ui/components/spectrum/toggle_button.py +++ b/plugins/ui/src/deephaven/ui/components/spectrum/toggle_button.py @@ -31,6 +31,7 @@ def toggle_button( is_quiet: bool | None = None, static_color: StaticColor | None = None, type: ButtonType = "button", + on_change: Callable[[bool], None] | None = None, on_press: PressEventCallable | None = None, on_press_start: PressEventCallable | None = None, on_press_end: PressEventCallable | None = None, @@ -103,6 +104,7 @@ def toggle_button( is_quiet: Whether the button should be quiet. static_color: The static color style to apply. Useful when the button appears over a color background. type: The type of button to render. (default: "button") + on_change: Handler that is called when the element's selection state changes. on_press: Function called when the button is pressed. on_press_start: Function called when the button is pressed. on_press_end: Function called when a press interaction ends, either over the target or when the pointer leaves the target. diff --git a/plugins/ui/src/deephaven/ui/hooks/use_table_data.py b/plugins/ui/src/deephaven/ui/hooks/use_table_data.py index 3f2714612..200fe7049 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_table_data.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_table_data.py @@ -142,6 +142,10 @@ def use_table_data( table_updated = lambda: _set_new_data(table, sentinel, set_data, set_is_sentinel) + # call table_updated in the case of new table or sentinel + ui.use_effect(table_updated, [table, sentinel]) + + # call table_updated every time the table updates ui.use_table_listener( table, partial(_on_update, ctx, table_updated, executor_name), [] ) diff --git a/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py b/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py index a7eef4ad5..06ab38fc8 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import partial -from typing import Any, Callable, Sequence +from typing import Callable from deephaven.table import Table from deephaven.table_listener import listen, TableUpdate, TableListener @@ -86,10 +86,6 @@ def use_table_listener( replay_lock: The lock type used during replay, default is ‘shared’, can also be ‘exclusive’. """ - if not table.is_refreshing and not do_replay: - # if the table is not refreshing, and is not replaying, there is nothing to listen to - return - def start_listener() -> Callable[[], None]: """ Start the listener. Returns a function that can be called to stop the listener by the use_effect hook. @@ -97,6 +93,9 @@ def start_listener() -> Callable[[], None]: Returns: A function that can be called to stop the listener by the use_effect hook. """ + if not table.is_refreshing and not do_replay: + return lambda: None + handle = listen( table, wrap_listener(listener), diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index c48bde538..34f713d94 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -1,5 +1,9 @@ +import datetime +import pandas +import numpy from typing import Any, Dict, Literal, Union, List, Tuple, Callable, TypedDict, Sequence from deephaven import SortDirection +from deephaven.dtypes import DType class CellData(TypedDict): @@ -107,6 +111,34 @@ class RowDataValue(CellData): Stringable = Union[str, int, float, bool] Key = Stringable ActionKey = Key +LocalDate = DType +Instant = DType +ZonedDateTime = DType +JavaDate = Union[LocalDate, Instant, ZonedDateTime] +LocalDateConvertible = Union[ + None, + LocalDate, + str, + datetime.date, + datetime.datetime, + numpy.datetime64, + pandas.Timestamp, +] +InstantConvertible = Union[ + None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp # type: ignore +] +ZonedDateTimeConvertible = Union[ + None, ZonedDateTime, str, datetime.datetime, numpy.datetime64, pandas.Timestamp # type: ignore +] +Date = Union[ + Instant, + LocalDate, + ZonedDateTime, + LocalDateConvertible, + InstantConvertible, + ZonedDateTimeConvertible, +] +Granularity = Literal["DAY", "HOUR", "MINUTE", "SECOND"] Dependencies = Union[Tuple[Any], List[Any]] Selection = Sequence[Key] diff --git a/plugins/ui/src/js/package.json b/plugins/ui/src/js/package.json index a367cb3ea..5045bf969 100644 --- a/plugins/ui/src/js/package.json +++ b/plugins/ui/src/js/package.json @@ -1,6 +1,6 @@ { "name": "@deephaven/js-plugin-ui", - "version": "0.11.0", + "version": "0.13.0", "description": "Deephaven UI plugin", "keywords": [ "Deephaven", diff --git a/plugins/ui/src/js/src/elements/ElementUtils.tsx b/plugins/ui/src/js/src/elements/ElementUtils.tsx index 3058ec267..9fb4fca60 100644 --- a/plugins/ui/src/js/src/elements/ElementUtils.tsx +++ b/plugins/ui/src/js/src/elements/ElementUtils.tsx @@ -25,7 +25,7 @@ export type ObjectNode = { */ export type ElementNode< K extends string = string, - P extends Record = Record + P extends Record = Record, > = { /** * The type of this element. Can be something like `deephaven.ui.components.Panel`, or @@ -37,7 +37,7 @@ export type ElementNode< export type ElementNodeWithChildren< K extends string = string, - P extends Record = Record + P extends Record = Record, > = ElementNode & { props: React.PropsWithChildren

; }; diff --git a/plugins/ui/src/js/src/elements/ObjectView.tsx b/plugins/ui/src/js/src/elements/ObjectView.tsx index cc6083283..8f8b32f1f 100644 --- a/plugins/ui/src/js/src/elements/ObjectView.tsx +++ b/plugins/ui/src/js/src/elements/ObjectView.tsx @@ -6,7 +6,7 @@ import type { dh } from '@deephaven/jsapi-types'; const log = Log.module('@deephaven/js-plugin-ui/ObjectView'); export type ObjectViewProps = { object: dh.WidgetExportedObject }; -function ObjectView(props: ObjectViewProps) { +function ObjectView(props: ObjectViewProps): JSX.Element { const { object } = props; log.info('Object is', object); const { type } = object; diff --git a/plugins/ui/src/js/src/elements/Picker.tsx b/plugins/ui/src/js/src/elements/Picker.tsx index 7cc7f023f..1e564a33f 100644 --- a/plugins/ui/src/js/src/elements/Picker.tsx +++ b/plugins/ui/src/js/src/elements/Picker.tsx @@ -22,7 +22,7 @@ type WrappedDHPickerJSApiProps = Omit & { export type PickerProps = (DHPickerProps | WrappedDHPickerJSApiProps) & SerializedPickerEventProps; -function Picker({ children, ...props }: PickerProps) { +function Picker({ children, ...props }: PickerProps): JSX.Element { const settings = useSelector(getSettings); const pickerProps = usePickerProps(props); diff --git a/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts b/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts index 1f3131040..94c56e405 100644 --- a/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts +++ b/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts @@ -14,13 +14,12 @@ import { TabList, Text, ToggleButton, - View, } from '@adobe/react-spectrum'; import { ValueOf } from '@deephaven/utils'; +import { Flex, View } from '@deephaven/components'; import { ActionButton, Button, - Flex, Form, RangeSlider, Slider, diff --git a/plugins/ui/src/js/src/elements/UITable.tsx b/plugins/ui/src/js/src/elements/UITable.tsx index 9d901f4df..0c137c27e 100644 --- a/plugins/ui/src/js/src/elements/UITable.tsx +++ b/plugins/ui/src/js/src/elements/UITable.tsx @@ -30,7 +30,7 @@ function UITable({ sorts, alwaysFetchColumns, table: exportedTable, -}: UITableProps) { +}: UITableProps): JSX.Element | null { const dh = useApi(); const [model, setModel] = useState(); const [columns, setColumns] = useState(); diff --git a/plugins/ui/src/js/src/elements/spectrum/ActionButton.tsx b/plugins/ui/src/js/src/elements/spectrum/ActionButton.tsx index f04f800dd..fac0936e5 100644 --- a/plugins/ui/src/js/src/elements/spectrum/ActionButton.tsx +++ b/plugins/ui/src/js/src/elements/spectrum/ActionButton.tsx @@ -7,7 +7,7 @@ import { SerializedButtonEventProps, useButtonProps } from './useButtonProps'; function ActionButton( props: SpectrumActionButtonProps & SerializedButtonEventProps -) { +): JSX.Element { const buttonProps = useButtonProps(props); // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/plugins/ui/src/js/src/elements/spectrum/Button.tsx b/plugins/ui/src/js/src/elements/spectrum/Button.tsx index c15ee7939..7db01e1b5 100644 --- a/plugins/ui/src/js/src/elements/spectrum/Button.tsx +++ b/plugins/ui/src/js/src/elements/spectrum/Button.tsx @@ -5,7 +5,9 @@ import { } from '@adobe/react-spectrum'; import { SerializedButtonEventProps, useButtonProps } from './useButtonProps'; -function Button(props: SpectrumButtonProps & SerializedButtonEventProps) { +function Button( + props: SpectrumButtonProps & SerializedButtonEventProps +): JSX.Element { const buttonProps = useButtonProps(props); // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/plugins/ui/src/js/src/elements/spectrum/Flex.tsx b/plugins/ui/src/js/src/elements/spectrum/Flex.tsx deleted file mode 100644 index 23362fce3..000000000 --- a/plugins/ui/src/js/src/elements/spectrum/Flex.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { Flex as SpectrumFlex, FlexProps } from '@adobe/react-spectrum'; - -function Flex(props: FlexProps) { - // eslint-disable-next-line react/jsx-props-no-spreading - return ; -} - -Flex.displayName = 'Flex'; - -export default Flex; diff --git a/plugins/ui/src/js/src/elements/spectrum/Form.tsx b/plugins/ui/src/js/src/elements/spectrum/Form.tsx index 9dffdadfb..928effc33 100644 --- a/plugins/ui/src/js/src/elements/spectrum/Form.tsx +++ b/plugins/ui/src/js/src/elements/spectrum/Form.tsx @@ -5,7 +5,7 @@ function Form( props: SpectrumFormProps & { onSubmit?: (data: { [key: string]: FormDataEntryValue }) => void; } -) { +): JSX.Element { const { onSubmit: propOnSubmit, ...otherProps } = props; const onSubmit = useCallback( diff --git a/plugins/ui/src/js/src/elements/spectrum/RangeSlider.tsx b/plugins/ui/src/js/src/elements/spectrum/RangeSlider.tsx index 0f164b7a8..073fb5fa3 100644 --- a/plugins/ui/src/js/src/elements/spectrum/RangeSlider.tsx +++ b/plugins/ui/src/js/src/elements/spectrum/RangeSlider.tsx @@ -9,7 +9,7 @@ const VALUE_CHANGE_DEBOUNCE = 250; const EMPTY_FUNCTION = () => undefined; -function RangeSlider(props: SpectrumRangeSliderProps) { +function RangeSlider(props: SpectrumRangeSliderProps): JSX.Element { const { defaultValue = { start: 0, end: 0 }, value: propValue, diff --git a/plugins/ui/src/js/src/elements/spectrum/Slider.tsx b/plugins/ui/src/js/src/elements/spectrum/Slider.tsx index 28de56e84..f2bf43880 100644 --- a/plugins/ui/src/js/src/elements/spectrum/Slider.tsx +++ b/plugins/ui/src/js/src/elements/spectrum/Slider.tsx @@ -9,7 +9,7 @@ const VALUE_CHANGE_DEBOUNCE = 250; const EMPTY_FUNCTION = () => undefined; -function Slider(props: SpectrumSliderProps) { +function Slider(props: SpectrumSliderProps): JSX.Element { const { defaultValue = 0, value: propValue, diff --git a/plugins/ui/src/js/src/elements/spectrum/TabPanels.tsx b/plugins/ui/src/js/src/elements/spectrum/TabPanels.tsx index b13310dc5..47febf562 100644 --- a/plugins/ui/src/js/src/elements/spectrum/TabPanels.tsx +++ b/plugins/ui/src/js/src/elements/spectrum/TabPanels.tsx @@ -4,7 +4,9 @@ import { SpectrumTabPanelsProps, } from '@adobe/react-spectrum'; -function TabPanels(props: SpectrumTabPanelsProps) { +function TabPanels( + props: SpectrumTabPanelsProps +): JSX.Element { const { UNSAFE_style: unsafeStyle, ...otherProps } = props; return ( diff --git a/plugins/ui/src/js/src/elements/spectrum/TextField.tsx b/plugins/ui/src/js/src/elements/spectrum/TextField.tsx index 40726a5ba..1cac27244 100644 --- a/plugins/ui/src/js/src/elements/spectrum/TextField.tsx +++ b/plugins/ui/src/js/src/elements/spectrum/TextField.tsx @@ -9,7 +9,7 @@ const VALUE_CHANGE_DEBOUNCE = 250; const EMPTY_FUNCTION = () => undefined; -function TextField(props: SpectrumTextFieldProps) { +function TextField(props: SpectrumTextFieldProps): JSX.Element { const { defaultValue = '', value: propValue, diff --git a/plugins/ui/src/js/src/elements/spectrum/index.ts b/plugins/ui/src/js/src/elements/spectrum/index.ts index c7b144d93..a3e908593 100644 --- a/plugins/ui/src/js/src/elements/spectrum/index.ts +++ b/plugins/ui/src/js/src/elements/spectrum/index.ts @@ -6,7 +6,6 @@ */ export { default as ActionButton } from './ActionButton'; export { default as Button } from './Button'; -export { default as Flex } from './Flex'; export { default as Form } from './Form'; export { default as RangeSlider } from './RangeSlider'; export { default as Slider } from './Slider'; diff --git a/plugins/ui/src/js/src/elements/spectrum/mapSpectrumProps.ts b/plugins/ui/src/js/src/elements/spectrum/mapSpectrumProps.ts index daba81688..c7c693473 100644 --- a/plugins/ui/src/js/src/elements/spectrum/mapSpectrumProps.ts +++ b/plugins/ui/src/js/src/elements/spectrum/mapSpectrumProps.ts @@ -6,7 +6,7 @@ import mapSpectrumChildren from './mapSpectrumChildren'; * @param props Props to map as spectrum props */ export function mapSpectrumProps< - T extends PropsWithChildren> + T extends PropsWithChildren>, >(props: T): T { return { ...props, diff --git a/plugins/ui/src/js/src/elements/spectrum/useButtonProps.ts b/plugins/ui/src/js/src/elements/spectrum/useButtonProps.ts index 4ecf16829..d0daa1297 100644 --- a/plugins/ui/src/js/src/elements/spectrum/useButtonProps.ts +++ b/plugins/ui/src/js/src/elements/spectrum/useButtonProps.ts @@ -43,7 +43,10 @@ export type SerializedButtonEventProps = { onKeyUp?: SerializedKeyboardEventCallback; }; -export function useButtonProps(props: SerializedButtonEventProps & T) { +// returns SpectrumButtonProps +export function useButtonProps( + props: SerializedButtonEventProps & T +): T & SerializedButtonEventProps { const { onPress: propOnPress, onPressStart: propsOnPressStart, @@ -75,5 +78,5 @@ export function useButtonProps(props: SerializedButtonEventProps & T) { onKeyDown, onKeyUp, ...mapSpectrumProps(otherProps), - }; + } as T & SerializedButtonEventProps; } diff --git a/plugins/ui/src/js/src/elements/spectrum/useFocusEventCallback.ts b/plugins/ui/src/js/src/elements/spectrum/useFocusEventCallback.ts index e259d0ee7..adb1565b8 100644 --- a/plugins/ui/src/js/src/elements/spectrum/useFocusEventCallback.ts +++ b/plugins/ui/src/js/src/elements/spectrum/useFocusEventCallback.ts @@ -31,7 +31,9 @@ export type SerializedFocusEventCallback = ( * @param callback FocusEvent callback to be called with the serialized event * @returns A callback to be passed into the Spectrum component that transforms the event and calls the provided callback */ -export function useFocusEventCallback(callback?: SerializedFocusEventCallback) { +export function useFocusEventCallback( + callback?: SerializedFocusEventCallback +): (e: FocusEvent) => void { return useCallback( (e: FocusEvent) => { callback?.(serializeFocusEvent(e)); diff --git a/plugins/ui/src/js/src/elements/spectrum/useKeyboardEventCallback.ts b/plugins/ui/src/js/src/elements/spectrum/useKeyboardEventCallback.ts index bd06e6fed..b04a207ca 100644 --- a/plugins/ui/src/js/src/elements/spectrum/useKeyboardEventCallback.ts +++ b/plugins/ui/src/js/src/elements/spectrum/useKeyboardEventCallback.ts @@ -33,7 +33,7 @@ export type SerializedKeyboardEventCallback = ( */ export function useKeyboardEventCallback( callback?: SerializedKeyboardEventCallback -) { +): (e: KeyboardEvent) => void { return useCallback( (e: KeyboardEvent) => { callback?.(serializeKeyboardEvent(e)); diff --git a/plugins/ui/src/js/src/elements/spectrum/usePressEventCallback.ts b/plugins/ui/src/js/src/elements/spectrum/usePressEventCallback.ts index da68bdd88..563660f0a 100644 --- a/plugins/ui/src/js/src/elements/spectrum/usePressEventCallback.ts +++ b/plugins/ui/src/js/src/elements/spectrum/usePressEventCallback.ts @@ -34,7 +34,9 @@ export type SerializedPressEventCallback = ( * @param callback PressEvent callback to be called with the serialized event * @returns A callback to be passed into the Spectrum component that transforms the event and calls the provided callback */ -export function usePressEventCallback(callback?: SerializedPressEventCallback) { +export function usePressEventCallback( + callback?: SerializedPressEventCallback +): (e: PressEvent) => void { return useCallback( (e: PressEvent) => { callback?.(serializePressEvent(e)); diff --git a/plugins/ui/src/js/src/elements/usePickerProps.ts b/plugins/ui/src/js/src/elements/usePickerProps.ts index 5261f3de6..d4d71d1df 100644 --- a/plugins/ui/src/js/src/elements/usePickerProps.ts +++ b/plugins/ui/src/js/src/elements/usePickerProps.ts @@ -26,7 +26,9 @@ export interface SerializedPickerEventProps { * @param props Props to wrap * @returns Wrapped props */ -export function usePickerProps(props: SerializedPickerEventProps & T) { +export function usePickerProps( + props: SerializedPickerEventProps & T +): T & SerializedPickerEventProps { const { onFocus, onBlur, onKeyDown, onKeyUp, ...otherProps } = props; const serializedOnFocus = useFocusEventCallback(onFocus); @@ -44,5 +46,5 @@ export function usePickerProps(props: SerializedPickerEventProps & T) { // handles nested children inside of `Item` and `Section` components, so // we are intentionally not wrapping `otherProps` in `mapSpectrumProps` ...otherProps, - }; + } as T & SerializedPickerEventProps; } diff --git a/plugins/ui/src/js/src/layout/Column.tsx b/plugins/ui/src/js/src/layout/Column.tsx index eb9f21901..c60cbae36 100644 --- a/plugins/ui/src/js/src/layout/Column.tsx +++ b/plugins/ui/src/js/src/layout/Column.tsx @@ -1,13 +1,13 @@ import React, { useEffect, useMemo } from 'react'; import { useLayoutManager } from '@deephaven/dashboard'; import type { RowOrColumn } from '@deephaven/golden-layout'; +import { Flex } from '@deephaven/components'; import { normalizeColumnChildren, type ColumnElementProps, } from './LayoutUtils'; import { ParentItemContext, useParentItem } from './ParentItemContext'; import { usePanelId } from './ReactPanelContext'; -import { Flex } from '../elements/spectrum'; function LayoutColumn({ children, diff --git a/plugins/ui/src/js/src/layout/ParentItemContext.ts b/plugins/ui/src/js/src/layout/ParentItemContext.ts index e6ae4c205..ad613ac40 100644 --- a/plugins/ui/src/js/src/layout/ParentItemContext.ts +++ b/plugins/ui/src/js/src/layout/ParentItemContext.ts @@ -4,7 +4,7 @@ import type { ContentItem } from '@deephaven/golden-layout'; export const ParentItemContext = createContext(null); -export function useParentItem() { +export function useParentItem(): ContentItem { const layoutManager = useLayoutManager(); const parentContextItem = useContext(ParentItemContext); const parentItem = useMemo( diff --git a/plugins/ui/src/js/src/layout/PortalPanelManagerContext.ts b/plugins/ui/src/js/src/layout/PortalPanelManagerContext.ts index 7ecc58902..c304098e4 100644 --- a/plugins/ui/src/js/src/layout/PortalPanelManagerContext.ts +++ b/plugins/ui/src/js/src/layout/PortalPanelManagerContext.ts @@ -7,7 +7,7 @@ export type PortalPanelMap = ReadonlyMap; export const PortalPanelManagerContext = React.createContext(null); -export function usePortalPanelManager() { +export function usePortalPanelManager(): PortalPanelMap { return useContextOrThrow(PortalPanelManagerContext, 'PortalPanelManager'); } diff --git a/plugins/ui/src/js/src/layout/ReactPanel.tsx b/plugins/ui/src/js/src/layout/ReactPanel.tsx index 17165eade..c4202739a 100644 --- a/plugins/ui/src/js/src/layout/ReactPanel.tsx +++ b/plugins/ui/src/js/src/layout/ReactPanel.tsx @@ -7,6 +7,7 @@ import { useLayoutManager, useListener, } from '@deephaven/dashboard'; +import { View, ViewProps, Flex, FlexProps } from '@deephaven/components'; import Log from '@deephaven/log'; import PortalPanel from './PortalPanel'; import { ReactPanelControl, useReactPanel } from './ReactPanelManager'; @@ -17,13 +18,64 @@ import { usePortalPanelManager } from './PortalPanelManagerContext'; const log = Log.module('@deephaven/js-plugin-ui/ReactPanel'); +interface Props + extends ReactPanelProps, + Pick< + ViewProps, + | 'backgroundColor' + | 'padding' + | 'paddingTop' + | 'paddingBottom' + | 'paddingStart' + | 'paddingEnd' + | 'paddingX' + | 'paddingY' + | 'UNSAFE_style' + | 'UNSAFE_className' + >, + Pick< + FlexProps, + | 'wrap' + | 'direction' + | 'justifyContent' + | 'alignContent' + | 'alignItems' + | 'gap' + | 'rowGap' + | 'columnGap' + > {} + /** * Adds and tracks a panel to the GoldenLayout. When the child element is updated, the contents of the panel will also be updated. When unmounted, the panel will be removed. * Will trigger an `onOpen` when the portal is opened, and `onClose` when closed. * Note that because the `PortalPanel` will be saved with the GoldenLayout config, it's possible there is already a panel that exists with the same ID. * In that case, the existing panel will be re-used. */ -function ReactPanel({ children, title }: ReactPanelProps) { +function ReactPanel({ + // Apply the same defaults as panel.py + // but also defined here, incase the panel + // is being implicitly created + children, + title, + backgroundColor, + direction = 'column', + wrap, + justifyContent, + alignContent, + alignItems, + gap = 'size-100', + rowGap, + columnGap, + padding = 'size-100', + paddingTop, + paddingBottom, + paddingStart, + paddingEnd, + paddingX, + paddingY, + UNSAFE_style, + UNSAFE_className, +}: Props): JSX.Element | null { const layoutManager = useLayoutManager(); const { metadata, onClose, onOpen, panelId } = useReactPanel(); const portalManager = usePortalPanelManager(); @@ -117,7 +169,38 @@ function ReactPanel({ children, title }: ReactPanelProps) { return portal ? ReactDOM.createPortal( - {children} + + + {children} + + , portal, contentKey diff --git a/plugins/ui/src/js/src/layout/ReactPanelContext.ts b/plugins/ui/src/js/src/layout/ReactPanelContext.ts index ff10a545d..590fb3e3c 100644 --- a/plugins/ui/src/js/src/layout/ReactPanelContext.ts +++ b/plugins/ui/src/js/src/layout/ReactPanelContext.ts @@ -9,6 +9,6 @@ export const ReactPanelContext = createContext(null); * Gets the panel ID from the nearest panel context. * @returns The panel ID or null if not in a panel */ -export function usePanelId() { +export function usePanelId(): string | null { return useContext(ReactPanelContext); } diff --git a/plugins/ui/src/js/src/layout/Row.tsx b/plugins/ui/src/js/src/layout/Row.tsx index 8491aa93a..cad61b97e 100644 --- a/plugins/ui/src/js/src/layout/Row.tsx +++ b/plugins/ui/src/js/src/layout/Row.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useMemo } from 'react'; import { useLayoutManager } from '@deephaven/dashboard'; import type { RowOrColumn } from '@deephaven/golden-layout'; +import { Flex } from '@deephaven/components'; import { normalizeRowChildren, type RowElementProps } from './LayoutUtils'; import { ParentItemContext, useParentItem } from './ParentItemContext'; import { usePanelId } from './ReactPanelContext'; -import { Flex } from '../elements/spectrum'; function LayoutRow({ children, height }: RowElementProps): JSX.Element | null { const layoutManager = useLayoutManager(); diff --git a/plugins/ui/src/js/src/styles.scss b/plugins/ui/src/js/src/styles.scss index fde7155b8..840e5fc52 100644 --- a/plugins/ui/src/js/src/styles.scss +++ b/plugins/ui/src/js/src/styles.scss @@ -1,15 +1,32 @@ +@import '@deephaven/components/scss/custom.scss'; + .ui-portal-panel { - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - overflow: hidden; - position: relative; + display: contents; } .ui-object-container { display: contents; - flex-grow: 1; - flex-shrink: 1; position: relative; } + +.dh-react-panel { + .dh-inner-react-panel .iris-grid { + border: 1px solid var(--dh-color-bg); + border-radius: $border-radius; + } + + &:has(.dh-inner-react-panel > .iris-grid:only-child), + &:has( + .dh-inner-react-panel + > .ui-object-container:only-child + > .iris-grid:only-child + ), + &:has(.dh-inner-react-panel > .chart-wrapper:only-child) { + // remove the default panel padding when grid or chart is the only child + padding: 0 !important; // important required to override inline spectrum style + .iris-grid { + border: none; + border-radius: 0; + } + } +} diff --git a/plugins/ui/src/js/src/widget/DashboardWidgetHandler.tsx b/plugins/ui/src/js/src/widget/DashboardWidgetHandler.tsx index e5e2f81cb..126db25b0 100644 --- a/plugins/ui/src/js/src/widget/DashboardWidgetHandler.tsx +++ b/plugins/ui/src/js/src/widget/DashboardWidgetHandler.tsx @@ -35,7 +35,7 @@ function DashboardWidgetHandler({ onClose, onDataChange, ...otherProps -}: DashboardWidgetHandlerProps) { +}: DashboardWidgetHandlerProps): JSX.Element { const handleClose = useCallback(() => { log.debug('handleClose', id); onClose?.(id); diff --git a/plugins/ui/src/js/src/widget/DocumentHandler.tsx b/plugins/ui/src/js/src/widget/DocumentHandler.tsx index 449e00da7..f0ae8a48e 100644 --- a/plugins/ui/src/js/src/widget/DocumentHandler.tsx +++ b/plugins/ui/src/js/src/widget/DocumentHandler.tsx @@ -44,7 +44,7 @@ function DocumentHandler({ initialData: data = EMPTY_OBJECT, onDataChange = EMPTY_FUNCTION, onClose, -}: DocumentHandlerProps) { +}: DocumentHandlerProps): JSX.Element { log.debug('Rendering document', widget); const panelOpenCountRef = useRef(0); const panelIdIndex = useRef(0); diff --git a/plugins/ui/src/js/src/widget/WidgetHandler.tsx b/plugins/ui/src/js/src/widget/WidgetHandler.tsx index 057be65b7..c27dbdb44 100644 --- a/plugins/ui/src/js/src/widget/WidgetHandler.tsx +++ b/plugins/ui/src/js/src/widget/WidgetHandler.tsx @@ -58,7 +58,7 @@ function WidgetHandler({ fetch, widget: descriptor, initialData: initialDataProp, -}: WidgetHandlerProps) { +}: WidgetHandlerProps): JSX.Element | null { const [widget, setWidget] = useState(); const [document, setDocument] = useState(); const [initialData] = useState(initialDataProp); diff --git a/plugins/ui/src/js/src/widget/WidgetTestUtils.ts b/plugins/ui/src/js/src/widget/WidgetTestUtils.ts index f7cec022e..278c0e50b 100644 --- a/plugins/ui/src/js/src/widget/WidgetTestUtils.ts +++ b/plugins/ui/src/js/src/widget/WidgetTestUtils.ts @@ -4,7 +4,7 @@ import type { dh } from '@deephaven/jsapi-types'; export function makeDocumentUpdatedJsonRpc( document: Record = {} -) { +): { jsonrpc: string; method: string; params: string[] } { return { jsonrpc: '2.0', method: 'documentUpdated', @@ -14,7 +14,7 @@ export function makeDocumentUpdatedJsonRpc( export function makeDocumentUpdatedJsonRpcString( document: Record = {} -) { +): string { return JSON.stringify(makeDocumentUpdatedJsonRpc(document)); } diff --git a/plugins/ui/src/js/src/widget/WidgetTypes.ts b/plugins/ui/src/js/src/widget/WidgetTypes.ts index 8c870cb53..20d898ac2 100644 --- a/plugins/ui/src/js/src/widget/WidgetTypes.ts +++ b/plugins/ui/src/js/src/widget/WidgetTypes.ts @@ -3,8 +3,8 @@ import type { dh } from '@deephaven/jsapi-types'; export type WidgetId = string; export interface WidgetMessageDetails { - getDataAsBase64(): string; - getDataAsString(): string; + getDataAsBase64: () => string; + getDataAsString: () => string; exportedObjects: dh.WidgetExportedObject[]; } diff --git a/plugins/ui/test/deephaven/ui/test_date_picker.py b/plugins/ui/test/deephaven/ui/test_date_picker.py new file mode 100644 index 000000000..d7a2d9624 --- /dev/null +++ b/plugins/ui/test/deephaven/ui/test_date_picker.py @@ -0,0 +1,88 @@ +import unittest + +from .BaseTest import BaseTestCase + + +class DatePickerTest(BaseTestCase): + def test_convert_date_props(self): + from deephaven.time import to_j_instant, to_j_zdt, to_j_local_date + from deephaven.ui.components.date_picker import _convert_date_picker_props + from deephaven.ui._internal.utils import get_jclass_name, convert_list_prop + + def verify_is_local_date(date): + self.assertEqual(get_jclass_name(date), "java.time.LocalDate") + + def verify_is_instant(date): + self.assertEqual(get_jclass_name(date), "java.time.Instant") + + def verify_is_zdt(date): + self.assertEqual(get_jclass_name(date), "java.time.ZonedDateTime") + + def empty_on_change(): + pass + + props1 = { + "placeholder_value": "2021-01-01", + "value": "2021-01-01 UTC", + "default_value": "2021-01-01 ET", + "unavailable_dates": [to_j_instant("2021-01-01 UTC"), "2021-01-01"], + "min_value": to_j_zdt("2021-01-01 ET"), + "max_value": to_j_local_date("2021-01-01"), + } + + props2 = { + "value": to_j_local_date("2021-01-01"), + "default_value": to_j_zdt("2021-01-01 ET"), + "placeholder_value": to_j_instant("2021-01-01 UTC"), + "on_change": verify_is_local_date, + "unavailable_dates": None, + } + + props3 = { + "default_value": to_j_instant("2021-01-01 UTC"), + "placeholder_value": to_j_zdt("2021-01-01 ET"), + "on_change": verify_is_instant, + } + + props4 = { + "placeholder_value": to_j_zdt("2021-01-01 ET"), + "on_change": verify_is_zdt, + } + + props5 = {"on_change": verify_is_instant} + + props6 = {"on_change": empty_on_change} + + _convert_date_picker_props(props1) + props1["unavailable_dates"] = convert_list_prop( + "unavailable_dates", props1["unavailable_dates"] + ) + _convert_date_picker_props(props2) + props2["unavailable_dates"] = convert_list_prop( + "unavailable_dates", props2["unavailable_dates"] + ) + _convert_date_picker_props(props3) + _convert_date_picker_props(props4) + _convert_date_picker_props(props5) + _convert_date_picker_props(props6) + + verify_is_local_date(props1["max_value"]) + verify_is_zdt(props1["min_value"]) + verify_is_instant(props1["unavailable_dates"][0]) + verify_is_local_date(props1["unavailable_dates"][1]) + verify_is_instant(props1["value"]) + verify_is_instant(props1["default_value"]) + verify_is_local_date(props1["placeholder_value"]) + + props2["on_change"]("2021-01-01") + self.assertIsNone(props2["unavailable_dates"]) + props3["on_change"]("2021-01-01 UTC") + props4["on_change"]("2021-01-01 ET") + props5["on_change"]("2021-01-01 UTC") + + # pass an Instant but it should be dropped with no error + props6["on_change"]("2021-01-01 UTC") + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/ui/test/deephaven/ui/test_hooks.py b/plugins/ui/test/deephaven/ui/test_hooks.py index dce874bb1..40cdf1900 100644 --- a/plugins/ui/test/deephaven/ui/test_hooks.py +++ b/plugins/ui/test/deephaven/ui/test_hooks.py @@ -274,6 +274,51 @@ def _test_table_data(t=table): self.assertEqual(result, expected) + def test_swapping_table_data(self): + from deephaven.ui.hooks import use_table_data + from deephaven import new_table + from deephaven.column import int_col + from deephaven import DynamicTableWriter + import deephaven.dtypes as dht + + table = new_table( + [ + int_col("X", [1, 2, 3]), + int_col("Y", [2, 4, 6]), + ] + ) + + def _test_table_data(t=table): + result = use_table_data(t, sentinel="sentinel") + return result + + render_result = render_hook(_test_table_data) + + result, rerender = itemgetter("result", "rerender")(render_result) + + column_definitions = {"Numbers": dht.int32, "Words": dht.string} + + table_writer = DynamicTableWriter(column_definitions) + dynamic_table = table_writer.table + + # Need two rerenders because the first one will call set_data, which queues state updates + # that are resolved at the start of the second rerender + # The second rerender will then have the expected state values and return the expected result + rerender(dynamic_table) + result = rerender(dynamic_table) + + # the initial render should return the sentinel value since the table is empty + self.assertEqual(result, "sentinel") + + self.verify_table_updated(table_writer, dynamic_table, (1, "Testing")) + + rerender(dynamic_table) + result = rerender(dynamic_table) + + expected = {"Numbers": [1], "Words": ["Testing"]} + + self.assertEqual(result, expected) + def test_column_data(self): from deephaven.ui.hooks import use_column_data from deephaven import new_table diff --git a/plugins/ui/test/deephaven/ui/test_utils.py b/plugins/ui/test/deephaven/ui/test_utils.py index 4d23dd545..20a5fec14 100644 --- a/plugins/ui/test/deephaven/ui/test_utils.py +++ b/plugins/ui/test/deephaven/ui/test_utils.py @@ -218,6 +218,50 @@ def test_func_with_all_args(a, /, b, *args, c=1, **kwargs): # Test that wrapping a function without a signature doesn't throw an error wrapped = wrap_callable(print) + def test_create_props(self): + from deephaven.ui._internal.utils import create_props + + children1, props1 = create_props( + { + "foo": "bar", + "baz": 42, + "fizz": "buzz", + } + ) + + self.assertEqual(children1, tuple()) + self.assertDictEqual( + props1, + { + "foo": "bar", + "baz": 42, + "fizz": "buzz", + }, + ) + + children2, props2 = create_props( + { + "children": ["item1", "item2"], + "test": "value", + "props": { + "foo": "bar", + "baz": 42, + "fizz": "buzz", + }, + } + ) + + self.assertEqual(children2, ["item1", "item2"]) + self.assertDictEqual( + props2, + { + "foo": "bar", + "baz": 42, + "fizz": "buzz", + "test": "value", + }, + ) + def test_unpack_item_table_source(self): from deephaven.ui._internal.utils import unpack_item_table_source from deephaven.ui import item_table_source diff --git a/tests/ui.spec.ts b/tests/ui.spec.ts index ddc2897ff..179655618 100644 --- a/tests/ui.spec.ts +++ b/tests/ui.spec.ts @@ -3,6 +3,6 @@ import { openPanel, gotoPage } from './utils'; test('UI loads', async ({ page }) => { await gotoPage(page, ''); - await openPanel(page, 'ui_component', '.ui-portal-panel'); - await expect(page.locator('.ui-portal-panel')).toHaveScreenshot(); + await openPanel(page, 'ui_component', '.dh-react-panel'); + await expect(page.locator('.dh-react-panel')).toHaveScreenshot(); }); diff --git a/tests/ui.spec.ts-snapshots/UI-loads-1-chromium-linux.png b/tests/ui.spec.ts-snapshots/UI-loads-1-chromium-linux.png index 30c17ce6c..44c520c36 100644 Binary files a/tests/ui.spec.ts-snapshots/UI-loads-1-chromium-linux.png and b/tests/ui.spec.ts-snapshots/UI-loads-1-chromium-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-loads-1-firefox-linux.png b/tests/ui.spec.ts-snapshots/UI-loads-1-firefox-linux.png index a6bf6f6df..ca0111fdb 100644 Binary files a/tests/ui.spec.ts-snapshots/UI-loads-1-firefox-linux.png and b/tests/ui.spec.ts-snapshots/UI-loads-1-firefox-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-loads-1-webkit-linux.png b/tests/ui.spec.ts-snapshots/UI-loads-1-webkit-linux.png index 183a38341..813549a39 100644 Binary files a/tests/ui.spec.ts-snapshots/UI-loads-1-webkit-linux.png and b/tests/ui.spec.ts-snapshots/UI-loads-1-webkit-linux.png differ