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

✨ Render close button for lightbox ads in inabox #16886

Merged
merged 24 commits into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ads/inabox/frame-overlay-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class FrameOverlayManager {
this.isExpanded_ = true;
this.viewportChangedSinceExpand_ = false;
this.collapsedRect_ = collapsedRect;

Copy link
Contributor

Choose a reason for hiding this comment

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

revert?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

callback(expandedRect);
});
}
Expand Down
12 changes: 11 additions & 1 deletion css/lightbox-ad.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,20 @@ i-amphtml-ad-close-header {
left: 0 !important;
right: 0 !important;
z-index: 1000 !important; /* same as amp-lightbox */
transition: 0.1s opacity ease-in; /* same as amp-lightbox */
display: flex !important;
align-items: center !important;
justify-content: right !important;

/* same as <amp-lightbox [animate-in="fade-in"]> */
transition: 0.1s opacity ease-in;
}

[animate-in="fly-in-bottom"] > i-amphtml-ad-close-header,
[animate-in="fly-in-top"] > i-amphtml-ad-close-header,
i-amphtml-ad-close-header.amp-animate-in-fly-in-bottom,
i-amphtml-ad-close-header.amp-animate-in-fly-in-top {
/* show after transition has finished */
transition-delay: 0.3s;
}

.amp-ad-close-header {
Expand Down
3 changes: 2 additions & 1 deletion examples/ad-lightbox.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@

<amp-lightbox id="main-lightbox"
class="main-lightbox"
layout="nodisplay">
layout="nodisplay"
animate-in="fly-in-bottom">
<amp-carousel type="slides"
height="300"
layout="fixed-height">
Expand Down
86 changes: 77 additions & 9 deletions extensions/amp-lightbox/0.1/amp-lightbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,21 @@ import {Gestures} from '../../../src/gesture';
import {KeyCodes} from '../../../src/utils/key-codes';
import {Services} from '../../../src/services';
import {SwipeXYRecognizer} from '../../../src/gesture-recognizers';
import {computedStyle, setImportantStyles} from '../../../src/style';
import {createCustomEvent} from '../../../src/event-helper';
import {computedStyle, px, setImportantStyles} from '../../../src/style';
import {createCustomEvent, listenOnce} from '../../../src/event-helper';
import {debounce} from '../../../src/utils/rate-limit';
import {dev, user} from '../../../src/log';
import {dict, hasOwn} from '../../../src/utils/object';
import {getMode} from '../../../src/mode';
import {installStylesForDoc} from '../../../src/style-installer';
import {isInFie} from '../../../src/friendly-iframe-embed';
import {cssText as lightboxAdCss} from '../../../build/lightbox-ad.css.js';
import {removeElement, tryFocus} from '../../../src/dom';
import {
renderCloseButtonHeader,
showCloseButtonHeader,
} from '../../../src/full-overlay-frame-helper';
import {toArray} from '../../../src/types';
import {tryFocus} from '../../../src/dom';

/** @const {string} */
const TAG = 'amp-lightbox';
Expand Down Expand Up @@ -118,6 +124,9 @@ class AmpLightbox extends AMP.BaseElement {
this.animationPreset_ =
(element.getAttribute('animate-in') || DEFAULT_ANIMATION).toLowerCase();

/** @private {?Element} */
this.closeButtonHeader_ = null;

/** @const {function()} */
this.boundReschedule_ = debounce(this.win, () => {
const container = dev().assertElement(this.container_);
Expand Down Expand Up @@ -277,6 +286,7 @@ class AmpLightbox extends AMP.BaseElement {
});

this.handleAutofocus_();
this.maybeRenderCloseButtonHeader_();

// TODO (jridgewell): expose an API accomodating this per PR #14676
this.mutateElement(() => {
Expand Down Expand Up @@ -305,6 +315,39 @@ class AmpLightbox extends AMP.BaseElement {
this.active_ = true;
}

/** @private */
maybeRenderCloseButtonHeader_() {
if (!this.isInabox_()) {
return;
}

const header = renderCloseButtonHeader(this.element);

this.closeButtonHeader_ = header;

listenOnce(header, 'click', () => this.close());

installStylesForDoc(this.getAmpDoc(), lightboxAdCss, () => {
this.element.insertBefore(header, this.container_);

let headerHeight;

this.measureMutateElement(() => {
headerHeight = header./*OK*/getBoundingClientRect().height;
}, () => {
// Done in vsync in order to apply transition.
showCloseButtonHeader(header);

setImportantStyles(dev().assertElement(this.container_), {
'margin-top': px(headerHeight),
'min-height': `calc(100vh - ${px(headerHeight)})`,
});
});
},
/* opt_isRuntimeCss */ false,
/* opt_ext */ 'amp-lightbox-ad');
}

/**
* @private
* @return {!AnimationPresetDef}
Expand Down Expand Up @@ -334,6 +377,10 @@ class AmpLightbox extends AMP.BaseElement {
if (this.isScrollable_) {
st.setStyle(this.element, 'webkitOverflowScrolling', '');
}
if (this.closeButtonHeader_) {
removeElement(this.closeButtonHeader_);
this.closeButtonHeader_ = null;
}
this.getViewport().leaveLightboxMode(this.element)
.then(() => this.finalizeClose_());
}
Expand All @@ -343,22 +390,27 @@ class AmpLightbox extends AMP.BaseElement {
*/
finalizeClose_() {
const {element} = this;

st.setStyles(element, this.getAnimationPresetDef_().closedStyle);

const event = ++this.eventCounter_;

const collapseAndReschedule = () => {
// Don't collapse on transitionend if there was a previous event.
// Don't collapse on transitionend if there was a subsequent event.
if (event != this.eventCounter_) {
return;
}
this./*OK*/collapse();
this.boundReschedule_();
};

element.addEventListener('transitionend', collapseAndReschedule);
element.addEventListener('animationend', collapseAndReschedule);
// Disable transition for ads since the frame gets immediately collapsed.
if (this.isInAd_()) {
st.resetStyles(element, ['transition']);
collapseAndReschedule();
} else {
element.addEventListener('transitionend', collapseAndReschedule);
element.addEventListener('animationend', collapseAndReschedule);
}

st.setStyles(element, this.getAnimationPresetDef_().closedStyle);

if (this.historyId_ != -1) {
this.getHistory_().pop(this.historyId_);
Expand All @@ -371,6 +423,22 @@ class AmpLightbox extends AMP.BaseElement {
this.triggerEvent_(LightboxEvents.CLOSE);
}

/**
* @return {boolean}
* @private
*/
isInAd_() {
Copy link
Contributor

Choose a reason for hiding this comment

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

or, can we just check if the element is in an iframe? Is there a way to check if the owner document is the one AMP runtime is on?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted.

return this.isInabox_() || isInFie(this.element);
}

/**
* @return {boolean}
* @private
*/
isInabox_() {
Copy link
Contributor

Choose a reason for hiding this comment

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

what do you think we make the close button a feature generic to amp-lightbox in both FIE or regular AMP. The feature is enabled by having element attribute "close-button".

We then let the A4A validator to ensure its existence in ads.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a great idea, and decreases complexity! Done.

return getMode(this.win).runtime == 'inabox';
}

/**
* Handles scroll on the amp-lightbox.
* The scroll throttling and visibility calculation is similar to
Expand Down
85 changes: 57 additions & 28 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,29 @@ function css() {
return compileCss();
}

const cssEntryPoints = [
Copy link
Contributor

Choose a reason for hiding this comment

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

{
path: 'amp.css',
outJs: 'css.js',
outCss: 'v0.css',
},
{
path: 'lightbox-ad.css',
outJs: 'lightbox-ad.css.js',
outCss: 'lightbox-ad.css',
},
{
path: 'video-docking.css',
outJs: 'video-docking.css.js',
outCss: 'video-docking.css',
},
{
path: 'video-autoplay.css',
outJs: 'video-autoplay.css.js',
outCss: 'video-autoplay.css',
},
];

/**
* Compile all the css and drop in the build folder
* @param {boolean} watch
Expand Down Expand Up @@ -432,34 +455,37 @@ function compileCss(watch, opt_compileAll) {
}));
}

/**
* @param {string} path
* @param {string} outJs
* @param {string} outCss
*/
function writeCssEntryPoint(path, outJs, outCss) {
const startTime = Date.now();

return jsifyCssAsync(`css/${path}`)
.then(css => writeCss(css, path, outJs, outCss))
.then(() => {
endBuildStep('Recompiled CSS in', path, startTime);
});
}

// Used by `gulp test --local-changes` to map CSS files to JS files.
fs.writeFileSync('EXTENSIONS_CSS_MAP', JSON.stringify(extensions));

const startTime = Date.now();
return jsifyCssAsync('css/amp.css')
.then(css => writeCss(css, 'amp.css', 'css.js', 'v0.css'))
.then(() => {
endBuildStep('Recompiled CSS in', 'amp.css', startTime);
})
.then(() => jsifyCssAsync('css/video-docking.css'))
.then(css => writeCss(css,
'video-docking.css', 'video-docking.css.js', 'video-docking.css'))
.then(() => {
endBuildStep('Recompiled CSS in', 'video-docking.css', startTime);
})
.then(() => jsifyCssAsync('css/video-autoplay.css'))
.then(css => writeCss(css,
'video-autoplay.css', 'video-autoplay.css.js', 'video-autoplay.css'))
.then(() => {
endBuildStep('Recompiled CSS in', 'video-autoplay.css', startTime);
})
.then(() => {
return buildExtensions({
bundleOnlyIfListedInFiles: false,
compileOnlyCss: true,
compileAll: opt_compileAll,
});
});

let promise = Promise.resolve();

cssEntryPoints.forEach(entryPoint => {
const {path, outJs, outCss} = entryPoint;
promise = promise.then(() => writeCssEntryPoint(path, outJs, outCss));
});

return promise.then(() => buildExtensions({
bundleOnlyIfListedInFiles: false,
compileOnlyCss: true,
compileAll: opt_compileAll,
}));
}

/**
Expand All @@ -468,12 +494,15 @@ function compileCss(watch, opt_compileAll) {
*/
function copyCss() {
const startTime = Date.now();
fs.copySync('build/css/v0.css', 'dist/v0.css');
fs.copySync('build/css/video-docking.css', 'dist/video-docking.css');

cssEntryPoints.forEach(({outCss}) => {
fs.copySync(`build/css/${outCss}`, `dist/${outCss}`);
});

return toPromise(gulp.src('build/css/amp-*.css')
.pipe(gulp.dest('dist/v0')))
.then(() => {
endBuildStep('Copied', 'build/css/v0.css to dist/v0.css', startTime);
endBuildStep('Copied', 'build/css/*.css to dist/*.css', startTime);
});
}

Expand Down
34 changes: 24 additions & 10 deletions src/friendly-iframe-embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {closestBySelector, escapeHtml, removeElement} from './dom';
import {createCustomEvent, listenOnce, loadPromise} from './event-helper';
import {dev, rethrowAsync, user} from './log';
import {disposeServicesForEmbed, getTopWindow} from './service';
import {htmlFor} from './static-template';
import {isDocumentReady} from './document-ready';
import {layoutRectLtwh} from './layout-rect';
import {
Expand All @@ -33,6 +32,10 @@ import {
setStyle,
setStyles,
} from './style';
import {
renderCloseButtonHeader,
showCloseButtonHeader,
} from './full-overlay-frame-helper';
import {toWin} from './types';


Expand Down Expand Up @@ -523,7 +526,8 @@ export class FriendlyIframeEmbed {
'Only <amp-ad> is allowed to enter lightbox mode.');

const header =
renderCloseButtonHeader(this.win, ampAdParent, requestingElement);
renderClickableCloseButtonHeader(
this.win, ampAdParent, requestingElement);

ampAdParent.appendChild(header);

Expand Down Expand Up @@ -580,7 +584,7 @@ export class FriendlyIframeEmbed {
setImportantStyles(this.iframe, iframeStyle);

// Done in vsync in order to apply transition.
header.classList.add('amp-ad-close-header');
showCloseButtonHeader(header);

// We need to override runtime-level !important rules
setImportantStyles(this.getBodyElement(), bodyStyle);
Expand Down Expand Up @@ -635,13 +639,12 @@ export class FriendlyIframeEmbed {
* @param {!Element} ampLightbox
* @visibleForTesting
*/
export function renderCloseButtonHeader(win, ampAdParent, ampLightbox) {
const el = htmlFor(ampAdParent)`
<i-amphtml-ad-close-header role=button tabindex=0 aria-label="Close Ad">
<div>Ad</div>
<i-amphtml-ad-close-button class="amp-ad-close-button">
</i-amphtml-ad-close-button>
</i-amphtml-ad-close-header>`;
export function renderClickableCloseButtonHeader(
win, ampAdParent, ampLightbox) {

const el = renderCloseButtonHeader(/* ctx */ ampAdParent);

annotateAnimationStyle(el, ampLightbox);

listenOnce(el, 'click', () => {
triggerLightboxClose(win, ampLightbox, /* caller */ ampAdParent);
Expand All @@ -651,6 +654,17 @@ export function renderCloseButtonHeader(win, ampAdParent, ampLightbox) {
return el;
}

/**
* @param {!Element} header
* @param {!Element} ampLightbox
*/
function annotateAnimationStyle(header, ampLightbox) {
const animation = ampLightbox.getAttribute('animate-in');
if (animation) {
header.classList.add(`amp-animate-in-${animation}`);
}
}

/**
* @param {!Window} win
* @param {!Element} target An amp-lightbox target.
Expand Down
Loading