diff --git a/build-system/tasks/extension-helpers.js b/build-system/tasks/extension-helpers.js index a038ccebe714..7a7597a91163 100644 --- a/build-system/tasks/extension-helpers.js +++ b/build-system/tasks/extension-helpers.js @@ -742,12 +742,10 @@ async function getBentoBuildFilename(dir, name, mode, options) { function generateBentoEntryPointSource(name, toExport) { return dedent(` import {BaseElement} from '../base-element'; + import {defineBentoElement} from '../../../../src/preact/bento-ce'; function defineElement() { - customElements.define( - __name__, - BaseElement.CustomElement(BaseElement) - ); + defineBentoElement(__name__, BaseElement); } ${toExport ? 'export {defineElement};' : 'defineElement();'} diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js index e44d7a2f77dc..552ae39d1748 100644 --- a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js @@ -1,12 +1,13 @@ import {BaseElement} from './base-element'; __css_import__; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; import {userAssert} from '#utils/log'; /** @const {string} */ const TAG = 'amp-__component_name_hyphenated__'; -class Amp__component_name_pascalcase__ extends BaseElement { +class Amp__component_name_pascalcase__ extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ init() { // __do_not_submit__: This is example code only. diff --git a/build-system/test-configs/dep-check-config.js b/build-system/test-configs/dep-check-config.js index 2eb1610b089d..4d68453bafee 100644 --- a/build-system/test-configs/dep-check-config.js +++ b/build-system/test-configs/dep-check-config.js @@ -223,8 +223,9 @@ exports.rules = [ 'extensions/amp-facebook-page/0.1/amp-facebook-page.js->extensions/amp-facebook/0.1/facebook-loader.js', 'extensions/amp-facebook-comments/0.1/amp-facebook-comments.js->extensions/amp-facebook/0.1/facebook-loader.js', - // VideoBaseElement, VideoIframe and VideoWrapper are meant to be shared. + // AmpVideoBaseElement, BentoVideoBaseElement, VideoIframe and VideoWrapper are meant to be shared. 'extensions/**->extensions/amp-video/1.0/video-base-element.js', + 'extensions/**->extensions/amp-video/1.0/base-element.js', 'extensions/**->extensions/amp-video/1.0/video-iframe.js', // versions share this message API definition. diff --git a/docs/building-a-bento-amp-extension.md b/docs/building-a-bento-amp-extension.md index a369b33b1c2c..904925b4fb27 100644 --- a/docs/building-a-bento-amp-extension.md +++ b/docs/building-a-bento-amp-extension.md @@ -272,7 +272,7 @@ actionServiceForDoc(doc.documentElement).trigger( createCustomEvent( win, `amp-base-carousel.${name}`, - dict({'index': index}) + {'index': index} ), ActionTrust.DEFAULT ); @@ -351,6 +351,8 @@ You must document your element's actions and events in its own reference documen The following shows the overall structure of your element implementation file (`extensions/amp-my-element/1.0/amp-my-element.js`). See [Experiments](#experiments) to make sure your component is experimentally gated if necessary. ```js +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {func1, func2} from '../../../src/module'; import {BaseElement} from './base-element'; // Preact base element. import {CSS} from '../../../build/amp-my-element-1.0.css'; @@ -362,7 +364,7 @@ const EXPERIMENT = 'amp-my-element'; /** @const */ const TAG = 'amp-my-element'; -class AmpMyElement extends BaseElement { +class AmpMyElement extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ init() { // Perform any additional processing of prop values that are not @@ -371,10 +373,10 @@ class AmpMyElement extends BaseElement { this.registerApiAction('close', (api) => api.close()); const processedProp = parseInt(element.getAttribute('data-binary'), 2); - return dict({ + return { 'processedProp': processedProp, - 'onClose': (event) => fireAmpEvent(event)} - ); + 'onClose': (event) => fireAmpEvent(event) + }; } /** @override */ diff --git a/docs/building-a-bento-iframe-component.md b/docs/building-a-bento-iframe-component.md index 8a58d577b969..72f649cb6630 100644 --- a/docs/building-a-bento-iframe-component.md +++ b/docs/building-a-bento-iframe-component.md @@ -242,9 +242,9 @@ In your AMP element implementation, you will use `requestResize` to pass in the class AmpFantasticEmbed extends BaseElement { /** @override */ init() { - return dict({ + return { 'requestResize': (height) => this.attemptChangeHeight(height), - }); + }; } } ``` diff --git a/docs/building-a-bento-video-player.md b/docs/building-a-bento-video-player.md index d632632a10e1..bed705eb1a87 100644 --- a/docs/building-a-bento-video-player.md +++ b/docs/building-a-bento-video-player.md @@ -14,7 +14,7 @@ - [How Video Player Components Work](#how-video-player-components-work) - [Getting Started](#getting-started) - [Directory Structure](#directory-structure) -- [Extend `VideoBaseElement` for AMP](#extend-videobaseelement-for-amp) +- [Extend `AmpVideoBaseElement` for AMP](#extend-ampvideobaseelement-for-amp) - [`props`](#props) - [Define a Preact component](#define-a-preact-component) - [Forwarding `ref`](#forwarding-ref) @@ -51,7 +51,7 @@ However, most video players are embedded through an iframe so they should use ** return ``` -To enable component support for **AMP documents**, our video player element must extend from a base class `VideoBaseElement`. This enables [actions](https://amp.dev/documentation/guides-and-tutorials/learn/amp-actions-and-events/#amp-video-and-other-video-elements_1) and [analytics](https://github.com/ampproject/amphtml/blob/main/extensions/amp-analytics/amp-video-analytics.md), and allows us to define further behavior specific to the AMP layer, like parsing element attributes into Preact props. +To enable component support for **AMP documents**, our video player element must extend from a base class `AmpVideoBaseElement`. This enables [actions](https://amp.dev/documentation/guides-and-tutorials/learn/amp-actions-and-events/#amp-video-and-other-video-elements_1) and [analytics](https://github.com/ampproject/amphtml/blob/main/extensions/amp-analytics/amp-video-analytics.md), and allows us to define further behavior specific to the AMP layer, like parsing element attributes into Preact props. This guide covers how to implement video player components that are internally implemented using these Preact and AMP components. @@ -75,17 +75,17 @@ A [full directory for a Bento component](./building-a-bento-amp-extension.md#dir └── amp-my-fantastic-player.css # Custom CSS ``` -## Extend `VideoBaseElement` for AMP +## Extend `AmpVideoBaseElement` for AMP -Our `BaseElement` should be a superclass of `VideoBaseElement`. In **`base-element.js`**, we change: +Our `BaseElement` should be a superclass of `BentoVideoBaseElement`. In **`base-element.js`**, we change: ```diff import {MyFantasticPlayer} from './component'; - import {PreactBaseElement} from '#preact/base-element'; -+ import {VideoBaseElement} from '../../amp-video/1.0/video-base-element'; ++ import {BentoVideoBaseElement} from '../../amp-video/1.0/base-element'; - export class BaseElement extends PreactBaseElement {} -+ export class BaseElement extends VideoBaseElement {} ++ export class BaseElement extends BentoVideoBaseElement {} ``` into: @@ -94,16 +94,37 @@ into: // base-element.js // ... import {MyFantasticPlayer} from './component'; -import {VideoBaseElement} from '../../amp-video/1.0/base-element'; +import {BentoVideoBaseElement} from '../../amp-video/1.0/base-element'; -export class BaseElement extends VideoBaseElement {} +export class BaseElement extends BentoVideoBaseElement {} +``` + +Our `AmpFantasticPlayer` should be a superclass of `AmpVideoBaseElement`. in amp-fantastic-player.js, we chagne: + +```diff ++ import {setSuperClass} from '#preact/amp-base-element'; + ++ import {AmpVideoBaseElement} from '../../amp-video/1.0/video-base-element'; + +- class AmpFantasticPlayer extends BaseElement {} ++ class AmpFantasticPlayer extends setSuperClass(BaseElement, AmpVideoBaseElement) {} +``` + +into: + +```js +import {setSuperClass} from '#preact/amp-base-element'; + +import {AmpVideoBaseElement} from '../../amp-video/1.0/video-base-element'; + +class AmpFantasticPlayer extends setSuperClass(BaseElement, AmpVideoBaseElement) {} ``` This enables support for AMP actions and analytics, once we map attributes to their prop counterparts in `BaseElement['props']`, and we implement the Preact component. ### `props` -[**`props`**](https://github.com/ampproject/amphtml/blob/main/docs/building-a-bento-amp-extension.md#preactbaseelementprops) map the AMP element's attributes to the Preact component props. Take a look at [`VideoBaseElement`](../extensions/amp-video/1.0/base-element.js) for how most video properties are mapped. On your own `base-element.js`, you should specify any of them you support. +[**`props`**](https://github.com/ampproject/amphtml/blob/main/docs/building-a-bento-amp-extension.md#preactbaseelementprops) map the AMP element's attributes to the Preact component props. Take a look at [`AmpVideoBaseElement`](../extensions/amp-video/1.0/video-base-element.js) for how most video properties are mapped. On your own `base-element.js`, you should specify any of them you support. ```js // base-element.js @@ -510,7 +531,7 @@ When we click the following button on an AMP document: We call the corresponding function `play`: -> The AMP action `my-element.play` is declared to be forwarded to the Preact component's method. See the [`init()` method on `VideoBaseElement`](../extensions/amp-video/1.0/base-element.js) for a list of the supported actions. +> The AMP action `my-element.play` is declared to be forwarded to the Preact component's method. See the [`init()` method on `AmpVideoBaseElement`](../extensions/amp-video/1.0/video-base-element.js) for a list of the supported actions. ``` -> FantasticPlayer.play() diff --git a/extensions/amp-accordion/1.0/amp-accordion.js b/extensions/amp-accordion/1.0/amp-accordion.js index 7c29fd763559..74e32c817ab4 100644 --- a/extensions/amp-accordion/1.0/amp-accordion.js +++ b/extensions/amp-accordion/1.0/amp-accordion.js @@ -3,6 +3,8 @@ import {getWin} from '#core/window'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {createCustomEvent} from '#utils/event-helper'; @@ -16,7 +18,7 @@ import {CSS} from '../../../build/amp-accordion-1.0.css'; const TAG = 'amp-accordion'; /** @extends {PreactBaseElement} */ -class AmpAccordion extends BaseElement { +class AmpAccordion extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ init() { this.registerApiAction('toggle', (api, invocation) => diff --git a/extensions/amp-accordion/1.0/test/test-bento-accordion.js b/extensions/amp-accordion/1.0/test/test-bento-accordion.js new file mode 100644 index 000000000000..70d123df307e --- /dev/null +++ b/extensions/amp-accordion/1.0/test/test-bento-accordion.js @@ -0,0 +1,541 @@ +import {CSS} from '#build/bento-accordion-1.0.css'; + +import {subscribe, unsubscribe} from '#core/context'; +import {htmlFor} from '#core/dom/static-template'; + +import {defineBentoElement} from '#preact/bento-ce'; +import {CanRender} from '#preact/contextprops'; + +import {waitFor} from '#testing/helpers/service'; + +import {BaseElement as BentoAccordion} from '../base-element'; + +describes.realWin( + 'bento-accordion:1.0', + { + amp: false, + }, + (env) => { + // eslint-disable-next-line no-undef + xdescribe('', () => { + let win; + let html; + let element; + + async function waitForExpanded(el, expanded) { + const isExpandedOrNot = () => + el.hasAttribute('expanded') === expanded && + el.firstElementChild.getAttribute('aria-expanded') === + String(expanded); + await waitFor(isExpandedOrNot, 'element expanded updated'); + } + + function readContextProp(element, prop) { + return new Promise((resolve) => { + const handler = (value) => { + resolve(value); + unsubscribe(element, [prop], handler); + }; + subscribe(element, [prop], handler); + }); + } + + /** + * @param {Window} win + */ + function adoptAccordionStyles(win) { + const style = win.document.createElement('style'); + style.innerHTML = ``; + win.document.head.appendChild(style); + } + + beforeEach(async () => { + win = env.win; + html = htmlFor(win.document); + element = html` + +
+

header1

+
content1
+
+
+

header2

+
content2
+
+
+

header3

+
content3
+
+
+ `; + defineBentoElement('bento-accordion', BentoAccordion, win); + adoptAccordionStyles(win); + win.document.body.appendChild(element); + await element.getApi(); + }); + + it('should render expanded and collapsed sections', () => { + const sections = element.children; + expect(sections[0]).to.have.attribute('expanded'); + expect( + sections[0].firstElementChild.getAttribute('aria-expanded') + ).to.equal('true'); + expect(sections[0].lastElementChild).to.have.display('block'); + + expect(sections[1]).to.not.have.attribute('expanded'); + expect( + sections[1].firstElementChild.getAttribute('aria-expanded') + ).to.equal('false'); + expect(sections[1].lastElementChild).to.have.display('none'); + + expect(sections[2]).to.not.have.attribute('expanded'); + expect( + sections[2].firstElementChild.getAttribute('aria-expanded') + ).to.equal('false'); + expect(sections[2].lastElementChild).to.have.display('none'); + }); + + it('should propagate renderable context', async () => { + const sections = element.children; + const renderables = await Promise.all([ + readContextProp(sections[0].lastElementChild, CanRender), + readContextProp(sections[1].lastElementChild, CanRender), + readContextProp(sections[2].lastElementChild, CanRender), + ]); + expect(renderables[0]).to.be.true; + expect(renderables[1]).to.be.false; + expect(renderables[2]).to.be.false; + }); + + it('should have amp specific classes for CSS', () => { + const sections = element.children; + const {firstElementChild: header0, lastElementChild: content0} = + sections[0]; + const {firstElementChild: header1, lastElementChild: content1} = + sections[1]; + const {firstElementChild: header2, lastElementChild: content2} = + sections[2]; + + // Check classes + expect(header0.className).to.include('i-amphtml-accordion-header'); + expect(header1.className).to.include('i-amphtml-accordion-header'); + expect(header2.className).to.include('i-amphtml-accordion-header'); + expect(content0.className).to.include('i-amphtml-accordion-content'); + expect(content1.className).to.include('i-amphtml-accordion-content'); + expect(content2.className).to.include('i-amphtml-accordion-content'); + + // Check computed styles + expect(win.getComputedStyle(header0).margin).to.equal('0px'); + expect(win.getComputedStyle(header0).cursor).to.equal('pointer'); + expect(win.getComputedStyle(header0).backgroundColor).to.equal( + 'rgb(239, 239, 239)' + ); + expect(win.getComputedStyle(header0).paddingRight).to.equal('20px'); + expect(win.getComputedStyle(header0).border).to.equal( + '1px solid rgb(223, 223, 223)' + ); + + expect(win.getComputedStyle(header1).margin).to.equal('0px'); + expect(win.getComputedStyle(header1).cursor).to.equal('pointer'); + expect(win.getComputedStyle(header1).backgroundColor).to.equal( + 'rgb(239, 239, 239)' + ); + expect(win.getComputedStyle(header1).paddingRight).to.equal('20px'); + expect(win.getComputedStyle(header1).border).to.equal( + '1px solid rgb(223, 223, 223)' + ); + + expect(win.getComputedStyle(header2).margin).to.equal('0px'); + expect(win.getComputedStyle(header2).cursor).to.equal('pointer'); + expect(win.getComputedStyle(header2).backgroundColor).to.equal( + 'rgb(239, 239, 239)' + ); + expect(win.getComputedStyle(header2).paddingRight).to.equal('20px'); + expect(win.getComputedStyle(header2).border).to.equal( + '1px solid rgb(223, 223, 223)' + ); + + expect(win.getComputedStyle(content0).margin).to.equal('0px'); + expect(win.getComputedStyle(content1).margin).to.equal('0px'); + expect(win.getComputedStyle(content2).margin).to.equal('0px'); + }); + + it('should expand and collapse on click', async () => { + const sections = element.children; + + sections[1].firstElementChild.click(); + await waitForExpanded(sections[1], true); + + expect(sections[0]).to.have.attribute('expanded'); + expect(sections[0].lastElementChild).to.have.display('block'); + + expect(sections[1]).to.have.attribute('expanded'); + expect(sections[1].lastElementChild).to.have.display('block'); + + expect(sections[2]).to.not.have.attribute('expanded'); + expect(sections[2].lastElementChild).to.have.display('none'); + + sections[0].firstElementChild.click(); + await waitForExpanded(sections[0], false); + + expect(sections[0]).to.not.have.attribute('expanded'); + expect(sections[0].lastElementChild).to.have.display('none'); + + expect(sections[1]).to.have.attribute('expanded'); + expect(sections[1].lastElementChild).to.have.display('block'); + + expect(sections[2]).to.not.have.attribute('expanded'); + expect(sections[2].lastElementChild).to.have.display('none'); + }); + + it('should switch expand-single-section value', async () => { + const sections = element.children; + sections[1].firstElementChild.click(); + await waitForExpanded(sections[1], true); + + const getExpandedCount = () => + element.querySelectorAll('[aria-expanded="true"]').length; + expect(getExpandedCount()).to.equal(2); + + element.setAttribute('expand-single-section', ''); + await waitFor( + () => getExpandedCount() == 1, + 'only one element stays expanded' + ); + }); + + it('should expand and collapse on attribute change', async () => { + const sections = element.children; + + sections[1].setAttribute('expanded', ''); + await waitForExpanded(sections[1], true); + + expect(sections[0]).to.have.attribute('expanded'); + expect(sections[0].lastElementChild).to.have.display('block'); + + expect(sections[1]).to.have.attribute('expanded'); + expect(sections[1].lastElementChild).to.have.display('block'); + + expect(sections[2]).to.not.have.attribute('expanded'); + expect(sections[2].lastElementChild).to.have.display('none'); + + sections[0].removeAttribute('expanded'); + await waitForExpanded(sections[0], false); + + expect(sections[0]).to.not.have.attribute('expanded'); + expect(sections[0].lastElementChild).to.have.display('none'); + + expect(sections[1]).to.have.attribute('expanded'); + expect(sections[1].lastElementChild).to.have.display('block'); + + expect(sections[2]).to.not.have.attribute('expanded'); + expect(sections[2].lastElementChild).to.have.display('none'); + }); + + it('should include a11y related attributes', async () => { + const sections = element.children; + + const {firstElementChild: header0, lastElementChild: content0} = + sections[0]; + const {firstElementChild: header1, lastElementChild: content1} = + sections[1]; + const {firstElementChild: header2, lastElementChild: content2} = + sections[2]; + + expect(header0).to.have.attribute('tabindex'); + expect(header0).to.have.attribute('aria-controls'); + expect(header0).to.have.attribute('role'); + expect(header0).to.have.attribute('aria-expanded'); + expect(header0).to.have.attribute('id'); + expect(header0.getAttribute('aria-expanded')).to.equal('true'); + expect(content0).to.have.attribute('id'); + expect(content0).to.have.attribute('aria-labelledby'); + expect(content0).to.have.attribute('role'); + expect(header0.getAttribute('aria-controls')).to.equal( + content0.getAttribute('id') + ); + expect(header0.getAttribute('id')).to.equal( + content0.getAttribute('aria-labelledby') + ); + + expect(header1).to.have.attribute('tabindex'); + expect(header1).to.have.attribute('aria-controls'); + expect(header1).to.have.attribute('role'); + expect(header1).to.have.attribute('aria-expanded'); + expect(header1).to.have.attribute('id'); + expect(header1.getAttribute('aria-expanded')).to.equal('false'); + expect(content1).to.have.attribute('id'); + expect(content1).to.have.attribute('aria-labelledby'); + expect(content1).to.have.attribute('role'); + expect(header1.getAttribute('aria-controls')).to.equal( + content1.getAttribute('id') + ); + expect(header1.getAttribute('id')).to.equal( + content1.getAttribute('aria-labelledby') + ); + + expect(header2).to.have.attribute('tabindex'); + expect(header2).to.have.attribute('aria-controls'); + expect(header2).to.have.attribute('role'); + expect(header2).to.have.attribute('aria-expanded'); + expect(header2).to.have.attribute('id'); + expect(header2.getAttribute('aria-expanded')).to.equal('false'); + expect(content2).to.have.attribute('id'); + expect(content2).to.have.attribute('aria-labelledby'); + expect(content2).to.have.attribute('role'); + expect(header2.getAttribute('aria-controls')).to.equal( + content2.getAttribute('id') + ); + expect(header2.getAttribute('id')).to.equal( + content2.getAttribute('aria-labelledby') + ); + }); + + it('should not overwrite existing header and content ids', async () => { + element = html` + +
+

header1

+
content1
+
+
+

header2

+
content2
+
+
+

header3

+
content3
+
+
+ `; + win.document.body.appendChild(element); + await element.getApi(); + + const sections = element.children; + const {firstElementChild: header0, lastElementChild: content0} = + sections[0]; + const {firstElementChild: header1, lastElementChild: content1} = + sections[1]; + const {firstElementChild: header2, lastElementChild: content2} = + sections[2]; + + expect(header0.getAttribute('id')).to.equal('h1'); + expect(content0.getAttribute('id')).to.equal('c1'); + expect(header0.getAttribute('aria-controls')).to.equal( + content0.getAttribute('id') + ); + expect(header0.getAttribute('id')).to.equal( + content0.getAttribute('aria-labelledby') + ); + + expect(header1.getAttribute('id')).to.equal('h2'); + expect(header1.getAttribute('aria-controls')).to.equal( + content1.getAttribute('id') + ); + expect(header1.getAttribute('id')).to.equal( + content1.getAttribute('aria-labelledby') + ); + + expect(content2.getAttribute('id')).to.equal('c3'); + expect(header2.getAttribute('aria-controls')).to.equal( + content2.getAttribute('id') + ); + expect(header2.getAttribute('id')).to.equal( + content2.getAttribute('aria-labelledby') + ); + }); + + it('should not overwrite existing role attributes', async () => { + element = html` + +
+

header1

+
content1
+
+
+

header2

+
content2
+
+
+ `; + win.document.body.appendChild(element); + await element.getApi(); + + const sections = element.children; + const {firstElementChild: header0, lastElementChild: content0} = + sections[0]; + const {firstElementChild: header1, lastElementChild: content1} = + sections[1]; + + expect(header0).to.have.attribute('role'); + expect(header0.getAttribute('role')).to.equal('cat'); + expect(content0).to.have.attribute('role'); + expect(content0.getAttribute('role')).to.equal('dog'); + + expect(header1).to.have.attribute('role'); + expect(header1.getAttribute('role')).to.equal('button'); + expect(content1).to.have.attribute('role'); + expect(content1.getAttribute('role')).to.equal('region'); + }); + + it('should pick up new children', async () => { + const newSection = document.createElement('section'); + newSection.setAttribute('expanded', ''); + newSection.appendChild(document.createElement('h2')); + newSection.appendChild(document.createElement('div')); + element.appendChild(newSection); + + await waitForExpanded(newSection, true); + + expect(newSection.firstElementChild.className).to.include( + 'i-amphtml-accordion-header' + ); + expect(newSection.lastElementChild.className).to.include( + 'i-amphtml-accordion-content' + ); + }); + + describe('animate', () => { + let animateStub; + + beforeEach(async () => { + animateStub = env.sandbox.stub(win.Element.prototype, 'animate'); + element = html` + +
+

header1

+
content1
+
+
+

header2

+
content2
+
+
+ `; + win.document.body.appendChild(element); + await element.getApi(); + }); + + it('should not animate on build', () => { + expect(animateStub).to.not.be.called; + }); + + it('should animate expand', async () => { + const animation = {}; + animateStub.returns(animation); + const sections = element.children; + const section = sections[1]; + + section.setAttribute('expanded', ''); + await waitForExpanded(sections[1], true); + + expect(animateStub).to.be.calledOnce; + animation.onfinish(); + + expect(section).to.have.attribute('expanded'); + expect(section.lastElementChild).to.have.display('block'); + }); + + it('should animate collapse', async () => { + const animation = {}; + animateStub.returns(animation); + const sections = element.children; + const section = sections[0]; + + section.removeAttribute('expanded'); + await waitFor(() => animateStub.callCount > 0, 'animation started'); + + expect(animateStub).to.be.calledOnce; + expect(section).to.not.have.attribute('expanded'); + // Still displayed while animating. + expect(section.lastElementChild).to.have.display('block'); + + animation.onfinish(); + await waitForExpanded(sections[0], false); + expect(section.lastElementChild).to.have.display('none'); + }); + }); + + describe('imperative api', () => { + beforeEach(async () => { + element = html` + +
+

Section 1

+
Content 1
+
+
+

Section 2

+
Bunch of awesome content
+
+
+

Section 3

+
Content 3
+
+
+ `; + win.document.body.appendChild(element); + await element.getApi(); + }); + + it('should capture events in bento mode (w/o "on" attribute)', async () => { + const section1 = element.children[0]; + const section3 = element.children[2]; + + // Set up section 1 to trigger expand of section 3 on expand + // and collapse of section 3 on collapse + const api = await element.getApi(); + section1.addEventListener('expand', () => api.expand('section3')); + section1.addEventListener('collapse', () => api.collapse('section3')); + + // initally both section 1 and 3 are collapsed + expect(section1).to.not.have.attribute('expanded'); + expect(section3).to.not.have.attribute('expanded'); + + // expand section 1 + section1.firstElementChild.click(); + await waitForExpanded(section1, true); + + // both section 1 and 3 are expanded + expect(section1).to.have.attribute('expanded'); + expect(section3).to.have.attribute('expanded'); + + // collapse section 1 + section1.firstElementChild.click(); + await waitForExpanded(section1, false); + + // both section 1 and 3 are collapsed + expect(section1).to.not.have.attribute('expanded'); + expect(section3).to.not.have.attribute('expanded'); + }); + + it('should fire and listen for "expand" and "collapse" events', async () => { + const section1 = element.children[0]; + + // Add spy functions for expand and collapse + const spyE = env.sandbox.spy(); + const spyC = env.sandbox.spy(); + section1.addEventListener('expand', spyE); + section1.addEventListener('collapse', spyC); + + expect(spyE).to.not.be.called; + expect(spyC).to.not.be.called; + + // expand section 1 + section1.firstElementChild.click(); + await waitForExpanded(section1, true); + + expect(spyE).to.be.calledOnce; + expect(spyC).to.not.be.called; + + // collapse section 1 + section1.firstElementChild.click(); + await waitForExpanded(section1, false); + + expect(spyE).to.be.calledOnce; + expect(spyC).to.be.calledOnce; + }); + }); + }); + } +); diff --git a/extensions/amp-base-carousel/1.0/amp-base-carousel.js b/extensions/amp-base-carousel/1.0/amp-base-carousel.js index fc8fcf4a255c..f393da90dfd8 100644 --- a/extensions/amp-base-carousel/1.0/amp-base-carousel.js +++ b/extensions/amp-base-carousel/1.0/amp-base-carousel.js @@ -3,6 +3,8 @@ import {getWin} from '#core/window'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {createCustomEvent} from '#utils/event-helper'; @@ -16,7 +18,7 @@ import {CSS} from '../../../build/amp-base-carousel-1.0.css'; const TAG = 'amp-base-carousel'; /** @extends {PreactBaseElement} */ -class AmpBaseCarousel extends BaseElement { +class AmpBaseCarousel extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ init() { this.registerApiAction('prev', (api) => api.prev(), ActionTrust_Enum.LOW); diff --git a/extensions/amp-brightcove/1.0/amp-brightcove.js b/extensions/amp-brightcove/1.0/amp-brightcove.js index 67dd5604c7b2..9340d01ec4f1 100644 --- a/extensions/amp-brightcove/1.0/amp-brightcove.js +++ b/extensions/amp-brightcove/1.0/amp-brightcove.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {userAssert} from '#utils/log'; @@ -12,11 +14,12 @@ import { getConsentPolicySharedData, getConsentPolicyState, } from '../../../src/consent'; +import {AmpVideoBaseElement} from '../../amp-video/1.0/video-base-element'; /** @const {string} */ const TAG = 'amp-brightcove'; -class AmpBrightcove extends BaseElement { +class AmpBrightcove extends setSuperClass(BaseElement, AmpVideoBaseElement) { /** @override @nocollapse */ static getPreconnects() { return ['https://players.brightcove.net']; diff --git a/extensions/amp-brightcove/1.0/base-element.js b/extensions/amp-brightcove/1.0/base-element.js index 0120e2a10707..2385751b1bf6 100644 --- a/extensions/amp-brightcove/1.0/base-element.js +++ b/extensions/amp-brightcove/1.0/base-element.js @@ -2,9 +2,9 @@ import {createParseAttrsWithPrefix} from '#preact/parse-props'; import {BentoBrightcove} from './component'; -import {VideoBaseElement} from '../../amp-video/1.0/video-base-element'; +import {BentoVideoBaseElement} from '../../amp-video/1.0/base-element'; -export class BaseElement extends VideoBaseElement {} +export class BaseElement extends BentoVideoBaseElement {} /** @override */ BaseElement['Component'] = BentoBrightcove; diff --git a/extensions/amp-dailymotion/1.0/amp-dailymotion.js b/extensions/amp-dailymotion/1.0/amp-dailymotion.js index 51be237b01cb..8c6f25daf366 100644 --- a/extensions/amp-dailymotion/1.0/amp-dailymotion.js +++ b/extensions/amp-dailymotion/1.0/amp-dailymotion.js @@ -1,7 +1,11 @@ import {isExperimentOn} from '#experiments'; +import {setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; +import {AmpVideoBaseElement} from 'extensions/amp-video/1.0/video-base-element'; + import {BaseElement} from './base-element'; import {CSS} from '../../../build/amp-dailymotion-1.0.css'; @@ -9,7 +13,7 @@ import {CSS} from '../../../build/amp-dailymotion-1.0.css'; /** @const {string} */ const TAG = 'amp-dailymotion'; -class AmpDailymotion extends BaseElement { +class AmpDailymotion extends setSuperClass(BaseElement, AmpVideoBaseElement) { /** @override */ init() { super.init(); diff --git a/extensions/amp-dailymotion/1.0/base-element.js b/extensions/amp-dailymotion/1.0/base-element.js index eeba22237d94..1f86777f27ad 100644 --- a/extensions/amp-dailymotion/1.0/base-element.js +++ b/extensions/amp-dailymotion/1.0/base-element.js @@ -1,8 +1,8 @@ import {BentoDailymotion} from './component'; -import {VideoBaseElement} from '../../amp-video/1.0/video-base-element'; +import {BentoVideoBaseElement} from '../../amp-video/1.0/base-element'; -export class BaseElement extends VideoBaseElement {} +export class BaseElement extends BentoVideoBaseElement {} /** @override */ BaseElement['Component'] = BentoDailymotion; diff --git a/extensions/amp-date-countdown/1.0/amp-date-countdown.js b/extensions/amp-date-countdown/1.0/amp-date-countdown.js index 405d5a779d42..24e1044afb6c 100644 --- a/extensions/amp-date-countdown/1.0/amp-date-countdown.js +++ b/extensions/amp-date-countdown/1.0/amp-date-countdown.js @@ -2,6 +2,8 @@ import {isLayoutSizeDefined} from '#core/dom/layout'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {dev, userAssert} from '#utils/log'; @@ -11,7 +13,10 @@ import {BaseElement} from './base-element'; /** @const {string} */ const TAG = 'amp-date-countdown'; -class AmpDateCountdown extends BaseElement { +class AmpDateCountdown extends setSuperClass( + BaseElement, + AmpPreactBaseElement +) { /** @param {!AmpElement} element */ constructor(element) { super(element); diff --git a/extensions/amp-date-display/1.0/amp-date-display.js b/extensions/amp-date-display/1.0/amp-date-display.js index c45f413f21f9..a0640c576760 100644 --- a/extensions/amp-date-display/1.0/amp-date-display.js +++ b/extensions/amp-date-display/1.0/amp-date-display.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {dev, userAssert} from '#utils/log'; @@ -9,7 +11,7 @@ import {BaseElement} from './base-element'; /** @const {string} */ const TAG = 'amp-date-display'; -class AmpDateDisplay extends BaseElement { +class AmpDateDisplay extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @param {!AmpElement} element */ constructor(element) { super(element); diff --git a/extensions/amp-embedly-card/1.0/amp-embedly-card.js b/extensions/amp-embedly-card/1.0/amp-embedly-card.js index 5005495a6602..1118ad2c45d2 100644 --- a/extensions/amp-embedly-card/1.0/amp-embedly-card.js +++ b/extensions/amp-embedly-card/1.0/amp-embedly-card.js @@ -2,6 +2,8 @@ import {Layout_Enum} from '#core/dom/layout'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {AmpEmbedlyKey, TAG as KEY_TAG} from './amp-embedly-key'; @@ -10,7 +12,7 @@ import {BaseElement} from './base-element'; /** @const {string} */ const TAG = 'amp-embedly-card'; -class AmpEmbedlyCard extends BaseElement { +class AmpEmbedlyCard extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ init() { return { diff --git a/extensions/amp-embedly-card/1.0/amp-embedly-key.js b/extensions/amp-embedly-card/1.0/amp-embedly-key.js index 4b5e3f722bde..352256684241 100644 --- a/extensions/amp-embedly-card/1.0/amp-embedly-key.js +++ b/extensions/amp-embedly-card/1.0/amp-embedly-key.js @@ -2,6 +2,8 @@ import {Layout_Enum} from '#core/dom/layout'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {EmbedlyKeyBaseElement} from './key-base-element'; @@ -9,7 +11,10 @@ import {EmbedlyKeyBaseElement} from './key-base-element'; /** @const {string} */ export const TAG = 'amp-embedly-key'; -export class AmpEmbedlyKey extends EmbedlyKeyBaseElement { +export class AmpEmbedlyKey extends setSuperClass( + EmbedlyKeyBaseElement, + AmpPreactBaseElement +) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-embedly-card/1.0/web-component.js b/extensions/amp-embedly-card/1.0/web-component.js index 20d34087728a..da198b5094c6 100644 --- a/extensions/amp-embedly-card/1.0/web-component.js +++ b/extensions/amp-embedly-card/1.0/web-component.js @@ -1,3 +1,5 @@ +import {defineBentoElement} from '#preact/bento-ce'; + import {BENTO_TAG, BaseElement} from './base-element'; import { BENTO_TAG as EMBEDLY_KEY_BENTO_TAG, @@ -8,7 +10,7 @@ import { * Registers ` component to CustomElements registry */ export function defineElement() { - customElements.define(BENTO_TAG, BaseElement.CustomElement(BaseElement)); + defineBentoElement(BENTO_TAG, BaseElement); defineElementEmbedlyKey(); } @@ -16,8 +18,5 @@ export function defineElement() { * Registers ` component to CustomElements registry */ export function defineElementEmbedlyKey() { - customElements.define( - EMBEDLY_KEY_BENTO_TAG, - EmbedlyKeyBaseElement.CustomElement(EmbedlyKeyBaseElement) - ); + defineBentoElement(EMBEDLY_KEY_BENTO_TAG, EmbedlyKeyBaseElement); } diff --git a/extensions/amp-facebook/1.0/amp-facebook.js b/extensions/amp-facebook/1.0/amp-facebook.js index 1f0724b1d9eb..c60723e36181 100644 --- a/extensions/amp-facebook/1.0/amp-facebook.js +++ b/extensions/amp-facebook/1.0/amp-facebook.js @@ -2,6 +2,8 @@ import {dashToUnderline} from '#core/types/string'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import { @@ -21,62 +23,58 @@ const LIKE_TAG = 'amp-facebook-like'; const PAGE_TAG = 'amp-facebook-page'; const TYPE = 'facebook'; -/** - * Mixin to implement base amp functionality for all facebook components - * @param {*} clazz1 - * @return {*} mixin - */ -function AmpFacebookMixin(clazz1) { - return class extends clazz1 { - /** @override @nocollapse */ - static createLoaderLogoCallback(element) { - return createLoaderLogo(element); - } +class AmpFacebookBase extends setSuperClass(BaseElement, AmpPreactBaseElement) { + /** @override @nocollapse */ + static createLoaderLogoCallback(element) { + return createLoaderLogo(element); + } - /** @override @nocollapse */ - static getPreconnects(element) { - const ampdoc = element.getAmpDoc(); - const {win} = ampdoc; - const locale = element.hasAttribute('data-locale') - ? element.getAttribute('data-locale') - : dashToUnderline(window.navigator.language); - return [ - // Base URL for 3p bootstrap iframes - getBootstrapBaseUrl(win, ampdoc), - // Script URL for iframe - getBootstrapUrl(TYPE), - 'https://facebook.com', - // This domain serves the actual tweets as JSONP. - 'https://connect.facebook.net/' + locale + '/sdk.js', - ]; - } + /** @override @nocollapse */ + static getPreconnects(element) { + const ampdoc = element.getAmpDoc(); + const {win} = ampdoc; + const locale = element.hasAttribute('data-locale') + ? element.getAttribute('data-locale') + : dashToUnderline(window.navigator.language); + return [ + // Base URL for 3p bootstrap iframes + getBootstrapBaseUrl(win, ampdoc), + // Script URL for iframe + getBootstrapUrl(TYPE), + 'https://facebook.com', + // This domain serves the actual tweets as JSONP. + 'https://connect.facebook.net/' + locale + '/sdk.js', + ]; + } - /** @override */ - init() { - return { - 'requestResize': (height) => this.attemptChangeHeight(height), - }; - } + /** @override */ + init() { + return { + 'requestResize': (height) => this.attemptChangeHeight(height), + }; + } - /** @override */ - isLayoutSupported(layout) { - userAssert( - isExperimentOn(this.win, 'bento') || - isExperimentOn(this.win, 'bento-facebook'), - 'expected global "bento" or specific "bento-facebook" experiment to be enabled' - ); - return super.isLayoutSupported(layout); - } - }; + /** @override */ + isLayoutSupported(layout) { + userAssert( + isExperimentOn(this.win, 'bento') || + isExperimentOn(this.win, 'bento-facebook'), + 'expected global "bento" or specific "bento-facebook" experiment to be enabled' + ); + return super.isLayoutSupported(layout); + } } -class AmpFacebook extends AmpFacebookMixin(BaseElement) {} +class AmpFacebook extends AmpFacebookBase {} -class AmpFacebookComments extends AmpFacebookMixin(CommentsBaseElement) {} +class AmpFacebookComments extends setSuperClass( + CommentsBaseElement, + AmpFacebookBase +) {} -class AmpFacebookLike extends AmpFacebookMixin(LikeBaseElement) {} +class AmpFacebookLike extends setSuperClass(LikeBaseElement, AmpFacebookBase) {} -class AmpFacebookPage extends AmpFacebookMixin(PageBaseElement) {} +class AmpFacebookPage extends setSuperClass(PageBaseElement, AmpFacebookBase) {} AMP.extension(TAG, '1.0', (AMP) => { AMP.registerElement(TAG, AmpFacebook); diff --git a/extensions/amp-facebook/1.0/web-component.js b/extensions/amp-facebook/1.0/web-component.js index a6261e06fc2f..bb5c069e0818 100644 --- a/extensions/amp-facebook/1.0/web-component.js +++ b/extensions/amp-facebook/1.0/web-component.js @@ -1,3 +1,5 @@ +import {defineBentoElement} from '#preact/bento-ce'; + import { BaseElement, COMMENTS_TAG, @@ -13,7 +15,7 @@ import { *Register BentoFacebook component to CustomElements registry */ export function defineElement() { - customElements.define(TAG, BaseElement.CustomElement(BaseElement)); + defineBentoElement(TAG, BaseElement); defineCommentsElement(); defineLikeElement(); @@ -24,28 +26,19 @@ export function defineElement() { * Register BentoFacebookComments component to CustomElements registry */ export function defineCommentsElement() { - customElements.define( - COMMENTS_TAG, - CommentsBaseElement.CustomElement(CommentsBaseElement) - ); + defineBentoElement(COMMENTS_TAG, CommentsBaseElement); } /** * Register BentoFacebook component to CustomElements registry */ export function defineLikeElement() { - customElements.define( - LIKE_TAG, - LikeBaseElement.CustomElement(LikeBaseElement) - ); + defineBentoElement(LIKE_TAG, LikeBaseElement); } /** * Register BentoFacebook component to CustomElements registry */ export function definePageElement() { - customElements.define( - PAGE_TAG, - PageBaseElement.CustomElement(PageBaseElement) - ); + defineBentoElement(PAGE_TAG, PageBaseElement); } diff --git a/extensions/amp-fit-text/1.0/amp-fit-text.js b/extensions/amp-fit-text/1.0/amp-fit-text.js index 17795cc4af9e..a6d0f2d151f0 100644 --- a/extensions/amp-fit-text/1.0/amp-fit-text.js +++ b/extensions/amp-fit-text/1.0/amp-fit-text.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {BaseElement} from './base-element'; @@ -9,7 +11,7 @@ import {CSS} from '../../../build/amp-fit-text-1.0.css'; /** @const {string} */ const TAG = 'amp-fit-text'; -class AmpFitText extends BaseElement { +class AmpFitText extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-iframe/1.0/amp-iframe.js b/extensions/amp-iframe/1.0/amp-iframe.js index c76e293d1d1b..c93ab99c4972 100644 --- a/extensions/amp-iframe/1.0/amp-iframe.js +++ b/extensions/amp-iframe/1.0/amp-iframe.js @@ -2,6 +2,8 @@ import {measureIntersection} from '#core/dom/layout/intersection'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {BaseElement} from './base-element'; @@ -13,7 +15,7 @@ const MINIMUM_DISTANCE_FROM_TOP_PX = 600; /** @const {number} */ const MINIMUM_VIEWPORT_PROPORTION = 0.75; -class AmpIframe extends BaseElement { +class AmpIframe extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-inline-gallery/1.0/amp-inline-gallery-pagination.js b/extensions/amp-inline-gallery/1.0/amp-inline-gallery-pagination.js index 1b9a6189c62d..c69078504e03 100644 --- a/extensions/amp-inline-gallery/1.0/amp-inline-gallery-pagination.js +++ b/extensions/amp-inline-gallery/1.0/amp-inline-gallery-pagination.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {PaginationBaseElement} from './pagination-base-element'; @@ -7,7 +9,10 @@ import {PaginationBaseElement} from './pagination-base-element'; /** @const {string} */ export const TAG = 'amp-inline-gallery-pagination'; -export class AmpInlineGalleryPagination extends PaginationBaseElement { +export class AmpInlineGalleryPagination extends setSuperClass( + PaginationBaseElement, + AmpPreactBaseElement +) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-inline-gallery/1.0/amp-inline-gallery-thumbnails.js b/extensions/amp-inline-gallery/1.0/amp-inline-gallery-thumbnails.js index 64547b27912b..7005cd31561f 100644 --- a/extensions/amp-inline-gallery/1.0/amp-inline-gallery-thumbnails.js +++ b/extensions/amp-inline-gallery/1.0/amp-inline-gallery-thumbnails.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {ThumbnailsBaseElement} from './thumbnails-base-element'; @@ -7,7 +9,10 @@ import {ThumbnailsBaseElement} from './thumbnails-base-element'; /** @const {string} */ export const TAG = 'amp-inline-gallery-thumbnails'; -export class AmpInlineGalleryThumbnails extends ThumbnailsBaseElement { +export class AmpInlineGalleryThumbnails extends setSuperClass( + ThumbnailsBaseElement, + AmpPreactBaseElement +) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-inline-gallery/1.0/amp-inline-gallery.js b/extensions/amp-inline-gallery/1.0/amp-inline-gallery.js index 22ac32488215..e7d3162d38bb 100644 --- a/extensions/amp-inline-gallery/1.0/amp-inline-gallery.js +++ b/extensions/amp-inline-gallery/1.0/amp-inline-gallery.js @@ -2,6 +2,8 @@ import {Layout_Enum} from '#core/dom/layout'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import { @@ -19,7 +21,10 @@ import {CSS as PAGINATION_CSS} from '../../../build/amp-inline-gallery-paginatio /** @const {string} */ const TAG = 'amp-inline-gallery'; -class AmpInlineGallery extends BaseElement { +class AmpInlineGallery extends setSuperClass( + BaseElement, + AmpPreactBaseElement +) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-inline-gallery/1.0/web-component.js b/extensions/amp-inline-gallery/1.0/web-component.js index 3aea66627eeb..7cdc8d791f64 100644 --- a/extensions/amp-inline-gallery/1.0/web-component.js +++ b/extensions/amp-inline-gallery/1.0/web-component.js @@ -1,3 +1,5 @@ +import {defineBentoElement} from '#preact/bento-ce'; + import {BaseElement, TAG} from './base-element'; import { TAG as PAGINATION_TAG, @@ -12,7 +14,7 @@ import { * Registers ` component to CustomElements registry */ export function defineElement() { - customElements.define(TAG, BaseElement.CustomElement(BaseElement)); + defineBentoElement(TAG, BaseElement); defineThumbnailsElement(); definePaginationElement(); } @@ -21,18 +23,12 @@ export function defineElement() { * Registers ` component to CustomElements registry */ export function definePaginationElement() { - customElements.define( - PAGINATION_TAG, - PaginationBaseElement.CustomElement(PaginationBaseElement) - ); + defineBentoElement(PAGINATION_TAG, PaginationBaseElement); } /** * Registers ` component to CustomElements registry */ export function defineThumbnailsElement() { - customElements.define( - THUMBNAIL_TAG, - ThumbnailsBaseElement.CustomElement(ThumbnailsBaseElement) - ); + defineBentoElement(THUMBNAIL_TAG, ThumbnailsBaseElement); } diff --git a/extensions/amp-instagram/1.0/amp-instagram.js b/extensions/amp-instagram/1.0/amp-instagram.js index 77763315fd10..f71e5c816c8e 100644 --- a/extensions/amp-instagram/1.0/amp-instagram.js +++ b/extensions/amp-instagram/1.0/amp-instagram.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {BaseElement} from './base-element'; @@ -9,7 +11,7 @@ import {CSS} from '../../../build/amp-instagram-1.0.css'; /** @const {string} */ const TAG = 'amp-instagram'; -class AmpInstagram extends BaseElement { +class AmpInstagram extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-jwplayer/1.0/amp-jwplayer.js b/extensions/amp-jwplayer/1.0/amp-jwplayer.js index d930c889ebb7..55c0acbb058e 100644 --- a/extensions/amp-jwplayer/1.0/amp-jwplayer.js +++ b/extensions/amp-jwplayer/1.0/amp-jwplayer.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {BaseElement} from './base-element'; @@ -9,12 +11,13 @@ import { getConsentPolicyInfo, getConsentPolicyState, } from '../../../src/consent'; +import {AmpVideoBaseElement} from '../../amp-video/1.0/video-base-element'; /** @const {string} */ const TAG = 'amp-jwplayer'; /** @implements {../../../src/video-interface.VideoInterface} */ -class AmpJwplayer extends BaseElement { +class AmpJwplayer extends setSuperClass(BaseElement, AmpVideoBaseElement) { /** @override */ init() { const consentPolicy = this.getConsentPolicy(); diff --git a/extensions/amp-jwplayer/1.0/base-element.js b/extensions/amp-jwplayer/1.0/base-element.js index 33d10c00bc3c..33b41035019d 100644 --- a/extensions/amp-jwplayer/1.0/base-element.js +++ b/extensions/amp-jwplayer/1.0/base-element.js @@ -4,9 +4,9 @@ import {createParseAttrsWithPrefix} from '#preact/parse-props'; import {BentoJwplayer} from './component'; -import {VideoBaseElement} from '../../amp-video/1.0/video-base-element'; +import {BentoVideoBaseElement} from '../../amp-video/1.0/base-element'; -export class BaseElement extends VideoBaseElement { +export class BaseElement extends BentoVideoBaseElement { /** @override */ init() { super.init(); diff --git a/extensions/amp-lightbox-gallery/1.0/amp-lightbox-gallery.js b/extensions/amp-lightbox-gallery/1.0/amp-lightbox-gallery.js index f1eb45991390..4024021f2c33 100644 --- a/extensions/amp-lightbox-gallery/1.0/amp-lightbox-gallery.js +++ b/extensions/amp-lightbox-gallery/1.0/amp-lightbox-gallery.js @@ -7,6 +7,8 @@ import {elementByTag} from '#core/dom/query'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {triggerAnalyticsEvent} from '#utils/analytics'; @@ -22,7 +24,10 @@ const TAG = 'amp-lightbox-gallery'; /** @const {string} */ const DEFAULT_GALLERY_ID = 'amp-lightbox-gallery'; -class AmpLightboxGallery extends BaseElement { +class AmpLightboxGallery extends setSuperClass( + BaseElement, + AmpPreactBaseElement +) { /** @override */ constructor(element) { super(element); diff --git a/extensions/amp-lightbox/1.0/amp-lightbox.js b/extensions/amp-lightbox/1.0/amp-lightbox.js index 61d538cc6d75..280b1d7176b5 100644 --- a/extensions/amp-lightbox/1.0/amp-lightbox.js +++ b/extensions/amp-lightbox/1.0/amp-lightbox.js @@ -6,6 +6,8 @@ import {getWin} from '#core/window'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {createCustomEvent} from '#utils/event-helper'; @@ -19,7 +21,7 @@ import {CSS} from '../../../build/amp-lightbox-1.0.css'; const TAG = 'amp-lightbox'; /** @extends {PreactBaseElement} */ -class AmpLightbox extends BaseElement { +class AmpLightbox extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ constructor(element) { super(element); diff --git a/extensions/amp-mathml/1.0/amp-mathml.js b/extensions/amp-mathml/1.0/amp-mathml.js index 46c7b32ce9bf..2f41e60fb05d 100644 --- a/extensions/amp-mathml/1.0/amp-mathml.js +++ b/extensions/amp-mathml/1.0/amp-mathml.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {BaseElement} from './base-element'; @@ -11,7 +13,7 @@ import {getBootstrapBaseUrl, getBootstrapUrl} from '../../../src/3p-frame'; /** @const {string} */ const TAG = 'amp-mathml'; -class AmpMathml extends BaseElement { +class AmpMathml extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override @nocollapse */ static getPreconnects(element) { const ampdoc = element.getAmpDoc(); diff --git a/extensions/amp-render/1.0/amp-render.js b/extensions/amp-render/1.0/amp-render.js index 7ad007cd6e6d..78536202e827 100644 --- a/extensions/amp-render/1.0/amp-render.js +++ b/extensions/amp-render/1.0/amp-render.js @@ -2,6 +2,8 @@ import {Layout_Enum} from '#core/dom/layout'; import {computedStyle, setStyles} from '#core/dom/style'; import {toArray} from '#core/types/array'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {dev, user, userAssert} from '#utils/log'; @@ -116,7 +118,10 @@ function getTemplateNonEmptyNodeCount(doc, template) { ); } -export class AmpRender extends BaseElement { +export class AmpRender extends setSuperClass( + BaseElement, + AmpPreactBaseElement +) { /** @param {!AmpElement} element */ constructor(element) { super(element); diff --git a/extensions/amp-selector/1.0/amp-selector.js b/extensions/amp-selector/1.0/amp-selector.js index 9c61a123a5f8..71adf148a935 100644 --- a/extensions/amp-selector/1.0/amp-selector.js +++ b/extensions/amp-selector/1.0/amp-selector.js @@ -3,6 +3,8 @@ import {getWin} from '#core/window'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {createCustomEvent} from '#utils/event-helper'; @@ -15,7 +17,7 @@ import {CSS} from '../../../build/amp-selector-1.0.css'; /** @const {string} */ const TAG = 'amp-selector'; -class AmpSelector extends BaseElement { +class AmpSelector extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ init() { // Set up API diff --git a/extensions/amp-sidebar/1.0/amp-sidebar.js b/extensions/amp-sidebar/1.0/amp-sidebar.js index 84233a736ee8..1677c229066c 100644 --- a/extensions/amp-sidebar/1.0/amp-sidebar.js +++ b/extensions/amp-sidebar/1.0/amp-sidebar.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service/'; import {userAssert} from '#utils/log'; @@ -11,7 +13,7 @@ import {CSS} from '../../../build/amp-sidebar-1.0.css'; /** @const {string} */ const TAG = 'amp-sidebar'; -class AmpSidebar extends BaseElement { +class AmpSidebar extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ constructor(element) { super(element); diff --git a/extensions/amp-social-share/1.0/amp-social-share.js b/extensions/amp-social-share/1.0/amp-social-share.js index 1f67436c5a1c..302edb10c25a 100644 --- a/extensions/amp-social-share/1.0/amp-social-share.js +++ b/extensions/amp-social-share/1.0/amp-social-share.js @@ -6,6 +6,8 @@ import {getWin} from '#core/window'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {userAssert} from '#utils/log'; @@ -114,7 +116,7 @@ const updateTypeConfig = (element, mutations, prevTypeValue) => { return typeConfig; }; -class AmpSocialShare extends BaseElement { +class AmpSocialShare extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @param {!AmpElement} element */ constructor(element) { super(element); diff --git a/extensions/amp-soundcloud/1.0/amp-soundcloud.js b/extensions/amp-soundcloud/1.0/amp-soundcloud.js index da42b0395d23..2c64d9734502 100644 --- a/extensions/amp-soundcloud/1.0/amp-soundcloud.js +++ b/extensions/amp-soundcloud/1.0/amp-soundcloud.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {BaseElement} from './base-element'; @@ -7,7 +9,7 @@ import {BaseElement} from './base-element'; /** @const {string} */ const TAG = 'amp-soundcloud'; -class AmpSoundcloud extends BaseElement { +class AmpSoundcloud extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-stream-gallery/1.0/amp-stream-gallery.js b/extensions/amp-stream-gallery/1.0/amp-stream-gallery.js index 187210f19834..bba77530ece3 100644 --- a/extensions/amp-stream-gallery/1.0/amp-stream-gallery.js +++ b/extensions/amp-stream-gallery/1.0/amp-stream-gallery.js @@ -3,6 +3,8 @@ import {getWin} from '#core/window'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {Services} from '#service'; import {createCustomEvent} from '#utils/event-helper'; @@ -15,7 +17,10 @@ import {CSS} from '../../../build/amp-stream-gallery-1.0.css'; /** @const {string} */ const TAG = 'amp-stream-gallery'; -class AmpStreamGallery extends BaseElement { +class AmpStreamGallery extends setSuperClass( + BaseElement, + AmpPreactBaseElement +) { /** @override */ init() { this.registerApiAction('prev', (api) => api.prev(), ActionTrust_Enum.LOW); diff --git a/extensions/amp-timeago/1.0/amp-timeago.js b/extensions/amp-timeago/1.0/amp-timeago.js index 81ee8cc06f46..2f89eb250560 100644 --- a/extensions/amp-timeago/1.0/amp-timeago.js +++ b/extensions/amp-timeago/1.0/amp-timeago.js @@ -1,5 +1,7 @@ import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {userAssert} from '#utils/log'; import {BaseElement} from './base-element'; @@ -7,7 +9,7 @@ import {BaseElement} from './base-element'; /** @const {string} */ const TAG = 'amp-timeago'; -class AmpTimeago extends BaseElement { +class AmpTimeago extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-twitter/1.0/amp-twitter.js b/extensions/amp-twitter/1.0/amp-twitter.js index 6c85a1ad0367..ccddc1cf3d34 100644 --- a/extensions/amp-twitter/1.0/amp-twitter.js +++ b/extensions/amp-twitter/1.0/amp-twitter.js @@ -3,6 +3,8 @@ import {htmlFor} from '#core/dom/static-template'; import {isExperimentOn} from '#experiments'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + import {BaseElement} from './base-element'; import {getBootstrapBaseUrl, getBootstrapUrl} from '../../../src/3p-frame'; @@ -11,7 +13,7 @@ import {getBootstrapBaseUrl, getBootstrapUrl} from '../../../src/3p-frame'; const TAG = 'amp-twitter'; const TYPE = 'twitter'; -class AmpTwitter extends BaseElement { +class AmpTwitter extends setSuperClass(BaseElement, AmpPreactBaseElement) { /** @param {!AmpElement} element */ constructor(element) { super(element); diff --git a/extensions/amp-video-iframe/1.0/amp-video-iframe.js b/extensions/amp-video-iframe/1.0/amp-video-iframe.js index 727244374aec..6b458a857970 100644 --- a/extensions/amp-video-iframe/1.0/amp-video-iframe.js +++ b/extensions/amp-video-iframe/1.0/amp-video-iframe.js @@ -2,6 +2,8 @@ import {measureIntersection} from '#core/dom/layout/intersection'; import {isExperimentOn} from '#experiments'; +import {setSuperClass} from '#preact/amp-base-element'; + import {createCustomEvent} from '#utils/event-helper'; import {userAssert} from '#utils/log'; @@ -10,6 +12,7 @@ import {BaseElement} from './base-element'; import {CSS} from '../../../build/amp-video-iframe-1.0.css'; import {postMessageWhenAvailable} from '../../../src/iframe-video'; import {MIN_VISIBILITY_RATIO_FOR_AUTOPLAY} from '../../../src/video-interface'; +import {AmpVideoBaseElement} from '../../amp-video/1.0/video-base-element'; import {BUBBLE_MESSAGE_EVENTS} from '../amp-video-iframe-api'; /** @const {string} */ @@ -29,7 +32,7 @@ function getIntersectionRatioMinAutoplay(element) { ); } -class AmpVideoIframe extends BaseElement { +class AmpVideoIframe extends setSuperClass(BaseElement, AmpVideoBaseElement) { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-video-iframe/1.0/base-element.js b/extensions/amp-video-iframe/1.0/base-element.js index f2f280c6e47e..53c399853431 100644 --- a/extensions/amp-video-iframe/1.0/base-element.js +++ b/extensions/amp-video-iframe/1.0/base-element.js @@ -1,8 +1,8 @@ import {BentoVideoIframe} from './component'; -import {VideoBaseElement} from '../../amp-video/1.0/video-base-element'; +import {BentoVideoBaseElement} from '../../amp-video/1.0/base-element'; -export class BaseElement extends VideoBaseElement {} +export class BaseElement extends BentoVideoBaseElement {} /** @override */ BaseElement['Component'] = BentoVideoIframe; diff --git a/extensions/amp-video/1.0/amp-video.js b/extensions/amp-video/1.0/amp-video.js index 0fb035decb22..20321d3b6a5b 100644 --- a/extensions/amp-video/1.0/amp-video.js +++ b/extensions/amp-video/1.0/amp-video.js @@ -2,14 +2,14 @@ import {isExperimentOn} from '#experiments'; import {userAssert} from '#utils/log'; -import {VideoBaseElement} from './video-base-element'; +import {AmpVideoBaseElement} from './video-base-element'; import {CSS} from '../../../build/amp-video-1.0.css'; /** @const {string} */ const TAG = 'amp-video'; -class AmpVideo extends VideoBaseElement { +class AmpVideo extends AmpVideoBaseElement { /** @override */ isLayoutSupported(layout) { userAssert( diff --git a/extensions/amp-video/1.0/base-element.js b/extensions/amp-video/1.0/base-element.js index d7b1460c5bee..39bac447db6f 100644 --- a/extensions/amp-video/1.0/base-element.js +++ b/extensions/amp-video/1.0/base-element.js @@ -4,31 +4,33 @@ import {CSS as CSS_AUTOPLAY} from './autoplay.jss'; import {BentoVideo} from './component'; import {CSS} from './component.jss'; -export class BaseElement extends PreactBaseElement {} +export class BentoVideoBaseElement extends PreactBaseElement {} +// export with alias for bento builds +export {BentoVideoBaseElement as BaseElement}; /** @override */ -BaseElement['Component'] = BentoVideo; +BentoVideoBaseElement['Component'] = BentoVideo; /** @override */ -BaseElement['loadable'] = true; +BentoVideoBaseElement['loadable'] = true; /** @override */ -BaseElement['layoutSizeDefined'] = true; +BentoVideoBaseElement['layoutSizeDefined'] = true; /** * Defaults to `{component: 'video'}` from `BentoVideo` component. * Subclasses may set: * ``` - * AmpMyPlayer['staticProps'] = dict({ + * AmpMyPlayer['staticProps'] = { * 'component': MyPlayerComponent, - * }); + * }; * ``` * @override */ -BaseElement['staticProps']; +BentoVideoBaseElement['staticProps']; /** @override */ -BaseElement['props'] = { +BentoVideoBaseElement['props'] = { 'album': {attr: 'album'}, 'alt': {attr: 'alt'}, 'artist': {attr: 'artist'}, @@ -60,7 +62,7 @@ BaseElement['props'] = { }; /** @override */ -BaseElement['shadowCss'] = CSS + CSS_AUTOPLAY; +BentoVideoBaseElement['shadowCss'] = CSS + CSS_AUTOPLAY; /** @override */ -BaseElement['usesShadowDom'] = true; +BentoVideoBaseElement['usesShadowDom'] = true; diff --git a/extensions/amp-video/1.0/video-base-element.js b/extensions/amp-video/1.0/video-base-element.js index c4563b131a44..9bb4de6b3fc4 100644 --- a/extensions/amp-video/1.0/video-base-element.js +++ b/extensions/amp-video/1.0/video-base-element.js @@ -1,9 +1,14 @@ import {ActionTrust_Enum} from '#core/constants/action-constants'; -import {BaseElement} from './base-element'; +import {AmpPreactBaseElement, setSuperClass} from '#preact/amp-base-element'; + +import {BentoVideoBaseElement} from './base-element'; /** @extends {PreactBaseElement} */ -export class VideoBaseElement extends BaseElement { +export class AmpVideoBaseElement extends setSuperClass( + BentoVideoBaseElement, + AmpPreactBaseElement +) { /** @override */ init() { this.registerApiAction_('play', (api) => api.play()); diff --git a/extensions/amp-video/1.0/video-iframe.js b/extensions/amp-video/1.0/video-iframe.js index 5d2c6fdf744a..f09dfac55f89 100644 --- a/extensions/amp-video/1.0/video-iframe.js +++ b/extensions/amp-video/1.0/video-iframe.js @@ -211,7 +211,7 @@ export {VideoIframeInternal}; /** * VideoWrapper using an