Skip to content

Commit ff406ca

Browse files
authored
Merge pull request #402 from rackerlabs/surf-1497-refactor-menus-to-use-mixins
refactor(hx-menu): surf-1497 use positionable mixin
2 parents 320251b + ff11e04 commit ff406ca

File tree

2 files changed

+13
-172
lines changed

2 files changed

+13
-172
lines changed

docs/elements/hx-menu/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<ul class="hxList">
3636
<li><code>close</code></li>
3737
<li><code>open</code></li>
38+
<li><code>reposition</code></li>
3839
</ul>
3940
</dd>
4041
</div>
@@ -44,10 +45,10 @@
4445

4546
{% block attributes %}
4647
<dl>
47-
<dt>open</dt>
48+
<dt>open <i>(optional)</i></dt>
4849
<dd>Opens the menu</dd>
4950

50-
<dt>position</dt>
51+
<dt>position <i>(optional)</i></dt>
5152
<dd>Positions the menu</dd>
5253

5354
<dt>relative-to <i>(optional)</i></dt>
Lines changed: 10 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,193 +1,33 @@
11
import { HXElement } from './HXElement';
2-
import { getPosition } from '../utils/position';
3-
import debounce from 'lodash/debounce';
42

5-
const DEFAULT_POSITION = 'bottom-start';
3+
import { mix } from '../utils';
4+
import { Positionable } from '../mixins/Positionable';
65

7-
/**
8-
* Fires when the element is concealed.
9-
*
10-
* @event Menu:close
11-
* @since 0.6.0
12-
* @type {CustomEvent}
13-
*/
14-
15-
/**
16-
* Fires when the element is revealed.
17-
*
18-
* @event Menu:open
19-
* @since 0.6.0
20-
* @type {CustomEvent}
21-
*/
6+
class _ProtoClass extends mix(HXElement, Positionable) {}
227

238
/**
249
* Defines behavior for the `<hx-menu>` element.
2510
*
26-
* @emits Menu:close
27-
* @emits Menu:open
2811
* @extends HXElement
12+
* @extends Positionable
2913
* @hideconstructor
3014
* @since 0.2.0
3115
*/
32-
export class HXMenuElement extends HXElement {
16+
export class HXMenuElement extends _ProtoClass {
3317
static get is () {
3418
return 'hx-menu';
3519
}
3620

21+
/** @override */
3722
$onCreate () {
38-
this._onDocumentClick = this._onDocumentClick.bind(this);
39-
this._onDocumentScroll = this._onDocumentScroll.bind(this);
40-
this._reposition = this._reposition.bind(this);
41-
42-
this._onWindowResize = debounce(this._reposition, 50);
23+
super.$onCreate();
24+
this.DEFAULT_POSITION = 'bottom-start';
4325
}
4426

27+
/** @override */
4528
$onConnect () {
46-
this.$upgradeProperty('open');
47-
this.$upgradeProperty('position');
48-
this.$upgradeProperty('relativeTo');
49-
50-
this.$defaultAttribute('position', DEFAULT_POSITION);
29+
super.$onConnect();
5130
this.$defaultAttribute('role', 'menu');
52-
53-
this.setAttribute('aria-hidden', !this.open);
5431
this.setAttribute('aria-expanded', this.open);
5532
}
56-
57-
static get $observedAttributes () {
58-
return [ 'open' ];
59-
}
60-
61-
$onAttributeChange (attr, oldVal, newVal) {
62-
if (attr === 'open') {
63-
this._attrOpenChange(oldVal, newVal);
64-
}
65-
}
66-
67-
/**
68-
* External element that controls menu visibility.
69-
* This is commonly a `<hx-disclosure>`.
70-
*
71-
* @readonly
72-
* @type {HTMLElement}
73-
*/
74-
get controlElement () {
75-
return this.getRootNode().querySelector(`[aria-controls="${this.id}"]`);
76-
}
77-
78-
/**
79-
* Determines if the menu is revealed.
80-
*
81-
* @default false
82-
* @type {Boolean}
83-
*/
84-
get open () {
85-
return this.hasAttribute('open');
86-
}
87-
set open (value) {
88-
if (value) {
89-
this.setAttribute('open', '');
90-
} else {
91-
this.removeAttribute('open');
92-
}
93-
}
94-
95-
// TODO: Need to re-evaluate how we handle positioning when scrolling
96-
/**
97-
* Where to position the open menu in relation to its reference element.
98-
*
99-
* @default 'bottom-start'
100-
* @type {PositionString}
101-
*/
102-
get position () {
103-
return this.getAttribute('position') || DEFAULT_POSITION;
104-
}
105-
set position (value) {
106-
this.setAttribute('position', value);
107-
}
108-
109-
/**
110-
* Reference element used to calculate open menu position.
111-
*
112-
* @readonly
113-
* @type {HTMLElement}
114-
*/
115-
get relativeElement () {
116-
if (this.relativeTo) {
117-
return this.getRootNode().getElementById(this.relativeTo);
118-
} else {
119-
return this.controlElement;
120-
}
121-
}
122-
123-
/**
124-
* ID of the element to position the menu.
125-
*
126-
* @type {String}
127-
*/
128-
get relativeTo () {
129-
return this.getAttribute('relative-to');
130-
}
131-
set relativeTo (value) {
132-
this.setAttribute('relative-to', value);
133-
}
134-
135-
/** @private */
136-
_addOpenListeners () {
137-
document.addEventListener('click', this._onDocumentClick);
138-
document.addEventListener('scroll', this._onDocumentScroll);
139-
window.addEventListener('resize', this._onWindowResize);
140-
}
141-
142-
/** @private */
143-
_attrOpenChange (oldVal, newVal) {
144-
let isOpen = (newVal !== null);
145-
this.setAttribute('aria-hidden', !isOpen);
146-
this.setAttribute('aria-expanded', isOpen);
147-
this.$emit(isOpen ? 'open' : 'close');
148-
149-
if (isOpen) {
150-
this._addOpenListeners();
151-
this._reposition();
152-
} else {
153-
this._removeOpenListeners();
154-
}
155-
}
156-
157-
/** @private */
158-
_onDocumentClick (evt) {
159-
let isDescendant = this.contains(evt.target);
160-
let withinControl = this.controlElement.contains(evt.target);
161-
let isBackground = (!isDescendant && !withinControl);
162-
163-
if (this.open && isBackground) {
164-
this.open = false;
165-
}
166-
}
167-
168-
/** @private */
169-
_onDocumentScroll () {
170-
this._reposition();
171-
}
172-
173-
/** @private */
174-
_removeOpenListeners () {
175-
document.removeEventListener('click', this._onDocumentClick);
176-
document.removeEventListener('scroll', this._onDocumentScroll);
177-
window.removeEventListener('resize', this._onWindowResize);
178-
}
179-
180-
/** @private */
181-
_reposition () {
182-
if (this.relativeElement) {
183-
let { x, y } = getPosition({
184-
element: this,
185-
reference: this.relativeElement,
186-
position: this.position,
187-
});
188-
189-
this.style.top = `${y}px`;
190-
this.style.left = `${x}px`;
191-
}
192-
}
19333
}

0 commit comments

Comments
 (0)