@@ -37,52 +37,53 @@ class DetailsMenuElement extends HTMLElement {
37
37
if ( ! summary . hasAttribute ( 'role' ) ) summary . setAttribute ( 'role' , 'button' )
38
38
}
39
39
40
- this . addEventListener ( 'compositionstart' , trackComposition )
41
- this . addEventListener ( 'compositionend' , trackComposition )
42
- details . addEventListener ( 'click' , shouldCommit )
43
- details . addEventListener ( 'change' , shouldCommit )
44
- details . addEventListener ( 'keydown' , keydown )
45
- details . addEventListener ( 'toggle' , loadFragment , { once : true } )
46
- details . addEventListener ( 'toggle' , closeCurrentMenu )
47
- if ( this . preload ) {
48
- details . addEventListener ( 'mouseover' , loadFragment , { once : true } )
49
- }
50
-
51
- const subscriptions = [ focusOnOpen ( details ) ]
52
- states . set ( this , { details, subscriptions, loaded : false , isComposing : false } )
40
+ const subscriptions = [
41
+ fromEvent ( details , 'compositionstart' , e => trackComposition ( this , e ) ) ,
42
+ fromEvent ( details , 'compositionend' , e => trackComposition ( this , e ) ) ,
43
+ fromEvent ( details , 'click' , e => shouldCommit ( details , this , e ) ) ,
44
+ fromEvent ( details , 'change' , e => shouldCommit ( details , this , e ) ) ,
45
+ fromEvent ( details , 'keydown' , e => keydown ( details , this , e ) ) ,
46
+ fromEvent ( details , 'toggle' , ( ) => loadFragment ( details , this ) , { once : true } ) ,
47
+ fromEvent ( details , 'toggle' , ( ) => closeCurrentMenu ( details ) ) ,
48
+ this . preload
49
+ ? fromEvent ( details , 'mouseover' , ( ) => loadFragment ( details , this ) , { once : true } )
50
+ : NullSubscription ,
51
+ ...focusOnOpen ( details )
52
+ ]
53
+
54
+ states . set ( this , { subscriptions, loaded : false , isComposing : false } )
53
55
}
54
56
55
57
disconnectedCallback ( ) {
56
58
const state = states . get ( this )
57
59
if ( ! state ) return
58
-
59
60
states . delete ( this )
60
-
61
- const { details, subscriptions} = state
62
- for ( const sub of subscriptions ) {
61
+ for ( const sub of state . subscriptions ) {
63
62
sub . unsubscribe ( )
64
63
}
65
-
66
- this . addEventListener ( 'compositionstart' , trackComposition )
67
- this . addEventListener ( 'compositionend' , trackComposition )
68
- details . removeEventListener ( 'click' , shouldCommit )
69
- details . removeEventListener ( 'change' , shouldCommit )
70
- details . removeEventListener ( 'keydown' , keydown )
71
- details . removeEventListener ( 'toggle' , loadFragment , { once : true } )
72
- details . removeEventListener ( 'toggle' , closeCurrentMenu )
73
- details . removeEventListener ( 'mouseover' , loadFragment , { once : true } )
74
64
}
75
65
}
76
66
77
67
const states = new WeakMap ( )
78
68
79
- function loadFragment ( event : Event ) {
80
- const details = event . currentTarget
81
- if ( ! ( details instanceof Element ) ) return
69
+ type Subscription = { unsubscribe ( ) : void }
70
+ const NullSubscription = { unsubscribe ( ) { } }
82
71
83
- const menu = details . querySelector ( 'details-menu' )
84
- if ( ! menu ) return
72
+ function fromEvent (
73
+ target : EventTarget ,
74
+ eventName : string ,
75
+ onNext : EventHandler ,
76
+ options : EventListenerOptionsOrUseCapture = false
77
+ ) : Subscription {
78
+ target . addEventListener ( eventName , onNext , options )
79
+ return {
80
+ unsubscribe : ( ) => {
81
+ target . removeEventListener ( eventName , onNext , options )
82
+ }
83
+ }
84
+ }
85
85
86
+ function loadFragment ( details : Element , menu : DetailsMenuElement ) {
86
87
const src = menu . getAttribute ( 'src' )
87
88
if ( ! src ) return
88
89
@@ -99,7 +100,7 @@ function loadFragment(event: Event) {
99
100
}
100
101
}
101
102
102
- function focusOnOpen ( details : Element ) {
103
+ function focusOnOpen ( details : Element ) : Array < Subscription > {
103
104
let isMouse = false
104
105
const onmousedown = ( ) => ( isMouse = true )
105
106
const onkeydown = ( ) => ( isMouse = false )
@@ -109,27 +110,19 @@ function focusOnOpen(details: Element) {
109
110
if ( ! isMouse ) focusFirstItem ( details )
110
111
}
111
112
112
- details . addEventListener ( 'mousedown' , onmousedown )
113
- details . addEventListener ( 'keydown' , onkeydown )
114
- details . addEventListener ( 'toggle' , ontoggle )
115
-
116
- return {
117
- unsubscribe : ( ) => {
118
- details . removeEventListener ( 'mousedown' , onmousedown )
119
- details . removeEventListener ( 'keydown' , onkeydown )
120
- details . removeEventListener ( 'toggle' , ontoggle )
121
- }
122
- }
113
+ return [
114
+ fromEvent ( details , 'mousedown' , onmousedown ) ,
115
+ fromEvent ( details , 'keydown' , onkeydown ) ,
116
+ fromEvent ( details , 'toggle' , ontoggle )
117
+ ]
123
118
}
124
119
125
- function closeCurrentMenu ( event : Event ) {
126
- const el = event . currentTarget
127
- if ( ! ( el instanceof Element ) ) return
128
- if ( ! el . hasAttribute ( 'open' ) ) return
120
+ function closeCurrentMenu ( details : Element ) {
121
+ if ( ! details . hasAttribute ( 'open' ) ) return
129
122
130
123
for ( const menu of document . querySelectorAll ( 'details[open] > details-menu' ) ) {
131
124
const opened = menu . closest ( 'details' )
132
- if ( opened && opened !== el && ! opened . contains ( el ) ) {
125
+ if ( opened && opened !== details && ! opened . contains ( details ) ) {
133
126
opened . removeAttribute ( 'open' )
134
127
}
135
128
}
@@ -168,13 +161,10 @@ function sibling(details: Element, next: boolean): ?HTMLElement {
168
161
169
162
const ctrlBindings = navigator . userAgent . match ( / M a c i n t o s h / )
170
163
171
- function shouldCommit ( event : Event ) {
164
+ function shouldCommit ( details : Element , menu : DetailsMenuElement , event : Event ) {
172
165
const target = event . target
173
166
if ( ! ( target instanceof Element ) ) return
174
167
175
- const details = event . currentTarget
176
- if ( ! ( details instanceof Element ) ) return
177
-
178
168
// Ignore clicks from nested details.
179
169
if ( target . closest ( 'details' ) !== details ) return
180
170
@@ -224,16 +214,11 @@ function commit(selected: Element, details: Element) {
224
214
)
225
215
}
226
216
227
- function keydown ( event : KeyboardEvent ) {
228
- const details = event . currentTarget
229
- if ( ! ( details instanceof Element ) ) return
230
- const menu = details . querySelector ( 'details-menu' )
231
- if ( ! menu ) return
217
+ function keydown ( details : Element , menu : DetailsMenuElement , event : Event ) {
218
+ if ( ! ( event instanceof KeyboardEvent ) ) return
232
219
const state = states . get ( menu )
233
220
if ( ! state ) return
234
- const { isComposing} = state
235
- if ( isComposing ) return
236
-
221
+ if ( state . isComposing ) return
237
222
const isSummaryFocused = event . target instanceof Element && event . target . tagName === 'SUMMARY'
238
223
239
224
// Ignore key presses from nested details.
@@ -338,8 +323,8 @@ function labelHTML(el: ?Element): ?string {
338
323
return contentsEl ? contentsEl . innerHTML : null
339
324
}
340
325
341
- function trackComposition ( event : Event ) {
342
- const state = states . get ( event . currentTarget )
326
+ function trackComposition ( menu , event : Event ) {
327
+ const state = states . get ( menu )
343
328
if ( ! state ) return
344
329
state . isComposing = event . type === 'compositionstart'
345
330
}
0 commit comments