refactor: use ids instead of attrs for delegate/shortcut/focus (#1035)

* refactor: use ids instead of attrs for delegate/shortcut/focus

fixes #1034

* console log on error

* fix test
This commit is contained in:
Nolan Lawson 2019-02-23 12:32:00 -08:00 committed by GitHub
parent c9ca605cfe
commit 547ee14f88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 125 additions and 114 deletions

View file

@ -1,4 +1,3 @@
{#if delegateKey}
<button type="button" <button type="button"
title={label} title={label}
aria-label={label} aria-label={label}
@ -6,27 +5,12 @@
aria-hidden={ariaHidden} aria-hidden={ariaHidden}
class={computedClass} class={computedClass}
{disabled} {disabled}
delegate-key={delegateKey} ref:node
focus-key={focusKey || ''} > >
<svg class="icon-button-svg {svgClassName || ''}" ref:svg> <svg class="icon-button-svg {svgClassName || ''}" ref:svg>
<use xlink:href={href} /> <use xlink:href={href} />
</svg> </svg>
</button> </button>
{:else}
<button type="button"
title={label}
aria-label={label}
aria-pressed={pressable ? !!pressed : void 0}
aria-hidden={ariaHidden}
class={computedClass}
focus-key={focusKey || ''}
{disabled}
on:click >
<svg class="icon-button-svg {svgClassName || ''}" ref:svg>
<use xlink:href={href} />
</svg>
</button>
{/if}
<style> <style>
.icon-button { .icon-button {
padding: 6px 10px; padding: 6px 10px;
@ -110,18 +94,34 @@
import { animate } from '../_utils/animate' import { animate } from '../_utils/animate'
export default { export default {
oncreate () {
let { clickListener, elementId } = this.get()
if (clickListener) {
this.onClick = this.onClick.bind(this)
this.refs.node.addEventListener('click', this.onClick)
}
if (elementId) {
this.refs.node.setAttribute('id', elementId)
}
},
ondestroy () {
let { clickListener } = this.get()
if (clickListener) {
this.refs.node.removeEventListener('click', this.onClick)
}
},
data: () => ({ data: () => ({
big: false, big: false,
muted: false, muted: false,
disabled: false, disabled: false,
svgClassName: void 0, svgClassName: void 0,
focusKey: void 0, elementId: void 0,
pressable: false, pressable: false,
pressed: false, pressed: false,
className: void 0, className: void 0,
delegateKey: void 0,
sameColorWhenPressed: false, sameColorWhenPressed: false,
ariaHidden: false ariaHidden: false,
clickListener: true
}), }),
store: () => store, store: () => store,
computed: { computed: {
@ -145,6 +145,9 @@
} }
let svg = this.refs.svg let svg = this.refs.svg
animate(svg, animation) animate(svg, animation)
},
onClick (e) {
this.fire('click', e)
} }
} }
} }

View file

@ -12,8 +12,8 @@
const VISIBILITY_CHECK_DELAY_MS = 600 const VISIBILITY_CHECK_DELAY_MS = 600
const keyToElement = key => document.querySelector(`[shortcut-key=${JSON.stringify(key)}]`) const keyToElement = key => document.getElementById(key)
const elementToKey = element => element.getAttribute('shortcut-key') const elementToKey = element => element.getAttribute('id')
const scope = 'global' const scope = 'global'
export default { export default {
@ -90,7 +90,7 @@
if (!activeElement) { if (!activeElement) {
return null return null
} }
let activeItem = activeElement.getAttribute('shortcut-key') let activeItem = activeElement.getAttribute('id')
if (!activeItem) { if (!activeItem) {
return null return null
} }
@ -109,7 +109,7 @@
preventScroll: true preventScroll: true
}) })
} catch (err) { } catch (err) {
console.error(err) console.error('Ignored focus error', err)
} }
} }
} }

View file

@ -1,8 +1,8 @@
{#if type === 'video'} {#if type === 'video'}
<button type="button" <button id={elementId}
type="button"
class="play-video-button {$largeInlineMedia ? '' : 'fixed-size'}" class="play-video-button {$largeInlineMedia ? '' : 'fixed-size'}"
aria-label="Play video: {description}" aria-label="Play video: {description}"
delegate-key={delegateKey}
style="width: {inlineWidth}px; height: {inlineHeight}px;"> style="width: {inlineWidth}px; height: {inlineHeight}px;">
<PlayVideoIcon /> <PlayVideoIcon />
<LazyImage <LazyImage
@ -17,11 +17,11 @@
/> />
</button> </button>
{:else} {:else}
<button type="button" <button id={elementId}
type="button"
class="show-image-button {$largeInlineMedia ? '' : 'fixed-size'}" class="show-image-button {$largeInlineMedia ? '' : 'fixed-size'}"
aria-label="Show image: {description}" aria-label="Show image: {description}"
title={description} title={description}
delegate-key={delegateKey}
on:mouseover="set({mouseover: event})" on:mouseover="set({mouseover: event})"
style="width: {inlineWidth}px; height: {inlineHeight}px;"> style="width: {inlineWidth}px; height: {inlineHeight}px;">
{#if type === 'gifv' && $autoplayGifs} {#if type === 'gifv' && $autoplayGifs}
@ -90,8 +90,8 @@
export default { export default {
oncreate () { oncreate () {
let { delegateKey } = this.get() let { elementId } = this.get()
registerClickDelegate(this, delegateKey, () => this.onClick()) registerClickDelegate(this, elementId, () => this.onClick())
}, },
computed: { computed: {
focus: ({ meta }) => meta && meta.focus, focus: ({ meta }) => meta && meta.focus,
@ -119,7 +119,7 @@
originalWidth: ({ original }) => original && original.width, originalWidth: ({ original }) => original && original.width,
originalHeight: ({ original }) => original && original.height, originalHeight: ({ original }) => original && original.height,
noNativeWidthHeight: ({ smallWidth, smallHeight }) => typeof smallWidth !== 'number' || typeof smallHeight !== 'number', noNativeWidthHeight: ({ smallWidth, smallHeight }) => typeof smallWidth !== 'number' || typeof smallHeight !== 'number',
delegateKey: ({ media, uuid }) => `media-${uuid}-${media.id}`, elementId: ({ media, uuid }) => `media-${uuid}-${media.id}`,
description: ({ media }) => media.description || '', description: ({ media }) => media.description || '',
previewUrl: ({ media }) => media.preview_url, previewUrl: ({ media }) => media.preview_url,
url: ({ media }) => media.url, url: ({ media }) => media.url,

View file

@ -3,13 +3,12 @@
{status} {notification} {enableShortcuts} on:recalculateHeight {status} {notification} {enableShortcuts} on:recalculateHeight
/> />
{:else} {:else}
<article class={className} <article id={elementId}
class={className}
tabindex="0" tabindex="0"
aria-posinset={index} aria-posinset={index}
aria-setsize={length} aria-setsize={length}
aria-label={ariaLabel} aria-label={ariaLabel}
focus-key={uuid}
shortcut-key={uuid}
on:focus="onFocus()" on:focus="onFocus()"
on:blur="onBlur()" on:blur="onBlur()"
ref:article ref:article
@ -17,8 +16,8 @@
<StatusHeader {notification} {notificationId} {status} {statusId} {timelineType} <StatusHeader {notification} {notificationId} {status} {statusId} {timelineType}
{account} {accountId} {uuid} isStatusInNotification="true" /> {account} {accountId} {uuid} isStatusInNotification="true" />
{#if enableShortcuts} {#if enableShortcuts}
<Shortcut scope={uuid} key="p" on:pressed="openAuthorProfile()" /> <Shortcut scope={shortcutScope} key="p" on:pressed="openAuthorProfile()" />
<Shortcut scope={uuid} key="m" on:pressed="mentionAuthor()" /> <Shortcut scope={shortcutScope} key="m" on:pressed="mentionAuthor()" />
{/if} {/if}
</article> </article>
{/if} {/if}
@ -69,6 +68,8 @@
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => { uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => {
return `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId}/${statusId || ''}` return `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId}/${statusId || ''}`
}, },
elementId: ({ uuid }) => `notification-${uuid}`,
shortcutScope: ({ elementId }) => elementId,
ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => ( ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => (
!status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}` !status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}`
), ),

View file

@ -1,8 +1,6 @@
<article class={className} <article id={elementId}
class={className}
tabindex="0" tabindex="0"
delegate-key={uuid}
focus-key={uuid}
shortcut-key={uuid}
aria-posinset={index} aria-posinset={index}
aria-setsize={length} aria-setsize={length}
aria-label={ariaLabel} aria-label={ariaLabel}
@ -41,9 +39,9 @@
{/if} {/if}
</article> </article>
{#if enableShortcuts} {#if enableShortcuts}
<Shortcut scope={uuid} key="o" on:pressed="open()" /> <Shortcut scope={shortcutScope} key="o" on:pressed="open()" />
<Shortcut scope={uuid} key="p" on:pressed="openAuthorProfile()" /> <Shortcut scope={shortcutScope} key="p" on:pressed="openAuthorProfile()" />
<Shortcut scope={uuid} key="m" on:pressed="mentionAuthor()" /> <Shortcut scope={shortcutScope} key="m" on:pressed="mentionAuthor()" />
{/if} {/if}
<style> <style>
@ -146,11 +144,11 @@
export default { export default {
oncreate () { oncreate () {
let { uuid, isStatusInOwnThread, showContent } = this.get() let { elementId, isStatusInOwnThread, showContent } = this.get()
let { disableTapOnStatus } = this.store.get() let { disableTapOnStatus } = this.store.get()
if (!isStatusInOwnThread && !disableTapOnStatus) { if (!isStatusInOwnThread && !disableTapOnStatus) {
// the whole <article> is clickable in this case // the whole <article> is clickable in this case
registerClickDelegate(this, uuid, (e) => this.onClickOrKeydown(e)) registerClickDelegate(this, elementId, (e) => this.onClickOrKeydown(e))
} }
if (!showContent) { if (!showContent) {
scheduleIdleTask(() => { scheduleIdleTask(() => {
@ -248,6 +246,8 @@
uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => ( uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => (
`${$currentInstance}/${timelineType}/${timelineValue}/${notificationId || ''}/${statusId}` `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId || ''}/${statusId}`
), ),
elementId: ({ uuid }) => `status-${uuid}`,
shortcutScope: ({ elementId }) => elementId,
isStatusInOwnThread: ({ timelineType, timelineValue, originalStatusId }) => ( isStatusInOwnThread: ({ timelineType, timelineValue, originalStatusId }) => (
(timelineType === 'status' || timelineType === 'reply') && timelineValue === originalStatusId (timelineType === 'status' || timelineType === 'reply') && timelineValue === originalStatusId
), ),
@ -297,7 +297,7 @@
account, accountId, uuid, isStatusInNotification, isStatusInOwnThread, account, accountId, uuid, isStatusInNotification, isStatusInOwnThread,
originalAccount, originalAccountId, spoilerShown, visibility, replyShown, originalAccount, originalAccountId, spoilerShown, visibility, replyShown,
replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId, replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId,
createdAtDate, timeagoFormattedDate, enableShortcuts, absoluteFormattedDate }) => ({ createdAtDate, timeagoFormattedDate, enableShortcuts, absoluteFormattedDate, shortcutScope }) => ({
notification, notification,
notificationId, notificationId,
status, status,
@ -321,7 +321,8 @@
createdAtDate, createdAtDate,
timeagoFormattedDate, timeagoFormattedDate,
enableShortcuts, enableShortcuts,
absoluteFormattedDate absoluteFormattedDate,
shortcutScope
}) })
} }
} }

View file

@ -1,8 +1,8 @@
<a class="status-author-name {isStatusInNotification ? 'status-in-notification' : '' } {isStatusInOwnThread ? 'status-in-own-thread' : ''}" <a id={elementId}
class="status-author-name {isStatusInNotification ? 'status-in-notification' : '' } {isStatusInOwnThread ? 'status-in-own-thread' : ''}"
rel="prefetch" rel="prefetch"
href="/accounts/{originalAccountId}" href="/accounts/{originalAccountId}"
title="{'@' + originalAccount.acct}" title="{'@' + originalAccount.acct}"
focus-key={focusKey}
> >
<AccountDisplayName account={originalAccount} /> <AccountDisplayName account={originalAccount} />
</a> </a>
@ -39,7 +39,7 @@
export default { export default {
computed: { computed: {
focusKey: ({ uuid }) => `status-author-name-${uuid}` elementId: ({ uuid }) => `status-author-name-${uuid}`
}, },
components: { components: {
AccountDisplayName AccountDisplayName

View file

@ -94,7 +94,7 @@
for (let tag of tags) { for (let tag of tags) {
if (anchor.getAttribute('href').endsWith(`/tags/${tag.name}`)) { if (anchor.getAttribute('href').endsWith(`/tags/${tag.name}`)) {
anchor.setAttribute('href', `/tags/${tag.name}`) anchor.setAttribute('href', `/tags/${tag.name}`)
anchor.setAttribute('focus-key', `status-content-link-${uuid}-${++count}`) anchor.setAttribute('id', `status-content-link-${uuid}-${++count}`)
anchor.removeAttribute('target') anchor.removeAttribute('target')
anchor.removeAttribute('rel') anchor.removeAttribute('rel')
} }
@ -105,7 +105,7 @@
if (anchor.getAttribute('href') === mention.url) { if (anchor.getAttribute('href') === mention.url) {
anchor.setAttribute('href', `/accounts/${mention.id}`) anchor.setAttribute('href', `/accounts/${mention.id}`)
anchor.setAttribute('title', `@${mention.acct}`) anchor.setAttribute('title', `@${mention.acct}`)
anchor.setAttribute('focus-key', `status-content-link-${uuid}-${++count}`) anchor.setAttribute('id', `status-content-link-${uuid}-${++count}`)
anchor.removeAttribute('target') anchor.removeAttribute('target')
anchor.removeAttribute('rel') anchor.removeAttribute('rel')
} }

View file

@ -12,11 +12,12 @@
Pinned toot Pinned toot
</span> </span>
{:else} {:else}
<a href="/accounts/{accountId}" <a id={elementId}
href="/accounts/{accountId}"
rel="prefetch" rel="prefetch"
class="status-header-author" class="status-header-author"
title="{'@' + account.acct}" title="{'@' + account.acct}"
focus-key={focusKey} > >
<AccountDisplayName {account} /> <AccountDisplayName {account} />
</a> </a>
{/if} {/if}
@ -112,7 +113,7 @@
AccountDisplayName AccountDisplayName
}, },
computed: { computed: {
focusKey: ({ uuid }) => `status-header-${uuid}`, elementId: ({ uuid }) => `status-header-${uuid}`,
icon: ({ notification, status, timelineType }) => { icon: ({ notification, status, timelineType }) => {
if (timelineType === 'pinned') { if (timelineType === 'pinned') {
return '#fa-thumb-tack' return '#fa-thumb-tack'

View file

@ -2,10 +2,10 @@
<div class={computedClass} style={customSize}> <div class={computedClass} style={customSize}>
<div class="status-sensitive-inner-div"> <div class="status-sensitive-inner-div">
{#if sensitiveShown} {#if sensitiveShown}
<button type="button" <button id={elementId}
type="button"
class="status-sensitive-media-button" class="status-sensitive-media-button"
aria-label="Hide sensitive media" aria-label="Hide sensitive media" >
delegate-key={delegateKey} >
<div class="svg-wrapper"> <div class="svg-wrapper">
<svg class="status-sensitive-media-svg"> <svg class="status-sensitive-media-svg">
<use xlink:href="#fa-eye-slash" /> <use xlink:href="#fa-eye-slash" />
@ -14,10 +14,10 @@
</button> </button>
<MediaAttachments {mediaAttachments} {sensitive} {uuid} /> <MediaAttachments {mediaAttachments} {sensitive} {uuid} />
{:else} {:else}
<button type="button" <button id={elementId}
type="button"
class="status-sensitive-media-button" class="status-sensitive-media-button"
aria-label="Show sensitive media" aria-label="Show sensitive media" >
delegate-key={delegateKey} >
<div class="status-sensitive-media-warning"> <div class="status-sensitive-media-warning">
Sensitive content. Click to show. Sensitive content. Click to show.
@ -32,7 +32,7 @@
</div> </div>
</div> </div>
{#if enableShortcuts} {#if enableShortcuts}
<Shortcut scope={uuid} key="y" on:pressed="toggleSensitiveMedia()"/> <Shortcut scope={shortcutScope} key="y" on:pressed="toggleSensitiveMedia()"/>
{/if} {/if}
{:else} {:else}
<MediaAttachments {mediaAttachments} {sensitive} {uuid} /> <MediaAttachments {mediaAttachments} {sensitive} {uuid} />
@ -158,8 +158,8 @@
export default { export default {
oncreate () { oncreate () {
let { delegateKey } = this.get() let { elementId } = this.get()
registerClickDelegate(this, delegateKey, () => this.toggleSensitiveMedia()) registerClickDelegate(this, elementId, () => this.toggleSensitiveMedia())
}, },
components: { components: {
MediaAttachments, MediaAttachments,
@ -177,7 +177,7 @@
sensitive: ({ originalStatus, $markMediaAsSensitive, $neverMarkMediaAsSensitive }) => ( sensitive: ({ originalStatus, $markMediaAsSensitive, $neverMarkMediaAsSensitive }) => (
!$neverMarkMediaAsSensitive && ($markMediaAsSensitive || originalStatus.sensitive) !$neverMarkMediaAsSensitive && ($markMediaAsSensitive || originalStatus.sensitive)
), ),
delegateKey: ({ uuid }) => `sensitive-${uuid}`, elementId: ({ uuid }) => `sensitive-${uuid}`,
customSize: ({ $largeInlineMedia, mediaAttachments }) => { customSize: ({ $largeInlineMedia, mediaAttachments }) => {
if ($largeInlineMedia || !mediaAttachments || mediaAttachments.length < 5) { if ($largeInlineMedia || !mediaAttachments || mediaAttachments.length < 5) {
return '' return ''

View file

@ -7,10 +7,11 @@
&nbsp; &nbsp;
<!-- empty space --> <!-- empty space -->
{/if} {/if}
<a href="/accounts/{mention.id}" <a id="status-mention-link-{uuid}-{mention.id}"
href="/accounts/{mention.id}"
rel="prefetch" rel="prefetch"
title="@{mention.acct}" title="@{mention.acct}"
focus-key="status-mention-link-{uuid}-{mention.id}"> >
@{mention.username} @{mention.username}
</a> </a>
{/each} {/each}

View file

@ -1,7 +1,7 @@
<a class="status-relative-date {isStatusInNotification ? 'status-in-notification' : '' }" <a id={elementId}
class="status-relative-date {isStatusInNotification ? 'status-in-notification' : '' }"
href="/statuses/{originalStatusId}" href="/statuses/{originalStatusId}"
rel="prefetch" rel="prefetch"
focus-key={focusKey}
> >
<time datetime={createdAtDate} title={absoluteFormattedDate} <time datetime={createdAtDate} title={absoluteFormattedDate}
aria-label="{timeagoFormattedDate} click to show thread"> aria-label="{timeagoFormattedDate} click to show thread">
@ -32,7 +32,7 @@
<script> <script>
export default { export default {
computed: { computed: {
focusKey: ({ uuid }) => `status-relative-date-${uuid}` elementId: ({ uuid }) => `status-relative-date-${uuid}`
} }
} }
</script> </script>

View file

@ -1,7 +1,7 @@
<a class="status-sidebar size-{size}" <a id={elementId}
class="status-sidebar size-{size}"
rel="prefetch" rel="prefetch"
href="/accounts/{originalAccountId}" href="/accounts/{originalAccountId}"
focus-key={focusKey}
aria-hidden="true" aria-hidden="true"
> >
<Avatar account={originalAccount} <Avatar account={originalAccount}
@ -37,7 +37,7 @@
Avatar Avatar
}, },
computed: { computed: {
focusKey: ({ uuid }) => `status-author-avatar-${uuid}`, elementId: ({ uuid }) => `status-author-avatar-${uuid}`,
size: ({ isStatusInOwnThread }) => isStatusInOwnThread ? 'medium' : 'small' size: ({ isStatusInOwnThread }) => isStatusInOwnThread ? 'medium' : 'small'
} }
} }

View file

@ -2,12 +2,12 @@
<p>{@html massagedSpoilerText}</p> <p>{@html massagedSpoilerText}</p>
</div> </div>
<div class="status-spoiler-button {isStatusInOwnThread ? 'status-in-own-thread' : ''}"> <div class="status-spoiler-button {isStatusInOwnThread ? 'status-in-own-thread' : ''}">
<button type="button" delegate-key={delegateKey}> <button id={elementId} type="button" >
{spoilerShown ? 'Show less' : 'Show more'} {spoilerShown ? 'Show less' : 'Show more'}
</button> </button>
</div> </div>
{#if enableShortcuts} {#if enableShortcuts}
<Shortcut scope={uuid} key="x" on:pressed="toggleSpoilers()"/> <Shortcut scope={shortcutScope} key="x" on:pressed="toggleSpoilers()"/>
{/if} {/if}
<style> <style>
.status-spoiler { .status-spoiler {
@ -56,8 +56,8 @@
export default { export default {
oncreate () { oncreate () {
let { delegateKey } = this.get() let { elementId } = this.get()
registerClickDelegate(this, delegateKey, () => this.toggleSpoilers()) registerClickDelegate(this, elementId, () => this.toggleSpoilers())
}, },
store: () => store, store: () => store,
components: { components: {
@ -69,7 +69,7 @@
spoilerText = escapeHtml(spoilerText) spoilerText = escapeHtml(spoilerText)
return emojifyText(spoilerText, emojis, $autoplayGifs) return emojifyText(spoilerText, emojis, $autoplayGifs)
}, },
delegateKey: ({ uuid }) => `spoiler-${uuid}` elementId: ({ uuid }) => `spoiler-${uuid}`
}, },
methods: { methods: {
toggleSpoilers () { toggleSpoilers () {

View file

@ -5,8 +5,8 @@
pressable="true" pressable="true"
pressed={replyShown} pressed={replyShown}
href={replyIcon} href={replyIcon}
delegateKey={replyKey} clickListener={false}
focusKey={replyKey} elementId={replyKey}
/> />
<IconButton <IconButton
label={reblogLabel} label={reblogLabel}
@ -14,7 +14,8 @@
pressed={reblogged} pressed={reblogged}
disabled={reblogDisabled} disabled={reblogDisabled}
href={reblogIcon} href={reblogIcon}
delegateKey={reblogKey} clickListener={false}
elementId={reblogKey}
ref:reblogIcon ref:reblogIcon
/> />
<IconButton <IconButton
@ -22,19 +23,21 @@
pressable="true" pressable="true"
pressed={favorited} pressed={favorited}
href="#fa-star" href="#fa-star"
delegateKey={favoriteKey} clickListener={false}
elementId={favoriteKey}
ref:favoriteIcon ref:favoriteIcon
/> />
<IconButton <IconButton
label="Show more options" label="Show more options"
href="#fa-ellipsis-h" href="#fa-ellipsis-h"
delegateKey={optionsKey} clickListener={false}
elementId={optionsKey}
/> />
</div> </div>
{#if enableShortcuts} {#if enableShortcuts}
<Shortcut scope={uuid} key="f" on:pressed="toggleFavorite()"/> <Shortcut scope={shortcutScope} key="f" on:pressed="toggleFavorite()"/>
<Shortcut scope={uuid} key="r" on:pressed="reply()"/> <Shortcut scope={shortcutScope} key="r" on:pressed="reply()"/>
<Shortcut scope={uuid} key="b" on:pressed="reblog()"/> <Shortcut scope={shortcutScope} key="b" on:pressed="reblog()"/>
{/if} {/if}
<style> <style>
.status-toolbar { .status-toolbar {

View file

@ -241,17 +241,14 @@
try { try {
let { currentInstance } = this.store.get() let { currentInstance } = this.store.get()
let { timeline } = this.get() let { timeline } = this.get()
let lastFocusedElementSelector let lastFocusedElementId
let activeElement = e.target let activeElement = e.target
if (activeElement) { if (activeElement) {
let focusKey = activeElement.getAttribute('focus-key') lastFocusedElementId = activeElement.getAttribute('id')
if (focusKey) {
lastFocusedElementSelector = `[focus-key=${JSON.stringify(focusKey)}]`
} }
} console.log('saving focus to ', lastFocusedElementId)
console.log('saving focus to ', lastFocusedElementSelector)
this.store.setForTimeline(currentInstance, timeline, { this.store.setForTimeline(currentInstance, timeline, {
lastFocusedElementSelector lastFocusedElementId: lastFocusedElementId
}) })
} catch (err) { } catch (err) {
console.error('unable to save focus', err) console.error('unable to save focus', err)
@ -267,21 +264,21 @@
let { currentInstance } = this.store.get() let { currentInstance } = this.store.get()
let { timeline } = this.get() let { timeline } = this.get()
this.store.setForTimeline(currentInstance, timeline, { this.store.setForTimeline(currentInstance, timeline, {
lastFocusedElementSelector: null lastFocusedElementId: null
}) })
} catch (err) { } catch (err) {
console.error('unable to clear focus', err) console.error('unable to clear focus', err)
} }
}, },
restoreFocus () { restoreFocus () {
let { lastFocusedElementSelector } = this.store.get() let { lastFocusedElementId } = this.store.get()
if (!lastFocusedElementSelector) { if (!lastFocusedElementId) {
return return
} }
console.log('restoreFocus', lastFocusedElementSelector) console.log('restoreFocus', lastFocusedElementId)
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
let element = document.querySelector(lastFocusedElementSelector) let element = document.getElementById(lastFocusedElementId)
if (element) { if (element) {
element.focus() element.focus()
} }

View file

@ -12,7 +12,7 @@ function computeForTimeline (store, key, defaultValue) {
export function timelineComputations (store) { export function timelineComputations (store) {
computeForTimeline(store, 'timelineItemIds', null) computeForTimeline(store, 'timelineItemIds', null)
computeForTimeline(store, 'runningUpdate', false) computeForTimeline(store, 'runningUpdate', false)
computeForTimeline(store, 'lastFocusedElementSelector', null) computeForTimeline(store, 'lastFocusedElementId', null)
computeForTimeline(store, 'ignoreBlurEvents', false) computeForTimeline(store, 'ignoreBlurEvents', false)
computeForTimeline(store, 'itemIdsToAdd', null) computeForTimeline(store, 'itemIdsToAdd', null)
computeForTimeline(store, 'showHeader', false) computeForTimeline(store, 'showHeader', false)

View file

@ -15,7 +15,7 @@ function onEvent (e) {
let key let key
let element = target let element = target
while (element) { while (element) {
if ((key = element.getAttribute('delegate-key'))) { if ((key = element.getAttribute('id'))) {
break break
} }
element = element.parentElement element = element.parentElement

View file

@ -163,8 +163,12 @@ export const isNthStatusActive = (idx) => (exec(() => {
})) }))
export const isActiveStatusPinned = exec(() => { export const isActiveStatusPinned = exec(() => {
return document.activeElement && let el = document.activeElement
document.activeElement.getAttribute('delegate-key').includes('pinned') return el &&
(
(el.parentElement.getAttribute('class') || '').includes('pinned') ||
(el.parentElement.parentElement.getAttribute('class') || '').includes('pinned')
)
}) })
export const scrollToBottom = exec(() => { export const scrollToBottom = exec(() => {