@readium/navigator-html-injectables 2.8.1 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- function x(t){return(t.document.documentElement.dir||t.document.body.dir).toLowerCase()==="rtl"}function C(t){return(t.getComputedStyle(t.document.documentElement).writingMode||t.getComputedStyle(t.document.body).writingMode)==="vertical-lr"}function v(t){const e=t.getComputedStyle(t.document.documentElement).writingMode||t.getComputedStyle(t.document.body).writingMode;return e==="vertical-rl"||e==="vertical-lr"}function b(t){const e=v(t),r=e&&C(t),i=t.innerWidth,u=t.innerHeight,s=t.document.scrollingElement,f=s.scrollLeft,g=s.scrollTop,d=parseInt(t.getComputedStyle(t.document.documentElement).getPropertyValue("column-count")),l=e&&!r?s.scrollWidth-i+f:f,n=g;return{isVertical:e,isVertLR:r,viewportInlineSize:e?u:i,viewportBlockSize:e?i:u,pageInlineSize:e?u:i/(d||1),xDocOffset:l,yDocOffset:n,inlineScrollOffset:e?n:l,blockScrollOffset:e?l:n,inlineStart:o=>e?o.top:o.left,blockStart:o=>e?o.left:o.top,inlineSize:o=>e?o.height:o.width,blockSize:o=>e?o.width:o.height,applyPosition(o,m,p,a,y,c){o.style.position="absolute",e?(o.style.top=`${m*c}px`,o.style.left=`${p*c}px`,o.style.height=`${a*c}px`,o.style.width=`${y*c}px`):(o.style.left=`${m*c}px`,o.style.top=`${p*c}px`,o.style.width=`${a*c}px`,o.style.height=`${y*c}px`)},toRect(o,m,p,a){return e?new DOMRect(m,o,a,p):new DOMRect(o,m,p,a)}}}function S(t){return parseInt(t.getComputedStyle(t.document.documentElement).getPropertyValue("column-count"))}function h(t){const e=getComputedStyle(t),r=parseFloat(e.paddingTop||"0"),i=parseFloat(e.paddingBottom||"0");return t.clientHeight-r-i}function E(t){const e=S(t);if(!e)return!1;const r=t.document.querySelectorAll("div[id^='readium-virtual-page']");for(const l of r)l.remove();const i=r.length,u=t.document.scrollingElement.scrollWidth,s=t.visualViewport.width,g=Math.round(u/s*e)%e,d=e===1||g===0?0:e-g;if(d>0)for(let l=0;l<d;l++){const n=t.document.createElement("div");n.setAttribute("id",`readium-virtual-page-${l}`),n.dataset.readium="true",CSS.supports("break-before","column")?n.style.breakBefore="column":(CSS.supports("break-inside","avoid-column")&&(n.style.breakInside="avoid-column"),n.style.height=h(t.document.documentElement)+"px"),n.innerHTML="&#8203;",t.document.body.appendChild(n)}return i!==d}function M(t){const e=t.document.createElement("style");e.appendChild(t.document.createTextNode("*{}")),t.document.body.appendChild(e),t.document.body.removeChild(e)}export{E as appendVirtualColumnIfNeeded,M as forceWebkitRecalc,S as getColumnCountPerScreen,h as getContentHeight,x as isRTL,C as isVerticalLR,v as isVerticalWriting,b as makeWritingContext};
1
+ function S(t){return(t.document.documentElement.dir||t.document.body.dir).toLowerCase()==="rtl"}function C(t){return(t.getComputedStyle(t.document.documentElement).writingMode||t.getComputedStyle(t.document.body).writingMode)==="vertical-lr"}function v(t){const e=t.getComputedStyle(t.document.documentElement).writingMode||t.getComputedStyle(t.document.body).writingMode;return e==="vertical-rl"||e==="vertical-lr"}function b(t){const e=v(t),r=e&&C(t),i=t.innerWidth,u=t.innerHeight,s=t.document.scrollingElement,f=s.scrollLeft,g=s.scrollTop,d=parseInt(t.getComputedStyle(t.document.documentElement).getPropertyValue("column-count")),l=e&&!r?s.scrollWidth-i+f:f,n=g;return{isVertical:e,isVertLR:r,viewportInlineSize:e?u:i,viewportBlockSize:e?i:u,pageInlineSize:e?u:i/(d||1),xDocOffset:l,yDocOffset:n,inlineScrollOffset:e?n:l,blockScrollOffset:e?l:n,inlineStart:o=>e?o.top:o.left,blockStart:o=>e?o.left:o.top,inlineSize:o=>e?o.height:o.width,blockSize:o=>e?o.width:o.height,applyPosition(o,m,p,a,y,c){o.style.position="absolute",o.style.zIndex="2147483646",e?(o.style.top=`${m*c}px`,o.style.left=`${p*c}px`,o.style.height=`${a*c}px`,o.style.width=`${y*c}px`):(o.style.left=`${m*c}px`,o.style.top=`${p*c}px`,o.style.width=`${a*c}px`,o.style.height=`${y*c}px`)},toRect(o,m,p,a){return e?new DOMRect(m,o,a,p):new DOMRect(o,m,p,a)}}}function x(t){return parseInt(t.getComputedStyle(t.document.documentElement).getPropertyValue("column-count"))}function h(t){const e=getComputedStyle(t),r=parseFloat(e.paddingTop||"0"),i=parseFloat(e.paddingBottom||"0");return t.clientHeight-r-i}function E(t){const e=x(t);if(!e)return!1;const r=t.document.querySelectorAll("div[id^='readium-virtual-page']");for(const l of r)l.remove();const i=r.length,u=t.document.scrollingElement.scrollWidth,s=t.visualViewport.width,g=Math.round(u/s*e)%e,d=e===1||g===0?0:e-g;if(d>0)for(let l=0;l<d;l++){const n=t.document.createElement("div");n.setAttribute("id",`readium-virtual-page-${l}`),n.dataset.readium="true",CSS.supports("break-before","column")?n.style.breakBefore="column":(CSS.supports("break-inside","avoid-column")&&(n.style.breakInside="avoid-column"),n.style.height=h(t.document.documentElement)+"px"),n.innerHTML="&#8203;",t.document.body.appendChild(n)}return i!==d}function M(t){const e=t.document.createElement("style");e.appendChild(t.document.createTextNode("*{}")),t.document.body.appendChild(e),t.document.body.removeChild(e)}export{E as appendVirtualColumnIfNeeded,M as forceWebkitRecalc,x as getColumnCountPerScreen,h as getContentHeight,S as isRTL,C as isVerticalLR,v as isVerticalWriting,b as makeWritingContext};
@@ -1,4 +1,4 @@
1
- import{Locator as K}from"@readium/shared";import{Module as N}from"./Module.js";import{rangeFromLocator as P}from"../helpers/locator.js";import{getClientRectsNoOverlap as H,rectContainsPoint as E,getTextClientRects as U}from"../helpers/rect.js";import{getProperty as I}from"../helpers/css.js";import{sML as j,adjustColorForContrast as T,getContrastingTextColor as O,colorToRgba as M,isDarkColor as q}from"@readium/helpers";import{makeWritingContext as z}from"../helpers/document.js";import{sanitizeHTML as V}from"../helpers/sanitize.js";function x(S){switch(S){case d.Mask:return"rgba(255, 255, 255, 0.5)";case d.Highlight:case d.HighlightUnderline:return"#FFFF00";default:return"#FF0000"}}const d={Highlight:"highlight",HighlightUnderline:"highlightUnderline",Underline:"underline",Strikethrough:"strikethrough",Outline:"outline",TextColor:"textColor",Mask:"mask",Template:"template"};var G=(S=>(S.Wrap="wrap",S.Viewport="viewport",S.Bounds="bounds",S.Page="page",S))(G||{}),X=(S=>(S.Boxes="boxes",S.Bounds="bounds",S))(X||{});const Z=()=>"Highlight"in window,F=["IMG","IMAGE","AUDIO","VIDEO","SVG"];class Y{constructor(e,t,i,o){this.wnd=e,this.comms=t,this.id=i,this.name=o,this.items=[],this.lastItemId=0,this.container=void 0,this._activatable=!1,this._hoverable=!1,this.hoveredItem=void 0,this.experimentalHighlights=!1,this._tintSubKeys=new Map,this._subKeyCounter=0,this.maskSvg=void 0,this.shadowHost=void 0,this.shadowRoot=void 0,this.currentRender=0,Z()&&(this.experimentalHighlights=!0,this.notTextFlag=new Map),this.activationHandler=this.handleActivation.bind(this),this.wnd.document.addEventListener("pointerup",this.activationHandler),this.hoverHandler=this.handleHover.bind(this),this.wnd.document.addEventListener("pointermove",this.hoverHandler)}get activatable(){return this._activatable}set activatable(e){this._activatable=e}get hoverable(){return this._hoverable}get hasOverlayItems(){return!this.experimentalHighlights||(this.notTextFlag?.size??0)>0}set hoverable(e){if(this._hoverable=e,!e&&this.hoveredItem){const t=this.hoveredItem.range.getBoundingClientRect(),i=this.wnd.devicePixelRatio;this.comms.send("decoration_pointer_leave",{decorationId:this.hoveredItem.decoration.id,group:this.name,rect:{top:t.top*i,left:t.left*i,width:t.width*i,height:t.height*i}}),this.hoveredItem=void 0}}add(e){const t=`${this.id}-${this.lastItemId++}`,i=P(this.wnd.document,e.locator);if(!i){this.comms.log("Can't locate DOM range for decoration",e);return}const o=i.commonAncestorContainer;if(o.nodeType!==Node.TEXT_NODE&&this.experimentalHighlights&&(F.includes(o.nodeName.toUpperCase())&&this.notTextFlag?.set(t,!0),i.cloneContents().querySelector(F.join(", ").toLowerCase())&&this.notTextFlag?.set(t,!0),(o.textContent?.trim()||"").length===0&&this.notTextFlag?.set(t,!0)),this.experimentalHighlights&&!this.notTextFlag?.has(t)){const n=r=>{for(;r&&r.nodeType===Node.ELEMENT_NODE;){if(r.namespaceURI?.includes("svg"))return!0;r=r.parentNode}return!1};(n(i.startContainer)||n(i.endContainer))&&this.notTextFlag?.set(t,!0)}if(this.experimentalHighlights){const{type:n}=e.style,{layout:r,width:p,expand:a}=e.style;n!==d.TextColor&&(n===d.Outline||n===d.Template||n===d.Mask||r!==void 0&&r!=="boxes"||p!==void 0&&p!=="wrap"||a)&&this.notTextFlag?.set(t,!0)}const s={decoration:e,id:t,range:i,hitRects:[],clickableElements:void 0,container:void 0};this.items.push(s),this.layout(s),s.hitRects=this.clientRectsToDocCoords(H(s.range,!1,!1,(s.decoration.style.expand??0)+this.hitGap())),this.renderLayout([s])}remove(e){const t=this.items.findIndex(s=>s.decoration.id===e);if(t<0)return;const i=this.items[t],o=i.decoration.style?.type===d.Mask;if(this.items.splice(t,1),i.clickableElements=void 0,i.container&&(i.container.remove(),i.container=void 0),this.experimentalHighlights&&!this.notTextFlag?.has(i.id)&&i.highlightSubKey){const s=this.wnd.CSS.highlights;s.get(i.highlightSubKey)?.delete(i.range),this.items.some(r=>r.highlightSubKey===i.highlightSubKey)||s.delete(i.highlightSubKey);const n=this.wnd.document.getElementById(`${this.id}-style`);n&&this._rebuildHighlightStylesheet(n)}this.notTextFlag?.delete(i.id),this.hoveredItem===i&&(this.hoveredItem=void 0),o&&this.updateSharedMask()}update(e){this.remove(e.id),this.add(e)}clear(){this.clearContainer(),this.items.length=0,this.notTextFlag?.clear(),this.hoveredItem=void 0,this.maskSvg&&(this.maskSvg.remove(),this.maskSvg=void 0),this.shadowHost&&(this.shadowHost.remove(),this.shadowHost=void 0,this.shadowRoot=void 0)}destroy(){this.clear(),this.wnd.document.removeEventListener("pointerup",this.activationHandler),this.wnd.document.removeEventListener("pointermove",this.hoverHandler)}clientRectsToDocCoords(e,t=z(this.wnd)){const i=t.xDocOffset,o=t.yDocOffset;return i===0&&o===0?e:e.map(s=>({left:s.left+i,top:s.top+o,right:s.right+i,bottom:s.bottom+o,width:s.width,height:s.height}))}pointerToDocCoords(e){const t=z(this.wnd);return{docX:e.clientX+t.xDocOffset,docY:e.clientY+t.yDocOffset}}effectiveZoom(){if(!j.UA.Blink)return 1;const e=parseFloat(this.wnd.getComputedStyle(this.wnd.document.documentElement).zoom),t=parseFloat(this.wnd.getComputedStyle(this.wnd.document.body).zoom);return(e||1)*(t||1)}hitGap(){return 2*this.effectiveZoom()}handleActivation(e){if(!this._activatable)return;const{docX:t,docY:i}=this.pointerToDocCoords(e),o=this.wnd.devicePixelRatio;for(const s of this.items){let n;if(s.decoration.style.type===d.Template)for(const r of s.clickableElements??[]){const p=r.getBoundingClientRect();if(E(p,e.clientX,e.clientY,0)){n=p;break}}else for(const r of s.hitRects)if(E(r,t,i,0)){n=s.range.getBoundingClientRect();break}if(n){this.comms.send("decoration_activated",{decorationId:s.decoration.id,group:this.name,rect:{top:n.top*o,left:n.left*o,width:n.width*o,height:n.height*o},point:{x:e.clientX*o,y:e.clientY*o}});return}}}handleHover(e){if(!this._hoverable)return;const{docX:t,docY:i}=this.pointerToDocCoords(e),o=this.wnd.devicePixelRatio;let s,n;for(const r of this.items){if(r.decoration.style.type===d.Template)for(const p of r.clickableElements??[]){const a=p.getBoundingClientRect();if(E(a,e.clientX,e.clientY,0)){s=r,n=a;break}}else for(const p of r.hitRects)if(E(p,t,i,0)){s=r,n=r.range.getBoundingClientRect();break}if(s)break}if(s!==this.hoveredItem){if(this.hoveredItem){const p=this.hoveredItem.range.commonAncestorContainer.isConnected?this.hoveredItem.range.getBoundingClientRect():null;this.comms.send("decoration_pointer_leave",{decorationId:this.hoveredItem.decoration.id,group:this.name,rect:p?{top:p.top*o,left:p.left*o,width:p.width*o,height:p.height*o}:void 0,point:{x:e.clientX*o,y:e.clientY*o}})}this.hoveredItem=s,s&&n&&this.comms.send("decoration_pointer_enter",{decorationId:s.decoration.id,group:this.name,rect:{top:n.top*o,left:n.left*o,width:n.width*o,height:n.height*o},point:{x:e.clientX*o,y:e.clientY*o}})}}requestLayout(){this.wnd.cancelAnimationFrame(this.currentRender),this.clearContainer(),this.wnd.document.fonts.ready.then(()=>{this.currentRender=this.wnd.requestAnimationFrame(()=>{this.items.forEach(e=>{this.layout(e),e.hitRects=this.clientRectsToDocCoords(H(e.range,!1,!1,(e.decoration.style.expand??0)+this.hitGap()))}),this.renderLayout(this.items),this.updateSharedMask()})})}repositionOverlays(){let e=!1;const t=[],i=z(this.wnd);this.items.forEach(o=>{if(!(this.experimentalHighlights&&!this.notTextFlag?.has(o.id))){if(o.decoration.style?.type===d.Mask){e=!0;return}this.repositionItem(o,i)||(o.container?.remove(),this.layout(o),t.push(o)),o.hitRects=this.clientRectsToDocCoords(H(o.range,!1,!1,(o.decoration.style.expand??0)+this.hitGap()),i)}}),t.length&&this.renderLayout(t),e&&this.updateSharedMask()}experimentalLayout(e){const t=this.requireContainer(!0),i=this.wnd.CSS.highlights,o=e.decoration.style,s=o.type??d.Highlight,n=o.tint??x(s),r=o.width,p=o.layout,a=this._getSubKey(s,n);e.highlightSubKey&&(i.get(e.highlightSubKey)?.delete(e.range),e.highlightSubKey!==a&&!this.items.some(f=>f!==e&&f.highlightSubKey===e.highlightSubKey)&&i.delete(e.highlightSubKey)),e.highlightSubKey=a;let g;i.has(a)?g=i.get(a):(g=new this.wnd.Highlight,i.set(a,g));const h=(c,f)=>this.wnd.document.caretPositionFromPoint?.(c,f)??null;if(s===d.TextColor&&(p==="bounds"||r==="bounds"||r==="page")){const c=z(this.wnd);if(c.isVertical)console.warn("Vertical writing detected: caretPositionFromPoint has known bugs, falling back to original range"),g.add(e.range);else{const f=e.range.getBoundingClientRect();let k,y;r==="page"?(k=Math.floor(c.inlineStart(f)/c.pageInlineSize)*c.pageInlineSize,y=c.pageInlineSize):(k=c.inlineStart(f),y=c.inlineSize(f));const v=h(k,c.blockStart(f)+1),w=h(k+y,c.blockStart(f)+c.blockSize(f)-1);if(v&&w){const C=this.wnd.document.createRange();C.setStart(v.offsetNode,v.offset),C.setEnd(w.offsetNode,w.offset),g.add(C),e.range=C}else g.add(e.range)}}else g.add(e.range);const l=this.getBackgroundColor(),b=o.enforceContrast!==!1?T(n,l):n;let m;switch(s){case d.Underline:m=`::highlight(${a}) {
1
+ import{Locator as D}from"@readium/shared";import{Module as K}from"./Module.js";import{rangeFromLocator as U}from"../helpers/locator.js";import{getClientRectsNoOverlap as z,rectContainsPoint as H,getTextClientRects as q}from"../helpers/rect.js";import{getProperty as I}from"../helpers/css.js";import{sML as O,adjustColorForContrast as E,getContrastingTextColor as F,colorToRgba as M,isDarkColor as W}from"@readium/helpers";import{makeWritingContext as T}from"../helpers/document.js";import{sanitizeHTML as j}from"../helpers/sanitize.js";function $(S){switch(S){case d.Mask:return"rgba(255, 255, 255, 0.5)";case d.Highlight:case d.HighlightUnderline:return"#FFFF00";default:return"#FF0000"}}const d={Highlight:"highlight",HighlightUnderline:"highlightUnderline",Underline:"underline",Strikethrough:"strikethrough",Outline:"outline",TextColor:"textColor",Mask:"mask",Template:"template"};var V=(S=>(S.Wrap="wrap",S.Viewport="viewport",S.Bounds="bounds",S.Page="page",S))(V||{}),Z=(S=>(S.Boxes="boxes",S.Bounds="bounds",S))(Z||{});const G=()=>"Highlight"in window,P=["IMG","IMAGE","AUDIO","VIDEO","SVG"],X=new Set(["SUP","SUB","SMALL","CODE"]);class Y{constructor(e,t,i,o){this.wnd=e,this.comms=t,this.id=i,this.name=o,this.items=[],this.lastItemId=0,this.container=void 0,this._activatable=!1,this._hoverable=!1,this.hoveredItem=void 0,this.experimentalHighlights=!1,this._tintSubKeys=new Map,this._subKeyCounter=0,this.pendingWebkitReflow=null,this.maskSvg=void 0,this.shadowHost=void 0,this.shadowRoot=void 0,this.currentRender=0,G()&&(this.experimentalHighlights=!0,this.notTextFlag=new Map),this.activationHandler=this.handleActivation.bind(this),this.wnd.document.addEventListener("pointerup",this.activationHandler),this.hoverHandler=this.handleHover.bind(this),this.wnd.document.addEventListener("pointermove",this.hoverHandler)}get activatable(){return this._activatable}set activatable(e){this._activatable=e}get hoverable(){return this._hoverable}get hasOverlayItems(){return!this.experimentalHighlights||(this.notTextFlag?.size??0)>0}set hoverable(e){if(this._hoverable=e,!e&&this.hoveredItem){const t=this.hoveredItem.range.getBoundingClientRect(),i=this.wnd.devicePixelRatio;this.comms.send("decoration_pointer_leave",{decorationId:this.hoveredItem.decoration.id,group:this.name,rect:{top:t.top*i,left:t.left*i,width:t.width*i,height:t.height*i}}),this.hoveredItem=void 0}}add(e){const t=`${this.id}-${this.lastItemId++}`,i=U(this.wnd.document,e.locator);if(!i){this.comms.log("Can't locate DOM range for decoration",e);return}const o=i.commonAncestorContainer;if(o.nodeType!==Node.TEXT_NODE&&this.experimentalHighlights&&(P.includes(o.nodeName.toUpperCase())&&this.notTextFlag?.set(t,!0),i.cloneContents().querySelector(P.join(", ").toLowerCase())&&this.notTextFlag?.set(t,!0),(o.textContent?.trim()||"").length===0&&this.notTextFlag?.set(t,!0)),this.experimentalHighlights&&!this.notTextFlag?.has(t)){const n=r=>{for(;r&&r.nodeType===Node.ELEMENT_NODE;){if(r.namespaceURI?.includes("svg"))return!0;r=r.parentNode}return!1};(n(i.startContainer)||n(i.endContainer))&&this.notTextFlag?.set(t,!0)}if(this.experimentalHighlights){const{type:n}=e.style,{layout:r,width:p,expand:a}=e.style;n!==d.TextColor&&(n===d.Outline||n===d.Template||n===d.Mask||r!==void 0&&r!=="boxes"||p!==void 0&&p!=="wrap"||a)&&this.notTextFlag?.set(t,!0)}const s={decoration:e,id:t,range:i,hitRects:[],clickableElements:void 0,container:void 0};this.items.push(s),this.layout(s),s.hitRects=this.clientRectsToDocCoords(z(s.range,!1,!1,(s.decoration.style.expand??0)+this.hitGap())),this.renderLayout([s])}remove(e){const t=this.items.findIndex(s=>s.decoration.id===e);if(t<0)return;const i=this.items[t],o=i.decoration.style?.type===d.Mask;if(this.items.splice(t,1),i.clickableElements=void 0,i.container&&(i.container.remove(),i.container=void 0),this.experimentalHighlights&&!this.notTextFlag?.has(i.id)&&i.highlightSubKey){const s=this.wnd.CSS.highlights;s.get(i.highlightSubKey)?.delete(i.range),this.items.some(r=>r.highlightSubKey===i.highlightSubKey)||s.delete(i.highlightSubKey);const n=this.wnd.document.getElementById(`${this.id}-style`);n&&this._rebuildHighlightStylesheet(n),this.scheduleWebkitRepaintFix([i])}this.notTextFlag?.delete(i.id),this.hoveredItem===i&&(this.hoveredItem=void 0),o&&this.updateSharedMask()}update(e){this.remove(e.id),this.add(e)}clear(){this.experimentalHighlights&&this.scheduleWebkitRepaintFix(this.items),this.clearContainer(),this.items.length=0,this.notTextFlag?.clear(),this.hoveredItem=void 0,this.maskSvg&&(this.maskSvg.remove(),this.maskSvg=void 0),this.shadowHost&&(this.shadowHost.remove(),this.shadowHost=void 0,this.shadowRoot=void 0)}destroy(){this.clear(),this.wnd.document.removeEventListener("pointerup",this.activationHandler),this.wnd.document.removeEventListener("pointermove",this.hoverHandler)}isWebkitRepaintProne(e){const t=this.wnd.getComputedStyle(e);if(X.has(e.tagName.toUpperCase())||t.display==="inline-block"||t.verticalAlign!=="baseline")return!0;const i=e.parentElement?this.wnd.getComputedStyle(e.parentElement):null;return!!i&&parseFloat(t.fontSize)!==parseFloat(i.fontSize)}findWebkitRepaintProneElement(e){let t=e.startContainer.nodeType===Node.ELEMENT_NODE?e.startContainer:e.startContainer.parentElement;for(;t;){if(this.isWebkitRepaintProne(t))return t;if(this.wnd.getComputedStyle(t).display!=="inline")break;t=t.parentElement}const i=e.commonAncestorContainer,o=i.nodeType===Node.ELEMENT_NODE?i:i.parentElement;if(!o)return null;const s=this.wnd.document.createTreeWalker(o,NodeFilter.SHOW_ELEMENT,n=>e.intersectsNode(n)?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_REJECT);for(let n=s.nextNode();n;n=s.nextNode())if(this.isWebkitRepaintProne(n))return n;return null}forceWebkitBlockReflow(e){let t=e;for(;t;){const n=this.wnd.getComputedStyle(t).display;if(n!=="inline"&&n!=="inline-block")break;t=t.parentElement}if(!t||!t.isConnected)return;const i=t,o=i.style.getPropertyValue("transform"),s=i.style.getPropertyPriority("transform");i.style.setProperty("transform","translateZ(0.001px)","important"),i.offsetHeight,i.style.setProperty("transform","translateZ(0px)","important"),i.offsetHeight,o?i.style.setProperty("transform",o,s):i.style.removeProperty("transform")}scheduleWebkitRepaintFix(e){if(O.UA.WebKit)for(const t of e){if(this.notTextFlag?.has(t.id)||!t.highlightSubKey)continue;const i=this.findWebkitRepaintProneElement(t.range);i&&this.queueWebkitReflow(i)}}queueWebkitReflow(e){this.pendingWebkitReflow||(this.pendingWebkitReflow=new Set,this.wnd.requestAnimationFrame(()=>{const t=this.pendingWebkitReflow;this.pendingWebkitReflow=null,t?.forEach(i=>this.forceWebkitBlockReflow(i))})),this.pendingWebkitReflow.add(e)}clientRectsToDocCoords(e,t=T(this.wnd)){const i=t.xDocOffset,o=t.yDocOffset;return i===0&&o===0?e:e.map(s=>({left:s.left+i,top:s.top+o,right:s.right+i,bottom:s.bottom+o,width:s.width,height:s.height}))}pointerToDocCoords(e){const t=T(this.wnd);return{docX:e.clientX+t.xDocOffset,docY:e.clientY+t.yDocOffset}}effectiveZoom(){if(!O.UA.Blink)return 1;const e=parseFloat(this.wnd.getComputedStyle(this.wnd.document.documentElement).zoom),t=parseFloat(this.wnd.getComputedStyle(this.wnd.document.body).zoom);return(e||1)*(t||1)}hitGap(){return 2*this.effectiveZoom()}handleActivation(e){if(!this._activatable)return;const{docX:t,docY:i}=this.pointerToDocCoords(e),o=this.wnd.devicePixelRatio;for(const s of this.items){let n;if(s.decoration.style.type===d.Template)for(const r of s.clickableElements??[]){const p=r.getBoundingClientRect();if(H(p,e.clientX,e.clientY,0)){n=p;break}}else for(const r of s.hitRects)if(H(r,t,i,0)){n=s.range.getBoundingClientRect();break}if(n){this.comms.send("decoration_activated",{decorationId:s.decoration.id,group:this.name,rect:{top:n.top*o,left:n.left*o,width:n.width*o,height:n.height*o},point:{x:e.clientX*o,y:e.clientY*o}});return}}}handleHover(e){if(!this._hoverable)return;const{docX:t,docY:i}=this.pointerToDocCoords(e),o=this.wnd.devicePixelRatio;let s,n;for(const r of this.items){if(r.decoration.style.type===d.Template)for(const p of r.clickableElements??[]){const a=p.getBoundingClientRect();if(H(a,e.clientX,e.clientY,0)){s=r,n=a;break}}else for(const p of r.hitRects)if(H(p,t,i,0)){s=r,n=r.range.getBoundingClientRect();break}if(s)break}if(s!==this.hoveredItem){if(this.hoveredItem){const p=this.hoveredItem.range.commonAncestorContainer.isConnected?this.hoveredItem.range.getBoundingClientRect():null;this.comms.send("decoration_pointer_leave",{decorationId:this.hoveredItem.decoration.id,group:this.name,rect:p?{top:p.top*o,left:p.left*o,width:p.width*o,height:p.height*o}:void 0,point:{x:e.clientX*o,y:e.clientY*o}})}this.hoveredItem=s,s&&n&&this.comms.send("decoration_pointer_enter",{decorationId:s.decoration.id,group:this.name,rect:{top:n.top*o,left:n.left*o,width:n.width*o,height:n.height*o},point:{x:e.clientX*o,y:e.clientY*o}})}}requestLayout(){this.wnd.cancelAnimationFrame(this.currentRender),this.clearContainer(),this.wnd.document.fonts.ready.then(()=>{this.currentRender=this.wnd.requestAnimationFrame(()=>{this.items.forEach(e=>{this.layout(e),e.hitRects=this.clientRectsToDocCoords(z(e.range,!1,!1,(e.decoration.style.expand??0)+this.hitGap()))}),this.renderLayout(this.items),this.updateSharedMask()})})}repositionOverlays(){let e=!1;const t=[],i=T(this.wnd);this.items.forEach(o=>{if(!(this.experimentalHighlights&&!this.notTextFlag?.has(o.id))){if(o.decoration.style?.type===d.Mask){e=!0;return}this.repositionItem(o,i)||(o.container?.remove(),this.layout(o),t.push(o)),o.hitRects=this.clientRectsToDocCoords(z(o.range,!1,!1,(o.decoration.style.expand??0)+this.hitGap()),i)}}),t.length&&this.renderLayout(t),e&&this.updateSharedMask()}experimentalLayout(e){const t=this.requireContainer(!0),i=this.wnd.CSS.highlights,o=e.decoration.style,s=o.type??d.Highlight,n=o.tint??$(s),r=o.width,p=o.layout,a=this._getSubKey(s,n);e.highlightSubKey&&(i.get(e.highlightSubKey)?.delete(e.range),e.highlightSubKey!==a&&!this.items.some(f=>f!==e&&f.highlightSubKey===e.highlightSubKey)&&i.delete(e.highlightSubKey)),e.highlightSubKey=a;let g;i.has(a)?g=i.get(a):(g=new this.wnd.Highlight,i.set(a,g));const l=(c,f)=>this.wnd.document.caretPositionFromPoint?.(c,f)??null;if(s===d.TextColor&&(p==="bounds"||r==="bounds"||r==="page")){const c=T(this.wnd);if(c.isVertical)console.warn("Vertical writing detected: caretPositionFromPoint has known bugs, falling back to original range"),g.add(e.range);else{const f=e.range.getBoundingClientRect();let k,v;r==="page"?(k=Math.floor(c.inlineStart(f)/c.pageInlineSize)*c.pageInlineSize,v=c.pageInlineSize):(k=c.inlineStart(f),v=c.inlineSize(f));const y=l(k,c.blockStart(f)+1),w=l(k+v,c.blockStart(f)+c.blockSize(f)-1);if(y&&w){const C=this.wnd.document.createRange();C.setStart(y.offsetNode,y.offset),C.setEnd(w.offsetNode,w.offset),g.add(C),e.range=C}else g.add(e.range)}}else g.add(e.range);const h=this.getBackgroundColor(),b=o.enforceContrast!==!1?E(n,h):n;let m;switch(s){case d.Underline:m=`::highlight(${a}) {
2
2
  text-decoration: underline;
3
3
  text-decoration-color: ${b};
4
4
  text-decoration-thickness: 0.1em;
@@ -11,14 +11,14 @@ import{Locator as K}from"@readium/shared";import{Module as N}from"./Module.js";i
11
11
  outline-offset: 1px;
12
12
  }`;break;case d.TextColor:m=`::highlight(${a}) {
13
13
  color: ${b};
14
- }`;break;case d.HighlightUnderline:{const{r:c,g:f,b:k}=M(b),y=`rgba(${c}, ${f}, ${k}, 0.3)`;m=`::highlight(${a}) {
15
- color: ${O(b,l)};
16
- background-color: ${y};
14
+ }`;break;case d.HighlightUnderline:{const{r:c,g:f,b:k}=M(b),v=`rgba(${c}, ${f}, ${k}, 0.3)`;m=`::highlight(${a}) {
15
+ color: ${F(b,h)};
16
+ background-color: ${v};
17
17
  text-decoration: underline;
18
18
  text-decoration-color: ${b};
19
19
  text-decoration-thickness: 0.1em;
20
20
  }`;break}case d.Highlight:default:m=`::highlight(${a}) {
21
- color: ${O(b,l)};
21
+ color: ${F(b,h)};
22
22
  background-color: ${b};
23
23
  }`}e.highlightCSS=m,this._rebuildHighlightStylesheet(t)}_getSubKey(e,t){const i=`${e}::${t}`;let o=this._tintSubKeys.get(i);return o||(o=`${this.id}--${this._subKeyCounter++}`,this._tintSubKeys.set(i,o)),o}_rebuildHighlightStylesheet(e){const t=new Set,i=[];for(const o of this.items)o.highlightSubKey&&o.highlightCSS&&!t.has(o.highlightSubKey)&&(t.add(o.highlightSubKey),i.push(o.highlightCSS));e.innerHTML=i.join(`
24
- `)}positionElement(e,t,i,o,s,n,r=0){const p=e.decoration?.style?.width,a=s;switch(p){case"viewport":{const g=Math.floor(t.inlineStart(a)/t.viewportInlineSize)*t.viewportInlineSize;t.applyPosition(o,g+t.inlineScrollOffset+r,t.blockStart(a)+t.blockScrollOffset,t.viewportInlineSize-2*r,t.blockSize(a),i);break}case"page":{const g=Math.floor(t.inlineStart(a)/t.pageInlineSize)*t.pageInlineSize;t.applyPosition(o,g+t.inlineScrollOffset+r,t.blockStart(a)+t.blockScrollOffset,t.pageInlineSize-2*r,t.blockSize(a),i);break}case"bounds":{t.applyPosition(o,t.inlineStart(n)+t.inlineScrollOffset,t.blockStart(a)+t.blockScrollOffset,t.inlineSize(n),t.blockSize(a),i);break}default:t.applyPosition(o,t.inlineStart(a)+t.inlineScrollOffset,t.blockStart(a)+t.blockScrollOffset,t.inlineSize(a),t.blockSize(a),i)}}computeOverlayRects(e,t,i,o){if(e.decoration?.style?.layout==="bounds")return[o?{left:i.left-o,right:i.right+o,top:i.top-o,bottom:i.bottom+o,width:i.width+o*2,height:i.height+o*2}:i];const n=e.decoration.style.type,r=n===d.Underline||n===d.Strikethrough,p=n===d.Strikethrough,a=r?U(e.range,["rt","rp"]):e.range;let g=H(a,!0,t.isVertical,r?0:o);return g=g.sort((h,l)=>t.isVertical?(t.isVertLR?1:-1)*(h.left-l.left):h.top-l.top),g.map(h=>{let l=h;if(p){const u=t.blockSize(h)*.1,m=t.blockStart(h)+t.blockSize(h)/2-u/2;l=t.isVertical?{left:m,right:m+u,top:h.top,bottom:h.bottom,width:u,height:h.height}:{top:m,bottom:m+u,left:h.left,right:h.right,height:u,width:h.width}}return o&&r&&(l=t.isVertical?{...l,top:l.top-o,bottom:l.bottom+o,height:l.height+o*2}:{...l,left:l.left-o,right:l.right+o,width:l.width+o*2}),l})}repositionItem(e,t){if(!e.container)return!0;const i=e.decoration.style;if(i.type!==d.Template){const g=i.type??d.Highlight;if(g===d.TextColor||g===d.Mask)return!0}const o=1/this.effectiveZoom(),s=i.expand??0,n=e.range.getBoundingClientRect(),r=(()=>{if(i.type!==d.Outline)return 0;const g=i.width;return g==="page"||g==="viewport"?3:0})(),p=this.computeOverlayRects(e,t,n,s),a=Array.from(e.container.children);return a.length!==p.length?!1:(a.forEach((g,h)=>this.positionElement(e,t,o,g,p[h],n,r)),!0)}layout(e){if(this.experimentalHighlights&&!this.notTextFlag?.has(e.id))return this.experimentalLayout(e);const t=this.wnd.document.createElement("div");t.setAttribute("id",e.id),t.dataset.highlightId=e.decoration.id,t.style.setProperty("pointer-events","none");const i=z(this.wnd),o=1/this.effectiveZoom(),s=e.decoration.style.expand??0,n=e.range.getBoundingClientRect(),r=e.decoration.style,p=(()=>{if(r.type!==d.Outline)return 0;const h=r.width;return h==="page"||h==="viewport"?3:0})();let a;if(r.type===d.Template){r.stylesheet&&this.injectCustomStylesheet(r.stylesheet);const h=V(this.wnd,r.element);if(!h){e.container=t,e.clickableElements=[];return}h.style.setProperty("pointer-events","none"),a=h}else{const h=r,l=h.type??d.Highlight,u=h.tint??x(l);if(l===d.TextColor){e.container=t,e.clickableElements=[];return}if(l===d.Mask){e.container=t,e.clickableElements=[],this.updateSharedMask();return}const b=this.getCurrentDarkMode(),m=this.getBackgroundColor(),c=h.enforceContrast!==!1,f=(()=>{switch(l){case d.Underline:{const v=c?T(u,m):u,w=h.layout==="bounds",[C,$]=i.isVertical?["border-right","border-left"]:["border-bottom","border-top"];return[w?`${$}: 0.1em solid ${v} !important`:null,`${C}: 0.1em solid ${v} !important`,"background-color: transparent !important","box-sizing: border-box !important"].filter(Boolean).join("; ")}case d.Strikethrough:{const v=c?T(u,m):u;return h.layout==="bounds"?[`background: repeating-linear-gradient(-45deg, transparent, transparent 19px, ${v} 19px, ${v} 20px) !important`,"background-color: transparent !important","box-sizing: border-box !important"].join("; "):[`background-color: ${v} !important`,"box-sizing: border-box !important"].join("; ")}case d.Outline:return[`outline: 2px solid ${c?T(u,m):u} !important`,"outline-offset: 1px !important","background-color: transparent !important","box-sizing: border-box !important"].join("; ");case d.HighlightUnderline:{const v=c?T(u,m):u,{r:w,g:C,b:$}=M(v),_=`rgba(${w}, ${C}, ${$}, 0.3)`,B=h.layout==="bounds",[D,L]=i.isVertical?["border-right","border-left"]:["border-bottom","border-top"];return[`background-color: ${_} !important`,B?`${L}: 0.1em solid ${v} !important`:null,`${D}: 0.1em solid ${v} !important`,"box-sizing: border-box !important"].filter(Boolean).join("; ")}case d.Highlight:default:return[`background-color: ${c?T(u,m):u} !important`,`mix-blend-mode: ${b?"exclusion":"multiply"} !important`,"opacity: 1 !important","box-sizing: border-box !important","transform: translateZ(0) !important"].join("; ")}})(),k=this.wnd.document.createElement("template");k.innerHTML=`<div data-readium="true" class="readium-${l}" style="${f}"></div>`.trim(),a=k.content.firstElementChild}const g=this.computeOverlayRects(e,i,n,s);for(const h of g){const l=a.cloneNode(!0);l.style.setProperty("pointer-events","none"),this.positionElement(e,i,o,l,h,n,p),t.append(l)}e.container=t,e.clickableElements=Array.from(t.querySelectorAll("[data-activable='1']")),e.clickableElements.length||(e.clickableElements=Array.from(t.children))}renderLayout(e){this.wnd.cancelAnimationFrame(this.currentRender),this.currentRender=this.wnd.requestAnimationFrame(()=>{if(e=e.filter(i=>!this.experimentalHighlights||!!this.notTextFlag?.has(i.id)),!e||e.length===0)return;this.requireContainer().append(...e.map(i=>i.container).filter(i=>!!i))})}requireContainer(e=!1){if(e){let t;return this.wnd.document.getElementById(`${this.id}-style`)?t=this.wnd.document.getElementById(`${this.id}-style`):(t=this.wnd.document.createElement("style"),t.dataset.readium="true",t.id=`${this.id}-style`,this.wnd.document.head.appendChild(t)),t}return this.container||(this.shadowRoot||(this.shadowHost=this.wnd.document.createElement("div"),this.shadowHost.style.cssText="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none",this.wnd.document.body.appendChild(this.shadowHost),this.shadowRoot=this.shadowHost.attachShadow({mode:"open"})),this.container=this.wnd.document.createElement("div"),this.container.setAttribute("id",this.id),this.container.dataset.group=this.name,this.container.dataset.readium="true",this.container.style.setProperty("pointer-events","none"),this.container.style.display="contents",this.shadowRoot.appendChild(this.container)),this.container}getCurrentDarkMode(){return I(this.wnd,"--USER__appearance")==="readium-night-on"||q(this.getBackgroundColor())}getBackgroundColor(){return I(this.wnd,"--USER__backgroundColor")||this.wnd.getComputedStyle(this.wnd.document.documentElement).getPropertyValue("background-color")}updateSharedMask(){const e=this.items.filter(l=>l.decoration.style?.type===d.Mask);if(e.length===0){this.maskSvg&&(this.maskSvg.remove(),this.maskSvg=void 0),this.shadowHost&&!this.container&&(this.shadowHost.remove(),this.shadowRoot=void 0,this.shadowHost=void 0);return}const t=z(this.wnd),i=1/this.effectiveZoom(),o=this.wnd.document.documentElement,s=o.scrollWidth,n=o.scrollHeight,r=[];for(const l of e){const u=l.decoration.style,b=u.layout??"boxes",m=u.width??"wrap",c=u.expand??0,f=l.range.getBoundingClientRect(),k=b==="bounds"?[c?{left:f.left-c,top:f.top-c,right:f.right+c,bottom:f.bottom+c,width:f.width+c*2,height:f.height+c*2}:f]:H(l.range,!1,!1,c);for(const y of k){let v;switch(m){case"viewport":{const w=Math.floor(t.inlineStart(y)/t.viewportInlineSize)*t.viewportInlineSize;v=t.toRect(w,t.blockStart(y),t.viewportInlineSize,t.blockSize(y));break}case"page":{const w=Math.floor(t.inlineStart(y)/t.pageInlineSize)*t.pageInlineSize;v=t.toRect(w,t.blockStart(y),t.pageInlineSize,t.blockSize(y));break}case"bounds":{v=t.toRect(t.inlineStart(f),t.blockStart(y),t.inlineSize(f),t.blockSize(y));break}default:v=t.toRect(t.inlineStart(y),t.blockStart(y),t.inlineSize(y),t.blockSize(y))}r.push(v)}}const p=[`M0 0 H${s} V${n} H0 Z`,...r.map(l=>{const u=(l.left+t.xDocOffset)*i,b=(l.top+t.yDocOffset)*i,m=(l.right+t.xDocOffset)*i,c=(l.bottom+t.yDocOffset)*i;return`M${u} ${b} H${m} V${c} H${u} Z`})].join(" "),a="http://www.w3.org/2000/svg";if(!this.maskSvg){this.shadowRoot||(this.shadowHost=this.wnd.document.createElement("div"),this.shadowHost.style.cssText="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none",this.wnd.document.body.appendChild(this.shadowHost),this.shadowRoot=this.shadowHost.attachShadow({mode:"open"})),this.maskSvg=this.wnd.document.createElementNS(a,"svg"),this.maskSvg.style.cssText=`position:absolute;top:0;left:0;width:${s}px;height:${n}px;pointer-events:none;z-index:9999`,this.maskSvg.dataset.readium="true";const l=this.wnd.document.createElementNS(a,"defs"),u=this.wnd.document.createElementNS(a,"clipPath"),b=`${this.id}-mask-clip`;u.setAttribute("id",b),u.setAttribute("clipPathUnits","userSpaceOnUse");const m=this.wnd.document.createElementNS(a,"path");m.setAttribute("clip-rule","evenodd"),u.appendChild(m),l.appendChild(u),this.maskSvg.appendChild(l);const c=this.wnd.document.createElementNS(a,"rect");c.setAttribute("id",`${this.id}-mask-rect`),c.setAttribute("clip-path",`url(#${b})`),c.style.pointerEvents="none",this.maskSvg.appendChild(c),this.shadowRoot.appendChild(this.maskSvg)}this.maskSvg.style.width=`${s}px`,this.maskSvg.style.height=`${n}px`;const g=this.maskSvg.querySelector("path");g&&g.setAttribute("d",p);const h=this.maskSvg.querySelector("rect");if(h){const u=e[0].decoration.style.tint,b=u??this.getBackgroundColor()??x(d.Mask),m=u?"1":"0.5";h.setAttribute("x","0"),h.setAttribute("y","0"),h.setAttribute("width",String(s)),h.setAttribute("height",String(n)),h.setAttribute("fill",b),h.setAttribute("fill-opacity",m)}}injectCustomStylesheet(e){const t=`${this.id}-custom-style`;let i=this.wnd.document.getElementById(t);i||(i=this.wnd.document.createElement("style"),i.id=t,i.dataset.readium="true",this.wnd.document.head.appendChild(i)),i.innerHTML=e}clearContainer(){if(this.experimentalHighlights){const e=this.wnd.CSS.highlights;for(const t of this._tintSubKeys.values())e.delete(t);this._tintSubKeys.clear(),this._subKeyCounter=0}this.wnd.document.getElementById(`${this.id}-custom-style`)?.remove(),this.container&&(this.container.remove(),this.container=void 0)}}const R=class R extends N{constructor(){super(...arguments),this.observedResizeTargets=new Map,this.resizeFrame=0,this.lastGroupId=0,this.groups=new Map,this.handleResizer=this.handleResize.bind(this),this.scrollScheduled=!1,this.handleScroller=this.handleScroll.bind(this)}cleanup(){this.groups.forEach(e=>e.destroy()),this.groups.clear(),this.observedResizeTargets.clear()}updateHighlightStyles(){this.groups.forEach(e=>{e.requestLayout()})}handleResize(){this.wnd.clearTimeout(this.resizeFrame),this.resizeFrame=this.wnd.setTimeout(()=>{this.groups.forEach(e=>e.requestLayout())},50)}handleScroll(){this.scrollScheduled||(this.scrollScheduled=!0,this.wnd.requestAnimationFrame(()=>{this.scrollScheduled=!1,this.groups.forEach(e=>{e.hasOverlayItems&&e.repositionOverlays()})}))}mount(e,t){return this.wnd=e,t.register("decorate",R.moduleName,(i,o)=>{const s=i;(s.action==="add"||s.action==="update")&&s.decoration.locator&&(s.decoration.locator=K.deserialize(s.decoration.locator)),this.groups.has(s.group)||this.groups.set(s.group,new Y(e,t,`readium-decoration-${this.lastGroupId++}`,s.group));const n=this.groups.get(s.group);switch(s.action){case"add":n?.add(s.decoration);break;case"remove":n?.remove(s.decoration.id);break;case"clear":n?.clear();break;case"update":n?.update(s.decoration);break}o(!0)}),t.register("decoration_resize",R.moduleName,(i,o)=>{const s=i;s.action==="watch"?this.addDecorationResizeTarget(s.selector):this.removeDecorationResizeTarget(s.selector),o(!0)}),t.register("decoration_activatable",R.moduleName,(i,o)=>{const s=i,n=this.groups.get(s.group);n&&(n.activatable=s.activatable),o(!0)}),t.register("decoration_hoverable",R.moduleName,(i,o)=>{const s=i,n=this.groups.get(s.group);n&&(n.hoverable=s.hoverable),o(!0)}),this.resizeObserver=new ResizeObserver(()=>e.requestAnimationFrame(()=>this.handleResize())),this.resizeObserver.observe(e.document.documentElement),e.addEventListener("orientationchange",this.handleResizer),e.addEventListener("resize",this.handleResizer),e.addEventListener("scroll",this.handleScroller,{passive:!0,capture:!0}),this.styleObserver=new MutationObserver(i=>{i.some(s=>s.type==="attributes"&&s.attributeName==="style"&&s.oldValue!==s.target.getAttribute("style"))&&this.updateHighlightStyles()}),this.styleObserver.observe(e.document.documentElement,{attributes:!0,attributeFilter:["style"],attributeOldValue:!0}),t.log("Decorator Mounted"),!0}unmount(e,t){return e.removeEventListener("orientationchange",this.handleResizer),e.removeEventListener("resize",this.handleResizer),e.removeEventListener("scroll",this.handleScroller,{capture:!0}),t.unregisterAll(R.moduleName),this.resizeObserver.disconnect(),this.styleObserver.disconnect(),this.cleanup(),t.log("Decorator Unmounted"),!0}addDecorationResizeTarget(e){const t=this.observedResizeTargets.get(e);t&&this.resizeObserver.unobserve(t);const i=this.wnd.document.querySelector(e);i?(this.resizeObserver.observe(i),this.observedResizeTargets.set(e,i)):this.observedResizeTargets.delete(e)}removeDecorationResizeTarget(e){const t=this.observedResizeTargets.get(e);t&&(this.resizeObserver.unobserve(t),this.observedResizeTargets.delete(e))}};R.moduleName="decorator";let A=R;export{X as DecorationLayout,d as DecorationStyleType,G as DecorationWidth,A as Decorator};
24
+ `)}positionElement(e,t,i,o,s,n,r=0){const p=e.decoration?.style?.width,a=s;switch(p){case"viewport":{const g=Math.floor(t.inlineStart(a)/t.viewportInlineSize)*t.viewportInlineSize;t.applyPosition(o,g+t.inlineScrollOffset+r,t.blockStart(a)+t.blockScrollOffset,t.viewportInlineSize-2*r,t.blockSize(a),i);break}case"page":{const g=Math.floor(t.inlineStart(a)/t.pageInlineSize)*t.pageInlineSize;t.applyPosition(o,g+t.inlineScrollOffset+r,t.blockStart(a)+t.blockScrollOffset,t.pageInlineSize-2*r,t.blockSize(a),i);break}case"bounds":{t.applyPosition(o,t.inlineStart(n)+t.inlineScrollOffset,t.blockStart(a)+t.blockScrollOffset,t.inlineSize(n),t.blockSize(a),i);break}default:t.applyPosition(o,t.inlineStart(a)+t.inlineScrollOffset,t.blockStart(a)+t.blockScrollOffset,t.inlineSize(a),t.blockSize(a),i)}}computeOverlayRects(e,t,i,o){if(e.decoration?.style?.layout==="bounds")return[o?{left:i.left-o,right:i.right+o,top:i.top-o,bottom:i.bottom+o,width:i.width+o*2,height:i.height+o*2}:i];const n=e.decoration.style.type,r=n===d.Underline||n===d.Strikethrough,p=n===d.Strikethrough,a=r?q(e.range,["rt","rp"]):e.range;let g=z(a,!0,t.isVertical,r?0:o);return g=g.sort((l,h)=>t.isVertical?(t.isVertLR?1:-1)*(l.left-h.left):l.top-h.top),g.map(l=>{let h=l;if(p){const u=t.blockSize(l)*.1,m=t.blockStart(l)+t.blockSize(l)/2-u/2;h=t.isVertical?{left:m,right:m+u,top:l.top,bottom:l.bottom,width:u,height:l.height}:{top:m,bottom:m+u,left:l.left,right:l.right,height:u,width:l.width}}return o&&r&&(h=t.isVertical?{...h,top:h.top-o,bottom:h.bottom+o,height:h.height+o*2}:{...h,left:h.left-o,right:h.right+o,width:h.width+o*2}),h})}repositionItem(e,t){if(!e.container)return!0;const i=e.decoration.style;if(i.type!==d.Template){const g=i.type??d.Highlight;if(g===d.TextColor||g===d.Mask)return!0}const o=1/this.effectiveZoom(),s=i.expand??0,n=e.range.getBoundingClientRect(),r=(()=>{if(i.type!==d.Outline)return 0;const g=i.width;return g==="page"||g==="viewport"?3:0})(),p=this.computeOverlayRects(e,t,n,s),a=Array.from(e.container.children);return a.length!==p.length?!1:(a.forEach((g,l)=>this.positionElement(e,t,o,g,p[l],n,r)),!0)}layout(e){if(this.experimentalHighlights&&!this.notTextFlag?.has(e.id))return this.experimentalLayout(e);const t=this.wnd.document.createElement("div");t.setAttribute("id",e.id),t.dataset.highlightId=e.decoration.id,t.style.setProperty("pointer-events","none");const i=T(this.wnd),o=1/this.effectiveZoom(),s=e.decoration.style.expand??0,n=e.range.getBoundingClientRect(),r=e.decoration.style,p=(()=>{if(r.type!==d.Outline)return 0;const l=r.width;return l==="page"||l==="viewport"?3:0})();let a;if(r.type===d.Template){r.stylesheet&&this.injectCustomStylesheet(r.stylesheet);const l=j(this.wnd,r.element);if(!l){e.container=t,e.clickableElements=[];return}l.style.setProperty("pointer-events","none"),a=l}else{const l=r,h=l.type??d.Highlight,u=l.tint??$(h);if(h===d.TextColor){e.container=t,e.clickableElements=[];return}if(h===d.Mask){e.container=t,e.clickableElements=[],this.updateSharedMask();return}const b=this.getCurrentDarkMode(),m=this.getBackgroundColor(),c=l.enforceContrast!==!1,f=(()=>{switch(h){case d.Underline:{const y=c?E(u,m):u,w=l.layout==="bounds",[C,x]=i.isVertical?["border-right","border-left"]:["border-bottom","border-top"];return[w?`${x}: 0.1em solid ${y} !important`:null,`${C}: 0.1em solid ${y} !important`,"background-color: transparent !important","box-sizing: border-box !important"].filter(Boolean).join("; ")}case d.Strikethrough:{const y=c?E(u,m):u;return l.layout==="bounds"?[`background: repeating-linear-gradient(-45deg, transparent, transparent 19px, ${y} 19px, ${y} 20px) !important`,"background-color: transparent !important","box-sizing: border-box !important"].join("; "):[`background-color: ${y} !important`,"box-sizing: border-box !important"].join("; ")}case d.Outline:return[`outline: 2px solid ${c?E(u,m):u} !important`,"outline-offset: 1px !important","background-color: transparent !important","box-sizing: border-box !important"].join("; ");case d.HighlightUnderline:{const y=c?E(u,m):u,{r:w,g:C,b:x}=M(y),N=`rgba(${w}, ${C}, ${x}, 0.3)`,L=l.layout==="bounds",[_,B]=i.isVertical?["border-right","border-left"]:["border-bottom","border-top"];return[`background-color: ${N} !important`,L?`${B}: 0.1em solid ${y} !important`:null,`${_}: 0.1em solid ${y} !important`,"box-sizing: border-box !important"].filter(Boolean).join("; ")}case d.Highlight:default:return[`background-color: ${c?E(u,m):u} !important`,`mix-blend-mode: ${b?"exclusion":"multiply"} !important`,"opacity: 1 !important","box-sizing: border-box !important","transform: translateZ(0) !important"].join("; ")}})(),k=this.wnd.document.createElement("template");k.innerHTML=`<div data-readium="true" class="readium-${h}" style="${f}"></div>`.trim(),a=k.content.firstElementChild}const g=this.computeOverlayRects(e,i,n,s);for(const l of g){const h=a.cloneNode(!0);h.style.setProperty("pointer-events","none"),this.positionElement(e,i,o,h,l,n,p),t.append(h)}e.container=t,e.clickableElements=Array.from(t.querySelectorAll("[data-activable='1']")),e.clickableElements.length||(e.clickableElements=Array.from(t.children))}renderLayout(e){this.wnd.cancelAnimationFrame(this.currentRender),this.currentRender=this.wnd.requestAnimationFrame(()=>{if(e=e.filter(i=>!this.experimentalHighlights||!!this.notTextFlag?.has(i.id)),!e||e.length===0)return;this.requireContainer().append(...e.map(i=>i.container).filter(i=>!!i))})}requireContainer(e=!1){if(e){let t;return this.wnd.document.getElementById(`${this.id}-style`)?t=this.wnd.document.getElementById(`${this.id}-style`):(t=this.wnd.document.createElement("style"),t.dataset.readium="true",t.id=`${this.id}-style`,this.wnd.document.head.appendChild(t)),t}return this.container||(this.shadowRoot||(this.shadowHost=this.wnd.document.createElement("div"),this.shadowHost.style.cssText="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none",this.wnd.document.body.appendChild(this.shadowHost),this.shadowRoot=this.shadowHost.attachShadow({mode:"open"})),this.container=this.wnd.document.createElement("div"),this.container.setAttribute("id",this.id),this.container.dataset.group=this.name,this.container.dataset.readium="true",this.container.style.setProperty("pointer-events","none"),this.container.style.display="contents",this.shadowRoot.appendChild(this.container)),this.container}getCurrentDarkMode(){return I(this.wnd,"--USER__appearance")==="readium-night-on"||W(this.getBackgroundColor())}getBackgroundColor(){return I(this.wnd,"--USER__backgroundColor")||this.wnd.getComputedStyle(this.wnd.document.documentElement).getPropertyValue("background-color")}updateSharedMask(){const e=this.items.filter(h=>h.decoration.style?.type===d.Mask);if(e.length===0){this.maskSvg&&(this.maskSvg.remove(),this.maskSvg=void 0),this.shadowHost&&!this.container&&(this.shadowHost.remove(),this.shadowRoot=void 0,this.shadowHost=void 0);return}const t=T(this.wnd),i=1/this.effectiveZoom(),o=this.wnd.document.documentElement,s=o.scrollWidth,n=o.scrollHeight,r=[];for(const h of e){const u=h.decoration.style,b=u.layout??"boxes",m=u.width??"wrap",c=u.expand??0,f=h.range.getBoundingClientRect(),k=b==="bounds"?[c?{left:f.left-c,top:f.top-c,right:f.right+c,bottom:f.bottom+c,width:f.width+c*2,height:f.height+c*2}:f]:z(h.range,!1,!1,c);for(const v of k){let y;switch(m){case"viewport":{const w=Math.floor(t.inlineStart(v)/t.viewportInlineSize)*t.viewportInlineSize;y=t.toRect(w,t.blockStart(v),t.viewportInlineSize,t.blockSize(v));break}case"page":{const w=Math.floor(t.inlineStart(v)/t.pageInlineSize)*t.pageInlineSize;y=t.toRect(w,t.blockStart(v),t.pageInlineSize,t.blockSize(v));break}case"bounds":{y=t.toRect(t.inlineStart(f),t.blockStart(v),t.inlineSize(f),t.blockSize(v));break}default:y=t.toRect(t.inlineStart(v),t.blockStart(v),t.inlineSize(v),t.blockSize(v))}r.push(y)}}const p=[`M0 0 H${s} V${n} H0 Z`,...r.map(h=>{const u=(h.left+t.xDocOffset)*i,b=(h.top+t.yDocOffset)*i,m=(h.right+t.xDocOffset)*i,c=(h.bottom+t.yDocOffset)*i;return`M${u} ${b} H${m} V${c} H${u} Z`})].join(" "),a="http://www.w3.org/2000/svg";if(!this.maskSvg){this.shadowRoot||(this.shadowHost=this.wnd.document.createElement("div"),this.shadowHost.style.cssText="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none",this.wnd.document.body.appendChild(this.shadowHost),this.shadowRoot=this.shadowHost.attachShadow({mode:"open"})),this.maskSvg=this.wnd.document.createElementNS(a,"svg"),this.maskSvg.style.cssText=`position:absolute;top:0;left:0;width:${s}px;height:${n}px;pointer-events:none;z-index:2147483647`,this.maskSvg.dataset.readium="true";const h=this.wnd.document.createElementNS(a,"defs"),u=this.wnd.document.createElementNS(a,"clipPath"),b=`${this.id}-mask-clip`;u.setAttribute("id",b),u.setAttribute("clipPathUnits","userSpaceOnUse");const m=this.wnd.document.createElementNS(a,"path");m.setAttribute("clip-rule","evenodd"),u.appendChild(m),h.appendChild(u),this.maskSvg.appendChild(h);const c=this.wnd.document.createElementNS(a,"rect");c.setAttribute("id",`${this.id}-mask-rect`),c.setAttribute("clip-path",`url(#${b})`),c.style.pointerEvents="none",this.maskSvg.appendChild(c),this.shadowRoot.appendChild(this.maskSvg)}this.maskSvg.style.width=`${s}px`,this.maskSvg.style.height=`${n}px`;const g=this.maskSvg.querySelector("path");g&&g.setAttribute("d",p);const l=this.maskSvg.querySelector("rect");if(l){const u=e[0].decoration.style.tint,b=u??this.getBackgroundColor()??$(d.Mask),m=u?"1":"0.5";l.setAttribute("x","0"),l.setAttribute("y","0"),l.setAttribute("width",String(s)),l.setAttribute("height",String(n)),l.setAttribute("fill",b),l.setAttribute("fill-opacity",m)}}injectCustomStylesheet(e){const t=`${this.id}-custom-style`;let i=this.wnd.document.getElementById(t);i||(i=this.wnd.document.createElement("style"),i.id=t,i.dataset.readium="true",this.wnd.document.head.appendChild(i)),i.innerHTML=e}clearContainer(){if(this.experimentalHighlights){const e=this.wnd.CSS.highlights;for(const t of this._tintSubKeys.values())e.delete(t);this._tintSubKeys.clear(),this._subKeyCounter=0}this.wnd.document.getElementById(`${this.id}-custom-style`)?.remove(),this.container&&(this.container.remove(),this.container=void 0)}}const R=class R extends K{constructor(){super(...arguments),this.observedResizeTargets=new Map,this.resizeFrame=0,this.lastGroupId=0,this.groups=new Map,this.handleResizer=this.handleResize.bind(this),this.scrollScheduled=!1,this.handleScroller=this.handleScroll.bind(this)}cleanup(){this.groups.forEach(e=>e.destroy()),this.groups.clear(),this.observedResizeTargets.clear()}updateHighlightStyles(){this.groups.forEach(e=>{e.requestLayout()})}handleResize(){this.wnd.clearTimeout(this.resizeFrame),this.resizeFrame=this.wnd.setTimeout(()=>{this.groups.forEach(e=>e.requestLayout())},50)}handleScroll(){this.scrollScheduled||(this.scrollScheduled=!0,this.wnd.requestAnimationFrame(()=>{this.scrollScheduled=!1,this.groups.forEach(e=>{e.hasOverlayItems&&e.repositionOverlays()})}))}mount(e,t){return this.wnd=e,t.register("decorate",R.moduleName,(i,o)=>{const s=i;(s.action==="add"||s.action==="update")&&s.decoration.locator&&(s.decoration.locator=D.deserialize(s.decoration.locator)),this.groups.has(s.group)||this.groups.set(s.group,new Y(e,t,`readium-decoration-${this.lastGroupId++}`,s.group));const n=this.groups.get(s.group);switch(s.action){case"add":n?.add(s.decoration);break;case"remove":n?.remove(s.decoration.id);break;case"clear":n?.clear();break;case"update":n?.update(s.decoration);break}o(!0)}),t.register("decoration_resize",R.moduleName,(i,o)=>{const s=i;s.action==="watch"?this.addDecorationResizeTarget(s.selector):this.removeDecorationResizeTarget(s.selector),o(!0)}),t.register("decoration_activatable",R.moduleName,(i,o)=>{const s=i,n=this.groups.get(s.group);n&&(n.activatable=s.activatable),o(!0)}),t.register("decoration_hoverable",R.moduleName,(i,o)=>{const s=i,n=this.groups.get(s.group);n&&(n.hoverable=s.hoverable),o(!0)}),this.resizeObserver=new ResizeObserver(()=>e.requestAnimationFrame(()=>this.handleResize())),this.resizeObserver.observe(e.document.documentElement),e.addEventListener("orientationchange",this.handleResizer),e.addEventListener("resize",this.handleResizer),e.addEventListener("scroll",this.handleScroller,{passive:!0,capture:!0}),this.styleObserver=new MutationObserver(i=>{i.some(s=>s.type==="attributes"&&s.attributeName==="style"&&s.oldValue!==s.target.getAttribute("style"))&&this.updateHighlightStyles()}),this.styleObserver.observe(e.document.documentElement,{attributes:!0,attributeFilter:["style"],attributeOldValue:!0}),t.log("Decorator Mounted"),!0}unmount(e,t){return e.removeEventListener("orientationchange",this.handleResizer),e.removeEventListener("resize",this.handleResizer),e.removeEventListener("scroll",this.handleScroller,{capture:!0}),t.unregisterAll(R.moduleName),this.resizeObserver.disconnect(),this.styleObserver.disconnect(),this.cleanup(),t.log("Decorator Unmounted"),!0}addDecorationResizeTarget(e){const t=this.observedResizeTargets.get(e);t&&this.resizeObserver.unobserve(t);const i=this.wnd.document.querySelector(e);i?(this.resizeObserver.observe(i),this.observedResizeTargets.set(e,i)):this.observedResizeTargets.delete(e)}removeDecorationResizeTarget(e){const t=this.observedResizeTargets.get(e);t&&(this.resizeObserver.unobserve(t),this.observedResizeTargets.delete(e))}};R.moduleName="decorator";let A=R;export{Z as DecorationLayout,d as DecorationStyleType,V as DecorationWidth,A as Decorator};
@@ -1 +1 @@
1
- import{Module as l}from"../Module.js";const i=class i extends l{constructor(){super(...arguments),this.mediaPlayingCount=0,this.allAnimations=new Set}wndOnErr(e){this.comms?.send("error",{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}unblock(e){for(e._readium_blockEvents=!1;e._readium_blockedEvents?.length>0;){const t=e._readium_blockedEvents.shift();switch(t[0]){case 0:Reflect.apply(t[1],t[2],t[3]);break;case 1:const a=t[1],n=t[2];e.removeEventListener(a.type,e._readium_eventBlocker,!0);const s=new Event(a.type,{bubbles:a.bubbles,cancelable:a.cancelable});n?n.dispatchEvent(s):e.dispatchEvent(s);break}}}onMediaPlayEvent(){this.mediaPlayingCount++,this.comms?.send("media_play",this.mediaPlayingCount)}onMediaPauseEvent(){this.mediaPlayingCount>0&&this.mediaPlayingCount--,this.comms?.send("media_pause",this.mediaPlayingCount)}pauseAllMedia(e){const t=e.document.querySelectorAll("audio,video");for(let a=0;a<t.length;a++)t[a].pause()}mount(e,t){this.comms=t,e.addEventListener("error",this.wndOnErr,!1),Reflect.defineProperty(e.navigator,"epubReadingSystem",{value:{name:"readium-ts-toolkit",version:"2.8.1",hasFeature:(n,s="")=>{switch(n){case"dom-manipulation":return!0;case"layout-changes":return!0;case"touch-events":return!0;case"mouse-events":return!0;case"keyboard-events":return!0;case"spine-scripting":return!0;case"embedded-web-content":return!0;default:return!1}}},writable:!1}),"getAnimations"in e.document&&e.document.getAnimations().forEach(n=>{n.cancel(),this.allAnimations.add(n)}),t.register("activate",i.moduleName,(n,s)=>{this.allAnimations.forEach(r=>{r.cancel(),r.play()}),s(!0)}),t.register("unfocus",i.moduleName,(n,s)=>{this.pauseAllMedia(e),this.allAnimations.forEach(r=>r.pause()),s(!0)});const a=e.document.querySelectorAll("audio,video");for(let n=0;n<a.length;n++){const s=a[n];s.addEventListener("play",this.onMediaPlayEvent,{passive:!0}),s.addEventListener("pause",this.onMediaPauseEvent,{passive:!0})}return t.log("Setup Mounted"),!0}unmount(e,t){return e.removeEventListener("error",this.wndOnErr),e.removeEventListener("play",this.onMediaPlayEvent),e.removeEventListener("pause",this.onMediaPauseEvent),this.allAnimations.forEach(a=>a.cancel()),this.allAnimations.clear(),t.log("Setup Unmounted"),!0}};i.moduleName="setup";let o=i;export{o as Setup};
1
+ import{Module as l}from"../Module.js";const i=class i extends l{constructor(){super(...arguments),this.mediaPlayingCount=0,this.allAnimations=new Set}wndOnErr(e){this.comms?.send("error",{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}unblock(e){for(e._readium_blockEvents=!1;e._readium_blockedEvents?.length>0;){const t=e._readium_blockedEvents.shift();switch(t[0]){case 0:Reflect.apply(t[1],t[2],t[3]);break;case 1:const a=t[1],n=t[2];e.removeEventListener(a.type,e._readium_eventBlocker,!0);const s=new Event(a.type,{bubbles:a.bubbles,cancelable:a.cancelable});n?n.dispatchEvent(s):e.dispatchEvent(s);break}}}onMediaPlayEvent(){this.mediaPlayingCount++,this.comms?.send("media_play",this.mediaPlayingCount)}onMediaPauseEvent(){this.mediaPlayingCount>0&&this.mediaPlayingCount--,this.comms?.send("media_pause",this.mediaPlayingCount)}pauseAllMedia(e){const t=e.document.querySelectorAll("audio,video");for(let a=0;a<t.length;a++)t[a].pause()}mount(e,t){this.comms=t,e.addEventListener("error",this.wndOnErr,!1),Reflect.defineProperty(e.navigator,"epubReadingSystem",{value:{name:"readium-ts-toolkit",version:"2.8.3",hasFeature:(n,s="")=>{switch(n){case"dom-manipulation":return!0;case"layout-changes":return!0;case"touch-events":return!0;case"mouse-events":return!0;case"keyboard-events":return!0;case"spine-scripting":return!0;case"embedded-web-content":return!0;default:return!1}}},writable:!1}),"getAnimations"in e.document&&e.document.getAnimations().forEach(n=>{n.cancel(),this.allAnimations.add(n)}),t.register("activate",i.moduleName,(n,s)=>{this.allAnimations.forEach(r=>{r.cancel(),r.play()}),s(!0)}),t.register("unfocus",i.moduleName,(n,s)=>{this.pauseAllMedia(e),this.allAnimations.forEach(r=>r.pause()),s(!0)});const a=e.document.querySelectorAll("audio,video");for(let n=0;n<a.length;n++){const s=a[n];s.addEventListener("play",this.onMediaPlayEvent,{passive:!0}),s.addEventListener("pause",this.onMediaPauseEvent,{passive:!0})}return t.log("Setup Mounted"),!0}unmount(e,t){return e.removeEventListener("error",this.wndOnErr),e.removeEventListener("play",this.onMediaPlayEvent),e.removeEventListener("pause",this.onMediaPauseEvent),this.allAnimations.forEach(a=>a.cancel()),this.allAnimations.clear(),t.log("Setup Unmounted"),!0}};i.moduleName="setup";let o=i;export{o as Setup};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator-html-injectables",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "type": "module",
5
5
  "description": "An embeddable solution for connecting frames of HTML publications with a Readium Navigator",
6
6
  "author": "readium",
@@ -112,6 +112,7 @@ export function makeWritingContext(wnd: Window): WritingContext {
112
112
 
113
113
  applyPosition(el, inlineStart, blockStart, inlineSize, blockSize, iz) {
114
114
  el.style.position = "absolute";
115
+ el.style.zIndex = "2147483646";
115
116
  if (isVert) {
116
117
  el.style.top = `${inlineStart * iz}px`;
117
118
  el.style.left = `${blockStart * iz}px`;
@@ -217,7 +218,7 @@ export function appendVirtualColumnIfNeeded(wnd: ReadiumWindow): boolean {
217
218
  * unless you force a recalc or reflow.
218
219
  * It is not needed in paginated mode.
219
220
  */
220
- export function forceWebkitRecalc(wnd: ReadiumWindow) {
221
+ export function forceWebkitRecalc(wnd: Window) {
221
222
  // Borrowed from APB themselves…
222
223
  const styleElement = wnd.document.createElement("style");
223
224
  styleElement.appendChild(wnd.document.createTextNode("*{}"));
@@ -120,6 +120,8 @@ interface DecorationItem {
120
120
 
121
121
  const canNativeHighlight = () => ("Highlight" in window);
122
122
  const cannotNativeHighlight = ["IMG", "IMAGE", "AUDIO", "VIDEO", "SVG"];
123
+ // Elements that trigger WebKit's CSS Custom Highlight repaint bug on removal.
124
+ const webkitRepaintProneTags = new Set(["SUP", "SUB", "SMALL", "CODE"]);
123
125
 
124
126
  class DecorationGroup {
125
127
  public readonly items: DecorationItem[] = [];
@@ -132,6 +134,7 @@ class DecorationGroup {
132
134
  private readonly notTextFlag: Map<string, boolean> | undefined;
133
135
  private readonly _tintSubKeys = new Map<string, string>(); // (type::adjustedTint) → subKey
134
136
  private _subKeyCounter = 0;
137
+ private pendingWebkitReflow: Set<Element> | null = null;
135
138
  private readonly activationHandler: (e: PointerEvent) => void;
136
139
  private readonly hoverHandler: (e: PointerEvent) => void;
137
140
  private maskSvg: SVGSVGElement | undefined = undefined;
@@ -295,6 +298,7 @@ class DecorationGroup {
295
298
  }
296
299
  const stylesheet = this.wnd.document.getElementById(`${this.id}-style`) as HTMLStyleElement | null;
297
300
  if (stylesheet) this._rebuildHighlightStylesheet(stylesheet);
301
+ this.scheduleWebkitRepaintFix([item]);
298
302
  }
299
303
  this.notTextFlag?.delete(item.id);
300
304
  if (this.hoveredItem === item) {
@@ -320,6 +324,9 @@ class DecorationGroup {
320
324
  * Removes all decorations from this group.
321
325
  */
322
326
  clear() {
327
+ if (this.experimentalHighlights) {
328
+ this.scheduleWebkitRepaintFix(this.items);
329
+ }
323
330
  this.clearContainer();
324
331
  this.items.length = 0;
325
332
  this.notTextFlag?.clear();
@@ -346,6 +353,90 @@ class DecorationGroup {
346
353
  this.wnd.document.removeEventListener("pointermove", this.hoverHandler);
347
354
  }
348
355
 
356
+ /** True if the element triggers WebKit's CSS Custom Highlight repaint bug. */
357
+ private isWebkitRepaintProne(el: Element): boolean {
358
+ const style = this.wnd.getComputedStyle(el);
359
+ if (webkitRepaintProneTags.has(el.tagName.toUpperCase()) || style.display === "inline-block" || style.verticalAlign !== "baseline") {
360
+ return true;
361
+ }
362
+ const parentStyle = el.parentElement ? this.wnd.getComputedStyle(el.parentElement) : null;
363
+ return !!parentStyle && parseFloat(style.fontSize) !== parseFloat(parentStyle.fontSize);
364
+ }
365
+
366
+ /** Finds a repaint-prone element (see isWebkitRepaintProne) intersecting the range, if any. */
367
+ private findWebkitRepaintProneElement(range: Range): Element | null {
368
+ let el: Element | null = range.startContainer.nodeType === Node.ELEMENT_NODE
369
+ ? range.startContainer as Element
370
+ : range.startContainer.parentElement;
371
+ while (el) {
372
+ if (this.isWebkitRepaintProne(el)) return el;
373
+ if (this.wnd.getComputedStyle(el).display !== "inline") break; // reached the containing block
374
+ el = el.parentElement;
375
+ }
376
+
377
+ const root = range.commonAncestorContainer;
378
+ const walkRoot = root.nodeType === Node.ELEMENT_NODE ? root as Element : root.parentElement;
379
+ if (!walkRoot) return null;
380
+ // Reject prunes subtrees the range doesn't touch, so a small range under a large
381
+ // common ancestor (e.g. spanning two paragraphs in a big section) doesn't walk the whole thing.
382
+ const walker = this.wnd.document.createTreeWalker(walkRoot, NodeFilter.SHOW_ELEMENT, node =>
383
+ range.intersectsNode(node as Element) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
384
+ );
385
+ for (let node = walker.nextNode() as Element | null; node; node = walker.nextNode() as Element | null) {
386
+ if (this.isWebkitRepaintProne(node)) return node;
387
+ }
388
+ return null;
389
+ }
390
+
391
+ /** Reflows the containing block of the offending element; its own box isn't enough since inline layout is computed per line. */
392
+ private forceWebkitBlockReflow(offender: Element) {
393
+ let el: Element | null = offender;
394
+ while (el) {
395
+ const display = this.wnd.getComputedStyle(el).display;
396
+ if (display !== "inline" && display !== "inline-block") break;
397
+ el = el.parentElement;
398
+ }
399
+ if (!el || !el.isConnected) return;
400
+ const block = el as HTMLElement;
401
+ const previousTransform = block.style.getPropertyValue("transform");
402
+ const previousPriority = block.style.getPropertyPriority("transform");
403
+ // Paint-only nudge: promotes then repaints the compositing layer without
404
+ // touching layout, so it can't disturb scroll position, focus, or selection.
405
+ block.style.setProperty("transform", "translateZ(0.001px)", "important");
406
+ void block.offsetHeight;
407
+ block.style.setProperty("transform", "translateZ(0px)", "important");
408
+ void block.offsetHeight;
409
+ if (previousTransform) {
410
+ block.style.setProperty("transform", previousTransform, previousPriority);
411
+ } else {
412
+ block.style.removeProperty("transform");
413
+ }
414
+ }
415
+
416
+ /** Finds WebKit repaint-prone elements among the given items and queues a reflow of their containing blocks. */
417
+ private scheduleWebkitRepaintFix(items: DecorationItem[]) {
418
+ if (!sML.UA.WebKit) return;
419
+ for (const item of items) {
420
+ if (this.notTextFlag?.has(item.id) || !item.highlightSubKey) continue;
421
+ const offender = this.findWebkitRepaintProneElement(item.range);
422
+ if (offender) this.queueWebkitReflow(offender);
423
+ }
424
+ }
425
+
426
+ /** Batches offenders behind a single animation frame so many decorations removed in the same tick only reflow each block once. */
427
+ private queueWebkitReflow(offender: Element) {
428
+ if (!this.pendingWebkitReflow) {
429
+ this.pendingWebkitReflow = new Set();
430
+ // Forcing layout synchronously doesn't guarantee a paint; defer to a real frame.
431
+ this.wnd.requestAnimationFrame(() => {
432
+ const pending = this.pendingWebkitReflow;
433
+ this.pendingWebkitReflow = null;
434
+ pending?.forEach(el => this.forceWebkitBlockReflow(el));
435
+ });
436
+ }
437
+ this.pendingWebkitReflow.add(offender);
438
+ }
439
+
349
440
  private clientRectsToDocCoords(rects: Rect[], ctx: WritingContext = makeWritingContext(this.wnd)): Rect[] {
350
441
  const dx = ctx.xDocOffset;
351
442
  const dy = ctx.yDocOffset;
@@ -1137,7 +1228,7 @@ class DecorationGroup {
1137
1228
 
1138
1229
  // Create SVG in shared shadow root
1139
1230
  this.maskSvg = this.wnd.document.createElementNS(svgNS, "svg") as SVGSVGElement;
1140
- this.maskSvg.style.cssText = `position:absolute;top:0;left:0;width:${docW}px;height:${docH}px;pointer-events:none;z-index:9999`;
1231
+ this.maskSvg.style.cssText = `position:absolute;top:0;left:0;width:${docW}px;height:${docH}px;pointer-events:none;z-index:2147483647`;
1141
1232
  this.maskSvg.dataset.readium = "true";
1142
1233
  const defs = this.wnd.document.createElementNS(svgNS, "defs");
1143
1234
  const clipPath = this.wnd.document.createElementNS(svgNS, "clipPath") as SVGClipPathElement;
@@ -90,4 +90,4 @@ export declare function appendVirtualColumnIfNeeded(wnd: ReadiumWindow): boolean
90
90
  * unless you force a recalc or reflow.
91
91
  * It is not needed in paginated mode.
92
92
  */
93
- export declare function forceWebkitRecalc(wnd: ReadiumWindow): void;
93
+ export declare function forceWebkitRecalc(wnd: Window): void;