Skip to content

Commit 96af81b

Browse files
authored
Merge pull request #368 from rackerlabs/surf-1417-hx-search
feat(hx-search-assistance): implement new positioning logic
2 parents a09aed0 + 68ac610 commit 96af81b

File tree

2 files changed

+147
-37
lines changed

2 files changed

+147
-37
lines changed

docs/elements/hx-search-assistance/index.html

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,48 @@
5555

5656
{% block properties %}
5757
<dl>
58-
<dt>open</dt>
59-
<dd>Opens the search assistance</dd>
58+
<dt>controlElement (HTMLElement)</dt>
59+
<dd>
60+
<p>
61+
<i>(read-only)</i>
62+
Returns the HTML element with the <code>aria-controls</code>
63+
attribute that matches the <code>&lt;hx-search&gt;</code> id.
64+
</p>
65+
</dd>
6066

61-
<dt>position {String="bottom-start"}</dt>
62-
<dd>Positions the search assistance</dd>
67+
<dt>open (Boolean [false])</dt>
68+
<dd>
69+
<p>
70+
Manipulates the <code>open</code> attribute. Opens the search
71+
assistance.
72+
</p>
73+
</dd>
6374

64-
<dt>relativeTo (optional)</dt>
65-
<dd>ID of an element that the search assistance is positioned relative to.</dd>
75+
<dt>position (String [bottom-start])</dt>
76+
<dd>
77+
<p>
78+
Manipulates the <code>position</code> attribute.
79+
</p>
80+
</dd>
81+
82+
<dt>relativeElement (HTMLElement)</dt>
83+
<dd>
84+
<p class="comfortable">
85+
<i>(read-only)</i>
86+
Returns the HTML element used as a reference to calculate the position of
87+
the visible search assistance. If the
88+
<code>relative-to</code> attribute is set, this will be the element with
89+
ID matching the <code>relativeTo</code> property value. Otherwise, this
90+
will be the <code>controlElement</code>.
91+
</p>
92+
</dd>
93+
94+
<dt>relativeTo (String)</dt>
95+
<dd>
96+
<p>
97+
Manipulates the <code>relative-to</code> attribute. ID of an element
98+
that the search assistance is positioned relative to.
99+
</p>
100+
</dd>
66101
</dl>
67102
{% endblock %}
Lines changed: 106 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { HXElement } from './HXElement';
22
import { getPosition } from '../utils/position';
3-
import { onScroll } from '../utils';
3+
import debounce from 'lodash/debounce';
4+
5+
const DEFAULT_POSITION = 'bottom-start';
46

57
/**
68
* Fires when the element's contents are concealed.
@@ -33,16 +35,22 @@ export class HXSearchAssistanceElement extends HXElement {
3335
return 'hx-search-assistance';
3436
}
3537

38+
$onCreate () {
39+
this._onDocumentScroll = this._onDocumentScroll.bind(this);
40+
this._reposition = this._reposition.bind(this);
41+
42+
this._onWindowResize = debounce(this._reposition, 50);
43+
}
44+
3645
$onConnect () {
3746
this.$upgradeProperty('open');
3847
this.$upgradeProperty('position');
3948
this.$upgradeProperty('relativeTo');
40-
this.$defaultAttribute('position', 'bottom-start');
41-
this.addEventListener('scroll', onScroll);
42-
}
4349

44-
$onDisconnect () {
45-
this.removeEventListener('scroll', onScroll);
50+
this.$defaultAttribute('position', DEFAULT_POSITION);
51+
this._initialPosition = this.position;
52+
53+
this.setAttribute('aria-hidden', !this.open);
4654
}
4755

4856
static get $observedAttributes () {
@@ -51,11 +59,46 @@ export class HXSearchAssistanceElement extends HXElement {
5159

5260
$onAttributeChange (attr, oldVal, newVal) {
5361
if (attr === 'open') {
54-
let isOpen = (newVal !== null);
55-
this.$emit(isOpen ? 'open' : 'close');
62+
this._attrOpenChange(oldVal, newVal);
5663
}
5764
}
5865

66+
/**
67+
* External element that controls <hx-search-assistance> visibility.
68+
*
69+
* @readonly
70+
* @type {HTMLElement}
71+
*/
72+
get controlElement () {
73+
return this.getRootNode().querySelector(`[aria-controls="${this.id}"]`);
74+
}
75+
76+
/**
77+
* Determine if <hx-search-assistance> is visible.
78+
*
79+
* @default false
80+
* @type {Boolean}
81+
*/
82+
get open () {
83+
return this.hasAttribute('open');
84+
}
85+
set open (value) {
86+
if (value) {
87+
this.setAttribute('open', '');
88+
} else {
89+
this.removeAttribute('open');
90+
}
91+
}
92+
93+
/**
94+
* Where to position <hx-search-assistance> in relation to its reference element.
95+
*
96+
* @default 'bottom-start'
97+
* @type {PositionString}
98+
*/
99+
get position () {
100+
return this.getAttribute('position') || DEFAULT_POSITION;
101+
}
59102
set position (value) {
60103
if (value) {
61104
this.setAttribute('position', value);
@@ -64,43 +107,75 @@ export class HXSearchAssistanceElement extends HXElement {
64107
}
65108
}
66109

67-
get position () {
68-
return this.getAttribute('position');
110+
/**
111+
* Reference element used to calculate <hx-search-assistance> position.
112+
*
113+
* @readonly
114+
* @type {HTMLElement}
115+
*/
116+
get relativeElement () {
117+
if (this.relativeTo) {
118+
return this.getRootNode().getElementById(this.relativeTo);
119+
} else {
120+
return this.controlElement;
121+
}
69122
}
70123

124+
/**
125+
* ID of an element relatively to <hx-search-assistance>.
126+
*
127+
* @type {String}
128+
*/
129+
get relativeTo () {
130+
return this.getAttribute('relative-to');
131+
}
71132
set relativeTo (value) {
72133
this.setAttribute('relative-to', value);
73134
}
74135

75-
get relativeTo () {
76-
return this.getAttribute('relative-to');
136+
/** @private */
137+
_addOpenListeners () {
138+
document.addEventListener('scroll', this._onDocumentScroll);
139+
window.addEventListener('resize', this._onWindowResize);
77140
}
78141

79-
get relativeElement () {
80-
return this.getRootNode().getElementById(this.relativeTo);
81-
}
142+
/** @private */
143+
_attrOpenChange (oldVal, newVal) {
144+
let isOpen = (newVal !== null);
145+
this.setAttribute('aria-hidden', !isOpen);
146+
this.$emit(isOpen ? 'open' : 'close');
82147

83-
set open (value) {
84-
if (value) {
85-
this.setAttribute('open', '');
86-
this._setPosition();
148+
if (isOpen) {
149+
this._addOpenListeners();
150+
this._reposition();
87151
} else {
88-
this.removeAttribute('open');
152+
this._removeOpenListeners();
153+
this.position = this._initialPosition;
89154
}
90155
}
91156

92-
get open () {
93-
return this.hasAttribute('open');
157+
/** @private */
158+
_onDocumentScroll () {
159+
this._reposition();
160+
}
161+
162+
/** @private */
163+
_removeOpenListeners () {
164+
document.removeEventListener('scroll', this._onDocumentScroll);
165+
window.removeEventListener('resize', this._onWindowResize);
94166
}
95167

96-
_setPosition () {
97-
let offset = getPosition({
98-
element: this,
99-
reference: this.relativeElement,
100-
position: this.position,
101-
margin: 4,
102-
});
103-
this.style.top = `${offset.y}px`;
104-
this.style.left = `${offset.x}px`;
168+
/** @private */
169+
_reposition () {
170+
if (this.relativeElement) {
171+
let { x, y } = getPosition({
172+
element: this,
173+
reference: this.relativeElement,
174+
position: this.position,
175+
});
176+
177+
this.style.top = `${y}px`;
178+
this.style.left = `${x}px`;
179+
}
105180
}
106181
}

0 commit comments

Comments
 (0)