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