Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Last active December 7, 2022 14:35
Show Gist options
  • Save jonathantneal/dad6c4e83485483298cfc5258024fbc8 to your computer and use it in GitHub Desktop.
Save jonathantneal/dad6c4e83485483298cfc5258024fbc8 to your computer and use it in GitHub Desktop.
Focus Starting Point

Focus Starting Point

Focus Starting Point lets you access and observe the focus starting point.

The library is 989 bytes, 554 bytes gzipped.

Example Usage

const focusStartingPoint = new FocusStartingPoint()

focusStartingPoint.addEventListener('change', event => {
  console.group('focus navigation point')
  console.log('old:', event.oldNode, event.oldOffset)
  console.log('new:', event.newNode, event.newOffset)
  console.groupEnd()
})
/** Focus Starting Point @version 0.1.2 @license CC0-1.0 @author Jonathan Neal (https://github.com/jonathantneal) */
let weakTarget = new WeakMap
let weakOffset = new WeakMap
export class FocusStartingPoint extends EventTarget {
constructor(window = globalThis) {
super()
let { document, history } = window
// @ts-expect-error "Property 'caretPositionFromPoint' does not exist on type 'Document'."
let { caretPositionFromPoint, body } = document
let compareTarget = (/** @type {Node} */ seekingTarget, /** @type {number} */ seekingOffset) => {
if (
currentTarget !== seekingTarget
|| currentOffset !== seekingOffset
) {
weakTarget.set(this, seekingTarget)
weakOffset.set(this, seekingOffset)
this.dispatchEvent(Object.assign(new Event('change'), {
oldNode: currentTarget,
newNode: seekingTarget,
oldOffset: currentOffset,
newOffset: seekingOffset,
}))
currentTarget = seekingTarget
currentOffset = seekingOffset
}
}
let currentTarget = /** @type {Node} */ (document.activeElement)
let currentOffset = 0
let historyLength = history.length
/** @type {Element} */
let element
weakTarget.set(this, currentTarget)
weakOffset.set(this, currentOffset)
if (!caretPositionFromPoint) {
caretPositionFromPoint = (/** @type {number} */ clientX, /** @type {number} */ clientY) => {
let range = /** @type {Range} */ (document.caretRangeFromPoint(clientX, clientY))
let offsetNode = range.startContainer
let offset = range.startOffset
let boundaryRect = range.selectNode(offsetNode) || range.getBoundingClientRect()
if (
offsetNode.nodeType === 1
|| clientX < boundaryRect.left
|| clientX > boundaryRect.right
|| clientY < boundaryRect.top
|| clientY > boundaryRect.bottom
) {
offsetNode = element
offset = 0
}
return { offsetNode, offset }
}
}
window.addEventListener('focusin', () => {
compareTarget(/** @type {Element} */(document.activeElement), 0)
}, true)
window.addEventListener('hashchange', () => {
if (historyLength !== (historyLength = history.length)) {
compareTarget(document.querySelector(':target') || body, 0)
}
}, true)
window.addEventListener('pointerdown', event => {
element = /** @type {Element} */ (document.elementFromPoint(event.clientX, event.clientY))
let { offsetNode, offset } = caretPositionFromPoint.call(document, event.clientX, event.clientY)
offsetNode.contains(element)
? compareTarget(element, 0)
: compareTarget(offsetNode, offset)
}, true)
}
get node() {
return weakTarget.get(this)
}
get offset() {
return weakOffset.get(this)
}
}
let e=new WeakMap,t=new WeakMap;export class FocusStartingPoint extends EventTarget{constructor(n=globalThis){super();let o,{document:s,history:i}=n,{caretPositionFromPoint:r,body:a}=s,l=(n,o)=>{c===n&&d===o||(e.set(this,n),t.set(this,o),this.dispatchEvent(Object.assign(new Event('change'),{oldNode:c,newNode:n,oldOffset:d,newOffset:o})),c=n,d=o)},c=s.activeElement,d=0,f=i.length;e.set(this,c),t.set(this,d),r||(r=(e,t)=>{let n=s.caretRangeFromPoint(e,t),i=n.startContainer,r=n.startOffset,a=n.selectNode(i)||n.getBoundingClientRect();return(1===i.nodeType||e<a.left||e>a.right||t<a.top||t>a.bottom)&&(i=o,r=0),{offsetNode:i,offset:r}}),n.addEventListener('focusin',(()=>{l(s.activeElement,0)}),!0),n.addEventListener('hashchange',(()=>{f!==(f=i.length)&&l(s.querySelector(':target')||a,0)}),!0),n.addEventListener('pointerdown',(e=>{o=s.elementFromPoint(e.clientX,e.clientY);let{offsetNode:t,offset:n}=r.call(s,e.clientX,e.clientY);t.contains(o)?l(o,0):l(t,n)}),!0)}get node(){return e.get(this)}get offset(){return t.get(this)}}
{let e=new WeakMap,t=new WeakMap;globalThis.FocusStartingPoint=class extends EventTarget{constructor(n=globalThis){super();let o,{document:s,history:i}=n,{caretPositionFromPoint:a,body:r}=s,l=(n,o)=>{c===n&&d===o||(e.set(this,n),t.set(this,o),this.dispatchEvent(Object.assign(new Event('change'),{oldNode:c,newNode:n,oldOffset:d,newOffset:o})),c=n,d=o)},c=s.activeElement,d=0,f=i.length;e.set(this,c),t.set(this,d),a||(a=(e,t)=>{let n=s.caretRangeFromPoint(e,t),i=n.startContainer,a=n.startOffset,r=n.selectNode(i)||n.getBoundingClientRect();return(1===i.nodeType||e<r.left||e>r.right||t<r.top||t>r.bottom)&&(i=o,a=0),{offsetNode:i,offset:a}}),n.addEventListener('focusin',(()=>{l(s.activeElement,0)}),!0),n.addEventListener('hashchange',(()=>{f!==(f=i.length)&&l(s.querySelector(':target')||r,0)}),!0),n.addEventListener('pointerdown',(e=>{o=s.elementFromPoint(e.clientX,e.clientY);let{offsetNode:t,offset:n}=a.call(s,e.clientX,e.clientY);t.contains(o)?l(o,0):l(t,n)}),!0)}get node(){return e.get(this)}get offset(){return t.get(this)}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment