diff --git a/extensions/amp-selector/0.1/amp-selector.js b/extensions/amp-selector/0.1/amp-selector.js index 0701a6c26350..ade6f4a05898 100644 --- a/extensions/amp-selector/0.1/amp-selector.js +++ b/extensions/amp-selector/0.1/amp-selector.js @@ -21,7 +21,7 @@ import {Services} from '../../../src/services'; import {closestBySelector, isRTL, tryFocus} from '../../../src/dom'; import {createCustomEvent} from '../../../src/event-helper'; import {dev, user} from '../../../src/log'; - +import {mod} from '../../../src/utils/math'; const TAG = 'amp-selector'; /** @@ -113,6 +113,18 @@ export class AmpSelector extends AMP.BaseElement { this.element.addEventListener('click', this.clickHandler_.bind(this)); this.element.addEventListener('keydown', this.keyDownHandler_.bind(this)); } + + this.registerAction('selectUp', invocation => { + const args = invocation.args; + const delta = (args && args['delta'] !== undefined) ? -args['delta'] : -1; + this.select_(delta); + }, ActionTrust.LOW); + + this.registerAction('selectDown', invocation => { + const args = invocation.args; + const delta = (args && args['delta'] !== undefined) ? args['delta'] : 1; + this.select_(delta); + }, ActionTrust.LOW); } /** @override */ @@ -319,6 +331,22 @@ export class AmpSelector extends AMP.BaseElement { } } + /** + * Handles selectUp events. + * @param {number} delta + */ + select_(delta) { + // Change the selection to the next element in the specified direction. + // The selection should loop around if the user attempts to go one + // past the beginning or end. + const previousIndex = this.options_.indexOf(this.selectedOptions_[0]); + const index = previousIndex + delta; + const normalizedIndex = mod(index, this.options_.length); + + this.setSelection_(this.options_[normalizedIndex]); + this.clearSelection_(this.options_[previousIndex]); + } + /** * Handles keyboard events. * @param {!Event} event diff --git a/extensions/amp-selector/0.1/test/test-amp-selector.js b/extensions/amp-selector/0.1/test/test-amp-selector.js index 1344cd4768ac..02695f40254e 100644 --- a/extensions/amp-selector/0.1/test/test-amp-selector.js +++ b/extensions/amp-selector/0.1/test/test-amp-selector.js @@ -651,6 +651,96 @@ describes.realWin('amp-selector', { ampSelector, 'select', /* CustomEvent */ eventMatcher); }); + it('should trigger `select` action when user uses ' + + '`selectUp`/`selectDown` action with default delta value of 1', () => { + const ampSelector = getSelector({ + attributes: { + id: 'ampSelector', + }, + config: { + count: 6, + }, + }); + ampSelector.children[0].setAttribute('selected', ''); + ampSelector.build(); + const impl = ampSelector.implementation_; + + expect(ampSelector.hasAttribute('multiple')).to.be.false; + expect(ampSelector.children[0].hasAttribute('selected')).to.be.true; + + impl.executeAction({method: 'selectDown', satisfiesTrust: () => true}); + expect(ampSelector.children[0].hasAttribute('selected')).to.be.false; + expect(ampSelector.children[1].hasAttribute('selected')).to.be.true; + + impl.executeAction({method: 'selectUp', satisfiesTrust: () => true}); + + expect(ampSelector.children[1].hasAttribute('selected')).to.be.false; + expect(ampSelector.children[0].hasAttribute('selected')).to.be.true; + + }); + + it('should trigger `select` action when user uses ' + + '`selectUp`/`selectDown` action with user specified delta value', () => { + const ampSelector = getSelector({ + attributes: { + id: 'ampSelector', + }, + config: { + count: 6, + }, + }); + ampSelector.children[0].setAttribute('selected', ''); + ampSelector.build(); + const impl = ampSelector.implementation_; + + expect(ampSelector.hasAttribute('multiple')).to.be.false; + expect(ampSelector.children[0].hasAttribute('selected')).to.be.true; + + let args = {'delta': 2}; + impl.executeAction( + {method: 'selectDown', args, satisfiesTrust: () => true}); + expect(ampSelector.children[0].hasAttribute('selected')).to.be.false; + expect(ampSelector.children[2].hasAttribute('selected')).to.be.true; + + args = {'delta': 2}; + impl.executeAction( + {method: 'selectUp', args, satisfiesTrust: () => true}); + expect(ampSelector.children[2].hasAttribute('selected')).to.be.false; + expect(ampSelector.children[0].hasAttribute('selected')).to.be.true; + }); + + it('should trigger `select` action when user uses ' + + '`selectUp`/`selectDown` action with user specified delta value ' + + '(test large values)', () => { + const ampSelector = getSelector({ + attributes: { + id: 'ampSelector', + }, + config: { + count: 5, + }, + }); + ampSelector.children[1].setAttribute('selected', ''); + ampSelector.build(); + const impl = ampSelector.implementation_; + + expect(ampSelector.hasAttribute('multiple')).to.be.false; + expect(ampSelector.children[1].hasAttribute('selected')).to.be.true; + + let args = {'delta': 1001}; + impl.executeAction( + {method: 'selectDown', args, satisfiesTrust: () => true}); + expect(ampSelector.children[1].hasAttribute('selected')).to.be.false; + expect(ampSelector.children[2].hasAttribute('selected')).to.be.true; + + args = {'delta': 1001}; + impl.executeAction( + {method: 'selectUp', args, satisfiesTrust: () => true}); + expect(ampSelector.children[2].hasAttribute('selected')).to.be.false; + expect(ampSelector.children[1].hasAttribute('selected')).to.be.true; + }); + + describe('keyboard-select-mode', () => { it('should have `none` mode by default', () => { diff --git a/spec/amp-actions-and-events.md b/spec/amp-actions-and-events.md index 1862c51cfc4f..52b3e5c4c702 100644 --- a/spec/amp-actions-and-events.md +++ b/spec/amp-actions-and-events.md @@ -362,6 +362,22 @@ event.response +### amp-selector +
Action | +Description | +
---|---|
selectUp(delta=INTEGER) |
+ Moves the selection up by the value of `delta`. The default `delta` is set to 1. | +
selectDown(delta=INTEGER) |
+ Moves the selection down by the value of `delta`. The default `delta` is set to -1. | +