-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(cdk/table) scrollable table body #20414
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(cdk/table) scrollable table body #20414
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.
Biggest thing for me is the API taking in the height - I would prefer that users don't need to pass this via their template, and it doesn't seem easy to have an auto
type maxheight that matches the container
@@ -0,0 +1,10 @@ | |||
/** |
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 expect this can be deleted - these tag names won't show up in the example and they already apply flex styles
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
src/cdk/table/table.ts
Outdated
/** Adds native table sections (e.g. tbody) and moves the row outlets into them. */ | ||
private _applyNativeTableSections() { | ||
private _applyNativeTableSections(): DocumentFragment { |
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.
Rename to reflect that this won't apply anything, but instead create the standard document fragment for the native table sections, e.g. _getNativeTableSections()
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
* Show a scroll bar if the table's body exceeds this height. The height may be specified with | ||
* any valid CSS unit of measurement. | ||
*/ | ||
@Input('scrollableBody') |
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 struggled a bit to get this case working: I want my table to expand in height to fit the content of the container, and I don't want to have to set a maxHeight
. Is that possible to do with this?
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 think the desired behavior will vary for each app:
- Fixed height (e.g.
200px
,60vh
,calc(100vh - OFFSET_FROM_TOP)
) - Fill height of immediate parent element.
- Fill remaining space in a higher level parent element (e.g immediate parent is a card and the table should fill the remaining space on the page).
The first use case is covered. IMO, the other two should be supported in another 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.
What are the options for letting people set this via CSS? Turning it into a TS API makes it harder to e.g. use media queries, your Sass layout constants, etc.
export type _TableLayout = DocumentFragment | null; | ||
|
||
/** Interface for a service that constructs the DOM structure for a {@link CdkTable}. */ | ||
export interface _TableLayoutStrategy { |
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 back and forth on whether the API should just be getLayout
, or what you have. I think what you have is fine - it's clear that there are two very separate layout types for the table and they should both be considered separately
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 also went back and forth on this and eventually chose this API for a few reasons:
- All implementations of a single
getLayout()
method would contain the same boilerplate code:
if (/* native table */) { /* native layout */ } else { /* flex layout */ }
- The explicit methods make the intent clearer and prevent erroneously returning the wrong layout for the table type.
- Some strategies may want to fall back on the default layout (e.g. scrollable body strategy is only supported for flex layouts, so the default layout is used for native tables).
src/cdk/table/table.ts
Outdated
} | ||
|
||
if (!layout && this._isNativeHtmlTable) { | ||
layout = this._applyNativeTableSections(); |
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 seems a little off to me that we use null
as a way to signal to the table that it should fall back on a default. Should there not just be a default layout strategy that is always used, and that contains the native table document fragment builder?
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.
sgtm. I extracted the default layout into its own _DefaultTableLayoutStrategy
. LMK what you think.
src/cdk-experimental/config.bzl
Outdated
@@ -6,6 +6,7 @@ CDK_EXPERIMENTAL_ENTRYPOINTS = [ | |||
"menu", | |||
"listbox", | |||
"popover-edit", | |||
"scrollable-table-body", |
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 whether this should be in its own separate module since it's very closely coupled to the table. Maybe we can have material-experimental/table
that exports a MatScrollableTableBody
module? I believe that would allow build tools to remove the code if it isn't used, while grouping under 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.
Yeah, I'd also prefer this. Alternatively a tertiary entry-point under material(-experimental)/table/
could work too.
|
||
/** A directive that enables scrollable body content for flex tables. */ | ||
@Directive({ | ||
selector: 'cdk-table[scrollableBody], mat-table[scrollableBody]', |
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 seems weird to be mixing CDK and Material selectors within the same module. Generally we have the CDK as the base class and the Material variant extends 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.
+1, we would need to separate these
src/cdk/table/table.ts
Outdated
protected readonly _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>) { | ||
protected readonly _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>, | ||
@Optional() @Inject(_TABLE_LAYOUT_STRATEGY) | ||
private readonly _layoutStrategy: _TableLayoutStrategy|null) { |
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.
While the |null
here is correct, I believe that ViewEngine will throw an error for it. You might have to make the parameter type non-null, declare a separate nullable property and assign to it:
private _layoutStrategy: _TableLayoutStrategy|null;
constructor(@Optional() @Inject(_TABLE_LAYOUT_STRATEGY) layoutStrategy: _TableLayoutStrategy) {
this._layoutStrategy = layoutStrategy;
}
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 we make this optional instead? Similar to this PR?
private readonly _layoutStrategy?: _TableLayoutStrategy
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, if I remember correctly optional parameters shouldn't throw.
/** | ||
* Add basic flex styling so that the cells evenly space themselves in the row. | ||
*/ | ||
cdk-row, cdk-header-row, cdk-footer-row { |
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 might be better to use the CSS classes for these selectors since they'll automatically work if we switch the example to use a table
-based mat-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.
+1, we prefer using css classes for styling over element selectors, and in examples we always prefix styled with example-
to make it clear which styles come from the component and which come from the example.
@@ -0,0 +1,33 @@ | |||
<button mat-button color="primary" (click)="addRow()">Add row</button> | |||
<button mat-button color="primary" (click)="removeRow()">Remove row</button> | |||
<button mat-button color="primary" (click)="nextMaxHeight()">Change max height</button> |
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.
nit: I don't think you're supposed to have multiple primary
buttons next to each other.
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 did that out of habit. My team's UX mocks tend to have primary
buttons next to each other.
I updated the template to align with the table
demo (raised, default buttons).
return this.data; | ||
} | ||
|
||
disconnect() {} |
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 the data
stream be completed 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.
Done
6200db9
to
92fc5d5
Compare
new InjectionToken<_TableLayoutStrategy>('_TableLayoutStrategy'); | ||
|
||
|
||
export class _DefaultTableLayoutStrategy implements _TableLayoutStrategy { |
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 prefer to name classes for what they do, rather than how they're used (e.g. "Default" here). How about something like _StandardTableLayoutStrategy
?
/** Interface for a service that constructs the DOM structure for a {@link CdkTable}. */ | ||
export interface _TableLayoutStrategy { | ||
/** Constructs the DOM structure for a native table. */ | ||
getNativeLayout(table: CdkTable<any>): DocumentFragment; |
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 believe all of the any
in this file should be unknown
(similar elsewhere where you don't know/care about the generic type)
readonly bodyCssClass = 'cdk-table-scrollable-table-body'; | ||
readonly footerCssClass = 'cdk-table-scrollable-table-footer'; | ||
|
||
constructor(@Inject(DOCUMENT) private readonly _document: any) { |
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.
Rather than making the injected _document
a class member with private
, you can create a separate property with type Document
so that the types are correct for uses throughout the file.
(we have to inject it as any otherwise SSR breaks due to how TypeScript decorators output)
* Returns the DOM structure for a native table. Scrollable body content is not supported for | ||
* native tables. Return `null` to use the default {@link CdkTable} native table layout. |
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.
Maybe you discussed this with @andrewseguin and @mmalerba, but what's the background on this only supporting flexbox-based table? We want to strongly encourage people to use the native table, so not supporting that for virtual-scroll seems like a problem to me.
|
||
for (const section of sections) { | ||
const element = this._document.createElement('div'); | ||
element.classList.add(section.selector); |
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.
selector
should be cssClass
since you're treating is as such here
|
||
/** A directive that enables scrollable body content for flex tables. */ | ||
@Directive({ | ||
selector: 'cdk-table[scrollableBody], mat-table[scrollableBody]', |
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.
+1, we would need to separate these
* Show a scroll bar if the table's body exceeds this height. The height may be specified with | ||
* any valid CSS unit of measurement. | ||
*/ | ||
@Input('scrollableBody') |
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.
What are the options for letting people set this via CSS? Turning it into a TS API makes it harder to e.g. use media queries, your Sass layout constants, etc.
/** | ||
* Add basic flex styling so that the cells evenly space themselves in the row. | ||
*/ | ||
cdk-row, cdk-header-row, cdk-footer-row { |
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.
+1, we prefer using css classes for styling over element selectors, and in examples we always prefix styled with example-
to make it clear which styles come from the component and which come from the example.
a69813b
to
e430a4c
Compare
@MichaelJamesParsons Would you still like to try and land this change? |
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. |
Changes
CdkTable
.Caveats