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

feat(cdk-scrollable): add methods to normalize scrolling in RTL #12607

Merged
merged 2 commits into from
Aug 13, 2018

Conversation

mmalerba
Copy link
Contributor

@mmalerba mmalerba commented Aug 9, 2018

Technically a major change due to tightening the ElementRef to ElementRef<HTMLElement>

@mmalerba mmalerba added the target: major This PR is targeted for the next major release label Aug 9, 2018
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Aug 9, 2018
@@ -8,6 +8,7 @@

/** Cached result of whether the user's browser supports passive event listeners. */
let supportsPassiveEvents: boolean;
let rtlScrollAxisType: 'normal' | 'negated' | 'inverted';
Copy link
Member

Choose a reason for hiding this comment

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

Add description?

* - inverted: scrollLeft is (scrollWidth - clientWidth) when scrolled all the way left and 0 when
* scrolled all the way right.
*/
export function getRtlScrollAxisType(): 'normal' | 'negated' | 'inverted' {
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment somewhere in this file like "As of time of this writing, Chrome is ..., Firefox is ..."

@@ -8,6 +8,7 @@

/** Cached result of whether the user's browser supports passive event listeners. */
let supportsPassiveEvents: boolean;
let rtlScrollAxisType: 'normal' | 'negated' | 'inverted';
Copy link
Member

Choose a reason for hiding this comment

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

I think this would be a good time to break features.ts into separate files for different features

@@ -8,6 +8,7 @@

/** Cached result of whether the user's browser supports passive event listeners. */
let supportsPassiveEvents: boolean;
let rtlScrollAxisType: 'normal' | 'negated' | 'inverted';
Copy link
Member

Choose a reason for hiding this comment

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

Could this use an enum? In other places where we use string literal types, it's usually because the value can be provided in a template

}

if (!rtlScrollAxisType) {
const viewport = document.createElement('div');
Copy link
Member

Choose a reason for hiding this comment

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

viewport -> scrollingContainer? Since viewport is often used to refer to the whole browser viewport.

class ScrollableViewport {
@Input() dir: Direction;
@ViewChild(CdkScrollable) scrollable: CdkScrollable;
@ViewChild('viewport') viewport: ElementRef<HTMLElement>;
Copy link
Member

Choose a reason for hiding this comment

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

viewport -> scrollingContainer?

*/
end?: number;
/** A distance relative to the bottom edge of the viewport. */
bottom?: number;
Copy link
Member

Choose a reason for hiding this comment

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

So, you could capture the mutual exclusivity with TypeScript. The error message is a little wordy.

(idea stolen from microsoft/TypeScript#14094)

}

getElementRef(): ElementRef {
return this._elementRef;
private _scrollTo(options: ScrollToOptions): void {
Copy link
Member

Choose a reason for hiding this comment

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

Could this have a different name than the public method that captures what it does differently / more?

* Measures the scroll offset relative to the specified edge of the viewport.
* @param from The edge to measure from.
*/
measureScrollOffset(from: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number {
Copy link
Member

Choose a reason for hiding this comment

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

Could the type for from be an enum, or does it come from something a user would put in a template?

If it can't be an enum, it might be good to alias it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one could appear in a template, I'll alias the values used more than once

from = isRtl ? 'left' : 'right';
}

if (isRtl && getRtlScrollAxisType() == 'inverted') {
Copy link
Member

Choose a reason for hiding this comment

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

I think it would make this code more understandable if you had a brief comment like like

// normal: 
// inverted: 
// negated:

Even though that's already written in the feature detection function.

It would also be good to explicitly say in a comment which system you're normalizing to (I assume it's normal).

*/

/** The possible ways the browser may handle the horizontal scroll axis in RTL languages. */
export enum RtlScrollAxisType {
Copy link
Member

Choose a reason for hiding this comment

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

Didn't we have something about not using enums @jelbourn?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jelbourn actually requested this, since its not a value that a user would supply through a template

Copy link
Member

Choose a reason for hiding this comment

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

I have no memory about not wanting to use enums (for things that aren't used via templates). It is very possible I forgot something, though.

Copy link
Member

Choose a reason for hiding this comment

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

I have some memory about it messing some internal tooling (Closure maybe?), but I could be mixing things up.


/** Check whether the browser supports scroll behaviors. */
export function supportsScrollBehavior(): boolean {
return !!(document && document.documentElement && document.documentElement.style &&
Copy link
Member

Choose a reason for hiding this comment

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

This will throw an error during server-side rendering. The check for document should be typeof document !== 'undefined'. Also you don't need checks for document.documentElement and documentElement.style since those are guaranteed to be there if we have a document.

scrollContainer.style.overflow = 'auto';
scrollContainer.style.visibility = 'hidden';
scrollContainer.style.pointerEvents = 'none';
scrollContainer.style.position = 'absolute';
Copy link
Member

Choose a reason for hiding this comment

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

Consider having a variable for scrollContainer.style so it doesn't have to be repeated as much. It should minify a bit better as well.

scrollContainer.scrollLeft == 0 ? RtlScrollAxisType.NEGATED : RtlScrollAxisType.INVERTED;
}

document.body.removeChild(scrollContainer);
Copy link
Member

Choose a reason for hiding this comment

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

This would be a little more future-proof as scrollContainer.parentNode.removeChild(scrollContainer). This would start failing if we started appending the element to something different from the body.

// return 0 when we read it again.
scrollContainer.scrollLeft = 1;
rtlScrollAxisType =
scrollContainer.scrollLeft == 0 ? RtlScrollAxisType.NEGATED : RtlScrollAxisType.INVERTED;
Copy link
Member

Choose a reason for hiding this comment

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

Should be triple equals.


function checkIntersecting(r1: ClientRect, r2: ClientRect, expected = true) {
const actual =
r1.left < r2.right && r1.right > r2.left && r1.top < r2.bottom && r1.bottom > r2.top;
Copy link
Member

Choose a reason for hiding this comment

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

Should these be <=, >= etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, elements that are side-by-side would be detected as overlapping if we did that

checkIntersecting(testComponent.scrollContainer.nativeElement.getBoundingClientRect(),
testComponent.lastRowEnd.nativeElement.getBoundingClientRect(), false);

expect(testComponent.scrollable.measureScrollOffset('top')).toBe(0);
Copy link
Member

Choose a reason for hiding this comment

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

Seems like a bunch of long expressions like testComponent.scrollContainer.nativeElement are being repeated a lot. Consider moving them into variables to reduce noise.

* start, and end properties.
*/
export interface ExtendedScrollToOptions extends ScrollToOptions {
/** A distance relative to the right edge of the viewport. */
Copy link
Member

Choose a reason for hiding this comment

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

super tiny nit: the property description don't have to start with "a".

@mmalerba mmalerba force-pushed the scrollable-rtl branch 3 times, most recently from b4da004 to e9c384b Compare August 10, 2018 17:09
@mmalerba
Copy link
Contributor Author

PTAL, comments addressed

Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

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

LGTM

@crisbeto crisbeto added pr: lgtm action: merge The PR is ready for merge by the caretaker labels Aug 10, 2018
Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

LGTM aside from one nti

export type _Left = { left?: number };
export type _Right = { right?: number };
export type _Start = { start?: number };
export type _End = { end?: number };
Copy link
Member

Choose a reason for hiding this comment

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

Omit spaces in braces

Copy link
Member

@devversion devversion Aug 10, 2018

Choose a reason for hiding this comment

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

We could probably make the new noImportSpacing rule also cover the spacing inside of objects. Similar as the object-curly-spacing rule from ESlint. I'll have a quick look at some point.

@mmalerba mmalerba merged commit 028746a into angular:master Aug 13, 2018
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants