Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Event subscriptions #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 12, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 42 additions & 50 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,47 +37,51 @@ class DetailsMenuElement extends HTMLElement {
if (!summary.hasAttribute('role')) summary.setAttribute('role', 'button')
}

details.addEventListener('click', shouldCommit)
details.addEventListener('change', shouldCommit)
details.addEventListener('keydown', keydown)
details.addEventListener('toggle', loadFragment, {once: true})
details.addEventListener('toggle', closeCurrentMenu)
if (this.preload) {
details.addEventListener('mouseover', loadFragment, {once: true})
}

const subscriptions = [focusOnOpen(details)]
states.set(this, {details, subscriptions, loaded: false})
const subscriptions = [
fromEvent(details, 'click', e => shouldCommit(details, this, e)),
fromEvent(details, 'change', e => shouldCommit(details, this, e)),
fromEvent(details, 'keydown', e => keydown(details, this, e)),
fromEvent(details, 'toggle', () => loadFragment(details, this), {once: true}),
fromEvent(details, 'toggle', () => closeCurrentMenu(details)),
this.preload
? fromEvent(details, 'mouseover', () => loadFragment(details, this), {once: true})
: NullSubscription,
...focusOnOpen(details)
]

states.set(this, {subscriptions, loaded: false})
}

disconnectedCallback() {
const state = states.get(this)
if (!state) return

states.delete(this)

const {details, subscriptions} = state
for (const sub of subscriptions) {
for (const sub of state.subscriptions) {
sub.unsubscribe()
}
details.removeEventListener('click', shouldCommit)
details.removeEventListener('change', shouldCommit)
details.removeEventListener('keydown', keydown)
details.removeEventListener('toggle', loadFragment, {once: true})
details.removeEventListener('toggle', closeCurrentMenu)
details.removeEventListener('mouseover', loadFragment, {once: true})
}
}

const states = new WeakMap()

function loadFragment(event: Event) {
const details = event.currentTarget
if (!(details instanceof Element)) return
type Subscription = {unsubscribe(): void}
const NullSubscription = {unsubscribe() {}}

const menu = details.querySelector('details-menu')
if (!menu) return
function fromEvent(
target: EventTarget,
eventName: string,
onNext: EventHandler,
options: EventListenerOptionsOrUseCapture = false
): Subscription {
target.addEventListener(eventName, onNext, options)
return {
unsubscribe: () => {
target.removeEventListener(eventName, onNext, options)
}
}
}

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

Expand All @@ -94,7 +98,7 @@ function loadFragment(event: Event) {
}
}

function focusOnOpen(details: Element) {
function focusOnOpen(details: Element): Array<Subscription> {
let isMouse = false
const onmousedown = () => (isMouse = true)
const onkeydown = () => (isMouse = false)
Expand All @@ -104,27 +108,19 @@ function focusOnOpen(details: Element) {
if (!isMouse) focusFirstItem(details)
}

details.addEventListener('mousedown', onmousedown)
details.addEventListener('keydown', onkeydown)
details.addEventListener('toggle', ontoggle)

return {
unsubscribe: () => {
details.removeEventListener('mousedown', onmousedown)
details.removeEventListener('keydown', onkeydown)
details.removeEventListener('toggle', ontoggle)
}
}
return [
fromEvent(details, 'mousedown', onmousedown),
fromEvent(details, 'keydown', onkeydown),
fromEvent(details, 'toggle', ontoggle)
]
}

function closeCurrentMenu(event: Event) {
const el = event.currentTarget
if (!(el instanceof Element)) return
if (!el.hasAttribute('open')) return
function closeCurrentMenu(details: Element) {
if (!details.hasAttribute('open')) return

for (const menu of document.querySelectorAll('details[open] > details-menu')) {
const opened = menu.closest('details')
if (opened && opened !== el && !opened.contains(el)) {
if (opened && opened !== details && !opened.contains(details)) {
opened.removeAttribute('open')
}
}
Expand Down Expand Up @@ -163,13 +159,10 @@ function sibling(details: Element, next: boolean): ?HTMLElement {

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

function shouldCommit(event: Event) {
function shouldCommit(details: Element, menu: DetailsMenuElement, event: Event) {
const target = event.target
if (!(target instanceof Element)) return

const details = event.currentTarget
if (!(details instanceof Element)) return

// Ignore clicks from nested details.
if (target.closest('details') !== details) return

Expand Down Expand Up @@ -219,9 +212,8 @@ function commit(selected: Element, details: Element) {
)
}

function keydown(event: KeyboardEvent) {
const details = event.currentTarget
if (!(details instanceof Element)) return
function keydown(details: Element, menu: DetailsMenuElement, event: Event) {
if (!(event instanceof KeyboardEvent)) return
const isSummaryFocused = event.target instanceof Element && event.target.tagName === 'SUMMARY'

// Ignore key presses from nested details.
Expand Down