diff --git a/vuu-ui/package-lock.json b/vuu-ui/package-lock.json index d4c10225d..725e7b8c9 100644 --- a/vuu-ui/package-lock.json +++ b/vuu-ui/package-lock.json @@ -11524,6 +11524,7 @@ "@finos/vuu-table-types": "0.0.26", "@finos/vuu-ui-controls": "0.0.26", "@finos/vuu-utils": "0.0.26", + "@salt-ds/core": "1.37.1", "@salt-ds/styles": "0.2.1", "@salt-ds/window": "0.1.1" }, @@ -11749,7 +11750,8 @@ "@salt-ds/core": "1.37.1", "@salt-ds/icons": "1.12.1", "@salt-ds/styles": "0.2.1", - "@salt-ds/window": "0.1.1" + "@salt-ds/window": "0.1.1", + "tabbable": "^6.0.0" }, "devDependencies": { "@finos/vuu-data-types": "0.0.26", diff --git a/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts b/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts index f0bdbf4f0..4ff631047 100644 --- a/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts +++ b/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts @@ -461,9 +461,10 @@ export class ArrayDataSource }); } - this.setRange(resetRange(this.#range), true); - - this.emit("config", this._config, this.range, undefined, configChanges); + if (this.#status === "subscribed") { + this.setRange(resetRange(this.#range), true); + this.emit("config", this._config, this.range, undefined, configChanges); + } } } diff --git a/vuu-ui/packages/vuu-data-remote/src/server-proxy/server-proxy.ts b/vuu-ui/packages/vuu-data-remote/src/server-proxy/server-proxy.ts index 1e36492e9..f5d06a6ac 100644 --- a/vuu-ui/packages/vuu-data-remote/src/server-proxy/server-proxy.ts +++ b/vuu-ui/packages/vuu-data-remote/src/server-proxy/server-proxy.ts @@ -306,7 +306,8 @@ export class ServerProxy { // Resend requests for links from other viewports already on page, they may be linkable to this viewport Array.from(this.viewports.entries()) .filter( - ([id, { disabled }]) => id !== serverViewportId && !disabled, + ([id, { disabled, status }]) => + id !== serverViewportId && !disabled && status === "subscribed", ) .forEach(([vpId]) => { this.sendMessageToServer({ diff --git a/vuu-ui/packages/vuu-data-test/src/simul/reference-data/parent-child-orders.ts b/vuu-ui/packages/vuu-data-test/src/simul/reference-data/parent-child-orders.ts index a4812878a..80b4dfdd0 100644 --- a/vuu-ui/packages/vuu-data-test/src/simul/reference-data/parent-child-orders.ts +++ b/vuu-ui/packages/vuu-data-test/src/simul/reference-data/parent-child-orders.ts @@ -13,8 +13,8 @@ const parentOrderData: VuuRowDataItemType[][] = []; const instrumentMap = buildDataColumnMap(schemas, "instruments"); -const PARENT_ORDER_COUNT = 100_000; -const CHILD_ORDER_COUNT = 500_000; +const PARENT_ORDER_COUNT = 75_000; +const CHILD_ORDER_COUNT = 200_000; const avgChildOrderPerOrder = Math.round( CHILD_ORDER_COUNT / PARENT_ORDER_COUNT, diff --git a/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.css b/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.css index 921116ba0..f8ef7c20a 100644 --- a/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.css +++ b/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.css @@ -22,5 +22,6 @@ } .vuuLinkedTableView-view { + overflow: hidden; padding: var(--salt-spacing-100); } \ No newline at end of file diff --git a/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.tsx b/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.tsx index 79cc81097..5287568b0 100644 --- a/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.tsx +++ b/vuu-ui/packages/vuu-datatable/src/linked-table-view/LinkedTableView.tsx @@ -17,6 +17,7 @@ import { import css from "./LinkedTableView.css"; import { TableLayoutToggleButton } from "./TableLayoutToggleButton"; +import { Filter } from "@finos/vuu-filter-types"; const classBase = "vuuLinkedTableView"; @@ -47,10 +48,25 @@ export type LinkedDataSources = { /** * Displays a vertical 'tower' of Tables with a hierarchical relationship. - * Selection of row(s) on tables higher in the hierarchy drives the population - * of data in tables below. (could be two-way ?) + * Currently supported levels are: + * - tier 1 - parent table(s) + * - tier 2 - child table(s) + * -tier 3 (optional) - grandchild table(s) + * + * Selection of row(s) on tables higher in the hierarchy drives the display + * of data in tables below. Currently this is one-way, top-down only */ export interface LinkedTableViewProps extends HTMLAttributes { + /** + * Optional filter to allow externally controlled filter to be applied. This will + * be applied to tier 1 table(s). + * If applicable, it will also be applied to tier2/tier3 table(s) when no selection + * is in effect from parent table, 'If applicable' means if the filter column is + * available on tier 2/tier 3 tables). + * If a filter is provided and it cannot be applied - because + * column name of filter is not available in tier 1 table, an exception will be thrown. + */ + filter?: Filter; linkedDataSources: LinkedDataSources; } const LinkedTables = ({ @@ -65,13 +81,14 @@ const LinkedTables = ({ window: targetWindow, }); - const { activeTabs, tableConfig, ...config } = useLinkedTableView({ + const { tableConfig, ...config } = useLinkedTableView({ linkedDataSources, }); const getLinkedTables = ( tdsConfig: TableDataSourceConfig | TableDataSourceConfig[], { + activeTab, onChangeTabbedView, onTabChange, tabbedView, @@ -99,7 +116,7 @@ const LinkedTables = ({ }} >
- + {tdsConfig.map(({ title }, i) => ( ))} @@ -112,7 +129,7 @@ const LinkedTables = ({
) => void; diff --git a/vuu-ui/packages/vuu-layout/src/flexbox/useSplitterResizing.ts b/vuu-ui/packages/vuu-layout/src/flexbox/useSplitterResizing.ts index 0074de01f..81ba05c8b 100644 --- a/vuu-ui/packages/vuu-layout/src/flexbox/useSplitterResizing.ts +++ b/vuu-ui/packages/vuu-layout/src/flexbox/useSplitterResizing.ts @@ -1,11 +1,5 @@ import { getUniqueId } from "@finos/vuu-utils"; -import React, { - ReactElement, - useCallback, - useMemo, - useRef, - useState, -} from "react"; +import React, { ReactElement, useCallback, useMemo, useRef } from "react"; import { Placeholder } from "../placeholder"; import { Splitter } from "./Splitter"; @@ -33,15 +27,10 @@ export const useSplitterResizing = ({ style, }: SplitterHookProps): SplitterHookResult => { const rootRef = useRef(null); + const flexElementsRef = useRef(); const metaRef = useRef(); const contentRef = useRef(); - const assignedKeys = useRef([]); - const [, forceUpdate] = useState({}); - - const setContent = (content: ReactElement[]) => { - contentRef.current = content; - forceUpdate({}); - }; + const assignedKeys = useRef([]); const isColumn = style?.flexDirection === "column"; const dimension = isColumn ? "height" : "width"; @@ -84,6 +73,10 @@ export const useSplitterResizing = ({ if (rootRef.current) { rootRef.current.classList.add("vuuSplitterResizing"); + console.log({ root: rootRef.current }); + flexElementsRef.current = Array.from( + rootRef.current.querySelectorAll(":scope > div"), + ); } } } @@ -91,21 +84,13 @@ export const useSplitterResizing = ({ [dimension], ); - const handleDrag = useCallback( - (idx, distance) => { - if (contentRef.current && metaRef.current) { - setContent( - resizeContent( - contentRef.current, - metaRef.current, - distance, - dimension, - ), - ); - } - }, - [dimension], - ); + const handleDrag = useCallback((idx, distance) => { + const { current: flexElements = [] } = flexElementsRef; + + if (contentRef.current && metaRef.current) { + resizeElements(flexElements, metaRef.current, distance /*, dimension*/); + } + }, []); const handleDragEnd = useCallback(() => { const contentMeta = metaRef.current; @@ -157,8 +142,8 @@ function buildContent( children: ReactElement[], dimension: "width" | "height", createSplitter: SplitterFactory, - keys: any[], -): [any[], ContentMeta[]] { + keys: string[], +): [ReactElement[], ContentMeta[]] { const childMeta = gatherChildMeta(children, dimension); const splitterAndPlaceholderPositions = findSplitterAndPlaceholderPositions(childMeta); @@ -189,37 +174,23 @@ function buildContent( return [content, meta]; } -function resizeContent( - content: ReactElement[], +function resizeElements( + flexElements: HTMLDivElement[], contentMeta: ContentMeta[], distance: number, - dimension: "width" | "height", ) { const metaUpdated = updateMeta(contentMeta, distance); if (!metaUpdated) { - return content; + return; } - return content.map((child, idx) => { + flexElements.forEach((element, idx) => { const meta = contentMeta[idx]; - const { currentSize, flexOpen, flexBasis } = meta; + const { currentSize, flexOpen, flexBasis, splitter } = meta; const hasCurrentSize = currentSize !== undefined; - if (hasCurrentSize || flexOpen) { - const { flexBasis: actualFlexBasis } = child.props.style || {}; + if (!splitter && (hasCurrentSize || flexOpen)) { const size = hasCurrentSize ? meta.currentSize : flexBasis; - if (size !== actualFlexBasis) { - return React.cloneElement(child, { - style: { - ...child.props.style, - flexBasis: size, - [dimension]: "auto", - }, - }); - } else { - return child; - } - } else { - return child; + element.style.flexBasis = `${size}px`; } }); } @@ -262,7 +233,7 @@ function createPlaceholder(index: number) { return React.createElement(Placeholder, { shim: index === 0, key: `placeholder-${index}`, - } as any); + }); } function measureElement( diff --git a/vuu-ui/packages/vuu-table/src/useDataSource.ts b/vuu-ui/packages/vuu-table/src/useDataSource.ts index aebb60aac..b311237b6 100644 --- a/vuu-ui/packages/vuu-table/src/useDataSource.ts +++ b/vuu-ui/packages/vuu-table/src/useDataSource.ts @@ -50,6 +50,7 @@ export const useDataSource = ({ // setRange calls at this point so dataWindow range will //not yet be set. If the dataWindow range is already set, // this is a no-op. + console.log("resumed"); const { range } = dataSource; if (range.to !== 0) { dataWindow.setRange(dataSource.range); @@ -81,6 +82,10 @@ export const useDataSource = ({ const size = dataWindow.data.length; dataWindow.setRowCount(message.size); if (dataWindow.data.length < size) { + if (isMounted.current === false) { + console.log("setting state whilst unmounted"); + } + forceUpdate({}); } } @@ -101,6 +106,11 @@ export const useDataSource = ({ onSizeChange?.(0); dataWindow.setRowCount(0); setData([]); + + if (isMounted.current === false) { + console.log("setting state whilst unmounted"); + } + forceUpdate({}); } else { console.log(`useDataSource unexpected message ${message.type}`); diff --git a/vuu-ui/packages/vuu-table/src/useTable.ts b/vuu-ui/packages/vuu-table/src/useTable.ts index 2fc0415f9..4c4f75876 100644 --- a/vuu-ui/packages/vuu-table/src/useTable.ts +++ b/vuu-ui/packages/vuu-table/src/useTable.ts @@ -1,5 +1,6 @@ import { DataSourceConfig, + DataSourceConfigChangeHandler, DataSourceRow, DataSourceSubscribedMessage, SelectionChangeHandler, @@ -346,8 +347,8 @@ export const useTable = ({ [dataSource], ); - useEffect(() => { - dataSource.on("config", (config, range, confirmed, changes) => { + const handleConfigChange = useCallback( + (config, range, confirmed, changes) => { const scrollSensitiveChanges = changes?.filterChanged || changes?.groupByChanged; if (scrollSensitiveChanges && range.from > 0) { @@ -361,8 +362,16 @@ export const useTable = ({ ...config, confirmed, }); - }); - }, [dataSource, dispatchTableModelAction]); + }, + [dispatchTableModelAction], + ); + + useEffect(() => { + dataSource.on("config", handleConfigChange); + return () => { + dataSource.removeListener("config", handleConfigChange); + }; + }, [dataSource, dispatchTableModelAction, handleConfigChange]); const handleCreateCalculatedColumn = useCallback( (column: ColumnDescriptor) => { diff --git a/vuu-ui/packages/vuu-ui-controls/package.json b/vuu-ui/packages/vuu-ui-controls/package.json index 7d7db7324..d3686d154 100644 --- a/vuu-ui/packages/vuu-ui-controls/package.json +++ b/vuu-ui/packages/vuu-ui-controls/package.json @@ -24,7 +24,8 @@ "@salt-ds/core": "1.37.1", "@salt-ds/icons": "1.12.1", "@salt-ds/styles": "0.2.1", - "@salt-ds/window": "0.1.1" + "@salt-ds/window": "0.1.1", + "tabbable": "^6.0.0" }, "peerDependencies": { "@internationalized/date": "^3.0.0", diff --git a/vuu-ui/packages/vuu-ui-controls/src/split-button/SplitButton.css b/vuu-ui/packages/vuu-ui-controls/src/split-button/SplitButton.css index 176d79d17..2104ebc80 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/split-button/SplitButton.css +++ b/vuu-ui/packages/vuu-ui-controls/src/split-button/SplitButton.css @@ -104,7 +104,7 @@ .vuuSplitButton-cta { --split-background: var(--background, var(--salt-actionable-accented-bold-background)); - --split-background-active: var(--salt-actionable-accented-bold-background-active; + --split-background-active: var(--salt-actionable-accented-bold-background-active); --split-color-active: var(--salt-actionable-bold-foreground-active); } .vuuSplitButton-cta:hover:not(.vuuSplitButton-disabled) { diff --git a/vuu-ui/packages/vuu-utils/src/react-utils.ts b/vuu-ui/packages/vuu-utils/src/react-utils.ts index c63b726d3..972fcc5c5 100644 --- a/vuu-ui/packages/vuu-utils/src/react-utils.ts +++ b/vuu-ui/packages/vuu-utils/src/react-utils.ts @@ -1,4 +1,11 @@ -import { Children, isValidElement, ReactElement, ReactNode } from "react"; +import { + Children, + isValidElement, + ReactElement, + ReactNode, + useEffect, + useRef, +} from "react"; const EMPTY_ARRAY: ReactElement[] = []; @@ -15,3 +22,17 @@ export const asReactElements = (children: ReactNode): ReactElement[] => { return EMPTY_ARRAY; } }; + +export const useIsMounted = (id = "") => { + const isMountedRef = useRef(false); + useEffect(() => { + console.log(`is MOUNTED ${id}`); + isMountedRef.current = true; + return () => { + console.log(`is UNMOUNTED ${id}`); + isMountedRef.current = false; + }; + }, [id]); + + return isMountedRef; +}; diff --git a/vuu-ui/showcase/src/examples/AppPatterns/CrossTableFiltering/CrossTableFiltering.examples.tsx b/vuu-ui/showcase/src/examples/AppPatterns/CrossTableFiltering/CrossTableFiltering.examples.tsx index 99c342932..7713566a1 100644 --- a/vuu-ui/showcase/src/examples/AppPatterns/CrossTableFiltering/CrossTableFiltering.examples.tsx +++ b/vuu-ui/showcase/src/examples/AppPatterns/CrossTableFiltering/CrossTableFiltering.examples.tsx @@ -1,21 +1,27 @@ import { Table as DataTable, + getSchema, TickingArrayDataSource, } from "@finos/vuu-data-test"; import { SelectionChangeHandler, TableSchema } from "@finos/vuu-data-types"; import { + Flexbox, FlexboxLayout, LayoutProvider, StackLayout, View, } from "@finos/vuu-layout"; -import { Table } from "@finos/vuu-table"; +import { Table, TableProps } from "@finos/vuu-table"; import { TableConfig, TableRowClickHandler, TableRowSelectHandler, } from "@finos/vuu-table-types"; import { useCallback, useMemo } from "react"; +import { LocalDataSourceProvider } from "@finos/vuu-data-test"; +import { LinkedDataSources, LinkedTableView } from "@finos/vuu-datatable"; +import { TableSearch } from "@finos/vuu-ui-controls"; +import { useDataSource } from "@finos/vuu-utils"; let displaySequence = 1; @@ -123,7 +129,7 @@ export const SimpleCrossTableFiltering = () => { rowSeparators: true, zebraStripes: true, }), - [] + [], ); const configChild = useMemo( @@ -132,7 +138,7 @@ export const SimpleCrossTableFiltering = () => { rowSeparators: true, zebraStripes: true, }), - [] + [], ); const dataSourceAll = useMemo(() => { @@ -197,7 +203,7 @@ export const SimpleCrossTableFiltering = () => { const parentId = row.data.id; dataSource4.filter = { filter: `parentId = "${parentId}"` }; }, - [dataSource4] + [dataSource4], ); const handleParentRowSelect = useCallback((row) => { @@ -208,7 +214,7 @@ export const SimpleCrossTableFiltering = () => { (selection) => { console.log({ selection }); }, - [] + [], ); const handleChildRowClick = useCallback( @@ -216,7 +222,7 @@ export const SimpleCrossTableFiltering = () => { const parentId = row.data.id; dataSource5.filter = { filter: `parentId = "${parentId}"` }; }, - [dataSource5] + [dataSource5], ); return ( @@ -278,3 +284,105 @@ export const SimpleCrossTableFiltering = () => { ); }; SimpleCrossTableFiltering.displaySequence = displaySequence++; + +const TableSearchTemplate = ({ + schema, + TableProps, +}: { + schema: TableSchema; + TableProps?: Partial; +}) => { + const { VuuDataSource } = useDataSource(); + const dataSource = useMemo(() => { + const { table } = schema; + const dataSource = new VuuDataSource({ + columns: schema.columns.map((c) => c.name), + table, + }); + return dataSource; + }, [VuuDataSource, schema]); + + return ( + + ); +}; + +export const FilteredLinkedTableView = () => { + const linkedDataSources = useMemo(() => { + return { + "1": { + dataSource: { + table: { module: "SIMUL", table: "instruments" }, + }, + title: "instruments", + }, + "2": { + dataSource: { + table: { module: "SIMUL", table: "parentOrders" }, + }, + title: "Orders", + vuuLink: { + fromColumn: "ric", + toColumn: "ric", + }, + }, + "3": [ + { + vuuLink: { + fromColumn: "parentOrderId", + toColumn: "id", + }, + dataSource: { + table: { module: "SIMUL", table: "childOrders" }, + title: "Child Orders", + }, + title: "Child Orders 1", + }, + { + vuuLink: { + fromColumn: "parentOrderId", + toColumn: "id", + }, + dataSource: { + table: { module: "SIMUL", table: "childOrders" }, + }, + title: "Child Orders 2", + }, + ], + }; + }, []); + + const schema = getSchema("instruments"); + + const onSelect = useCallback((row) => { + console.log({ row }); + }, []); + + return ( + + +
+ +
+ +
+
+ ); +}; +FilteredLinkedTableView.displaySequence = displaySequence++;