@@ -0,0 +1,699 @@
< script setup lang = "ts" >
import { sharedState , useNav } from '@slidev/client'
import { onBeforeUnmount , onMounted , watch } from 'vue'
type ScalableKind = 'table' | 'code'
interface ScaleMessage {
key : string
kind : ScalableKind
scale : number
}
interface TableBaseStyle {
tableFontSize : number
tableInlineFontSize : string
cells : Array < {
element : HTMLElement
inlinePaddingBottom : string
inlinePaddingLeft : string
inlinePaddingRight : string
inlinePaddingTop : string
paddingBottom : number
paddingLeft : number
paddingRight : number
paddingTop : number
} >
}
interface CodeBaseStyle {
codeFontSize : number
createdHost : boolean
}
interface ImageInteraction {
ariaLabel : string | null
role : string | null
tabIndex : string | null
click : ( event : MouseEvent ) => void
keydown : ( event : KeyboardEvent ) => void
}
interface ImagePreviewRequest {
alt : string
id : string
sentAt : number
src : string
}
interface ImagePreviewAcknowledgement {
requestId : string
sentAt : number
viewerId : string
}
interface EasySlidesSharedState {
easySlidesImagePreview ? : {
acknowledgement ? : ImagePreviewAcknowledgement
request ? : ImagePreviewRequest
}
}
interface PendingImagePreview {
cleanupTimer ? : ReturnType < typeof setTimeout >
fallbackOpened : boolean
fallbackTimer : ReturnType < typeof setTimeout >
image : HTMLImageElement
requestId : string
}
const nav = useNav ( )
const easySharedState = sharedState as typeof sharedState & EasySlidesSharedState
const clientId = createClientId ( )
const mountedAt = Date . now ( )
const scales = new Map < string , number > ( )
const tableStyles = new WeakMap < HTMLTableElement , TableBaseStyle > ( )
const codeStyles = new WeakMap < HTMLPreElement , CodeBaseStyle > ( )
const imageInteractions = new Map < HTMLImageElement , ImageInteraction > ( )
let observer : MutationObserver | undefined
let resizeObserver : ResizeObserver | undefined
let channel : BroadcastChannel | undefined
let lightbox : HTMLElement | undefined
let lightboxImage : HTMLImageElement | undefined
let lightboxClose : HTMLButtonElement | undefined
let activeImage : HTMLImageElement | undefined
let pendingImagePreview : PendingImagePreview | undefined
let stopAcknowledgementWatch : ( ( ) => void ) | undefined
let stopRequestWatch : ( ( ) => void ) | undefined
let fitFrame : number | undefined
let lastHandledRequestId = easySharedState . easySlidesImagePreview ? . request ? . id ? ? ''
let bodyOverflow = ''
function createClientId ( ) {
if ( typeof crypto . randomUUID === 'function' )
return crypto . randomUUID ( )
return ` ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } `
}
function isInteractiveRoute ( ) {
if ( /(?:^|\/)(?:overview|export|editor|notes|notes-edit|print)(?:\/|$)/ . test ( window . location . pathname ) )
return false
return nav . isPresenter . value || nav . hasPrimarySlide . value
}
function isAuditRoute ( ) {
return new URLSearchParams ( window . location . search ) . has ( 'easy-slides-audit' )
}
function isProjectionRoute ( ) {
return ! isAuditRoute ( ) && isInteractiveRoute ( ) && ! nav . isPresenter . value && nav . hasPrimarySlide . value
}
function clampScale ( scale : number ) {
return Math . min ( 1.6 , Math . max ( 0.6 , Math . round ( scale * 10 ) / 10 ) )
}
function findSlideNumber ( element : Element ) {
const page = element . closest < HTMLElement > ( '[class*="slidev-page-"]' )
const pageClass = page && Array . from ( page . classList ) . find ( className => / ^ slidev - page - \ d + $ / . test ( className ) )
return pageClass ? . replace ( 'slidev-page-' , '' ) ? ? 'unknown'
}
function contentKey ( element : Element , kind : ScalableKind ) {
const layout = element . closest ( '.slidev-layout' )
const selector = kind === 'table' ? 'table' : 'pre'
const elements = layout ? Array . from ( layout . querySelectorAll ( selector ) ) : [ element ]
const index = Math . max ( 0 , elements . indexOf ( element ) )
return ` slide: ${ findSlideNumber ( element ) } : ${ kind } : ${ index } `
}
function createButton ( label : string , text : string , onClick : ( ) => void ) {
const button = document . createElement ( 'button' )
button . type = 'button'
button . className = 'easy-content-scale-button'
button . setAttribute ( 'aria-label' , label )
button . title = label
button . textContent = text
button . addEventListener ( 'pointerdown' , event => event . stopPropagation ( ) )
button . addEventListener ( 'click' , ( event ) => {
event . preventDefault ( )
event . stopPropagation ( )
onClick ( )
} )
return button
}
function kindLabel ( kind : ScalableKind ) {
return kind === 'table' ? '表格' : '代码'
}
function createToolbar ( key : string , kind : ScalableKind ) {
const label = kindLabel ( kind )
const toolbar = document . createElement ( 'nav' )
toolbar . className = 'easy-content-scale-toolbar'
toolbar . setAttribute ( 'aria-label' , ` ${ label } 缩放 ` )
toolbar . dataset . easyScaleKey = key
toolbar . dataset . easyScaleKind = kind
const decrease = createButton ( ` 缩小 ${ label } ` , '− ' , ( ) => updateScale ( key , kind , currentScale ( key ) - 0.1 ) )
const reset = createButton ( ` 重置 ${ label } 缩放 ` , '100%' , ( ) => updateScale ( key , kind , 1 ) )
reset . classList . add ( 'easy-content-scale-reset' )
const increase = createButton ( ` 放大 ${ label } ` , '+' , ( ) => updateScale ( key , kind , currentScale ( key ) + 0.1 ) )
toolbar . append ( decrease , reset , increase )
return toolbar
}
function currentScale ( key : string ) {
return scales . get ( key ) ? ? 1
}
function updateScale ( key : string , kind : ScalableKind , requestedScale : number , shouldBroadcast = true ) {
const scale = clampScale ( requestedScale )
scales . set ( key , scale )
applyScale ( key , kind , scale )
if ( shouldBroadcast )
channel ? . postMessage ( { key , kind , scale } satisfies ScaleMessage )
}
function updateResetButton ( host : HTMLElement , scale : number ) {
const reset = host . querySelector < HTMLButtonElement > ( '.easy-content-scale-reset' )
if ( ! reset )
return
const percentage = ` ${ Math . round ( scale * 100 ) } % `
reset . textContent = percentage
reset . title = ` ${ reset . getAttribute ( 'aria-label' ) } (当前 ${ percentage } ) `
}
function applyScale ( key : string , kind : ScalableKind , scale : number ) {
const hostSelector = kind === 'table' ? '.easy-content-table-host' : '.easy-content-code-host'
document . querySelectorAll < HTMLElement > ( hostSelector ) . forEach ( ( host ) => {
if ( host . dataset . easyScaleKey !== key )
return
updateResetButton ( host , scale )
if ( kind === 'code' ) {
const code = host . querySelector < HTMLPreElement > ( 'pre' )
const base = code && codeStyles . get ( code )
if ( ! code || ! base )
return
if ( scale === 1 )
host . style . removeProperty ( '--easy-code-scaled-font-size' )
else
host . style . setProperty ( '--easy-code-scaled-font-size' , ` ${ base . codeFontSize * scale } px ` )
return
}
const table = host . querySelector < HTMLTableElement > ( 'table' )
const base = table && tableStyles . get ( table )
if ( ! table || ! base )
return
if ( scale === 1 ) {
table . style . fontSize = base . tableInlineFontSize
base . cells . forEach ( ( cell ) => {
cell . element . style . paddingTop = cell . inlinePaddingTop
cell . element . style . paddingRight = cell . inlinePaddingRight
cell . element . style . paddingBottom = cell . inlinePaddingBottom
cell . element . style . paddingLeft = cell . inlinePaddingLeft
} )
return
}
table . style . fontSize = ` ${ base . tableFontSize * scale } px `
base . cells . forEach ( ( cell ) => {
cell . element . style . paddingTop = ` ${ cell . paddingTop * scale } px `
cell . element . style . paddingRight = ` ${ cell . paddingRight * scale } px `
cell . element . style . paddingBottom = ` ${ cell . paddingBottom * scale } px `
cell . element . style . paddingLeft = ` ${ cell . paddingLeft * scale } px `
} )
} )
}
function createLightbox ( ) {
const overlay = document . createElement ( 'div' )
overlay . className = 'easy-image-lightbox'
overlay . hidden = true
overlay . setAttribute ( 'role' , 'dialog' )
overlay . setAttribute ( 'aria-modal' , 'true' )
overlay . setAttribute ( 'aria-label' , '图片全屏预览' )
const preview = document . createElement ( 'img' )
preview . className = 'easy-image-lightbox-preview'
preview . alt = ''
const close = document . createElement ( 'button' )
close . type = 'button'
close . className = 'easy-image-lightbox-close'
close . setAttribute ( 'aria-label' , '关闭图片全屏预览' )
close . title = '关闭(Esc) '
close . textContent = '× '
close . addEventListener ( 'click' , ( event ) => {
event . preventDefault ( )
event . stopPropagation ( )
closeLightbox ( )
} )
overlay . addEventListener ( 'pointerdown' , event => event . stopPropagation ( ) )
overlay . addEventListener ( 'click' , ( event ) => {
event . preventDefault ( )
event . stopPropagation ( )
if ( event . target === overlay )
closeLightbox ( )
} )
overlay . append ( preview , close )
document . body . append ( overlay )
lightbox = overlay
lightboxImage = preview
lightboxClose = close
}
function showLightbox ( src : string , alt : string , image ? : HTMLImageElement ) {
if ( ! lightbox || ! lightboxImage || ! lightboxClose )
return
const wasHidden = lightbox . hidden
activeImage = image
lightboxImage . src = src
lightboxImage . alt = alt
lightbox . hidden = false
if ( wasHidden )
bodyOverflow = document . body . style . overflow
document . body . style . overflow = 'hidden'
document . addEventListener ( 'keydown' , handleLightboxKeydown , true )
lightboxClose . focus ( )
}
function openLightbox ( image : HTMLImageElement ) {
showLightbox ( image . currentSrc || image . src , image . alt , image )
}
function closeLightbox ( restoreFocus = true ) {
if ( ! lightbox || lightbox . hidden )
return
document . removeEventListener ( 'keydown' , handleLightboxKeydown , true )
lightbox . hidden = true
if ( lightboxImage )
lightboxImage . removeAttribute ( 'src' )
document . body . style . overflow = bodyOverflow
if ( restoreFocus )
activeImage ? . focus ( )
activeImage = undefined
}
function handleLightboxKeydown ( event : KeyboardEvent ) {
if ( event . key === 'Escape' ) {
event . preventDefault ( )
event . stopPropagation ( )
closeLightbox ( )
}
else if ( event . key === 'Tab' ) {
event . preventDefault ( )
lightboxClose ? . focus ( )
}
}
function updateImagePreviewState ( patch : NonNullable < EasySlidesSharedState [ ' easySlidesImagePreview ' ] > ) {
easySharedState . easySlidesImagePreview = {
... easySharedState . easySlidesImagePreview ,
... patch ,
}
}
function clearPendingImagePreview ( ) {
if ( ! pendingImagePreview )
return
clearTimeout ( pendingImagePreview . fallbackTimer )
if ( pendingImagePreview . cleanupTimer )
clearTimeout ( pendingImagePreview . cleanupTimer )
pendingImagePreview = undefined
}
function requestProjectionLightbox ( image : HTMLImageElement ) {
clearPendingImagePreview ( )
const request : ImagePreviewRequest = {
alt : image . alt ,
id : createClientId ( ) ,
sentAt : Date . now ( ) ,
src : image . currentSrc || image . src ,
}
const pending : PendingImagePreview = {
fallbackOpened : false ,
fallbackTimer : setTimeout ( ( ) => {
if ( pendingImagePreview ? . requestId !== request . id )
return
pendingImagePreview . fallbackOpened = true
openLightbox ( image )
pendingImagePreview . cleanupTimer = setTimeout ( ( ) => {
if ( pendingImagePreview ? . requestId === request . id )
pendingImagePreview = undefined
} , 10 _000 )
} , 500 ) ,
image ,
requestId : request . id ,
}
pendingImagePreview = pending
updateImagePreviewState ( { request } )
}
function activateImage ( image : HTMLImageElement ) {
if ( nav . isPresenter . value )
requestProjectionLightbox ( image )
else
openLightbox ( image )
}
function handleImagePreviewAcknowledgement ( acknowledgement : ImagePreviewAcknowledgement | undefined ) {
if ( ! acknowledgement || ! pendingImagePreview || acknowledgement . requestId !== pendingImagePreview . requestId )
return
const pending = pendingImagePreview
clearPendingImagePreview ( )
if ( pending . fallbackOpened && activeImage === pending . image )
closeLightbox ( false )
}
function handleImagePreviewRequest ( request : ImagePreviewRequest | undefined ) {
if ( ! request || ! isProjectionRoute ( ) || request . id === lastHandledRequestId )
return
if ( request . sentAt < mountedAt - 100 || Date . now ( ) - request . sentAt > 10 _000 )
return
lastHandledRequestId = request . id
showLightbox ( request . src , request . alt )
updateImagePreviewState ( {
acknowledgement : {
requestId : request . id ,
sentAt : Date . now ( ) ,
viewerId : clientId ,
} ,
} )
}
function enhanceImage ( image : HTMLImageElement ) {
if ( imageInteractions . has ( image ) || image . closest ( 'a, button' ) || image . closest ( '.easy-image-lightbox' ) )
return
const click = ( event : MouseEvent ) => {
event . preventDefault ( )
event . stopPropagation ( )
activateImage ( image )
}
const keydown = ( event : KeyboardEvent ) => {
if ( event . key !== 'Enter' && event . key !== ' ' )
return
event . preventDefault ( )
event . stopPropagation ( )
activateImage ( image )
}
imageInteractions . set ( image , {
ariaLabel : image . getAttribute ( 'aria-label' ) ,
role : image . getAttribute ( 'role' ) ,
tabIndex : image . getAttribute ( 'tabindex' ) ,
click ,
keydown ,
} )
image . classList . add ( 'easy-image-preview-trigger' )
image . tabIndex = 0
image . setAttribute ( 'role' , 'button' )
if ( ! image . hasAttribute ( 'aria-label' ) )
image . setAttribute ( 'aria-label' , image . alt ? ` 全屏查看图片: ${ image . alt } ` : '全屏查看图片' )
image . addEventListener ( 'click' , click )
image . addEventListener ( 'keydown' , keydown )
}
function enhanceTable ( table : HTMLTableElement ) {
if ( table . closest ( '.easy-content-table-host' ) )
return
const key = contentKey ( table , 'table' )
const cells = Array . from ( table . querySelectorAll < HTMLElement > ( 'th, td' ) ) . map ( ( element ) => {
const style = getComputedStyle ( element )
return {
element ,
inlinePaddingBottom : element . style . paddingBottom ,
inlinePaddingLeft : element . style . paddingLeft ,
inlinePaddingRight : element . style . paddingRight ,
inlinePaddingTop : element . style . paddingTop ,
paddingBottom : Number . parseFloat ( style . paddingBottom ) ,
paddingLeft : Number . parseFloat ( style . paddingLeft ) ,
paddingRight : Number . parseFloat ( style . paddingRight ) ,
paddingTop : Number . parseFloat ( style . paddingTop ) ,
}
} )
tableStyles . set ( table , {
tableFontSize : Number . parseFloat ( getComputedStyle ( table ) . fontSize ) ,
tableInlineFontSize : table . style . fontSize ,
cells ,
} )
const host = document . createElement ( 'div' )
host . className = 'easy-content-scale-host easy-content-table-host'
host . dataset . easyScaleKey = key
host . dataset . easyScaleKind = 'table'
table . replaceWith ( host )
host . append ( table , createToolbar ( key , 'table' ) )
applyScale ( key , 'table' , currentScale ( key ) )
}
function enhanceCode ( code : HTMLPreElement ) {
if ( code . closest ( '.easy-content-code-host' ) )
return
const key = contentKey ( code , 'code' )
let host = code . closest < HTMLElement > ( '.slidev-code-wrapper' )
const createdHost = ! host
if ( ! host ) {
host = document . createElement ( 'div' )
code . replaceWith ( host )
host . append ( code )
}
host . classList . add ( 'easy-content-scale-host' , 'easy-content-code-host' )
host . dataset . easyScaleKey = key
host . dataset . easyScaleKind = 'code'
host . dataset . easyCodeHostCreated = createdHost ? 'true' : 'false'
codeStyles . set ( code , {
codeFontSize : Number . parseFloat ( getComputedStyle ( code ) . fontSize ) ,
createdHost ,
} )
host . append ( createToolbar ( key , 'code' ) )
applyScale ( key , 'code' , currentScale ( key ) )
resizeObserver ? . observe ( code . closest ( '.slidev-layout' ) ? ? host )
scheduleCodeHeightFit ( )
}
function enhanceContent ( root : ParentNode = document ) {
const images = Array . from ( root . querySelectorAll < HTMLImageElement > ( '.slidev-layout img' ) )
const tables = Array . from ( root . querySelectorAll < HTMLTableElement > ( '.slidev-layout table' ) )
const codes = Array . from ( root . querySelectorAll < HTMLPreElement > ( '.slidev-layout pre' ) )
if ( root instanceof HTMLImageElement && root . closest ( '.slidev-layout' ) )
images . unshift ( root )
if ( root instanceof HTMLTableElement && root . closest ( '.slidev-layout' ) )
tables . unshift ( root )
if ( root instanceof HTMLPreElement && root . closest ( '.slidev-layout' ) )
codes . unshift ( root )
images . forEach ( enhanceImage )
tables . forEach ( enhanceTable )
codes . forEach ( enhanceCode )
}
function codeHeightRegion ( code : HTMLPreElement ) {
return code . closest < HTMLElement > ( '.easy-two-cols-grid > div' )
? ? code . closest < HTMLElement > ( '.slidev-layout' )
}
function hasExplicitCodeHeight ( code : HTMLPreElement ) {
if ( code . style . height || code . style . maxHeight )
return true
return Boolean ( getComputedStyle ( code ) . getPropertyValue ( '--easy-code-height' ) . trim ( ) )
}
function availableCodeHeight ( code : HTMLPreElement , region : HTMLElement ) {
const layout = code . closest < HTMLElement > ( '.slidev-layout' )
if ( ! layout || layout . clientHeight <= 0 )
return 0
const layoutRect = layout . getBoundingClientRect ( )
const regionRect = region . getBoundingClientRect ( )
const codeRect = code . getBoundingClientRect ( )
if ( layoutRect . height <= 0 || regionRect . height <= 0 || codeRect . width <= 0 )
return 0
const scale = layoutRect . height / layout . clientHeight
if ( ! Number . isFinite ( scale ) || scale <= 0 )
return 0
const regionPaddingBottom = Number . parseFloat ( getComputedStyle ( region ) . paddingBottom ) || 0
const layoutPaddingBottom = Number . parseFloat ( getComputedStyle ( layout ) . paddingBottom ) || 0
const regionBottom = regionRect . bottom - regionPaddingBottom * scale
const layoutBottom = layoutRect . bottom - layoutPaddingBottom * scale
return Math . max ( 0 , Math . floor ( ( Math . min ( regionBottom , layoutBottom ) - codeRect . top ) / scale ) )
}
function setHostLength ( host : HTMLElement , property : string , value ? : number ) {
const next = value && value > 0 ? ` ${ value } px ` : ''
if ( host . style . getPropertyValue ( property ) === next )
return
if ( next )
host . style . setProperty ( property , next )
else
host . style . removeProperty ( property )
}
function fitCodeHeights ( ) {
const groups = new Map < HTMLElement , HTMLPreElement [ ] > ( )
document . querySelectorAll < HTMLPreElement > ( '.easy-content-code-host pre' ) . forEach ( ( code ) => {
const host = code . closest < HTMLElement > ( '.easy-content-code-host' )
if ( ! host )
return
if ( hasExplicitCodeHeight ( code ) ) {
setHostLength ( host , '--easy-code-auto-height' )
setHostLength ( host , '--easy-code-auto-max-height' )
return
}
const region = codeHeightRegion ( code )
if ( ! region )
return
const group = groups . get ( region ) ? ? [ ]
group . push ( code )
groups . set ( region , group )
} )
groups . forEach ( ( codes , region ) => {
const single = codes . length === 1
codes . forEach ( ( code ) => {
const host = code . closest < HTMLElement > ( '.easy-content-code-host' )
if ( ! host )
return
const available = availableCodeHeight ( code , region )
if ( ! available )
return
setHostLength ( host , '--easy-code-auto-height' , single ? available : undefined )
setHostLength ( host , '--easy-code-auto-max-height' , available )
} )
} )
}
function scheduleCodeHeightFit ( ) {
if ( fitFrame !== undefined )
return
fitFrame = requestAnimationFrame ( ( ) => {
fitFrame = undefined
fitCodeHeights ( )
} )
}
function cleanupImage ( image : HTMLImageElement , interaction : ImageInteraction ) {
image . removeEventListener ( 'click' , interaction . click )
image . removeEventListener ( 'keydown' , interaction . keydown )
image . classList . remove ( 'easy-image-preview-trigger' )
restoreAttribute ( image , 'aria-label' , interaction . ariaLabel )
restoreAttribute ( image , 'role' , interaction . role )
restoreAttribute ( image , 'tabindex' , interaction . tabIndex )
}
function restoreAttribute ( element : Element , name : string , value : string | null ) {
if ( value === null )
element . removeAttribute ( name )
else
element . setAttribute ( name , value )
}
function cleanupTableHosts ( ) {
document . querySelectorAll < HTMLElement > ( '.easy-content-table-host' ) . forEach ( ( host ) => {
const table = host . querySelector < HTMLTableElement > ( 'table' )
const base = table && tableStyles . get ( table )
if ( ! table )
return
if ( base ) {
table . style . fontSize = base . tableInlineFontSize
base . cells . forEach ( ( cell ) => {
cell . element . style . paddingTop = cell . inlinePaddingTop
cell . element . style . paddingRight = cell . inlinePaddingRight
cell . element . style . paddingBottom = cell . inlinePaddingBottom
cell . element . style . paddingLeft = cell . inlinePaddingLeft
} )
}
host . replaceWith ( table )
} )
}
function cleanupCodeHosts ( ) {
document . querySelectorAll < HTMLElement > ( '.easy-content-code-host' ) . forEach ( ( host ) => {
const code = host . querySelector < HTMLPreElement > ( 'pre' )
const base = code && codeStyles . get ( code )
host . querySelector ( ':scope > .easy-content-scale-toolbar' ) ? . remove ( )
host . style . removeProperty ( '--easy-code-scaled-font-size' )
host . style . removeProperty ( '--easy-code-auto-height' )
host . style . removeProperty ( '--easy-code-auto-max-height' )
host . classList . remove ( 'easy-content-scale-host' , 'easy-content-code-host' )
delete host . dataset . easyScaleKey
delete host . dataset . easyScaleKind
delete host . dataset . easyCodeHostCreated
if ( code && base ? . createdHost )
host . replaceWith ( code )
} )
}
function cleanupContent ( ) {
clearPendingImagePreview ( )
closeLightbox ( )
imageInteractions . forEach ( ( interaction , image ) => cleanupImage ( image , interaction ) )
imageInteractions . clear ( )
lightbox ? . remove ( )
lightbox = undefined
lightboxImage = undefined
lightboxClose = undefined
cleanupTableHosts ( )
cleanupCodeHosts ( )
}
function isScaleMessage ( value : unknown ) : value is ScaleMessage {
if ( ! value || typeof value !== 'object' )
return false
const message = value as Partial < ScaleMessage >
return typeof message . key === 'string'
&& ( message . kind === 'table' || message . kind === 'code' )
&& typeof message . scale === 'number'
&& Number . isFinite ( message . scale )
}
onMounted ( ( ) => {
if ( ! isInteractiveRoute ( ) )
return
createLightbox ( )
if ( 'BroadcastChannel' in window ) {
channel = new BroadcastChannel ( ` easy-slides:content-scale: ${ import . meta . env . BASE _URL } ` )
channel . addEventListener ( 'message' , ( event ) => {
if ( isScaleMessage ( event . data ) )
updateScale ( event . data . key , event . data . kind , event . data . scale , false )
} )
}
stopAcknowledgementWatch = watch (
( ) => easySharedState . easySlidesImagePreview ? . acknowledgement ,
handleImagePreviewAcknowledgement ,
)
stopRequestWatch = watch (
( ) => easySharedState . easySlidesImagePreview ? . request ,
handleImagePreviewRequest ,
)
resizeObserver = new ResizeObserver ( scheduleCodeHeightFit )
enhanceContent ( )
document . querySelectorAll < HTMLElement > ( '.slidev-layout' ) . forEach ( layout => resizeObserver ? . observe ( layout ) )
observer = new MutationObserver ( ( records ) => {
records . forEach ( record => enhanceContent ( record . target as ParentNode ) )
scheduleCodeHeightFit ( )
} )
observer . observe ( document . body , { childList : true , subtree : true } )
window . addEventListener ( 'resize' , scheduleCodeHeightFit )
void document . fonts ? . ready . then ( scheduleCodeHeightFit )
scheduleCodeHeightFit ( )
} )
onBeforeUnmount ( ( ) => {
observer ? . disconnect ( )
resizeObserver ? . disconnect ( )
channel ? . close ( )
stopAcknowledgementWatch ? . ( )
stopRequestWatch ? . ( )
window . removeEventListener ( 'resize' , scheduleCodeHeightFit )
if ( fitFrame !== undefined )
cancelAnimationFrame ( fitFrame )
cleanupContent ( )
} )
< / script >
< template >
< span hidden aria -hidden = " true " / >
< / template >