-
Notifications
You must be signed in to change notification settings - Fork 841
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
Clean up copy/pasting of tabular content in EuiDataGrid and EuiBasic/InMemoryTable #8019
Conversation
and move existing `copy_to_clipboard` there
- requires waterfalling `visibleColCount` number down to header cell wrapper for isLastColumn logic
68e2f99
to
3d837bf
Compare
- and as such not appending the correct copy markers
- requires adding an `append` prop to various cell components, for rendering this hidden text outside of the cell content wrapper
- my god this was a StackOverflow journey
2055f0e
to
43cc859
Compare
- we should be more specific with selectors anyway
works just fine in actual browsers, but typescript really wants document for whatever reason see https://stackoverflow.com/questions/74809554/cant-get-paste-event-to-work-in-typescript :|
43cc859
to
729f192
Compare
// Special visually hidden unicode characters that we use to manually clean content | ||
// and force our own newlines/horizontal tabs | ||
export const CHARS = { | ||
NEWLINE: '↵', | ||
TAB: '↦', | ||
TABULAR_CONTENT_BOUND: '', // U+2063 - invisible separator | ||
NO_COPY_BOUND: '', // U+2062 - invisible times | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd really appreciate people's thoughts on the (semi-random) characters I've chosen here and if we think they're unlikely enough to be used in consumer content to feel safe using them here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if the invisible times
might be used in a grid somehow as its usage seems to be in maths context.
The invisible times codepoint is used in mathematical type-setting to indicate the multiplication of two terms without a visible multiplication operator, e.g. when type-setting 2x (the multiplication of the number 2 and the variable x), the invisible times codepoint can be inserted in-between: 2 <U+2062> x.
And the invisible seperator
("invisible comma") is apparently commonly used. Question would be if that's a case for a datagrid though.
Do we need to use invisible characters here?
If not, I found this "Angle with Down Zig-zag Arrow" character ⍼
of the "Misc technical" block which seems to have no meaning and hence hopefully is not commonly used? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good with changing it to whatever! 👍 No strong feelings either way. I was also thinking maybe these scissors characters?
✁ UPPER BLADE SCISSORS
✃ LOWER BLADE SCISSORS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'm fine with kind of whatever too, in the end the important thing is it's not commonly used.
We could try the scissors and worst case we update if issues are reported.
... I just had another thought: How about combining 2 random characters? That'll reduce the likelyhood of them being used for sure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use two random characters for NO_COPY_BOUND
fairly easily, but I'm a little more hesitant for TABULAR_CONTENT_BOUND
, as that complicates the .split()
logic I'm using & would possibly require a regex at that point. Do you have any suggestions for characters? 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mgadewoll I misread your comment - I thought you meant using two different characters for the start & end bounds! Using two characters does seem super smart - I've made that tweak here (9a22861) while trying to keep the symbols still fairly relevant to the intent. Let me know if it looks good to you!
// Special visually hidden unicode characters that we use to manually clean content | ||
// and force our own newlines/horizontal tabs | ||
export const CHARS = { | ||
NEWLINE: '↵', | ||
TAB: '↦', | ||
TABULAR_CONTENT_BOUND: '', // U+2063 - invisible separator | ||
NO_COPY_BOUND: '', // U+2062 - invisible times | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if the invisible times
might be used in a grid somehow as its usage seems to be in maths context.
The invisible times codepoint is used in mathematical type-setting to indicate the multiplication of two terms without a visible multiplication operator, e.g. when type-setting 2x (the multiplication of the number 2 and the variable x), the invisible times codepoint can be inserted in-between: 2 <U+2062> x.
And the invisible seperator
("invisible comma") is apparently commonly used. Question would be if that's a case for a datagrid though.
Do we need to use invisible characters here?
If not, I found this "Angle with Down Zig-zag Arrow" character ⍼
of the "Misc technical" block which seems to have no meaning and hence hopefully is not commonly used? 🤔
@@ -80,6 +83,7 @@ const EuiDataGridHeaderRow = memo( | |||
<EuiDataGridControlHeaderCell | |||
key={controlColumn.id} | |||
index={index + leadingControlColumns.length + columns.length} | |||
visibleColCount={visibleColCount} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently the leading/trailing columns have a visually hidden (SR only) column title.
This one will be copied though it's not visible. Is that expected or should visually hidden content not be copied? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Screen reader text will generally be copied unless it's been marked with special characters to remove it from copied text (which is what I did for our enter key instructions). In this case I think it's entirely appropriate to copy.
@jughosta did request a configurable feature for control columns in Slack:
J: Would it be possible to exclude control columns and icons from the output too? Maybe we could allow some customization options.
C: In theory with this approach consumers could render their own 'exclude' bounds with the character I've created for that 🤔 but are you saying you'd prefer a prop likecolumns={[{ id: 'someColumn', excludeWhenCopying: true }]}
?
I'm not totally sure I have time for that right now though, so I wanted to ship this PR as-is (since it's a large improvement) and consider adding that in a future follow-up PR. My 2c is that users deleting a single column from a spreadsheet is very few clicks in comparison to completely cleaning newlines from every cell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I agree. It's not much effort to delete columns that are unwanted and it's already a great improvement and optionally removing columns could be a follow-up if required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not say that it would be simple for users to remove columns from the output as the output might become misaligned.
For example, I skip the controls and start my selection from text "Green..."
and after copying I get the following:
Agree that the PR already brings a huge improvement although many users would still run into the similar problems if we don't address it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, agreed, this was also the first thing I was running into, so this might be a common problem, would it be possible to exclude control columns by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker. Thanks for improving the selection!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, not a blocker, thx for taking care of this so quickly 🙏 Btw it's interesting how Security is using it, for me this is a edge case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed this is not a blocker and it's already a massive improvement! 🤘 The issue described here is true for native HTML tables too, so I wouldn't be too concerned. There's a risk trying to be too smart around control columns could result in unexpected behaviour for consumers anyway (or maybe not, hard to guess). The suggestion for consumers to selectively exclude what they don't want copied using the same characters seems safe and reasonable IMO, especially if that's already possible for control columns.
@cee-chen It looks like the utils from tabular_copy.tsx
are exported publicly, is that right? If so it should already be easy from our side to exclude the control columns in Discover if we want to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll be able to exclude screen reader only control column text, but the control columns themselves will always generate a hidden horizontal tab character - that is currently outside the scope of what's overrideable right now in this PR.
For the former use case, usage would look something like this:
import { EuiDataGrid, tabularCopyMarkers } from '@elastic/eui';
<EuiDataGrid
leadingControlColumns={[{
id: 'selection',
width: 32,
headerCellRender: () => (
<EuiScreenReaderOnly>
<span>
{tabularCopyMarkers.hiddenNoCopyBoundary}
Example text that does not show on copy
{tabularCopyMarkers.hiddenNoCopyBoundary}
</span>
</EuiScreenReaderOnly>
),
}]}
/>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, thanks for the clarification. In that case additional control over the control columns would be useful to consider as a follow up, but the changes in this PR are still a big improvement regardless 👍
Cypress.automation('remote:debugger:protocol', { | ||
command: 'Browser.grantPermissions', | ||
params: { | ||
permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went down so many google/stackoverflow rabbit holes to figure out how to get this working haha 🫠 honestly until the very end I wasn't really sure it was going to be, but very glad to have a regression test for this!
@kertal Before we ship this, do you see any issues or concerns with this approach in production Kibana/Discover? |
@cee-chen just a quick first take on it, @jughosta, could you check tomorrow, since you implemented and closed #192395 for this approach. many thx 🙏. we could also create a shared also FYI @davismcphee because of his focus on Discover rendering performance, and optimizations around the grid in Discover |
Shared Kibana/EUI PR instance to test btw: https://kertal-pr-192756-eui-v95-11-0.kbndev.co/app/r/s/KzSxC |
…nsumer + use 2 characters for increased chances + add more `data` attributes to help identify usages
- in case consumers want to use it outside SR text - thanks Davis for the prompt!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢 🐈⬛ The additional changes look good to me! There seems to just be a small linting error still blocking the CI.
Sorry for not directly following up on the re-review 🙈
@@ -17,10 +17,11 @@ import React, { PropsWithChildren, useEffect } from 'react'; | |||
export const CHARS = { | |||
NEWLINE: '↵', | |||
TAB: '↦', | |||
TABULAR_CONTENT_BOUND: '', // U+2063 - invisible separator | |||
NO_COPY_BOUND: '', // U+2062 - invisible times | |||
// Use multiple characters to reduce the chances of consumers also using these characters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should definitely reduce the likelihood of it being a problem 👍
packages/eui/src/components/datagrid/data_grid.stories.utils.tsx
Outdated
Show resolved
Hide resolved
💚 Build Succeeded
History
|
`v95.11.0`⏩`v95.12.0-backport.0` > [!note] > A few fun highlights from this release: > - Content within `EuiDataGrid`, `EuiBasicTable`, and `EuiInMemoryTable`, when manually selected/highlighted by users, should now more cleanly copy and paste into various spreadsheet apps > - `EuiProvider` will now detect the user's system-level dark or light mode and default to that if no `colorMode` prop is passed manually _[Questions? Please see our Kibana upgrade FAQ.](https://github.com/elastic/eui/blob/main/wiki/eui-team-processes/upgrading-kibana.md#faq-for-kibana-teams)_ --- ## [`v95.12.0`](https://github.com/elastic/eui/releases/v95.12.0) - Enhanced `EuiDataGrid` and `EuiBasic/InMemoryTable` to clean content newlines/tabs when users copy and paste from their tabular data ([#8019](elastic/eui#8019)) - Updated `EuiResizableButton` with a new `accountForScrollbars` prop ([#8021](elastic/eui#8021)) - Updated `EuiProvider` to inherit from the user's OS/system light/dark mode setting if a `colorMode` prop has not been passed ([#8026](elastic/eui#8026)) **Bug fixes** - Fixed `EuiDatePicker`'s `onClear` button to not appear when the input is `disabled` ([#8020](elastic/eui#8020)) - Fixed several `EuiDataGrid` row height bugs: ([#8025](elastic/eui#8025)) - Fixed row heights not recalculating when `rowHeightOptions.lineHeight`, `gridStyles.fontSize`, or `gridStyles.cellPadding` changed - Fixed incorrect height calculations for `rowHeightOptions.rowHeights` with `lineCount`s - Fixed control column content to align better with multi-line row heights, as well as custom line-heights ## [`v95.12.0-backport.0`](https://github.com/elastic/eui/releases/v95.12.0-backport.0) **This is a backport release only intended for use by Kibana.** **Bug fixes** - Fixed `EuiProvider`'s system color mode detection causing errors during server-side rendering ([#8040](elastic/eui#8040)) - Fixed an `EuiDataGrid` rendering bug that was causing bouncing scrollbar issues ([#8041](elastic/eui#8041))
`v95.11.0`⏩`v95.12.0-backport.0` > [!note] > A few fun highlights from this release: > - Content within `EuiDataGrid`, `EuiBasicTable`, and `EuiInMemoryTable`, when manually selected/highlighted by users, should now more cleanly copy and paste into various spreadsheet apps > - `EuiProvider` will now detect the user's system-level dark or light mode and default to that if no `colorMode` prop is passed manually _[Questions? Please see our Kibana upgrade FAQ.](https://github.com/elastic/eui/blob/main/wiki/eui-team-processes/upgrading-kibana.md#faq-for-kibana-teams)_ --- ## [`v95.12.0`](https://github.com/elastic/eui/releases/v95.12.0) - Enhanced `EuiDataGrid` and `EuiBasic/InMemoryTable` to clean content newlines/tabs when users copy and paste from their tabular data ([elastic#8019](elastic/eui#8019)) - Updated `EuiResizableButton` with a new `accountForScrollbars` prop ([elastic#8021](elastic/eui#8021)) - Updated `EuiProvider` to inherit from the user's OS/system light/dark mode setting if a `colorMode` prop has not been passed ([elastic#8026](elastic/eui#8026)) **Bug fixes** - Fixed `EuiDatePicker`'s `onClear` button to not appear when the input is `disabled` ([elastic#8020](elastic/eui#8020)) - Fixed several `EuiDataGrid` row height bugs: ([elastic#8025](elastic/eui#8025)) - Fixed row heights not recalculating when `rowHeightOptions.lineHeight`, `gridStyles.fontSize`, or `gridStyles.cellPadding` changed - Fixed incorrect height calculations for `rowHeightOptions.rowHeights` with `lineCount`s - Fixed control column content to align better with multi-line row heights, as well as custom line-heights ## [`v95.12.0-backport.0`](https://github.com/elastic/eui/releases/v95.12.0-backport.0) **This is a backport release only intended for use by Kibana.** **Bug fixes** - Fixed `EuiProvider`'s system color mode detection causing errors during server-side rendering ([elastic#8040](elastic/eui#8040)) - Fixed an `EuiDataGrid` rendering bug that was causing bouncing scrollbar issues ([elastic#8041](elastic/eui#8041)) (cherry picked from commit 8a89c85)
Summary
closes elastic/kibana#177952
This PR adds a tabular content copy service which listens for copy events, detects hidden characters that both EuiDataGrid and EuiBasicTable now embed, and strips those characters and any undesired newlines/tabs and replaces them with only newlines/tabs that correspond with rows and cells.
writeText()
because it only works onhttps
and with permissions. I went with the more old schoolclipboardData.setData()
instead: https://developer.mozilla.org/en-US/docs/Web/API/Element/copy_eventWarning
This will not work as expected for virtualized data grids that scroll (especially if the last cell is not visible and scrolled off the page). There is not a lot EUI can do about this as the cells are literally not rendered in the DOM - at most we could provide an
onCopy
API for consumers to hook into and provide their own cleaned data, but that would have to be a future feature request/enhancement.QA
General checklist
- [ ] Checked in both light and dark modes- [ ] Checked in mobile- [ ] Updated visual regression tests- [ ] If applicable, added the breaking change issue label (and filled out the breaking change checklist)