Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 701ed5c

Browse files
authored
feat(menu-surface): Update setPosition adapter API to use numeric values (#4351)
Accepting `string` values implies that we support all valid CSS units (`em`, `rem`, `%`, etc.), but in truth we only support `px`. Changing the type of the properties from `string` to `number` makes this clearer, and also simplifies converting the package to TypeScript in PR #4273. BREAKING CHANGE: `MDCMenuSurfaceAdapter.setPosition()` now expects an object with properties of type `number` rather than `string`. E.g., `setPosition({top: '5px', left: '10px'})` is now `setPosition({top: 5, left: 10})`.
1 parent af950fc commit 701ed5c

File tree

6 files changed

+221
-78
lines changed

6 files changed

+221
-78
lines changed

packages/mdc-menu-surface/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ Method Signature | Description
209209
`getBodyDimensions() => {width: number, height: number}` | Returns an object with width and height of the body, in pixels.
210210
`getWindowDimensions() => {width: number, height: number}` | Returns an object with width and height of the viewport, in pixels.
211211
`getWindowScroll() => {x: number, y: number}` | Returns an object with the amount the body has been scrolled on the `x` and `y` axis.
212-
`setPosition(position: {top: string, right: string, bottom: string, left: string}) => void` | Sets the position of the menu surface element.
212+
`setPosition(position: {top: number, right: number, bottom: number, left: number}) => void` | Sets the position of the menu surface element.
213213
`setMaxHeight(value: string) => void` | Sets `max-height` style for the menu surface element.
214214

215215
### `MDCMenuSurfaceFoundation`

packages/mdc-menu-surface/adapter.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ class MDCMenuSurfaceAdapter {
113113
getWindowScroll() {}
114114

115115
/** @param {!{
116-
* top: (string|undefined),
117-
* right: (string|undefined),
118-
* bottom: (string|undefined),
119-
* left: (string|undefined)
116+
* top: (number|undefined),
117+
* right: (number|undefined),
118+
* bottom: (number|undefined),
119+
* left: (number|undefined)
120120
* }} position */
121121
setPosition(position) {}
122122

packages/mdc-menu-surface/foundation.js

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ let AnchorMargin;
4141
* surfaceHeight: number,
4242
* surfaceWidth: number,
4343
* bodyDimensions,
44-
* windowScroll,
44+
* windowScroll
4545
* }}
4646
*/
4747
let AutoLayoutMeasurements;
@@ -410,8 +410,8 @@ class MDCMenuSurfaceFoundation extends MDCFoundation {
410410
const horizontalOffset = this.getHorizontalOriginOffset_(corner);
411411
const verticalOffset = this.getVerticalOriginOffset_(corner);
412412
let position = {
413-
[horizontalAlignment]: horizontalOffset ? horizontalOffset : '0',
414-
[verticalAlignment]: verticalOffset ? verticalOffset : '0',
413+
[horizontalAlignment]: horizontalOffset,
414+
[verticalAlignment]: verticalOffset,
415415
};
416416
const {anchorWidth, surfaceWidth} = this.measures_;
417417
// Center align when anchor width is comparable or greater than menu surface, otherwise keep corner.
@@ -424,12 +424,6 @@ class MDCMenuSurfaceFoundation extends MDCFoundation {
424424
position = this.adjustPositionForHoistedElement_(position);
425425
}
426426

427-
for (const prop in position) {
428-
if (position.hasOwnProperty(prop) && position[prop] !== '0') {
429-
position[prop] = `${parseInt(position[prop], 10)}px`;
430-
}
431-
}
432-
433427
this.adapter_.setTransformOrigin(`${horizontalAlignment} ${verticalAlignment}`);
434428
this.adapter_.setPosition(position);
435429
this.adapter_.setMaxHeight(maxMenuSurfaceHeight ? maxMenuSurfaceHeight + 'px' : '');
@@ -442,16 +436,16 @@ class MDCMenuSurfaceFoundation extends MDCFoundation {
442436
* Calculates the offsets for positioning the menu-surface when the menu-surface has been
443437
* hoisted to the body.
444438
* @param {!{
445-
* top: (string|undefined),
446-
* right: (string|undefined),
447-
* bottom: (string|undefined),
448-
* left: (string|undefined)
439+
* top: (number|undefined),
440+
* right: (number|undefined),
441+
* bottom: (number|undefined),
442+
* left: (number|undefined)
449443
* }} position
450444
* @return {!{
451-
* top: (string|undefined),
452-
* right: (string|undefined),
453-
* bottom: (string|undefined),
454-
* left: (string|undefined)
445+
* top: (number|undefined),
446+
* right: (number|undefined),
447+
* bottom: (number|undefined),
448+
* left: (number|undefined)
455449
* }} position
456450
* @private
457451
*/
@@ -463,20 +457,20 @@ class MDCMenuSurfaceFoundation extends MDCFoundation {
463457
// Hoisted surfaces need to have the anchor elements location on the page added to the
464458
// position properties for proper alignment on the body.
465459
if (viewportDistance.hasOwnProperty(prop)) {
466-
position[prop] = parseInt(position[prop], 10) + viewportDistance[prop];
460+
position[prop] += viewportDistance[prop];
467461
}
468462

469463
// Surfaces that are absolutely positioned need to have additional calculations for scroll
470464
// and bottom positioning.
471465
if (!this.isFixedPosition_) {
472466
if (prop === 'top') {
473-
position[prop] = parseInt(position[prop], 10) + windowScroll.y;
467+
position[prop] += windowScroll.y;
474468
} else if (prop === 'bottom') {
475-
position[prop] = parseInt(position[prop], 10) - windowScroll.y;
469+
position[prop] -= windowScroll.y;
476470
} else if (prop === 'left') {
477-
position[prop] = parseInt(position[prop], 10) + windowScroll.x;
471+
position[prop] += windowScroll.x;
478472
} else if (prop === 'right') {
479-
position[prop] = parseInt(position[prop], 10) - windowScroll.x;
473+
position[prop] -= windowScroll.x;
480474
}
481475
}
482476
}

packages/mdc-menu-surface/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ class MDCMenuSurface extends MDCComponent {
247247
getInnerDimensions: () => {
248248
return {width: this.root_.offsetWidth, height: this.root_.offsetHeight};
249249
},
250-
getAnchorDimensions: () => this.anchorElement && this.anchorElement.getBoundingClientRect(),
250+
getAnchorDimensions: () => this.anchorElement ? this.anchorElement.getBoundingClientRect() : null,
251251
getWindowDimensions: () => {
252252
return {width: window.innerWidth, height: window.innerHeight};
253253
},
@@ -258,10 +258,10 @@ class MDCMenuSurface extends MDCComponent {
258258
return {x: window.pageXOffset, y: window.pageYOffset};
259259
},
260260
setPosition: (position) => {
261-
this.root_.style.left = 'left' in position ? position.left : null;
262-
this.root_.style.right = 'right' in position ? position.right : null;
263-
this.root_.style.top = 'top' in position ? position.top : null;
264-
this.root_.style.bottom = 'bottom' in position ? position.bottom : null;
261+
this.root_.style.left = 'left' in position ? `${position.left}px` : '';
262+
this.root_.style.right = 'right' in position ? `${position.right}px` : '';
263+
this.root_.style.top = 'top' in position ? `${position.top}px` : '';
264+
this.root_.style.bottom = 'bottom' in position ? `${position.bottom}px` : '';
265265
},
266266
setMaxHeight: (height) => {
267267
this.root_.style.maxHeight = height;

test/unit/mdc-menu-surface/mdc-menu-surface.test.js

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ import domEvents from 'dom-events';
2727
import td from 'testdouble';
2828

2929
import {MDCMenuSurface, MDCMenuSurfaceFoundation} from '../../../packages/mdc-menu-surface/index';
30-
import {strings, cssClasses, Corner} from '../../../packages/mdc-menu-surface/constants';
30+
import {Corner, cssClasses, strings} from '../../../packages/mdc-menu-surface/constants';
3131
import {getTransformPropertyName} from '../../../packages/mdc-menu-surface/util';
3232

33-
function getFixture(open) {
33+
function getFixture(open, fixedPosition = false) {
34+
const openClass = open ? 'mdc-menu-surface--open' : '';
35+
const fixedClass = fixedPosition ? 'mdc-menu-surface--fixed' : '';
36+
3437
return bel`
35-
<div class="mdc-menu-surface ${open ? 'mdc-menu-surface--open' : ''}" tabindex="-1">
38+
<div class="mdc-menu-surface ${openClass} ${fixedClass}" tabindex="-1">
3639
<ul class="mdc-list" role="menu">
3740
<li class="mdc-list-item" role="menuitem" tabindex="0">Item</a>
3841
<li role="separator"></li>
@@ -42,8 +45,8 @@ function getFixture(open) {
4245
`;
4346
}
4447

45-
function setupTest(open = false) {
46-
const root = getFixture(open);
48+
function setupTest(open = false, fixedPosition = false) {
49+
const root = getFixture(open, fixedPosition);
4750
const MockFoundationConstructor = td.constructor(MDCMenuSurfaceFoundation);
4851
const mockFoundation = new MockFoundationConstructor();
4952
const component = new MDCMenuSurface(root, mockFoundation);
@@ -147,6 +150,11 @@ test('setIsHoisted', () => {
147150
td.verify(mockFoundation.setIsHoisted(true));
148151
});
149152

153+
test('setFixedPosition is called when CSS class is present', () => {
154+
const {mockFoundation} = setupTest(false, true);
155+
td.verify(mockFoundation.setFixedPosition(true));
156+
});
157+
150158
test('setFixedPosition is true', () => {
151159
const {root, component, mockFoundation} = setupTest();
152160
component.setFixedPosition(true);
@@ -174,12 +182,18 @@ test('setAnchorCorner', () => {
174182
td.verify(mockFoundation.setAnchorCorner(Corner.TOP_START));
175183
});
176184

177-
test('setAnchorMargin', () => {
185+
test('setAnchorMargin with all object properties defined', () => {
178186
const {component, mockFoundation} = setupTest();
179187
component.setAnchorMargin({top: 0, right: 0, bottom: 0, left: 0});
180188
td.verify(mockFoundation.setAnchorMargin({top: 0, right: 0, bottom: 0, left: 0}));
181189
});
182190

191+
test('setAnchorMargin with empty object', () => {
192+
const {component, mockFoundation} = setupTest();
193+
component.setAnchorMargin({});
194+
td.verify(mockFoundation.setAnchorMargin({}));
195+
});
196+
183197
test('setQuickOpen', () => {
184198
const {component, mockFoundation} = setupTest();
185199
component.quickOpen = false;
@@ -362,7 +376,7 @@ test('adapter#getAnchorDimensions returns undefined if there is no anchor elemen
362376
const {root, component} = setupTest(true);
363377
document.body.appendChild(root);
364378
component.initialSyncWithDOM();
365-
assert.isUndefined(component.getDefaultFoundation().adapter_.getAnchorDimensions());
379+
assert.isNull(component.getDefaultFoundation().adapter_.getAnchorDimensions());
366380
document.body.removeChild(root);
367381
});
368382

@@ -466,10 +480,10 @@ test('adapter#setTransformOrigin sets the correct transform origin on the menu s
466480

467481
test('adapter#setPosition sets the correct position on the menu surface element', () => {
468482
const {root, component} = setupTest();
469-
component.getDefaultFoundation().adapter_.setPosition({top: '10px', left: '11px'});
483+
component.getDefaultFoundation().adapter_.setPosition({top: 10, left: 11});
470484
assert.equal(root.style.top, '10px');
471485
assert.equal(root.style.left, '11px');
472-
component.getDefaultFoundation().adapter_.setPosition({bottom: '10px', right: '11px'});
486+
component.getDefaultFoundation().adapter_.setPosition({bottom: 10, right: 11});
473487
assert.equal(root.style.bottom, '10px');
474488
assert.equal(root.style.right, '11px');
475489
});
@@ -479,3 +493,35 @@ test('adapter#setMaxHeight sets the maxHeight style on the menu surface element'
479493
component.getDefaultFoundation().adapter_.setMaxHeight('100px');
480494
assert.equal(root.style.maxHeight, '100px');
481495
});
496+
497+
test('Pressing Shift+Tab on first element focuses the last menu surface element', () => {
498+
const root = getFixture(true);
499+
document.body.appendChild(root);
500+
const firstItem = root.querySelectorAll(strings.FOCUSABLE_ELEMENTS)[0];
501+
const lastItem = root.querySelectorAll(strings.FOCUSABLE_ELEMENTS)[1];
502+
const component = new MDCMenuSurface(root);
503+
component.open = true;
504+
505+
firstItem.focus();
506+
component.getDefaultFoundation().handleKeydown({key: 'Tab', shiftKey: true, preventDefault: () => {}});
507+
assert.equal(document.activeElement, lastItem);
508+
509+
component.open = false;
510+
document.body.removeChild(root);
511+
});
512+
513+
test('Pressing Tab on last element focuses the first menu surface element', () => {
514+
const root = getFixture(true);
515+
document.body.appendChild(root);
516+
const firstItem = root.querySelectorAll(strings.FOCUSABLE_ELEMENTS)[0];
517+
const lastItem = root.querySelectorAll(strings.FOCUSABLE_ELEMENTS)[1];
518+
const component = new MDCMenuSurface(root);
519+
component.open = true;
520+
521+
lastItem.focus();
522+
component.getDefaultFoundation().handleKeydown({key: 'Tab', shiftKey: false, preventDefault: () => {}});
523+
assert.equal(document.activeElement, firstItem);
524+
525+
component.open = false;
526+
document.body.removeChild(root);
527+
});

0 commit comments

Comments
 (0)