Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiDataGrid] Add openCellPopover and closeCellPopover to ref APIs #5550

Merged

Conversation

cee-chen
Copy link
Member

@cee-chen cee-chen commented Jan 20, 2022

Summary

The main goal of this PR is to expose APIs that allow consumers to manually control cell popovers (#5310)

However, because each cell contained its own individual popover and popover state, this required a refactor of how our cell popover architecture works:

  • Each data grid now only creates a single popover for the entire grid (instead of a popover per cell) which changes location/anchor depending on the cell that triggered it
  • A top-level cell popover context now exists and which manages open/closed popover state and the popover cell location
  • Each cell responds to changes in open/closed and location state to determine whether its popover should be open, and if so, it updates the top level context with its anchor (wrapping div element) and popover content (rendered cell values).

I strongly recommend:

  • Following along by commit (which I tried to make as atomic as possible and include specific details and reasons in commit messages)
  • Turning off whitespace changes, in particular for 100e314 which is mostly just indentation changes due to the new context wrapper

Screencaps

popover

Coverage

QA

Checklist

- [ ] Check against all themes for compatibility in both light and dark modes
- [ ] Checked in mobile

- [ ] Checked Code Sandbox works for any docs examples

- [ ] Checked for breaking changes and labeled appropriately

  • Checked for accessibility including keyboard-only and screenreader modes

- [ ] A changelog entry exists and is marked appropriately (Will add a finished changelog in final feature branch PR)

… recent state/value

- without this callback, the initial returned hook values will be stale/not properly return most recent values
- see next commit for example usage within useCellPopover
- set up initial open/location state, + open/close popover APIs returned to consumers

- improve auto props documentation - remove EuiDataGridCellLocation in favor of specifying rowIndex and colIndex (it's less DRY but it's easier for devs to not have to look up EuiDataGridCellLocation from our docs)
- I'm not using context here because we're already using this.context for focus, and unfortunately class components can only initialize one context at time using `static contextType` (see https://reactjs.org/docs/context.html#classcontexttype)
- This should now be handled by the overarching context state, and the cell should simply react to it or update it (similar to how focusContext works)

+ add new var for hasCellButtons

+ add unit tests for isFocusedCell alongside isPopoverOpen (since both methods perform similar functions)
- content is TODO, will likely be easier to compare when cleaning it up/moving it all at once
- It should no longer exist as a reusable component that belongs to every single cell, but instead as a single popover that exists at the top grid level and moves from cell to cell

- I cleaned and split up the JSX for the popover (e.g. moving popover actions to data_grid_cell_buttons, where it feels like it belongs more) and think it's significantly more DRY now - note the entire `anchorContent` branch removed from EuiDataGridCell that is no longer necessary

- Note that due to this change, we now have to mock EuiWrappingPopover in EuiDataGrid tests, as we see failures otherwise
- this is the same behavior as in prod

- causes weird DOM issues if we don't close the cell popover automatically
…f view

+ write bonus Cypress test for useScroll's focus effect now that we have access to the imperative ref
+ remove code snippet - it was starting to get redundant with the API bullet points, and is already available as tab if needed

+ fix control button widths
Copy link
Member Author

@cee-chen cee-chen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chandlerprall Once this lands, I'm thinking we can maybe open a PR and merge the ref feature branch into main and handle the 2nd bullet point of #5310 ("Allow consumers to completely control the popover contents including the custom controls") as a separate PR/concern - any thoughts?

Comment on lines +313 to -308
setFocusedCell: (cell: { rowIndex: number; colIndex: number }) => void;
/**
* Allows manually opening the popover of the specified cell in the grid.
*/
openCellPopover: (cell: { rowIndex: number; colIndex: number }) => void;
/**
* Closes any currently open popovers in the data grid.
*/
closeCellPopover: () => void;
}

export type EuiDataGridCellLocation = { rowIndex: number; colIndex: number };

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick note that I removed EuiDataGridCellLocation because of our auto props. I wanted to make rowIndex and colIndex immediately available in our props table, and if we used EuiDataGridCellLocation it unfortunately referenced that instead.

src/components/datagrid/data_grid_types.ts Outdated Show resolved Hide resolved
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5550/

@chandlerprall
Copy link
Contributor

@chandlerprall Once this lands, I'm thinking we can maybe open a PR and merge the ref feature branch into main and handle the 2nd bullet point of #5310 ("Allow consumers to completely control the popover contents including the custom controls") as a separate PR/concern - any thoughts?

That makes sense to me 👍

Copy link
Contributor

@chandlerprall chandlerprall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool restructuring! Pulled locally & poked at the tests and some possible performance things, but didn't find any issues there.

I think there's a disconnect somewhere around row ID vs. visible row ID, as this works great on the first page of content but no popover displays when on other pages.

src/components/datagrid/data_grid.tsx Outdated Show resolved Hide resolved
src/components/datagrid/data_grid_types.ts Outdated Show resolved Hide resolved
// If `openCellPopover` was somehow called on this cell but it's not
// supposed to be expandable, we should do nothing and set popoverIsOpen
// state back to false
popoverContext.closeCellPopover();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you run into this scenario?

Because the render function calls isPopoverOpen, this can trigger the side effect during rendering, which is a strong anti-pattern. If having the popover state set to open when the cell is non-expandable is an issue, we should try to find a different place to account for it.

Copy link
Member Author

@cee-chen cee-chen Jan 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this scenario can occur if a user manually calls openCellPopover() on a cell that they have set to isExpandable: false. Obviously it wouldn't occur from our internal grid state, but since we're exposing the API to consumers to use at will, I was attempting to guard against it. I tested it locally by adding isExpandable: false to various columns in the ref.js documentation example.

I'm fine removing this piece of logic if you think it's extraneous. The primary bug/scenario I can think of that would occur from not having this logic that sets popoverIsOpen back to false is:

  1. Consumer calls openCellPopover() on a cell [0,0] that is isExpandable: false, internal popoverIsOpen state is now set to true and cell location is set to [0,0]
  2. Consumer changes the column to isExpandable: true
  3. Consumer calls openCellPopover() on a cell [0,0] again, but nothing happens because popoverIsOpen state is already set to true

I admit that's not a likely edge case, so if you strongly think this is a risky change I can remove it 🤷‍♀️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I lean toward either 1. moving the isExpandable logic into openCellPopover itself, or 2. ignoring the edge case entirely, rather than triggering the side effect from the render.

Or, as a third option, maybe even a small check in componentDidUpdate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm good with just ignoring the edge case entirely! We can say the onus is on the consumer to know if their cells are not expandable and not call openCellPopover on cell that shouldn't expand

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f9ed152

Let's not mark the conversation as resolved BTW, I'd like the dialogue+decision to be immediately visible to anyone who comes back to this PR for historical context.

src/components/datagrid/body/data_grid_cell.test.tsx Outdated Show resolved Hide resolved
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5550/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5550/

Copy link
Contributor

@chandlerprall chandlerprall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only seeing one issue in testing. I think there's a disconnect somewhere around row ID vs. visible row ID, as this works great on the first page of content but no popover displays when on other pages.

@cee-chen
Copy link
Member Author

Oh oops, I totally missed that comment 🤦‍♀️ Looking into it now!

@cee-chen
Copy link
Member Author

cee-chen commented Jan 26, 2022

Talked this out with @chandlerprall synchronously and the fix for handling paginated and sorted rowIndexes is going to be a significant amount of code change and deviation from the current setFocusedCell and openCellPopover code (which currently treats rowIndex as visibleRowIndex). As such I'm going to go ahead and merge this as-is into the feature branch and I'll open a follow-up PR with the row index changes/fixes.

@cee-chen cee-chen merged commit f8a25bc into elastic:feat/datagrid/5310 Jan 26, 2022
@cee-chen cee-chen deleted the datagrid/5310/cell-popovers branch January 26, 2022 23:46
cee-chen pushed a commit that referenced this pull request Feb 2, 2022
…l state via `ref` (#5590)

* [EuiDataGrid] Set up `ref` that exposes focus/popover internal APIs (#5499)

* Set up types

* Set up forwardRef

* Add setFocusedCell API to returned grid ref obj

* Add colIndex prop to cell actions

- so that cell actions that trigger modals or flyouts can re-focus into the correct cell using the new ref API

* Add documentation + example + props

* Add changelog

* [PR feedback] Types

Co-authored-by: Chandler Prall <chandler.prall@gmail.com>

* [PR feedback] Clean up unit test

* [Rebase] Tweak useImperativeHandle location

- Moving it below fullscreen logic, as we're oging to expose setIsFullScreen as an API shortly

Co-authored-by: Chandler Prall <chandler.prall@gmail.com>

* [EuiDataGrid] Add `setIsFullScreen` to ref API (#5531)

* Expose `setIsFullScreen` to ref API

* Update documentation/examples

* [EuiDataGrid] Add `openCellPopover` and `closeCellPopover` to ref APIs (#5550)

* [setup] Update testCustomHook to expose fn that allows accessing most recent state/value

- without this callback, the initial returned hook values will be stale/not properly return most recent values
- see next commit for example usage within useCellPopover

* Set up cell popover context

- set up initial open/location state, + open/close popover APIs returned to consumers

- improve auto props documentation - remove EuiDataGridCellLocation in favor of specifying rowIndex and colIndex (it's less DRY but it's easier for devs to not have to look up EuiDataGridCellLocation from our docs)

* Pass down popoverContext to cells as a prop

- I'm not using context here because we're already using this.context for focus, and unfortunately class components can only initialize one context at time using `static contextType` (see https://reactjs.org/docs/context.html#classcontexttype)

* Remove internal cell popoverIsOpen state

- This should now be handled by the overarching context state, and the cell should simply react to it or update it (similar to how focusContext works)

+ add new var for hasCellButtons

+ add unit tests for isFocusedCell alongside isPopoverOpen (since both methods perform similar functions)

* Update cell popovers to set the popover anchor & content

- content is TODO, will likely be easier to compare when cleaning it up/moving it all at once

* Refactor EuiDataGridCellPopover

- It should no longer exist as a reusable component that belongs to every single cell, but instead as a single popover that exists at the top grid level and moves from cell to cell

- I cleaned and split up the JSX for the popover (e.g. moving popover actions to data_grid_cell_buttons, where it feels like it belongs more) and think it's significantly more DRY now - note the entire `anchorContent` branch removed from EuiDataGridCell that is no longer necessary

- Note that due to this change, we now have to mock EuiWrappingPopover in EuiDataGrid tests, as we see failures otherwise

* [bugfix] Handle cells with open popover being scrolled out of view

- this is the same behavior as in prod

- causes weird DOM issues if we don't close the cell popover automatically

* [bugfix] Workaround for popover DOM stuttering issues

* [enhancement] Account for openCellPopover being called on cells out of view

+ write bonus Cypress test for useScroll's focus effect now that we have access to the imperative ref

* Update documentation example

+ remove code snippet - it was starting to get redundant with the API bullet points, and is already available as tab if needed

+ fix control button widths

* [PR feedback] Be more specific about useImperativeHandle dependencies

+ add a few explanatory comments

* [PR feedback] Rename openCellLocation to cellLocation

- to make it sound less like a verb/method

* [PR feedback] Ignore edge case of `openCellPopover` being called on an `isExpandable: false` cell

* [EuiDataGrid] Handle exposed ref APIs potentially pointing to invalid, off-page, or out of view cells (#5572)

* Enable sorting + targeting row indices outside of the current page

- to test handling the exposed APIs when dealing with sorted/paginated data

* Switch data grid example to Typescript

- to test type issues during consumer usage

+ @ts-ignore faker complaints

* Fix cell expansion buttons on paginated pages not working correctly

* Attempt to more clearly document `rowIndex`s that are actually `visibleRowIndex`s

* [setup] Move imperative handler setup to its own util file

- this will let us set up ref-specific helpers & add more comment context without bloating the main file

* Add catch/check for cell locations that do not exist in the current grid

* Add getVisibleRowIndex helper

- Converts the `rowIndex` from the consumer to a `visibleRowIndex` that our internal state can use

- Account for sorted grid by finding the inversed index of the `sortedRowMap`
- To make this easier, I converted soredRowMap to an array (since it's already only uses numbers for both keys and values), since arrays have a handy .findIndex method

- Handles automatically paginating the grid if the targeted cell is on a different page

* Replace grid ref Jest tests with more complete Cypress tests

* Update documentation with new behavior

* [PR feedback] Rename fns to indicate multiple concerns

- having a side effect in a getter feels bad, so change that to a `find`
- rename use hook to indicate sorting and pagination concerns

* Improve changelog

* Data grid ref methods docs review

* Fix colIndex to be available in renderCellValue as well as cell actions

- primarily for use within trailing/leading cells and custom actions

- see 1609e45

* Fix main datagrid example to restore cell focus on modal/flyout close

* [a11y] Fix missing header cell text in main datagrid example

Co-authored-by: Chandler Prall <chandler.prall@gmail.com>
Co-authored-by: Elizabet Oliveira <elizabet.oliveira@elastic.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants