Skip to content

Commit

Permalink
ListView (#366)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Apr 8, 2024
1 parent 91e773b commit e73556c
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugins/ui/src/js/src/elements/ElementConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type HTMLElementType =
export const FRAGMENT_ELEMENT_NAME =
`${UI_COMPONENTS_NAMESPACE}.Fragment` as const;
export const ITEM_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Item` as const;
export const LIST_VIEW_NAME = `${UI_COMPONENTS_NAMESPACE}.ListView` as const;
export const PICKER_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Picker` as const;
export const SECTION_ELEMENT_NAME =
`${UI_COMPONENTS_NAMESPACE}.Section` as const;
58 changes: 58 additions & 0 deletions plugins/ui/src/js/src/elements/ListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { ReactElement } from 'react';
import { useSelector } from 'react-redux';
import {
ListView as DHListView,
ListViewProps as DHListViewProps,
} from '@deephaven/components';
import {
ListView as DHListViewJSApi,
ListViewProps as DHListViewJSApiProps,
useTableClose,
} from '@deephaven/jsapi-components';
import { isElementOfType, usePromiseFactory } from '@deephaven/react-hooks';
import { getSettings, RootState } from '@deephaven/redux';
import {
SerializedListViewEventProps,
useListViewProps,
} from './useListViewProps';
import ObjectView, { ObjectViewProps } from './ObjectView';
import { fetchReexportedTable } from './ElementUtils';

type WrappedDHListViewJSApiProps = Omit<DHListViewJSApiProps, 'table'> & {
children: ReactElement<ObjectViewProps>;
};

export type ListViewProps = (DHListViewProps | WrappedDHListViewJSApiProps) &
SerializedListViewEventProps;

function ListView({ children, ...props }: ListViewProps) {
const settings = useSelector(getSettings<RootState>);
const listViewProps = useListViewProps(props);

const isObjectView = isElementOfType(children, ObjectView);

const maybeExportedTable =
isObjectView && children.props.object.type === 'Table'
? children.props.object
: null;

const { data: table } = usePromiseFactory(fetchReexportedTable, [
maybeExportedTable,
]);

useTableClose(table);

if (isObjectView) {
return (
table && (
// eslint-disable-next-line react/jsx-props-no-spreading
<DHListViewJSApi {...listViewProps} table={table} settings={settings} />
)
);
}

// eslint-disable-next-line react/jsx-props-no-spreading
return <DHListView {...listViewProps}>{children}</DHListView>;
}

export default ListView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { ItemKey, ItemSelection } from '@deephaven/components';
import { useCallback } from 'react';

export type SerializedSelection = 'all' | ItemKey[];

export type SerializedSelectionEventCallback = (
event: SerializedSelection
) => void;

/**
* Selection can be 'all' or a Set of keys. If it is a Set, serialize it as an
* array.
* @param selection Selection to serialize
* @returns Serialized selection
*/
export function serializeSelectionEvent(
selection: ItemSelection
): SerializedSelection {
if (selection instanceof Set) {
return [...selection];
}

return selection;
}

/**
* Get a callback function that can be passed to selection change event handler
* props of Spectrum components.
* @param callback Callback to be called with the serialized selection
* @returns A callback to be passed into the Spectrum component that transforms
* the selection and calls the provided callback
*/
export function useSelectionEventCallback(
callback?: SerializedSelectionEventCallback
) {
return useCallback(
(selection: ItemSelection) => {
callback?.(serializeSelectionEvent(selection));
},
[callback]
);
}
74 changes: 74 additions & 0 deletions plugins/ui/src/js/src/elements/useListViewProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
SerializedFocusEventCallback,
useFocusEventCallback,
} from './spectrum/useFocusEventCallback';
import {
SerializedKeyboardEventCallback,
useKeyboardEventCallback,
} from './spectrum/useKeyboardEventCallback';
import {
SerializedSelectionEventCallback,
useSelectionEventCallback,
} from './spectrum/useSelectionEventCallback';

export interface SerializedListViewEventProps {
/** Handler that is called when selection changes */
onChange?: SerializedSelectionEventCallback;

/** Handler that is called when the element receives focus. */
onFocus?: SerializedFocusEventCallback;

/** Handler that is called when the element loses focus. */
onBlur?: SerializedFocusEventCallback;

/** Handler that is called when a key is pressed */
onKeyDown?: SerializedKeyboardEventCallback;

/** Handler that is called when a key is released */
onKeyUp?: SerializedKeyboardEventCallback;

/**
* Handler that is called when the selection changes.
* @deprecated Use `onChange` instead
*/
onSelectionChange?: SerializedSelectionEventCallback;
}

/**
* Wrap ListView props with the appropriate serialized event callbacks.
* @param props Props to wrap
* @returns Wrapped props
*/
export function useListViewProps<T>(props: SerializedListViewEventProps & T) {
const {
onFocus,
onBlur,
onKeyDown,
onKeyUp,
onChange,
onSelectionChange,
...otherProps
} = props;

const serializedOnFocus = useFocusEventCallback(onFocus);
const serializedOnBlur = useFocusEventCallback(onBlur);
const serializedOnKeyDown = useKeyboardEventCallback(onKeyDown);
const serializedOnKeyUp = useKeyboardEventCallback(onKeyUp);
const serializedOnChange = useSelectionEventCallback(onChange);
const serializedOnSelectionChange =
useSelectionEventCallback(onSelectionChange);

return {
onFocus: serializedOnFocus,
onBlur: serializedOnBlur,
onKeyDown: serializedOnKeyDown,
onKeyUp: serializedOnKeyUp,
onChange: serializedOnChange,
onSelectionChange: serializedOnSelectionChange,
// The @deephaven/components `ListView` has its own normalization logic that
// handles primitive children types (string, number, boolean). It also
// handles nested children inside of `Item` and `Section` components, so
// we are intentionally not wrapping `otherProps` in `mapSpectrumProps`
...otherProps,
};
}
3 changes: 3 additions & 0 deletions plugins/ui/src/js/src/widget/WidgetUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
DASHBOARD_ELEMENT_NAME,
FRAGMENT_ELEMENT_NAME,
ITEM_ELEMENT_NAME,
LIST_VIEW_NAME,
PANEL_ELEMENT_NAME,
PICKER_ELEMENT_NAME,
ROW_ELEMENT_NAME,
Expand All @@ -35,6 +36,7 @@ import Row from '../layout/Row';
import Stack from '../layout/Stack';
import Column from '../layout/Column';
import Dashboard from '../layout/Dashboard';
import ListView from '../elements/ListView';
import Picker from '../elements/Picker';

/*
Expand All @@ -48,6 +50,7 @@ export const elementComponentMap = {
[STACK_ELEMENT_NAME]: Stack,
[DASHBOARD_ELEMENT_NAME]: Dashboard,
[ITEM_ELEMENT_NAME]: Item,
[LIST_VIEW_NAME]: ListView,
[PICKER_ELEMENT_NAME]: Picker,
[SECTION_ELEMENT_NAME]: Section,
[UITABLE_ELEMENT_TYPE]: UITable,
Expand Down

0 comments on commit e73556c

Please sign in to comment.