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(true lazy rendering) #102

Merged
merged 4 commits into from Mar 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 35 additions & 30 deletions addon/components/attach-popover.js
Expand Up @@ -63,13 +63,24 @@ export default Component.extend({
},

registerAPI(api) {
// Only 'popperTarget' has observers, everything else can be a direct property
this._disableEventListeners = api.disableEventListeners;
this._enableEventListeners = api.enableEventListeners;
this._popperElement = api.popperElement;
this._update = api.update;

this.set('popperTarget', api.popperTarget);
if (this._isHidden && !this.isDestroying && !this.isDestroyed) {
// Hide the attachment until it has been positioned,
// preventing jank during initial positioning
this._popperElement.style.visibility = 'hidden';

// The attachment has no width if initially hidden. This can cause it to be positioned so
// far to the right that it overflows the screen until enough updates fix its position.
// We avoid this by positioning initially hidden elements in the top left of the screen.
// The attachment will then correctly update its position from the first this._show()
this._popperElement.style.transform = null;

this._popperElement.style.display = this.get('isShown') ? '' : 'none';
}
}
},

Expand Down Expand Up @@ -129,6 +140,13 @@ export default Component.extend({
}),

_setIsVisibleAfterDelay(isVisible, delay) {
if (!this._popperElement) {
this._animationTimeout = requestAnimationFrame(() => {
this._animationTimeout = this._setIsVisibleAfterDelay(isVisible, delay);
});

return;
}
const onChange = this.get('onChange');

if (delay) {
Expand All @@ -137,6 +155,10 @@ export default Component.extend({
if (!this.isDestroyed && !this.isDestroying) {
this._popperElement.style.display = isVisible ? '' : 'none';

// Prevent jank by making the attachment invisible until positioned.
// The visibility style will be toggled by this._startShowAnimation()
this._popperElement.style.visibility = isVisible ? 'hidden' : '';

if (onChange) {
onChange(isVisible);
}
Expand All @@ -146,6 +168,10 @@ export default Component.extend({
} else {
this._popperElement.style.display = isVisible ? '' : 'none';

// Prevent jank by making the attachment invisible until positioned.
// The visibility style will be toggled by this._startShowAnimation()
this._popperElement.style.visibility = isVisible ? 'hidden' : '';

if (onChange) {
onChange(isVisible);
}
Expand Down Expand Up @@ -179,13 +205,16 @@ export default Component.extend({
init() {
this._super(...arguments);

// Used to determine the attachments initial parent element
this._parentFinder = self.document ? self.document.createTextNode('') : '';

// Holds the current popper target so event listeners can be removed if the target changes
this._currentTarget = null;

// The debounced _hide() and _show() are stored here so they can be cancelled when necessary
this._delayedVisibilityToggle = null;

this.id = this.id || `${guidFor(this)}-tooltip`;
this.id = this.id || `${guidFor(this)}-popper`;

// The final source of truth on whether or not all _hide() or _show() actions have completed
this._isHidden = true;
Expand Down Expand Up @@ -243,37 +272,13 @@ export default Component.extend({
didInsertElement() {
this._super(...arguments);

// The attachment has no width if initially hidden. This can cause it to be positioned so far
// to the right that it overflows the screen until enough updates fix its position.
// We avoid this issue by positioning initially hidden elements in the top left of the screen.
// The attachment will then correctly update its position from the first this._show()
next(this, () => {
if (this._isHidden && !this.isDestroying && !this.isDestroyed) {
this._popperElement.style.transform = null;

// Hide the attachment until it has been positioned, preventing jank during initial positioning
this._popperElement.style.visibility = 'hidden';
}
});

this._popperElement.style.display = this.get('isShown') ? '' : 'none';

this._initializeAttacher();
},

_initializeAttacher() {
this._removeEventListeners();

this._currentTarget = this.get('popperTarget');

if (!this._currentTarget) {
// Hide the attachment until a valid target is found
if (!this._isHidden) {
this._hide();
}

return;
}
this.set('_currentTarget', this.get('popperTarget') || this._parentFinder.parentNode);

this._addListenersForShowEvents();

Expand Down Expand Up @@ -382,8 +387,8 @@ export default Component.extend({
const popperElement = this._popperElement;

// Wait until the element is visible before continuing
if (popperElement.style.display === 'none') {
this._startShowAnimation();
if (!popperElement || popperElement.style.display === 'none') {
this._animationTimeout = this._startShowAnimation();

return;
}
Expand Down
6 changes: 6 additions & 0 deletions addon/components/attach-tooltip.js
Expand Up @@ -17,6 +17,12 @@ export default AttachPopover.extend({
}
}),

didInsertElement() {
this._super(...arguments);

this._currentTarget.setAttribute('aria-describedby', this.id);
},

popperTargetChanged: observer('popperTarget', function() {
const oldTarget = this._currentTarget;
if (oldTarget) {
Expand Down
28 changes: 15 additions & 13 deletions addon/templates/components/attach-popover.hbs
@@ -1,14 +1,16 @@
{{#ember-popper ariaRole=ariaRole
class="ember-attacher"
eventsEnabled=false
id=id
modifiers=_modifiers
placement=placement
popperContainer=popperContainer
registerAPI=(action 'registerAPI')
renderInPlace=renderInPlace
popperTarget=popperTarget as |emberPopper|}}
{{#if _shouldRender}}
{{unbound _parentFinder}}

{{#if (and _currentTarget _shouldRender)}}
{{#ember-popper ariaRole=ariaRole
class="ember-attacher"
eventsEnabled=false
id=id
modifiers=_modifiers
placement=placement
popperContainer=popperContainer
popperTarget=_currentTarget
registerAPI=(action 'registerAPI')
renderInPlace=renderInPlace as |emberPopper|}}
<div class="{{_class}}" style="{{_transitionDurationCss}}">
{{yield (hash emberPopper=emberPopper hide=(action 'hide'))}}

Expand All @@ -19,5 +21,5 @@
<div x-circle style="{{_circleTransitionDuration}}"></div>
{{/if}}
</div>
{{/if}}
{{/ember-popper}}
{{/ember-popper}}
{{/if}}
1 change: 1 addition & 0 deletions app/templates/components/attach-popover.js
@@ -0,0 +1 @@
export { default } from 'ember-attacher/templates/components/attach-popover';
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -33,7 +33,7 @@
"ember-cli-babel": "^6.11.0",
"ember-cli-htmlbars": "^2.0.2",
"ember-cli-sass": "^7.1.4",
"ember-popper": "^0.8.3",
"ember-popper": "^0.9.0",
"ember-truth-helpers": "^2.0.0"
},
"devDependencies": {
Expand Down