mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
Paginator: use CSS grid
Massively simplify margin/padding and header/footer layout and eliminate unnecessary resizes and resize loops. Also remove layout and style API from View, as these are only applicable to the paginator.
This commit is contained in:
@@ -168,12 +168,15 @@ The filter only applies to the book itself, leaving overlaid elements such as hi
|
|||||||
|
|
||||||
There is a basic page transition effect that can be disabled by setting `.pageAnimation` to false.
|
There is a basic page transition effect that can be disabled by setting `.pageAnimation` to false.
|
||||||
|
|
||||||
The layout can be configured from the `.layout` object, which has the following properties:
|
The layout can be configured by setting the following attributes:
|
||||||
- `.flow`: either `'paginated'` or `'scrolled'`.
|
- `flow`: either `paginated` or `scrolled`.
|
||||||
- `.margin`: number, in pixels. The height of the header and footer.
|
- `margin`: a CSS `<length>`. The unit must be `px`. The height of the header and footer.
|
||||||
- `.gap`: number between 0 and 1. The size of the space between columns, relative to page size.
|
- `gap`: a CSS `<percentage>`. The size of the space between columns, relative to page size.
|
||||||
- `.maxColumnWidth`: number, in pixels. The maximum width of the text in each column.
|
- `max-inline-size`: a CSS `<length>`. The unit must be `px`. The maximum inline size of the text (column width in paginated mode).
|
||||||
- `.maxColumns`: integer. The maximum number of columns. Has no effect in scrolled mode.
|
- `max-block-size`: same as above, but for the size in the block direction.
|
||||||
|
- `max-column-count`: integer. The maximum number of columns. Has no effect in scrolled mode.
|
||||||
|
|
||||||
|
(Note: there's no JS property API. You must use `.setAttribute()`.)
|
||||||
|
|
||||||
It has built-in header and footer regions accessible via the `.heads` and `.feet` properties of the paginator instance. These can be used to display running heads and reading progress. They are only available in paginated mode, and there will be one element for each column. They are styleable with `::part(head)` and `::part(foot)`. E.g., to add a border under the running heads,
|
It has built-in header and footer regions accessible via the `.heads` and `.feet` properties of the paginator instance. These can be used to display running heads and reading progress. They are only available in paginated mode, and there will be one element for each column. They are styleable with `::part(head)` and `::part(foot)`. E.g., to add a border under the running heads,
|
||||||
|
|
||||||
|
|||||||
+119
-88
@@ -351,60 +351,92 @@ class View {
|
|||||||
|
|
||||||
// NOTE: everything here assumes the so-called "negative scroll type" for RTL
|
// NOTE: everything here assumes the so-called "negative scroll type" for RTL
|
||||||
export class Paginator extends HTMLElement {
|
export class Paginator extends HTMLElement {
|
||||||
|
static observedAttributes = [
|
||||||
|
'flow', 'gap', 'margin',
|
||||||
|
'max-inline-size', 'max-block-size', 'max-column-count',
|
||||||
|
]
|
||||||
#root = this.attachShadow({ mode: 'closed' })
|
#root = this.attachShadow({ mode: 'closed' })
|
||||||
#gap = 0
|
|
||||||
#shouldUpdateGap = true
|
|
||||||
#observer = new ResizeObserver(() => this.render())
|
#observer = new ResizeObserver(() => this.render())
|
||||||
#background
|
#background
|
||||||
#maxWidthContainer
|
|
||||||
#maxHeightContainer
|
|
||||||
#container
|
#container
|
||||||
#header
|
#header
|
||||||
#footer
|
#footer
|
||||||
#view
|
#view
|
||||||
#vertical = false
|
#vertical = false
|
||||||
#rtl = false
|
#rtl = false
|
||||||
|
#margin = 0
|
||||||
#index = -1
|
#index = -1
|
||||||
#anchor = 0 // anchor view to a fraction (0-1), Range, or Element
|
#anchor = 0 // anchor view to a fraction (0-1), Range, or Element
|
||||||
#locked = false // while true, prevent any further navigation
|
#locked = false // while true, prevent any further navigation
|
||||||
|
#styles
|
||||||
#styleMap = new WeakMap()
|
#styleMap = new WeakMap()
|
||||||
#scrollBounds
|
#scrollBounds
|
||||||
#touchState
|
#touchState
|
||||||
pageAnimation = true
|
pageAnimation = true
|
||||||
layout = {
|
|
||||||
margin: 48,
|
|
||||||
gap: 0.05,
|
|
||||||
maxColumnWidth: 700,
|
|
||||||
}
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.#root.innerHTML = `<style>
|
this.#root.innerHTML = `<style>
|
||||||
:host {
|
:host {
|
||||||
|
--_gap: 7%;
|
||||||
|
--_margin: 48px;
|
||||||
|
--_max-inline-size: 720px;
|
||||||
|
--_max-block-size: 1440px;
|
||||||
|
--_max-column-count: 2;
|
||||||
|
--_vertical: 0;
|
||||||
|
--_half-gap: calc(var(--_gap) / 2);
|
||||||
|
--_max-width: calc(
|
||||||
|
var(--_vertical) * var(--_max-block-size)
|
||||||
|
+ (1 - var(--_vertical)) * var(--_max-inline-size) * var(--_max-column-count)
|
||||||
|
);
|
||||||
|
--_max-height: calc(
|
||||||
|
var(--_vertical) * var(--_max-inline-size) * var(--_max-column-count)
|
||||||
|
+ (1 - var(--_vertical)) * var(--_max-block-size)
|
||||||
|
);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
minmax(var(--_half-gap), 1fr)
|
||||||
|
minmax(0, var(--_max-width))
|
||||||
|
minmax(var(--_half-gap), 1fr);
|
||||||
|
grid-template-rows:
|
||||||
|
minmax(var(--_margin), 1fr)
|
||||||
|
minmax(0, var(--_max-height))
|
||||||
|
minmax(var(--_margin), 1fr);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
#background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
#max-width {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
:host, #background, #max-width, #max-height, #container {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto;
|
}
|
||||||
|
#background {
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 4;
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: 4;
|
||||||
|
}
|
||||||
|
#container {
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-row-start: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
:host([flow="scrolled"]) #container {
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 4;
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: 4;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
}
|
||||||
|
#footer {
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-row-start: 3;
|
||||||
|
align-self: end;
|
||||||
}
|
}
|
||||||
#header, #footer {
|
#header, #footer {
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
margin: auto;
|
height: var(--_margin);
|
||||||
}
|
}
|
||||||
:is(#header, #footer) > * {
|
:is(#header, #footer) > * {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -422,23 +454,17 @@ export class Paginator extends HTMLElement {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div id="background" part="filter"></div>
|
<div id="background" part="filter"></div>
|
||||||
<div id="max-width">
|
<div id="header"></div>
|
||||||
<div id="max-height">
|
<div id="container"></div>
|
||||||
<div id="container"></div>
|
<div id="footer"></div>
|
||||||
</div>
|
|
||||||
<div id="header"></div>
|
|
||||||
<div id="footer"></div>
|
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
|
|
||||||
this.#background = this.#root.getElementById('background')
|
this.#background = this.#root.getElementById('background')
|
||||||
this.#maxWidthContainer = this.#root.getElementById('max-width')
|
|
||||||
this.#maxHeightContainer = this.#root.getElementById('max-height')
|
|
||||||
this.#container = this.#root.getElementById('container')
|
this.#container = this.#root.getElementById('container')
|
||||||
this.#header = this.#root.getElementById('header')
|
this.#header = this.#root.getElementById('header')
|
||||||
this.#footer = this.#root.getElementById('footer')
|
this.#footer = this.#root.getElementById('footer')
|
||||||
|
|
||||||
this.#observer.observe(this)
|
this.#observer.observe(this.#container)
|
||||||
this.#container.addEventListener('scroll', debounce(() => {
|
this.#container.addEventListener('scroll', debounce(() => {
|
||||||
if (this.scrolled) this.#afterScroll('scroll')
|
if (this.scrolled) this.#afterScroll('scroll')
|
||||||
}, 250))
|
}, 250))
|
||||||
@@ -453,6 +479,24 @@ export class Paginator extends HTMLElement {
|
|||||||
doc.addEventListener('touchend', this.#onTouchEnd.bind(this))
|
doc.addEventListener('touchend', this.#onTouchEnd.bind(this))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
attributeChangedCallback(name, _, value) {
|
||||||
|
switch (name) {
|
||||||
|
case 'flow':
|
||||||
|
this.render()
|
||||||
|
break
|
||||||
|
case 'gap':
|
||||||
|
case 'margin':
|
||||||
|
case 'max-block-size':
|
||||||
|
case 'max-column-count':
|
||||||
|
this.style.setProperty('--_' + name, value)
|
||||||
|
break
|
||||||
|
case 'max-inline-size':
|
||||||
|
// needs explicit `render()` as it doesn't necessarily resize
|
||||||
|
this.style.setProperty('--_' + name, value)
|
||||||
|
this.render()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
open(book) {
|
open(book) {
|
||||||
this.bookDir = book.dir
|
this.bookDir = book.dir
|
||||||
this.sections = book.sections
|
this.sections = book.sections
|
||||||
@@ -469,25 +513,46 @@ export class Paginator extends HTMLElement {
|
|||||||
#beforeRender({ vertical, rtl, background }) {
|
#beforeRender({ vertical, rtl, background }) {
|
||||||
this.#vertical = vertical
|
this.#vertical = vertical
|
||||||
this.#rtl = rtl
|
this.#rtl = rtl
|
||||||
|
this.style.setProperty('--_vertical', vertical ? 1 : 0)
|
||||||
|
|
||||||
// set background to `doc` background
|
// set background to `doc` background
|
||||||
// this is needed because the iframe does not fill the whole element
|
// this is needed because the iframe does not fill the whole element
|
||||||
this.#background.style.background = background
|
this.#background.style.background = background
|
||||||
|
|
||||||
const { flow, margin, gap: gape, maxColumnWidth, maxColumns } = this.layout
|
const { width, height } = this.#container.getBoundingClientRect()
|
||||||
|
const size = vertical ? height : width
|
||||||
|
|
||||||
|
const style = getComputedStyle(this)
|
||||||
|
const maxInlineSize = parseFloat(style.getPropertyValue('--_max-inline-size'))
|
||||||
|
const margin = parseFloat(style.getPropertyValue('--_margin'))
|
||||||
|
this.#margin = margin
|
||||||
|
|
||||||
|
const g = parseFloat(style.getPropertyValue('--_gap')) / 100
|
||||||
|
// The gap will be a percentage of the #container, not the whole view.
|
||||||
|
// This means the outer padding will be bigger than the column gap. Let
|
||||||
|
// `a` be the gap percentage. The actual percentage for the column gap
|
||||||
|
// will be (1 - a) * a. Let us call this `b`.
|
||||||
|
//
|
||||||
|
// To make them the same, we start by shrinking the outer padding
|
||||||
|
// setting to `b`, but keep the column gap setting the same at `a`. Then
|
||||||
|
// the actual size for the column gap will be (1 - b) * a. Repeating the
|
||||||
|
// process again and again, we get the sequence
|
||||||
|
// x₁ = (1 - b) * a
|
||||||
|
// x₂ = (1 - x₁) * a
|
||||||
|
// ...
|
||||||
|
// which converges to x = (1 - x) * a. Solving for x, x = a / (1 + a).
|
||||||
|
// So to make the spacing even, we must shrink the outer padding with
|
||||||
|
// f(x) = x / (1 + x).
|
||||||
|
// But we want to keep the outer padding, and make the inner gap bigger.
|
||||||
|
// So we apply the inverse, f⁻¹ = -x / (x - 1) to the column gap.
|
||||||
|
const gap = -g / (g - 1) * size
|
||||||
|
|
||||||
|
const flow = this.getAttribute('flow')
|
||||||
if (flow === 'scrolled') {
|
if (flow === 'scrolled') {
|
||||||
// FIXME: vertical-rl only, not -lr
|
// FIXME: vertical-rl only, not -lr
|
||||||
this.setAttribute('dir', vertical ? 'rtl' : 'ltr')
|
this.setAttribute('dir', vertical ? 'rtl' : 'ltr')
|
||||||
this.style.padding = '0'
|
this.style.padding = '0'
|
||||||
this.#container.style.overflow ='auto'
|
const columnWidth = maxInlineSize
|
||||||
this.#maxWidthContainer.style.maxWidth = 'none'
|
|
||||||
this.#maxHeightContainer.style.maxHeight = 'none'
|
|
||||||
const columnWidth = this.layout.maxColumnWidth
|
|
||||||
|
|
||||||
const { width, height } = this.#container.getBoundingClientRect()
|
|
||||||
const size = vertical ? height : width
|
|
||||||
const gap = Math.trunc(gape * size)
|
|
||||||
|
|
||||||
this.heads = null
|
this.heads = null
|
||||||
this.feet = null
|
this.feet = null
|
||||||
@@ -497,50 +562,14 @@ export class Paginator extends HTMLElement {
|
|||||||
return { flow, margin, gap, columnWidth }
|
return { flow, margin, gap, columnWidth }
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxSize = `${maxColumns * maxColumnWidth}px`
|
const divisor = Math.ceil(size / maxInlineSize)
|
||||||
this.#maxWidthContainer.style.maxWidth = vertical ? 'none' : maxSize
|
|
||||||
this.#maxHeightContainer.style.maxHeight = vertical ? maxSize : 'none'
|
|
||||||
|
|
||||||
if (this.#shouldUpdateGap) {
|
|
||||||
this.#shouldUpdateGap = false
|
|
||||||
const { width, height } = this.#container.getBoundingClientRect()
|
|
||||||
const size = vertical ? height : width
|
|
||||||
const gap = Math.trunc(gape * size)
|
|
||||||
|
|
||||||
const paddingH = `${vertical
|
|
||||||
? Math.min(margin, Math.trunc(gape * width))
|
|
||||||
: gap / 2}px`
|
|
||||||
const paddingV = `${vertical
|
|
||||||
? Math.max(margin - gap / 2, gap / 2)
|
|
||||||
: margin}px`
|
|
||||||
this.#gap = gap
|
|
||||||
this.style.padding = `${paddingV} ${paddingH}`
|
|
||||||
this.#header.style.top = `-${paddingV}`
|
|
||||||
this.#footer.style.bottom = `-${paddingV}`
|
|
||||||
|
|
||||||
const newRect = this.#container.getBoundingClientRect()
|
|
||||||
const newSize = vertical ? newRect.height : newRect.width
|
|
||||||
// if the size is different, don't do anything
|
|
||||||
// as the resize observer would fire in that case
|
|
||||||
if (newSize !== size) return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#shouldUpdateGap = true
|
|
||||||
const gap = this.#gap
|
|
||||||
|
|
||||||
const { width, height } = this.#container.getBoundingClientRect()
|
|
||||||
const size = vertical ? height : width
|
|
||||||
const divisor = Math.ceil(size / maxColumnWidth)
|
|
||||||
const columnWidth = (size / divisor) - gap
|
const columnWidth = (size / divisor) - gap
|
||||||
this.setAttribute('dir', rtl ? 'rtl' : 'ltr')
|
this.setAttribute('dir', rtl ? 'rtl' : 'ltr')
|
||||||
this.#container.style.overflow ='hidden'
|
|
||||||
|
|
||||||
const marginalDivisor = vertical
|
const marginalDivisor = vertical
|
||||||
? Math.min(2, Math.ceil(width / maxColumnWidth))
|
? Math.min(2, Math.ceil(width / maxInlineSize))
|
||||||
: divisor
|
: divisor
|
||||||
const marginalStyle = {
|
const marginalStyle = {
|
||||||
maxWidth: vertical ? 'none' : maxSize,
|
|
||||||
height: `${margin}px`,
|
|
||||||
gridTemplateColumns: `repeat(${marginalDivisor}, 1fr)`,
|
gridTemplateColumns: `repeat(${marginalDivisor}, 1fr)`,
|
||||||
gap: `${gap}px`,
|
gap: `${gap}px`,
|
||||||
padding: vertical ? '0' : `0 ${gap / 2}px`,
|
padding: vertical ? '0' : `0 ${gap / 2}px`,
|
||||||
@@ -566,7 +595,7 @@ export class Paginator extends HTMLElement {
|
|||||||
this.#scrollToAnchor()
|
this.#scrollToAnchor()
|
||||||
}
|
}
|
||||||
get scrolled() {
|
get scrolled() {
|
||||||
return this.layout.flow === 'scrolled'
|
return this.getAttribute('flow') === 'scrolled'
|
||||||
}
|
}
|
||||||
get scrollProp() {
|
get scrollProp() {
|
||||||
const { scrolled } = this
|
const { scrolled } = this
|
||||||
@@ -658,7 +687,7 @@ export class Paginator extends HTMLElement {
|
|||||||
#getRectMapper() {
|
#getRectMapper() {
|
||||||
if (this.scrolled) {
|
if (this.scrolled) {
|
||||||
const size = this.viewSize
|
const size = this.viewSize
|
||||||
const margin = this.layout.margin
|
const margin = this.#margin
|
||||||
return this.#vertical
|
return this.#vertical
|
||||||
? ({ left, right }) =>
|
? ({ left, right }) =>
|
||||||
({ left: size - right - margin, right: size - left - margin })
|
({ left: size - right - margin, right: size - left - margin })
|
||||||
@@ -674,11 +703,11 @@ export class Paginator extends HTMLElement {
|
|||||||
}
|
}
|
||||||
async #scrollToRect(rect, reason) {
|
async #scrollToRect(rect, reason) {
|
||||||
if (this.scrolled) {
|
if (this.scrolled) {
|
||||||
const offset = this.#getRectMapper()(rect).left - this.layout.margin
|
const offset = this.#getRectMapper()(rect).left - this.#margin
|
||||||
return this.#scrollTo(offset, reason)
|
return this.#scrollTo(offset, reason)
|
||||||
}
|
}
|
||||||
const offset = this.#getRectMapper()(rect).left
|
const offset = this.#getRectMapper()(rect).left
|
||||||
+ this.layout.margin / 2
|
+ this.#margin / 2
|
||||||
return this.#scrollToPage(Math.floor(offset / this.size) + (this.#rtl ? -1 : 1), reason)
|
return this.#scrollToPage(Math.floor(offset / this.size) + (this.#rtl ? -1 : 1), reason)
|
||||||
}
|
}
|
||||||
async #scrollTo(offset, reason, smooth) {
|
async #scrollTo(offset, reason, smooth) {
|
||||||
@@ -801,6 +830,7 @@ export class Paginator extends HTMLElement {
|
|||||||
const oldIndex = this.#index
|
const oldIndex = this.#index
|
||||||
const onLoad = detail => {
|
const onLoad = detail => {
|
||||||
this.sections[oldIndex]?.unload?.()
|
this.sections[oldIndex]?.unload?.()
|
||||||
|
this.setStyles(this.#styles)
|
||||||
this.dispatchEvent(new CustomEvent('load', { detail }))
|
this.dispatchEvent(new CustomEvent('load', { detail }))
|
||||||
}
|
}
|
||||||
await this.#display(Promise.resolve(this.sections[index].load())
|
await this.#display(Promise.resolve(this.sections[index].load())
|
||||||
@@ -890,7 +920,8 @@ export class Paginator extends HTMLElement {
|
|||||||
}]
|
}]
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
setStyle(styles) {
|
setStyles(styles) {
|
||||||
|
this.#styles = styles
|
||||||
const $$styles = this.#styleMap.get(this.#view?.document)
|
const $$styles = this.#styleMap.get(this.#view?.document)
|
||||||
if (!$$styles) return
|
if (!$$styles) return
|
||||||
const [$beforeStyle, $style] = $$styles
|
const [$beforeStyle, $style] = $$styles
|
||||||
|
|||||||
@@ -147,12 +147,6 @@ class Reader {
|
|||||||
justify: true,
|
justify: true,
|
||||||
hyphenate: true,
|
hyphenate: true,
|
||||||
}
|
}
|
||||||
layout = {
|
|
||||||
margin: 48,
|
|
||||||
gap: 0.05,
|
|
||||||
maxColumns: 2,
|
|
||||||
maxColumnWidth: 720,
|
|
||||||
}
|
|
||||||
annotations = new Map()
|
annotations = new Map()
|
||||||
annotationsByValue = new Map()
|
annotationsByValue = new Map()
|
||||||
closeSideBar() {
|
closeSideBar() {
|
||||||
@@ -176,8 +170,7 @@ class Reader {
|
|||||||
['Scrolled', 'scrolled'],
|
['Scrolled', 'scrolled'],
|
||||||
],
|
],
|
||||||
onclick: value => {
|
onclick: value => {
|
||||||
this.layout.flow = value
|
this.view?.renderer.setAttribute('flow', value)
|
||||||
this.setAppearance()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -194,7 +187,7 @@ class Reader {
|
|||||||
this.view.addEventListener('relocate', this.#onRelocate.bind(this))
|
this.view.addEventListener('relocate', this.#onRelocate.bind(this))
|
||||||
|
|
||||||
const { book } = this.view
|
const { book } = this.view
|
||||||
this.setAppearance()
|
this.view.renderer.setStyles?.(getCSS(this.style))
|
||||||
this.view.renderer.next()
|
this.view.renderer.next()
|
||||||
|
|
||||||
$('#header-bar').style.visibility = 'visible'
|
$('#header-bar').style.visibility = 'visible'
|
||||||
@@ -274,11 +267,6 @@ class Reader {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setAppearance = () => {
|
|
||||||
this.view?.setAppearance({ css: getCSS(this.style), layout: this.layout })
|
|
||||||
const scrolled = this.layout.flow === 'scrolled'
|
|
||||||
document.documentElement.classList.toggle('scrolled', scrolled)
|
|
||||||
}
|
|
||||||
#handleKeydown(event) {
|
#handleKeydown(event) {
|
||||||
const k = event.key
|
const k = event.key
|
||||||
if (k === 'ArrowLeft' || k === 'h') this.view.goLeft()
|
if (k === 'ArrowLeft' || k === 'h') this.view.goLeft()
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export class View extends HTMLElement {
|
|||||||
#sectionProgress
|
#sectionProgress
|
||||||
#tocProgress
|
#tocProgress
|
||||||
#pageProgress
|
#pageProgress
|
||||||
#css
|
|
||||||
isFixedLayout = false
|
isFixedLayout = false
|
||||||
lastLocation
|
lastLocation
|
||||||
history = new History()
|
history = new History()
|
||||||
@@ -162,7 +161,6 @@ export class View extends HTMLElement {
|
|||||||
if (!this.language.isCJK)
|
if (!this.language.isCJK)
|
||||||
doc.documentElement.dir ||= this.language.direction ?? ''
|
doc.documentElement.dir ||= this.language.direction ?? ''
|
||||||
|
|
||||||
this.renderer.setStyle?.(this.#css)
|
|
||||||
this.#handleLinks(doc, index)
|
this.#handleLinks(doc, index)
|
||||||
|
|
||||||
this.#emit('load', { doc, index })
|
this.#emit('load', { doc, index })
|
||||||
@@ -315,13 +313,6 @@ export class View extends HTMLElement {
|
|||||||
goRight() {
|
goRight() {
|
||||||
return this.book.dir === 'rtl' ? this.prev() : this.next()
|
return this.book.dir === 'rtl' ? this.prev() : this.next()
|
||||||
}
|
}
|
||||||
setAppearance({ layout, css }) {
|
|
||||||
if (this.isFixedLayout) return
|
|
||||||
Object.assign(this.renderer.layout, layout)
|
|
||||||
this.#css = css
|
|
||||||
this.renderer.setStyle(css)
|
|
||||||
this.renderer.render()
|
|
||||||
}
|
|
||||||
async * #searchSection(matcher, query, index) {
|
async * #searchSection(matcher, query, index) {
|
||||||
const doc = await this.book.sections[index].createDocument()
|
const doc = await this.book.sections[index].createDocument()
|
||||||
for (const { range, excerpt } of matcher(doc, query))
|
for (const { range, excerpt } of matcher(doc, query))
|
||||||
|
|||||||
Reference in New Issue
Block a user