From 7c83ec256d237736d6eee6be6972b503e77b925f Mon Sep 17 00:00:00 2001 From: ethanalvizo <55671206+ethanalvizo@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:17:31 -0500 Subject: [PATCH 1/3] feat: ui.logic button (#1050) Closes #941 --------- Co-authored-by: margaretkennedy <82049573+margaretkennedy@users.noreply.github.com> --- plugins/ui/docs/components/logic_button.md | 59 +++++ plugins/ui/docs/sidebar.json | 4 + .../src/deephaven/ui/components/__init__.py | 2 + .../deephaven/ui/components/logic_button.py | 233 ++++++++++++++++++ .../ui/src/js/src/elements/LogicButton.tsx | 18 ++ plugins/ui/src/js/src/elements/index.ts | 1 + .../js/src/elements/model/ElementConstants.ts | 1 + plugins/ui/src/js/src/widget/WidgetUtils.tsx | 2 + 8 files changed, 320 insertions(+) create mode 100644 plugins/ui/docs/components/logic_button.md create mode 100644 plugins/ui/src/deephaven/ui/components/logic_button.py create mode 100644 plugins/ui/src/js/src/elements/LogicButton.tsx diff --git a/plugins/ui/docs/components/logic_button.md b/plugins/ui/docs/components/logic_button.md new file mode 100644 index 000000000..7186813ed --- /dev/null +++ b/plugins/ui/docs/components/logic_button.md @@ -0,0 +1,59 @@ +# Logic Button + +A Logic Button shows an operator in a boolean logic sequence. + +## Example + +```python +from deephaven import ui + +my_logic_button_basic = ui.logic_button("Or", variant="or") +``` + +## Events + +Logic buttons handles user interaction through the `on_press` prop. + +```python +from deephaven import ui + + +@ui.component +def ui_toggle_logic_button(): + variant, set_variant = ui.use_state("or") + + return ui.logic_button( + variant, + variant=variant, + on_press=lambda: set_variant("and" if variant == "or" else "or"), + ) + + +my_toggle_logic_button = ui_toggle_logic_button() +``` + +## Variants + +```python +from deephaven import ui + + +@ui.component +def ui_logic_button_variants(): + + return [ + ui.logic_button("Or", variant="or"), + ui.logic_button("And", variant="and"), + ] + + +my_logic_button_variants = ui_logic_button_variants() +``` + +## Disabled state + +```python +from deephaven import ui + +my_logic_button_disabled = ui.logic_button("Or", variant="or", is_disabled=True) +``` diff --git a/plugins/ui/docs/sidebar.json b/plugins/ui/docs/sidebar.json index 107df561c..ccc492b52 100644 --- a/plugins/ui/docs/sidebar.json +++ b/plugins/ui/docs/sidebar.json @@ -166,6 +166,10 @@ "label": "list_view", "path": "components/list_view.md" }, + { + "label": "logic_button", + "path": "components/logic_button.md" + }, { "label": "markdown", "path": "components/markdown.md" diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index 80fcdf5bc..2702cd42a 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -36,6 +36,7 @@ from .list_action_group import list_action_group from .list_action_menu import list_action_menu from .list_view import list_view +from .logic_button import logic_button from .make_component import make_component as component from .markdown import markdown from .meter import meter @@ -108,6 +109,7 @@ "list_view", "list_action_group", "list_action_menu", + "logic_button", "html", "markdown", "meter", diff --git a/plugins/ui/src/deephaven/ui/components/logic_button.py b/plugins/ui/src/deephaven/ui/components/logic_button.py new file mode 100644 index 000000000..d836bdd0e --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/logic_button.py @@ -0,0 +1,233 @@ +from __future__ import annotations +from typing import Any, Callable +from .types import ( + # Accessibility + AriaExpanded, + AriaHasPopup, + AriaPressed, + # Events + ButtonType, + FocusEventCallable, + KeyboardEventCallable, + PressEventCallable, + StaticColor, + # Layout + AlignSelf, + CSSProperties, + DimensionValue, + JustifySelf, + LayoutFlex, + Position, +) +from .basic import component_element +from ..elements import Element + + +def logic_button( + *children: Any, + variant: str | None = None, + is_disabled: bool | None = None, + auto_focus: bool | None = None, + type: ButtonType = "button", + on_press: PressEventCallable | None = None, + on_press_start: PressEventCallable | None = None, + on_press_end: PressEventCallable | None = None, + on_press_change: Callable[[bool], None] | None = None, + on_press_up: PressEventCallable | 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, + flex: LayoutFlex | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, + flex_basis: DimensionValue | None = None, + align_self: AlignSelf | None = None, + justify_self: JustifySelf | None = None, + order: int | None = None, + grid_area: str | None = None, + grid_column: str | None = None, + grid_row: str | None = None, + grid_column_start: str | None = None, + grid_column_end: str | None = None, + grid_row_start: str | None = None, + grid_row_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, + left: DimensionValue | None = None, + right: DimensionValue | None = None, + start: DimensionValue | None = None, + end: DimensionValue | None = None, + z_index: int | None = None, + is_hidden: bool | None = None, + id: str | None = None, + exclude_from_tab_order: bool | None = None, + aria_expanded: AriaExpanded | None = None, + aria_haspopup: AriaHasPopup | None = None, + aria_controls: 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, + key: str | None = None, +) -> Element: + """ + + A LogicButton shows an operator in a boolean logic sequence. + + Args: + *children: The children to render inside the button. + variant: The variant of the button. (default: "primary") + is_disabled: Whether the button is disabled. + auto_focus: Whether the button should automatically get focus when the page loads. + type: The type of button to render. (default: "button") + 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. + on_press_up: Function called when the button is released. + on_press_change: Function called when the press state changes. + 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. + 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. + exclude_from_tab_order: Whether the element should be excluded from the tab order. + aria_expanded: Whether the element is expanded. + aria_haspopup: Whether the element has a popup. + aria_controls: The id of the element that the element controls. + 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. + key: A unique identifier used by React to render elements in a list. + + Returns: + The rendered toggle button element. + + """ + + return component_element( + "LogicButton", + *children, + variant=variant, + is_disabled=is_disabled, + type=type, + auto_focus=auto_focus, + on_press=on_press, + on_press_start=on_press_start, + on_press_end=on_press_end, + on_press_change=on_press_change, + on_press_up=on_press_up, + on_focus=on_focus, + on_blur=on_blur, + on_focus_change=on_focus_change, + on_key_down=on_key_down, + on_key_up=on_key_up, + flex=flex, + flex_grow=flex_grow, + flex_shrink=flex_shrink, + flex_basis=flex_basis, + align_self=align_self, + justify_self=justify_self, + order=order, + grid_area=grid_area, + grid_column=grid_column, + grid_row=grid_row, + grid_column_start=grid_column_start, + grid_column_end=grid_column_end, + grid_row_start=grid_row_start, + grid_row_end=grid_row_end, + margin=margin, + margin_top=margin_top, + margin_bottom=margin_bottom, + margin_start=margin_start, + margin_end=margin_end, + margin_x=margin_x, + margin_y=margin_y, + width=width, + height=height, + min_width=min_width, + min_height=min_height, + max_width=max_width, + max_height=max_height, + position=position, + top=top, + bottom=bottom, + left=left, + right=right, + start=start, + end=end, + z_index=z_index, + is_hidden=is_hidden, + id=id, + exclude_from_tab_order=exclude_from_tab_order, + aria_expanded=aria_expanded, + aria_haspopup=aria_haspopup, + aria_controls=aria_controls, + aria_label=aria_label, + aria_labelledby=aria_labelledby, + aria_describedby=aria_describedby, + aria_pressed=aria_pressed, + aria_details=aria_details, + UNSAFE_class_name=UNSAFE_class_name, + UNSAFE_style=UNSAFE_style, + key=key, + ) diff --git a/plugins/ui/src/js/src/elements/LogicButton.tsx b/plugins/ui/src/js/src/elements/LogicButton.tsx new file mode 100644 index 000000000..2be528af9 --- /dev/null +++ b/plugins/ui/src/js/src/elements/LogicButton.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { + LogicButton as DHCLogicButton, + LogicButtonProps as DHCLogicButtonProps, +} from '@deephaven/components'; +import { useButtonProps } from './hooks/useButtonProps'; +import { SerializedButtonEventProps } from './model/SerializedPropTypes'; + +export function LogicButton( + props: SerializedButtonEventProps +): JSX.Element { + const buttonProps = useButtonProps(props); + + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} + +export default LogicButton; diff --git a/plugins/ui/src/js/src/elements/index.ts b/plugins/ui/src/js/src/elements/index.ts index 02443263d..8bb2aec39 100644 --- a/plugins/ui/src/js/src/elements/index.ts +++ b/plugins/ui/src/js/src/elements/index.ts @@ -19,6 +19,7 @@ export * from './IllustratedMessage'; export * from './Image'; export * from './InlineAlert'; export * from './ListView'; +export * from './LogicButton'; export * from './Markdown'; export * from './Meter'; export * from './model'; diff --git a/plugins/ui/src/js/src/elements/model/ElementConstants.ts b/plugins/ui/src/js/src/elements/model/ElementConstants.ts index f5e148347..2b16d4678 100644 --- a/plugins/ui/src/js/src/elements/model/ElementConstants.ts +++ b/plugins/ui/src/js/src/elements/model/ElementConstants.ts @@ -54,6 +54,7 @@ export const ELEMENT_NAME = { listActionMenu: uiComponentName('ListActionMenu'), link: uiComponentName('Link'), listView: uiComponentName('ListView'), + logicButton: uiComponentName('LogicButton'), markdown: uiComponentName('Markdown'), meter: uiComponentName('Meter'), numberField: uiComponentName('NumberField'), diff --git a/plugins/ui/src/js/src/widget/WidgetUtils.tsx b/plugins/ui/src/js/src/widget/WidgetUtils.tsx index 868fa9352..ed42404b8 100644 --- a/plugins/ui/src/js/src/widget/WidgetUtils.tsx +++ b/plugins/ui/src/js/src/widget/WidgetUtils.tsx @@ -66,6 +66,7 @@ import { Image, InlineAlert, ListView, + LogicButton, Markdown, Meter, Picker, @@ -147,6 +148,7 @@ export const elementComponentMap = { [ELEMENT_NAME.listActionGroup]: ListActionGroup, [ELEMENT_NAME.listActionMenu]: ListActionMenu, [ELEMENT_NAME.listView]: ListView, + [ELEMENT_NAME.logicButton]: LogicButton, [ELEMENT_NAME.markdown]: Markdown, [ELEMENT_NAME.meter]: Meter, [ELEMENT_NAME.numberField]: NumberField, From 018b16a8bf59fd997b2210c79236dce35089b6c0 Mon Sep 17 00:00:00 2001 From: Steven Wu Date: Thu, 19 Dec 2024 11:35:54 -0500 Subject: [PATCH 2/3] ci: add TypeScript type check (#989) - Closes #618 - Fixes leftover type issues --------- Co-authored-by: Mike Bender --- .github/workflows/typescript-check.yml | 27 ++++++++++ .../src/DashboardPlugin/DashboardPlugin.tsx | 2 +- .../ui/src/js/src/elements/SearchField.tsx | 3 +- .../src/js/src/elements/UITable/UITable.tsx | 4 +- .../src/elements/UITable/UITableModel.test.ts | 12 +++++ tools/check_typescript_ci.py | 50 +++++++++++++++++++ 6 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/typescript-check.yml create mode 100644 tools/check_typescript_ci.py diff --git a/.github/workflows/typescript-check.yml b/.github/workflows/typescript-check.yml new file mode 100644 index 000000000..73042926a --- /dev/null +++ b/.github/workflows/typescript-check.yml @@ -0,0 +1,27 @@ +name: Check TypeScript types + +on: + push: + branches: + - main + - 'release/**' + - 'feature/**' + pull_request: + branches: + - main + - 'release/**' + - 'feature/**' + +jobs: + typescript-check: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: npm ci + - name: Check TypeScript types + run: python tools/check_typescript_ci.py + diff --git a/plugins/dashboard-object-viewer/src/js/src/DashboardPlugin/DashboardPlugin.tsx b/plugins/dashboard-object-viewer/src/js/src/DashboardPlugin/DashboardPlugin.tsx index 75e6c4cad..2475c1fb5 100644 --- a/plugins/dashboard-object-viewer/src/js/src/DashboardPlugin/DashboardPlugin.tsx +++ b/plugins/dashboard-object-viewer/src/js/src/DashboardPlugin/DashboardPlugin.tsx @@ -28,7 +28,7 @@ export function DashboardPlugin({ widget: VariableDefinition; }) => { const { id: widgetId, name, type } = widget; - if (type === dh.VariableType.TABLE || type === dh.VariableType.FIGURE) { + if (type === 'Table' || type === 'Figure') { // Just ignore table and figure types - only want interesting other types return; } diff --git a/plugins/ui/src/js/src/elements/SearchField.tsx b/plugins/ui/src/js/src/elements/SearchField.tsx index 4f58dd796..a58b292ce 100644 --- a/plugins/ui/src/js/src/elements/SearchField.tsx +++ b/plugins/ui/src/js/src/elements/SearchField.tsx @@ -8,9 +8,10 @@ import { useKeyboardEventCallback, } from './hooks'; import useDebouncedOnChange from './hooks/useDebouncedOnChange'; +import { SerializedTextInputEventProps } from './model'; export function SearchField( - props: DHCSearchFieldProps & { + props: SerializedTextInputEventProps & { onSubmit?: (value: string) => void; onClear?: () => void; } diff --git a/plugins/ui/src/js/src/elements/UITable/UITable.tsx b/plugins/ui/src/js/src/elements/UITable/UITable.tsx index f0f29b211..bd3283858 100644 --- a/plugins/ui/src/js/src/elements/UITable/UITable.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITable.tsx @@ -306,7 +306,9 @@ export function UITable({ if (alwaysFetchColumnsArray[0] === false) { return []; } - return alwaysFetchColumnsArray.filter(v => typeof v === 'string'); + return alwaysFetchColumnsArray.filter( + v => typeof v === 'string' + ) as string[]; }, [alwaysFetchColumnsArray, modelColumns]); const mouseHandlers = useMemo( diff --git a/plugins/ui/src/js/src/elements/UITable/UITableModel.test.ts b/plugins/ui/src/js/src/elements/UITable/UITableModel.test.ts index 0c7df094e..707086e1b 100644 --- a/plugins/ui/src/js/src/elements/UITable/UITableModel.test.ts +++ b/plugins/ui/src/js/src/elements/UITable/UITableModel.test.ts @@ -33,6 +33,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ color: 'red' }, { color: 'blue' }], + displayNameMap: {}, }); expect(model.getFormatOptionForCell(0, 0, 'color')).toBe('blue'); expect(model.getFormatOptionForCell(1, 1, 'color')).toBe('blue'); @@ -48,6 +49,7 @@ describe('Formatting', () => { { cols: 'column0', color: 'red' }, { cols: 'column1', color: 'blue' }, ], + displayNameMap: {}, }); expect(model.getFormatOptionForCell(0, 0, 'color')).toBe('red'); expect(model.getFormatOptionForCell(1, 1, 'color')).toBe('blue'); @@ -71,6 +73,7 @@ describe('Formatting', () => { { color: 'red', if_: 'even' }, { cols: 'column1', color: 'blue', if_: 'even' }, ], + displayNameMap: {}, }); expect(model.getFormatOptionForCell(0, 0, 'color')).toBe('red'); expect(model.getFormatOptionForCell(0, 1, 'color')).toBeUndefined(); @@ -86,6 +89,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ cols: 'column0', color: 'red' }], + displayNameMap: {}, }); expect(model.getFormatOptionForCell(1, 1, 'color')).toBeUndefined(); expect( @@ -104,6 +108,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ color: 'red', if_: 'even' }], + displayNameMap: {}, }); expect(model.getFormatOptionForCell(0, 0, 'color')).toBeUndefined(); expect(model.getFormatOptionForCell(0, 1, 'color')).toBeUndefined(); @@ -119,6 +124,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ color: 'red' }], + displayNameMap: {}, }); expect(model.colorForCell(0, 0, {} as IrisGridThemeType)).toBe('red'); }); @@ -130,6 +136,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [], + displayNameMap: {}, }); expect(model.colorForCell(0, 0, {} as IrisGridThemeType)).toBeUndefined(); expect(MOCK_BASE_MODEL.colorForCell).toHaveBeenCalledTimes(1); @@ -145,6 +152,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ background_color: 'black' }], + displayNameMap: {}, }); expect( model.colorForCell(0, 0, { white: 'white' } as IrisGridThemeType) @@ -162,6 +170,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ background_color: 'white' }], + displayNameMap: {}, }); expect( model.colorForCell(0, 0, { black: 'black' } as IrisGridThemeType) @@ -176,6 +185,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ color: 'foo' }], + displayNameMap: {}, }); model.setColorMap(new Map([['foo', 'bar']])); expect(model.colorForCell(0, 0, {} as IrisGridThemeType)).toBe('bar'); @@ -190,6 +200,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [], + displayNameMap: {}, }); expect( model.backgroundColorForCell(0, 0, {} as IrisGridThemeType) @@ -204,6 +215,7 @@ describe('Formatting', () => { table: MOCK_TABLE, databars: [], format: [{ background_color: 'foo' }], + displayNameMap: {}, }); model.setColorMap(new Map([['foo', 'bar']])); expect(model.backgroundColorForCell(0, 0, {} as IrisGridThemeType)).toBe( diff --git a/tools/check_typescript_ci.py b/tools/check_typescript_ci.py new file mode 100644 index 000000000..6f07f38dd --- /dev/null +++ b/tools/check_typescript_ci.py @@ -0,0 +1,50 @@ +from re import fullmatch +from subprocess import run +from sys import exit + + +def main(): + print("Checking TypeScript types...") + res = run( + ["npx", "tsc", "-p", ".", "--emitDeclarationOnly", "false", "--noEmit"], + capture_output=True, + ) + + if res.returncode == 0: + return 0 + + messages = [] + for line in res.stdout.decode("utf-8").splitlines(): + if len(line) == 0: + continue + # If there's an indent, that means it's a continuation of the previous line + # For example, the error message could be like: + # > Argument of type 'FUNCTION_1_TYPE | undefined' is not assignable to parameter of type 'FUNCTION_2_TYPE'. + # > Type 'FUNCTION_1_TYPE' is not assignable to type 'FUNCTION_2_TYPE'. + # > Types of parameters `PARAM_1` and `PARAM_2` are incompatible. + # > Type 'PARAM_1_TYPE' is not assignable to type 'PARAM_2_TYPE'. + if line[0] == " " and len(messages) > 0: + messages[-1] += "\n" + line + else: + messages.append(line) + + for message in messages: + # Check if the message is actually an error and extract the details + # Error message format: file(line,col): error_message + match = fullmatch(r"(.+?)\((\d+),(\d+)\): ([\s\S]+)", message) + if match is None: + continue + + file, line, col, error_message = match.groups() + # Newlines in GitHub Actions annotations are escaped as %0A + # https://github.com/actions/toolkit/issues/193#issuecomment-605394935 + error_message = error_message.replace("\n", "%0A") + # GitHub Actions annotation format + print(f"::error file={file},line={line},col={col}::{error_message}") + + return res.returncode + + +if __name__ == "__main__": + # Exit with returncode so GitHub Actions fails properly + exit(main()) From 30f37301940cb0eca2d8015e5409f2d4c2bae675 Mon Sep 17 00:00:00 2001 From: dgodinez-dh <77981300+dgodinez-dh@users.noreply.github.com> Date: Fri, 20 Dec 2024 06:15:42 -0700 Subject: [PATCH 3/3] docs: Pure Components (#1064) https://deephaven.atlassian.net/browse/DOC-237 --------- Co-authored-by: Mike Bender Co-authored-by: margaretkennedy <82049573+margaretkennedy@users.noreply.github.com> --- plugins/ui/docs/_assets/pure_components1.png | Bin 0 -> 10266 bytes plugins/ui/docs/_assets/pure_components2.png | Bin 0 -> 14894 bytes plugins/ui/docs/_assets/pure_components3.png | Bin 0 -> 48050 bytes plugins/ui/docs/describing/pure_components.md | 116 ++++++++++++++++++ plugins/ui/docs/sidebar.json | 4 + 5 files changed, 120 insertions(+) create mode 100644 plugins/ui/docs/_assets/pure_components1.png create mode 100644 plugins/ui/docs/_assets/pure_components2.png create mode 100644 plugins/ui/docs/_assets/pure_components3.png create mode 100644 plugins/ui/docs/describing/pure_components.md diff --git a/plugins/ui/docs/_assets/pure_components1.png b/plugins/ui/docs/_assets/pure_components1.png new file mode 100644 index 0000000000000000000000000000000000000000..d2af21eedfb735de8cda9a957592056f7279aeac GIT binary patch literal 10266 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 literal 0 HcmV?d00001 diff --git a/plugins/ui/docs/_assets/pure_components2.png b/plugins/ui/docs/_assets/pure_components2.png new file mode 100644 index 0000000000000000000000000000000000000000..480e41fef883321325899bc6058dc8ba2e6b151a GIT binary patch literal 14894 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 z z-tLhPXjec`vXkxAFcd-f#e^gjIl6vEa4`%d8&yev$qK);^DQG>%>rnaoT`Rh|H|EP zex~T00Bh3me*XSfcxumQ?_(*g#%Y+i7=3NS;)&%Nnb%`@p|Hk<{K|}UJ*NIEvnGa4l~rI!D6R@?h64yU@e1{U-#uLr8l zBv96+nsGnAO77WmZE2~Jv`zKo@jIG?lWnwm$79=!!p2xWiEBQg%`~0kn_;v3;|qJf zPTm&6EVku=cAPl*ABG`p$BKB;l4tM|u`W?H2q1y`V@HQ>wsvGRdzr&cl@5!@sQt)- z**)>Meo&Lg)sK{eNsk6>v!0V{8#3gp=4B>lkDmaUpHp3*9D?+Uo|yCFZpFwab9;>%A8 ztHzjdPbdn6jDMcyicNF5!~oE_`+LTaTfS`Lp#1}(%!m`-Y>7S432eQ&C)I33FQN-A zngtq*^dVLJDdRuX#4TAt8i4 z$`+xPvK_gIv)?aBB5zI$5Q{#oE4Cq3EI_M1 z$FC-fuJ1)cO-Nt+pG4B6^hXQ;={&$iDISWh&4`FlVHx8E$@Zax>5f*OOBxD*dZj-N z-bk`VOO+Lq$oH~!3n4xnPu#Xu;^cP~t^+<oFh(un1?q8tP+?e*<5r_WLS%Om8! u`d;4upTJWE@&CrY>woROT5U}xGJrMC{TOr%TR43v(m=;pyX^YiXa5aT$NyOX literal 0 HcmV?d00001 diff --git a/plugins/ui/docs/_assets/pure_components3.png b/plugins/ui/docs/_assets/pure_components3.png new file mode 100644 index 0000000000000000000000000000000000000000..9d4f355635468c0aedea334848fc373fb5c58dcd GIT binary patch literal 48050 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 z literal 0 HcmV?d00001 diff --git a/plugins/ui/docs/describing/pure_components.md b/plugins/ui/docs/describing/pure_components.md new file mode 100644 index 000000000..1e0f95d82 --- /dev/null +++ b/plugins/ui/docs/describing/pure_components.md @@ -0,0 +1,116 @@ +# Pure Components + +A [pure function](https://en.wikipedia.org/wiki/Pure_function) returns the same value given the same arguments and has no side effects. By writing `deephaven.ui` components as pure functions, you can avoid bugs and unpredictable behavior. + +## Unintentional side effects + +The rendering process must always be pure. Component functions should always return the same value for the same arguments. They should not _change_ any objects or variables that existed before rendering. That would not be pure. + +Here is a component that breaks this rule by reading and writing a `guest` variable declared outside of it: + +```python +from deephaven import ui + +guest = [0] + + +@ui.component +def cup(): + # changing a preexisting variable + guest[0] += 1 + return ui.text(f"Tea cup for guest {guest[0]}") + + +@ui.component +def tea_set(): + return ui.flex(cup(), cup(), cup(), direction="column") + + +my_tea_set1 = tea_set() +my_tea_set2 = tea_set() +``` + +![side effects](../_assets/pure_components1.png) + +Calling this component multiple times will produce different results. If other components read `guest`, they will produce different results, too, depending on when they are rendered. That is not predictable. + +You can fix this component by passing `guest` as a prop instead: + +```python +from deephaven import ui + + +@ui.component +def cup(guest): + return ui.text(f"Tea cup for guest {guest}") + + +@ui.component +def tea_set(): + return ui.flex(cup(guest=1), cup(guest=2), cup(guest=3), direction="column") + + +my_tea_set1 = tea_set() +my_tea_set2 = tea_set() +``` + +![side effects 2](../_assets/pure_components2.png) + +Now the component is pure. Its returns only depend on the `guest` prop. + +In general, you should not expect components to be rendered in any particular order. Each component should only “think for itself”, and not attempt to coordinate with or depend upon others during rendering. + +## Local mutations + +Pure functions do not mutate variables outside of the function's scope or objects that were created before the function call. However, it is fine to change variables and objects created inside the function. In this example, the component creates a list and adds a dozen cups to it: + +```python +from deephaven import ui + + +@ui.component +def cup(guest): + return ui.text(f"Tea cup for guest {guest}") + + +@ui.component +def tea_set(): + cups = [] + for i in range(1, 13): + cups.append(cup(guest=i)) + return ui.flex(cups, direction="column") + + +my_tea_set1 = tea_set() +my_tea_set2 = tea_set() +``` + +![local mutations](../_assets/pure_components3.png) + +If the `cups` variable was outside the `tea_set` function, this would be a problem. You would be changing a preexisting object by appending items to that list. + +However, because you created them during the same render, no code outside of `tea_set` will be impacted by this. This is a local mutation. + +## Intentional side effects + +While the rendering process must remain pure, at some point, something needs to change. You may need to print a message, update the screen, start an animation, or change data. These changes are called side effects. They must happen on the side rather than during rendering. + +In `deephaven.ui`, side effects usually belong in event handlers. Event handlers are functions that run when you perform some action like clicking a button. Even though the event handlers are defined inside your component, they do not run during rendering, so even handlers do not need to be pure. + +```python +from deephaven import ui + + +@ui.component +def event_handler_example(): + # An event handler for a button + def button_handler(): + print("button pressed") + + return ui.button("button", on_press=button_handler) + + +my_event_handler_example = event_handler_example() +``` + +If an event handler is not the correct place for a certain side effect, you can place it in a [`use_effect`](../hooks/use_effect.md) hook. This tells `deephaven.ui` to execute it later, after rendering, when side effects are allowed. diff --git a/plugins/ui/docs/sidebar.json b/plugins/ui/docs/sidebar.json index ccc492b52..25fe4e356 100644 --- a/plugins/ui/docs/sidebar.json +++ b/plugins/ui/docs/sidebar.json @@ -48,6 +48,10 @@ { "label": "Conditional Rendering", "path": "describing/conditional_rendering.md" + }, + { + "label": "Pure Components", + "path": "describing/pure_components.md" } ] },