11import { HXElement } from './HXElement' ;
22import { 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