mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
Paginator: respond to selection change
The view follows caret when using caret browsing (F7). Also allows selecting across pages. And if you click on or select some text and resize the window, the caret/selection always stays visible. Some quirks regarding caret browsing: - In Chromium, in paginated mode, it scrolls when changing selection (https://issues.chromium.org/issues/40889650) - In WebKitGTK, in scrolled mode, the view doesn't follow the caret (which would be a WebKit bug)
This commit is contained in:
+41
-4
@@ -138,6 +138,13 @@ const getVisibleRange = (doc, start, end, mapRect) => {
|
|||||||
return range
|
return range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectionIsBackward = sel => {
|
||||||
|
const range = document.createRange()
|
||||||
|
range.setStart(sel.anchorNode, sel.anchorOffset)
|
||||||
|
range.setEnd(sel.focusNode, sel.focusOffset)
|
||||||
|
return range.collapsed
|
||||||
|
}
|
||||||
|
|
||||||
const setSelectionTo = (target, collapse) => {
|
const setSelectionTo = (target, collapse) => {
|
||||||
let range
|
let range
|
||||||
if (target.startContainer) range = target.cloneRange()
|
if (target.startContainer) range = target.cloneRange()
|
||||||
@@ -427,6 +434,7 @@ export class Paginator extends HTMLElement {
|
|||||||
#scrollBounds
|
#scrollBounds
|
||||||
#touchState
|
#touchState
|
||||||
#touchScrolled
|
#touchScrolled
|
||||||
|
#lastVisibleRange
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.#root.innerHTML = `<style>
|
this.#root.innerHTML = `<style>
|
||||||
@@ -550,11 +558,8 @@ export class Paginator extends HTMLElement {
|
|||||||
doc.addEventListener('touchstart', this.#onTouchStart.bind(this), opts)
|
doc.addEventListener('touchstart', this.#onTouchStart.bind(this), opts)
|
||||||
doc.addEventListener('touchmove', this.#onTouchMove.bind(this), opts)
|
doc.addEventListener('touchmove', this.#onTouchMove.bind(this), opts)
|
||||||
doc.addEventListener('touchend', this.#onTouchEnd.bind(this))
|
doc.addEventListener('touchend', this.#onTouchEnd.bind(this))
|
||||||
|
|
||||||
doc.addEventListener('focusin', e => this.scrolled ? null :
|
|
||||||
// NOTE: `requestAnimationFrame` is needed in WebKit
|
|
||||||
requestAnimationFrame(() => this.#scrollToAnchor(e.target)))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.addEventListener('relocate', ({ detail }) => {
|
this.addEventListener('relocate', ({ detail }) => {
|
||||||
if (detail.reason === 'selection') setSelectionTo(this.#anchor, 0)
|
if (detail.reason === 'selection') setSelectionTo(this.#anchor, 0)
|
||||||
else if (detail.reason === 'navigation') {
|
else if (detail.reason === 'navigation') {
|
||||||
@@ -564,6 +569,37 @@ export class Paginator extends HTMLElement {
|
|||||||
else setSelectionTo(this.#anchor, -1)
|
else setSelectionTo(this.#anchor, -1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const checkPointerSelection = debounce((range, sel) => {
|
||||||
|
const selRange = sel.getRangeAt(0)
|
||||||
|
const backward = selectionIsBackward(sel)
|
||||||
|
if (backward && selRange.compareBoundaryPoints(Range.START_TO_START, range) < 0)
|
||||||
|
this.prev()
|
||||||
|
else if (!backward && selRange.compareBoundaryPoints(Range.END_TO_END, range) > 0)
|
||||||
|
this.next()
|
||||||
|
}, 700)
|
||||||
|
this.addEventListener('load', ({ detail: { doc } }) => {
|
||||||
|
let isPointerSelecting = false
|
||||||
|
doc.addEventListener('pointerdown', () => isPointerSelecting = true)
|
||||||
|
doc.addEventListener('pointerup', () => isPointerSelecting = false)
|
||||||
|
doc.addEventListener('selectionchange', () => {
|
||||||
|
if (this.scrolled) return
|
||||||
|
const range = this.#lastVisibleRange
|
||||||
|
if (!range) return
|
||||||
|
const sel = doc.getSelection()
|
||||||
|
if (!sel.rangeCount) return
|
||||||
|
if (isPointerSelecting && sel.type === 'Range')
|
||||||
|
checkPointerSelection(range, sel)
|
||||||
|
else {
|
||||||
|
const selRange = sel.getRangeAt(0).cloneRange()
|
||||||
|
const backward = selectionIsBackward(sel)
|
||||||
|
if (!backward) selRange.collapse()
|
||||||
|
this.#scrollToAnchor(selRange)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
doc.addEventListener('focusin', e => this.scrolled ? null :
|
||||||
|
// NOTE: `requestAnimationFrame` is needed in WebKit
|
||||||
|
requestAnimationFrame(() => this.#scrollToAnchor(e.target)))
|
||||||
|
})
|
||||||
|
|
||||||
this.#mediaQueryListener = () => {
|
this.#mediaQueryListener = () => {
|
||||||
if (!this.#view) return
|
if (!this.#view) return
|
||||||
@@ -881,6 +917,7 @@ export class Paginator extends HTMLElement {
|
|||||||
}
|
}
|
||||||
#afterScroll(reason) {
|
#afterScroll(reason) {
|
||||||
const range = this.#getVisibleRange()
|
const range = this.#getVisibleRange()
|
||||||
|
this.#lastVisibleRange = range
|
||||||
// don't set new anchor if relocation was to scroll to anchor
|
// don't set new anchor if relocation was to scroll to anchor
|
||||||
if (reason !== 'selection' && reason !== 'navigation' && reason !== 'anchor')
|
if (reason !== 'selection' && reason !== 'navigation' && reason !== 'anchor')
|
||||||
this.#anchor = range
|
this.#anchor = range
|
||||||
|
|||||||
Reference in New Issue
Block a user