Skip to content

Commit 0876179

Browse files
authored
Merge pull request #42 from github/event-subscriptions
Event subscriptions
2 parents 7629135 + 82ac984 commit 0876179

File tree

1 file changed

+42
-50
lines changed

1 file changed

+42
-50
lines changed

index.js

Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -37,47 +37,51 @@ class DetailsMenuElement extends HTMLElement {
3737
if (!summary.hasAttribute('role')) summary.setAttribute('role', 'button')
3838
}
3939

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})
5153
}
5254

5355
disconnectedCallback() {
5456
const state = states.get(this)
5557
if (!state) return
56-
5758
states.delete(this)
58-
59-
const {details, subscriptions} = state
60-
for (const sub of subscriptions) {
59+
for (const sub of state.subscriptions) {
6160
sub.unsubscribe()
6261
}
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})
6962
}
7063
}
7164

7265
const states = new WeakMap()
7366

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() {}}
7769

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+
}
8083

84+
function loadFragment(details: Element, menu: DetailsMenuElement) {
8185
const src = menu.getAttribute('src')
8286
if (!src) return
8387

@@ -94,7 +98,7 @@ function loadFragment(event: Event) {
9498
}
9599
}
96100

97-
function focusOnOpen(details: Element) {
101+
function focusOnOpen(details: Element): Array<Subscription> {
98102
let isMouse = false
99103
const onmousedown = () => (isMouse = true)
100104
const onkeydown = () => (isMouse = false)
@@ -104,27 +108,19 @@ function focusOnOpen(details: Element) {
104108
if (!isMouse) focusFirstItem(details)
105109
}
106110

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+
]
118116
}
119117

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
124120

125121
for (const menu of document.querySelectorAll('details[open] > details-menu')) {
126122
const opened = menu.closest('details')
127-
if (opened && opened !== el && !opened.contains(el)) {
123+
if (opened && opened !== details && !opened.contains(details)) {
128124
opened.removeAttribute('open')
129125
}
130126
}
@@ -163,13 +159,10 @@ function sibling(details: Element, next: boolean): ?HTMLElement {
163159

164160
const ctrlBindings = navigator.userAgent.match(/Macintosh/)
165161

166-
function shouldCommit(event: Event) {
162+
function shouldCommit(details: Element, menu: DetailsMenuElement, event: Event) {
167163
const target = event.target
168164
if (!(target instanceof Element)) return
169165

170-
const details = event.currentTarget
171-
if (!(details instanceof Element)) return
172-
173166
// Ignore clicks from nested details.
174167
if (target.closest('details') !== details) return
175168

@@ -219,9 +212,8 @@ function commit(selected: Element, details: Element) {
219212
)
220213
}
221214

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
225217
const isSummaryFocused = event.target instanceof Element && event.target.tagName === 'SUMMARY'
226218

227219
// Ignore key presses from nested details.

0 commit comments

Comments
 (0)