-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
feat(column-resize): Experimental column resize for mat-table #16114
Conversation
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 got about halfway through before I got too sleepy and decided to go home
protected readonly destroyed = new ReplaySubject<void>(); | ||
protected readonly document: Document; | ||
|
||
get minPx(): number { |
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.
How impractical would it be to support more units for minx/max?
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.
It would ultimately require the ability to convert them into pixels for use while resizing is in progress. I'm not going to do it, but someone probably could.
543b8fd
to
25043c6
Compare
Addressed existing comments and made some progress on getting tests set up. Still not ready to leave draft state yet. |
25043c6
to
753fc20
Compare
e2ca82c
to
472b594
Compare
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.
src/cdk-experimental/column-resize/column-resize-directives/column-resize-flex.ts
Outdated
Show resolved
Hide resolved
* The optimally performing resize strategy for flex mat-tables. | ||
* Tested against and outperformed: | ||
* CSS selector w/ CSS variable | ||
* Updating all mat-cell nodes |
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.
Were these tests something persistent that we could reference in the future, or just something local/transient?
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 just ran them locally in the development app with 100 and 500 rows.
I could imagine a unit test that recreates that scenario, but I'd rather not be the one to write it :P
@crisbeto @andrewseguin PTAL if you have time |
@jelbourn Is it patched in upcoming updates for mat-table ? |
TABLE_LAYOUT_FIXED_RESIZE_STRATEGY_PROVIDER, | ||
]; | ||
export const FLEX_PROVIDERS: Provider[] = [...PROVIDERS, FLEX_RESIZE_STRATEGY_PROVIDER]; | ||
export const HOST_BINDINGS = { |
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.
It feels like overkill to have to maintain this in a separate constant.
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'll punt this one up to @jelbourn for adjudication - I like the DRYness of it.
src/cdk-experimental/column-resize/column-resize-directives/column-resize-flex.ts
Show resolved
Hide resolved
this.ngZone.runOutsideAngular(() => { | ||
const element = this.elementRef.nativeElement!; | ||
|
||
fromEvent<MouseEvent>(element, 'mouseover').pipe( |
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.
Don't we want mouseenter
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.
mouseenter isn't very useful for delegated events. This is a single listener for the whole table.
takeUntil(this.destroyed), | ||
map(event => closest(event.target, HEADER_CELL_SELECTOR)), | ||
).subscribe(this.eventDispatcher.headerCellHovered); | ||
fromEvent<MouseEvent>(element, 'mouseleave').pipe( |
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.
How are we going to handle this for touch devices?
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 is a great question that I need to think more about.
My leading idea at the moment is to show the resize handles when there is focus within the header row (hopefully the user tapping there will trigger that), or failing that, listen for taps within the header row and show them until a tap outside of the row.
Added touch as a follow-up PR todo in the PR description.
|
||
private _dragStarted(mousedownEvent: MouseEvent) { | ||
const mouseup = fromEvent<MouseEvent>(this.document, 'mouseup'); | ||
const mousemove = fromEvent<MouseEvent>(this.document, 'mousemove'); |
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.
Should we handle touch events here? Usually it's much easier to do it up-front than adding it later on.
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 - I need to do this, but ideally in a follow up PR as this one is quite big already (will add to my to do list in the PR description).
// If the mouse moved further than the resize was able to match, limit the | ||
// movement of the overlay to match the actual size and position of the origin. | ||
if (overshot !== 0) { | ||
if (overshot < 0 && deltaX < 0 || overshot > 0 && deltaX > 0) { |
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.
Could we reuse the drag-drop
module from the CDK here? I know that @andrewseguin managed to implement a resizeable sidenav with it which is very similar to what we need to do for the table.
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 looked at it when I was designing this and it seemed more straightforward to do it this way. It's been a while, so I don't remember my exact thought process though.
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.
Worthwhile to add a TODO for now, we can revisit when we want to move out of experimental
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.
TODO added at top of file
overlayY: 'top', | ||
}]); | ||
|
||
return this.overlay.create({ |
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 not sure I follow why we need an overlay here. Is it just so we can move the handle while dragging?
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.
In my design doc for this (Google-internal, sorry), I cover this. Basically, there's no better way to get the styling right for the drag handle as it has to visually bleed into both the next and previous cells and also go down the entire table while the dragging is happening according to the internal UI spec I'm basing this on (which is an extension of the public Material Design spec that includes things like Datatable resizing and inline edit)
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.
Can you encode that context into a comment in this file?
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.
Added
applyMinColumnSize(cssFriendlyColumnName: string, _: HTMLElement, sizeInPx: number): void { | ||
const cssSize = coerceCssPixelValue(sizeInPx); | ||
|
||
this._applyProperty(cssFriendlyColumnName, 'min-width', cssSize, |
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.
Have you tried this against IE/Edge? They tend to not work very well with min-width
/min-height
and flexbox.
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.
Honestly I'm not even sure how I'd test that (I only have Macs and Linux boxes here at work). Is this still a concern with Chromium-based Edge?
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.
Probably not, but the Chromium-based Edge is still away from being the default.
const selector = `.${tableClassName} .${columnClassName}`; | ||
const body = propertyKeys.map(key => `${key}:${properties.get(key)}`).join(';'); | ||
|
||
this._getStyleSheet().insertRule(`${selector} {${body}}`, index!); |
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.
Wouldn't it be simpler to assign the styles directly on the element, rather than having to keep track of a separate stylesheet?
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.
It would be simpler, but I'm doing this as an optimization to avoid doing a dom manipulation for every row in the table.
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'd also be the issue of dealing with rows added later on, which this handles nicely.
Change looks good, and the demo works great. Users are going to be excited about this one My only comment on the PR is that it looks like a lot of the functions and properties are missing JSDocs. I know it's quite a bit to document, but do you mind sweeping through with comments for them? |
Working on reviving this at long last. Going to respond to some of the above comments then take on the actionable ones after I get all of this rebased to the modern world. |
@jelbourn Re: the padding question Yes - I think you'd want to have padding inside your cells when using the resize feature. The internal spec has 8px I think. |
472b594
to
e97a7bd
Compare
e97a7bd
to
0aa2d47
Compare
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.
Added a bunch of JSDoc. Please take a look
src/cdk-experimental/column-resize/column-resize-directives/column-resize-flex.ts
Outdated
Show resolved
Hide resolved
src/cdk-experimental/column-resize/column-resize-directives/column-resize-flex.ts
Show resolved
Hide resolved
53e2210
to
2c620fb
Compare
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.
Took a fresh look at this- just a handful more things I noticed.
Would also be good for @crisbeto and @andrewseguin to take a fresh look
readonly completeImmediately?: boolean; | ||
} | ||
|
||
/** Conduit for resize-releated events within the table. */ |
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.
/** Conduit for resize-releated events within the table. */ | |
/** Originating source of column resize events within a table. */ |
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.
Done
overlayY: 'top', | ||
}]); | ||
|
||
return this.overlay.create({ |
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.
Can you encode that context into a comment in this file?
this.resizeNotifier.resizeCanceled, | ||
this.resizeNotifier.resizeCompleted, | ||
).pipe(takeUntilDestroyed).subscribe(columnSize => { | ||
this.elementRef.nativeElement!.classList.remove(OVERLAY_ACTIVE_CLASS); |
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.
This method is called _listenForResizeEvents
, but this doesn't really describe what happens on resize. Break this part into one or more methods that have a descriptive name?
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.
Re: "Can you encode that context into a comment in this file?", I'm not sure I understand what you're asking me to do.
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.
(nevermind - this was super confusing in the conversation view but I see what you were talking about now)
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.
Split two methods out of _listenForResizeEvents
} | ||
} | ||
|
||
const OVERLAY_ACTIVE_CLASS = 'cdk-resizable-overlay-thumb-active'; |
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.
Move this to the top of the file?
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.
Done
@@ -0,0 +1,16 @@ | |||
/** |
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.
How about calling this file selectors.ts
?
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.
Done
// If the mouse moved further than the resize was able to match, limit the | ||
// movement of the overlay to match the actual size and position of the origin. | ||
if (overshot !== 0) { | ||
if (overshot < 0 && deltaX < 0 || overshot > 0 && deltaX > 0) { |
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.
Worthwhile to add a TODO for now, we can revisit when we want to move out of experimental
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.
LGTM once all the comments are addressed.
* Base class for ColumnResize directives which attach to mat-table elements to | ||
* provide common events and services for column resizing. | ||
*/ | ||
export abstract class ColumnResize implements AfterViewInit, OnDestroy { |
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.
This should have a blank @Directive()
decorator since it's using Angular features.
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.
Done
abstract readonly columnResizeNotifier: ColumnResizeNotifier; | ||
|
||
abstract readonly directionality: Directionality; | ||
protected abstract readonly elementRef: ElementRef; |
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.
Should be ElementRef<HTMLElement>
for stronger typing.
* Base class for a component shown over the edge of a resizable column that is responsible | ||
* for handling column resize mouse events and displaying any visible UI on the column edge. | ||
*/ | ||
export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy { |
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.
Should be marked as @Directive
.
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.
Done
* for handling column resize mouse events and displaying any visible UI on the column edge. | ||
*/ | ||
export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy { | ||
protected readonly destroyed = new ReplaySubject<void>(); |
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 don't see why this needs to be a ReplaySubject
. Since it only ever emits once and completes, it can be a regular Subject
.
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.
It's a safety precaution that I've adopted as my default practice - if something subscribes after this is destroyed (perhaps due to some async event or timer) then ReplaySubject will cause it to immediately unsubscribe whereas a normal subject never would.
* provide common events and services for column resizing. | ||
*/ | ||
export abstract class ColumnResize implements AfterViewInit, OnDestroy { | ||
protected readonly destroyed = new ReplaySubject<void>(); |
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.
This should also be a regular Subject
.
|
||
fromEvent<MouseEvent>(this.elementRef.nativeElement!, 'mouseleave').pipe( | ||
takeUntilDestroyed, | ||
map(event => event.relatedTarget && |
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.
Have you tested this out in different browsers? I know that IE at least used to have some issues with relatedTarget
.
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 don't have a good way to test it on IE (I just have access to Mac, Linux, ChomeOS and iOS). I'd appreciate help sorting things like this out before this graduates from experimental.
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.
Windows, IE11, and Edge (non-Chromium) run fine for me on my MBPs via VirtualBox, VMWare Fusion, and Bootcamp. VirtualBox and Bootcamp are free.
applyMinColumnSize(cssFriendlyColumnName: string, _: HTMLElement, sizeInPx: number): void { | ||
const cssSize = coerceCssPixelValue(sizeInPx); | ||
|
||
this._applyProperty(cssFriendlyColumnName, 'min-width', cssSize, |
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.
Probably not, but the Chromium-based Edge is still away from being the 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.
LGTM - though in general I would prefer to see more jsdocs for class methods
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.
LGTM
6d44a55
to
1d71ac2
Compare
@kseamon is there any estimate release date for this ? Thx. |
Not yet - I hope to get most of the rest of the coding done in April, but
then we will probably wait on some accessibility specs to be complete
before moving it out of experimental.
No promises, but I don't expect huge api changes so you *could* use it from
experimental as of Angular 10 probably.
…On Thu, Mar 5, 2020 at 3:49 PM Oleg Kuibar ***@***.***> wrote:
@kseamon <https://github.com/kseamon> is there any estimate release date
for this ? Thx.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#16114?email_source=notifications&email_token=AAHBQHJMMQEJSQVPF7NPSDTRGAF4NA5CNFSM4HPSUWL2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEN627PA#issuecomment-595439548>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAHBQHOXW2AKUHOLR2QHIY3RGAF4NANCNFSM4HPSUWLQ>
.
|
The current code is usable from |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Plus some incomplete cdk parts.
To come in follow-up PRs: