@readium/navigator-html-injectables 2.7.0 → 2.8.0
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.
- package/dist/helpers/locator.js +1 -1
- package/dist/modules/Decorator.js +17 -17
- package/dist/modules/setup/Setup.js +1 -1
- package/package.json +1 -1
- package/src/comms/keys.ts +1 -0
- package/src/helpers/locator.ts +5 -6
- package/src/modules/Decorator.ts +225 -95
- package/types/src/comms/keys.d.ts +1 -1
- package/types/src/modules/Decorator.d.ts +13 -0
package/dist/helpers/locator.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getDomRange as u,getTextFragment as
|
|
1
|
+
import{getDomRange as u,getTextFragment as d,getCssSelector as f}from"@readium/shared";import{processTextFragmentDirective as g}from"@readium/helpers";import{TextQuoteAnchor as m}from"../vendor/hypothesis/anchoring/types.js";function h(n){const t=n.tagName.toUpperCase();return t==="IMG"||t==="VIDEO"||t==="AUDIO"||t==="IFRAME"||t==="OBJECT"||t==="EMBED"||t==="CANVAS"}function a(n,t){const r=t&&f(t);let s=null;if(r)try{s=n.querySelector(r)}catch(e){console.warn(`Invalid cssSelector: ${r}`,e)}return s??n.body??n.documentElement}function i(n,t){let r;try{r=n.querySelector(t.cssSelector)}catch(e){return console.error(`Invalid domRange cssSelector: ${t.cssSelector}`,e),null}if(!r)return console.error(`Can't resolve domRange cssSelector: ${t.cssSelector}`),null;let s=0;for(const e of r.childNodes)if(e.nodeType===Node.TEXT_NODE){if(s===t.textNodeIndex)return{node:e,offset:t.charOffset};s++}return console.error(`Can't resolve domRange textNodeIndex ${t.textNodeIndex} for selector: ${t.cssSelector}`),null}function R(n,t){try{const r=t.locations,s=t.text;if(r){const e=u(r);if(e){const o=i(n,e.start),c=e.end?i(n,e.end):o;if(o&&c)try{const l=n.createRange();return o.offset!==void 0?l.setStart(o.node,o.offset):l.setStartBefore(o.node),c.offset!==void 0?l.setEnd(c.node,c.offset):l.setEndBefore(c.node),l}catch(l){console.warn("Invalid domRange, falling back:",l)}}}if(r){const e=d(r);if(e){const o=a(n,r),c=g(e,n,o);if(c.length>0)return c[0]}}if(s&&s.highlight){const e=a(n,r),o=new m(e,s.highlight,{prefix:s.before,suffix:s.after});try{return o.toRange()}catch{return console.warn("Quote not found:",o),null}}if(r){let e=null;if(!e&&f(r)&&(e=n.querySelector(f(r))),!e&&r.fragments){for(const o of r.fragments)if(e=n.getElementById(o),e)break}if(e){const o=n.createRange();return e.childNodes.length===0||h(e)?(o.selectNode(e),o):(o.setStartBefore(e),o.setEndAfter(e),o)}}}catch(r){console.error(r)}return null}export{R as rangeFromLocator};
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import{Locator as
|
|
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}) {
|
|
2
2
|
text-decoration: underline;
|
|
3
|
-
text-decoration-color: ${
|
|
3
|
+
text-decoration-color: ${b};
|
|
4
4
|
text-decoration-thickness: 0.1em;
|
|
5
|
-
}`;break;case
|
|
5
|
+
}`;break;case d.Strikethrough:m=`::highlight(${a}) {
|
|
6
6
|
text-decoration: line-through;
|
|
7
|
-
text-decoration-color: ${
|
|
7
|
+
text-decoration-color: ${b};
|
|
8
8
|
text-decoration-thickness: 0.1em;
|
|
9
|
-
}`;break;case
|
|
10
|
-
outline: 2px solid ${
|
|
9
|
+
}`;break;case d.Outline:m=`::highlight(${a}) {
|
|
10
|
+
outline: 2px solid ${b};
|
|
11
11
|
outline-offset: 1px;
|
|
12
|
-
}`;break;case
|
|
13
|
-
color: ${
|
|
14
|
-
}`;break;case
|
|
15
|
-
color: ${O(
|
|
16
|
-
background-color: ${
|
|
12
|
+
}`;break;case d.TextColor:m=`::highlight(${a}) {
|
|
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};
|
|
17
17
|
text-decoration: underline;
|
|
18
|
-
text-decoration-color: ${
|
|
18
|
+
text-decoration-color: ${b};
|
|
19
19
|
text-decoration-thickness: 0.1em;
|
|
20
|
-
}`;break}case
|
|
21
|
-
color: ${O(
|
|
22
|
-
background-color: ${
|
|
23
|
-
}`}
|
|
24
|
-
`)}layout(t){if(this.experimentalHighlights&&!this.notTextFlag?.has(t.id))return this.experimentalLayout(t);const i=this.wnd.document.createElement("div");i.setAttribute("id",t.id),i.dataset.highlightId=t.decoration.id,i.style.setProperty("pointer-events","none");const e=H(this.wnd),s=1/this.effectiveZoom(),o=t.decoration.style.expand??0,r=(h,d,u,m=0)=>{const p=t.decoration?.style?.width,n=d;switch(p){case"viewport":{const l=Math.floor(e.inlineStart(n)/e.viewportInlineSize)*e.viewportInlineSize;e.applyPosition(h,l+e.inlineScrollOffset+m,e.blockStart(n)+e.blockScrollOffset,e.viewportInlineSize-2*m,e.blockSize(n),s);break}case"page":{const l=Math.floor(e.inlineStart(n)/e.pageInlineSize)*e.pageInlineSize;e.applyPosition(h,l+e.inlineScrollOffset+m,e.blockStart(n)+e.blockScrollOffset,e.pageInlineSize-2*m,e.blockSize(n),s);break}case"bounds":{e.applyPosition(h,e.inlineStart(u)+e.inlineScrollOffset,e.blockStart(n)+e.blockScrollOffset,e.inlineSize(u),e.blockSize(n),s);break}default:e.applyPosition(h,e.inlineStart(n)+e.inlineScrollOffset,e.blockStart(n)+e.blockScrollOffset,e.inlineSize(n),e.blockSize(n),s)}},a=t.range.getBoundingClientRect(),g=t.decoration.style,f=(()=>{if(g.type!==c.Outline)return 0;const h=g.width;return h==="page"||h==="viewport"?3:0})();let w;if(g.type===c.Template){g.stylesheet&&this.injectCustomStylesheet(g.stylesheet);const h=q(this.wnd,g.element);if(!h){t.container=i,t.clickableElements=[];return}h.style.setProperty("pointer-events","none"),w=h}else{const h=g,d=h.type??c.Highlight,u=h.tint??T(d);if(d===c.TextColor){t.container=i,t.clickableElements=[];return}if(d===c.Mask){t.container=i,t.clickableElements=[],this.updateSharedMask();return}const m=this.getCurrentDarkMode(),p=this.getBackgroundColor(),n=h.enforceContrast!==!1,l=(()=>{switch(d){case c.Underline:{const v=n?R(u,p):u,S=h.layout==="bounds",[x,E]=e.isVertical?["border-right","border-left"]:["border-bottom","border-top"];return[S?`${E}: 0.1em solid ${v} !important`:null,`${x}: 0.1em solid ${v} !important`,"background-color: transparent !important","box-sizing: border-box !important"].filter(Boolean).join("; ")}case c.Strikethrough:{const v=n?R(u,p):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 c.Outline:return[`outline: 2px solid ${n?R(u,p):u} !important`,"outline-offset: 1px !important","background-color: transparent !important","box-sizing: border-box !important"].join("; ");case c.HighlightUnderline:{const v=n?R(u,p):u,{r:S,g:x,b:E}=M(v),_=`rgba(${S}, ${x}, ${E}, 0.3)`,B=h.layout==="bounds",[K,L]=e.isVertical?["border-right","border-left"]:["border-bottom","border-top"];return[`background-color: ${_} !important`,B?`${L}: 0.1em solid ${v} !important`:null,`${K}: 0.1em solid ${v} !important`,"box-sizing: border-box !important"].filter(Boolean).join("; ")}case c.Highlight:default:return[`background-color: ${n?R(u,p):u} !important`,`mix-blend-mode: ${m?"exclusion":"multiply"} !important`,"opacity: 1 !important","box-sizing: border-box !important"].join("; ")}})(),y=this.wnd.document.createElement("template");y.innerHTML=`<div data-readium="true" class="readium-${d}" style="${l}"></div>`.trim(),w=y.content.firstElementChild}if(t.decoration?.style?.layout==="bounds"){const h=w.cloneNode(!0);h.style.setProperty("pointer-events","none");const d=o?{left:a.left-o,right:a.right+o,top:a.top-o,bottom:a.bottom+o,width:a.width+o*2,height:a.height+o*2}:a;r(h,d,a,f),i.append(h)}else{const h=g.type,d=h===c.Underline||h===c.Strikethrough,u=h===c.Strikethrough,m=d?U(t.range,["rt","rp"]):t.range;let p=$(m,!0,e.isVertical,d?0:o);p=p.sort((n,l)=>e.isVertical?(e.isVertLR?1:-1)*(n.left-l.left):n.top-l.top);for(let n of p){const l=w.cloneNode(!0);l.style.setProperty("pointer-events","none");let y=n;if(u){const b=e.blockSize(n)*.1,S=e.blockStart(n)+e.blockSize(n)/2-b/2;y=e.isVertical?{left:S,right:S+b,top:n.top,bottom:n.bottom,width:b,height:n.height}:{top:S,bottom:S+b,left:n.left,right:n.right,height:b,width:n.width}}o&&d&&(y=e.isVertical?{...y,top:y.top-o,bottom:y.bottom+o,height:y.height+o*2}:{...y,left:y.left-o,right:y.right+o,width:y.width+o*2}),r(l,y,a,f),i.append(l)}}t.container=i,t.clickableElements=Array.from(i.querySelectorAll("[data-activable='1']")),t.clickableElements.length||(t.clickableElements=Array.from(i.children))}renderLayout(t){this.wnd.cancelAnimationFrame(this.currentRender),this.currentRender=this.wnd.requestAnimationFrame(()=>{if(t=t.filter(e=>!this.experimentalHighlights||!!this.notTextFlag?.has(e.id)),!t||t.length===0)return;this.requireContainer().append(...t.map(e=>e.container).filter(e=>!!e))})}requireContainer(t=!1){if(t){let i;return this.wnd.document.getElementById(`${this.id}-style`)?i=this.wnd.document.getElementById(`${this.id}-style`):(i=this.wnd.document.createElement("style"),i.dataset.readium="true",i.id=`${this.id}-style`,this.wnd.document.head.appendChild(i)),i}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"||V(this.getBackgroundColor())}getBackgroundColor(){return I(this.wnd,"--USER__backgroundColor")||this.wnd.getComputedStyle(this.wnd.document.documentElement).getPropertyValue("background-color")}updateSharedMask(){const t=this.items.filter(d=>d.decoration.style?.type===c.Mask);if(t.length===0){this.maskSvg&&(this.maskSvg.remove(),this.maskSvg=void 0),this.shadowRoot&&(this.shadowRoot.innerHTML="");return}const i=H(this.wnd),e=1/this.effectiveZoom(),s=this.wnd.document.documentElement,o=s.scrollWidth,r=s.scrollHeight,a=[];for(const d of t){const u=d.decoration.style,m=u.layout??"boxes",p=u.width??"wrap",n=u.expand??0,l=d.range.getBoundingClientRect(),y=m==="bounds"?[n?{left:l.left-n,top:l.top-n,right:l.right+n,bottom:l.bottom+n,width:l.width+n*2,height:l.height+n*2}:l]:$(d.range,!1,!1,n);for(const b of y){let v;switch(p){case"viewport":{const S=Math.floor(i.inlineStart(b)/i.viewportInlineSize)*i.viewportInlineSize;v=i.toRect(S,i.blockStart(b),i.viewportInlineSize,i.blockSize(b));break}case"page":{const S=Math.floor(i.inlineStart(b)/i.pageInlineSize)*i.pageInlineSize;v=i.toRect(S,i.blockStart(b),i.pageInlineSize,i.blockSize(b));break}case"bounds":{v=i.toRect(i.inlineStart(l),i.blockStart(b),i.inlineSize(l),i.blockSize(b));break}default:v=i.toRect(i.inlineStart(b),i.blockStart(b),i.inlineSize(b),i.blockSize(b))}a.push(v)}}const g=[`M0 0 H${o} V${r} H0 Z`,...a.map(d=>{const u=(d.left+i.xDocOffset)*e,m=(d.top+i.yDocOffset)*e,p=(d.right+i.xDocOffset)*e,n=(d.bottom+i.yDocOffset)*e;return`M${u} ${m} H${p} V${n} H${u} Z`})].join(" "),f="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(f,"svg"),this.maskSvg.style.cssText=`position:absolute;top:0;left:0;width:${o}px;height:${r}px;pointer-events:none;z-index:9999`,this.maskSvg.dataset.readium="true";const d=this.wnd.document.createElementNS(f,"defs"),u=this.wnd.document.createElementNS(f,"clipPath"),m=`${this.id}-mask-clip`;u.setAttribute("id",m),u.setAttribute("clipPathUnits","userSpaceOnUse");const p=this.wnd.document.createElementNS(f,"path");p.setAttribute("clip-rule","evenodd"),u.appendChild(p),d.appendChild(u),this.maskSvg.appendChild(d);const n=this.wnd.document.createElementNS(f,"rect");n.setAttribute("id",`${this.id}-mask-rect`),n.setAttribute("clip-path",`url(#${m})`),n.style.pointerEvents="none",this.maskSvg.appendChild(n),this.shadowRoot.appendChild(this.maskSvg)}this.maskSvg.style.width=`${o}px`,this.maskSvg.style.height=`${r}px`;const w=this.maskSvg.querySelector("path");w&&w.setAttribute("d",g);const h=this.maskSvg.querySelector("rect");if(h){const u=t[0].decoration.style.tint,m=u??this.getBackgroundColor()??T(c.Mask),p=u?"1":"0.5";h.setAttribute("x","0"),h.setAttribute("y","0"),h.setAttribute("width",String(o)),h.setAttribute("height",String(r)),h.setAttribute("fill",m),h.setAttribute("fill-opacity",p)}}injectCustomStylesheet(t){const i=`${this.id}-custom-style`;let e=this.wnd.document.getElementById(i);e||(e=this.wnd.document.createElement("style"),e.id=i,e.dataset.readium="true",this.wnd.document.head.appendChild(e)),e.innerHTML=t}clearContainer(){if(this.experimentalHighlights){const t=this.wnd.CSS.highlights;for(const i of this._tintSubKeys.values())t.delete(i);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 C=class C extends N{constructor(){super(...arguments),this.resizeFrame=0,this.lastGroupId=0,this.groups=new Map,this.handleResizer=this.handleResize.bind(this)}cleanup(){this.groups.forEach(t=>t.destroy()),this.groups.clear()}updateHighlightStyles(){this.groups.forEach(t=>{t.requestLayout()})}handleResize(){this.wnd.clearTimeout(this.resizeFrame),this.resizeFrame=this.wnd.setTimeout(()=>{this.groups.forEach(t=>{t.experimentalHighlights||t.requestLayout()})},50)}mount(t,i){return this.wnd=t,i.register("decorate",C.moduleName,(e,s)=>{const o=e;(o.action==="add"||o.action==="update")&&o.decoration.locator&&(o.decoration.locator=P.deserialize(o.decoration.locator)),this.groups.has(o.group)||this.groups.set(o.group,new Z(t,i,`readium-decoration-${this.lastGroupId++}`,o.group));const r=this.groups.get(o.group);switch(o.action){case"add":r?.add(o.decoration);break;case"remove":r?.remove(o.decoration.id);break;case"clear":r?.clear();break;case"update":r?.update(o.decoration);break}s(!0)}),i.register("decoration_activatable",C.moduleName,(e,s)=>{const o=e,r=this.groups.get(o.group);r&&(r.activatable=o.activatable),s(!0)}),i.register("decoration_hoverable",C.moduleName,(e,s)=>{const o=e,r=this.groups.get(o.group);r&&(r.hoverable=o.hoverable),s(!0)}),this.resizeObserver=new ResizeObserver(()=>t.requestAnimationFrame(()=>this.handleResize())),this.resizeObserver.observe(t.document.documentElement),t.addEventListener("orientationchange",this.handleResizer),t.addEventListener("resize",this.handleResizer),this.styleObserver=new MutationObserver(e=>{e.some(o=>o.type==="attributes"&&o.attributeName==="style"&&o.oldValue!==o.target.getAttribute("style"))&&this.updateHighlightStyles()}),this.styleObserver.observe(t.document.documentElement,{attributes:!0,attributeFilter:["style"],attributeOldValue:!0}),i.log("Decorator Mounted"),!0}unmount(t,i){return t.removeEventListener("orientationchange",this.handleResizer),t.removeEventListener("resize",this.handleResizer),i.unregisterAll(C.moduleName),this.resizeObserver.disconnect(),this.styleObserver.disconnect(),this.cleanup(),i.log("Decorator Unmounted"),!0}};C.moduleName="decorator";let A=C;export{G as DecorationLayout,c as DecorationStyleType,X as DecorationWidth,A as Decorator};
|
|
20
|
+
}`;break}case d.Highlight:default:m=`::highlight(${a}) {
|
|
21
|
+
color: ${O(b,l)};
|
|
22
|
+
background-color: ${b};
|
|
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};
|
|
@@ -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.
|
|
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.0",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
package/src/comms/keys.ts
CHANGED
package/src/helpers/locator.ts
CHANGED
|
@@ -35,14 +35,13 @@ function resolveDomRangePoint(doc: Document, point: DomRangePoint): { node: Text
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// textNodeIndex counts only direct-child text nodes (spec: html.md "child Text node").
|
|
39
39
|
let index = 0;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
for (const child of container.childNodes) {
|
|
41
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
42
|
+
if (index === point.textNodeIndex) return { node: child as Text, offset: point.charOffset };
|
|
43
|
+
index++;
|
|
44
44
|
}
|
|
45
|
-
index++;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
console.error(`Can't resolve domRange textNodeIndex ${point.textNodeIndex} for selector: ${point.cssSelector}`);
|
package/src/modules/Decorator.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { ModuleName } from "./ModuleLibrary.ts";
|
|
|
6
6
|
import { Rect, getClientRectsNoOverlap, getTextClientRects, rectContainsPoint } from "../helpers/rect.ts";
|
|
7
7
|
import { getProperty } from "../helpers/css.ts";
|
|
8
8
|
import { isDarkColor, getContrastingTextColor, adjustColorForContrast, colorToRgba } from "@readium/helpers";
|
|
9
|
-
import { makeWritingContext } from "../helpers/document.ts";
|
|
9
|
+
import { makeWritingContext, WritingContext } from "../helpers/document.ts";
|
|
10
10
|
import { sML } from "@readium/helpers";
|
|
11
11
|
import { sanitizeHTML } from "../helpers/sanitize.ts";
|
|
12
12
|
|
|
@@ -103,6 +103,10 @@ export type DecoratorRequest =
|
|
|
103
103
|
| { group: string; action: "remove"; decoration: Pick<Decoration, "id"> }
|
|
104
104
|
| { group: string; action: "clear" };
|
|
105
105
|
|
|
106
|
+
export type DecorationResizeRequest =
|
|
107
|
+
| { action: "watch"; selector: string }
|
|
108
|
+
| { action: "unwatch"; selector: string };
|
|
109
|
+
|
|
106
110
|
interface DecorationItem {
|
|
107
111
|
id: string;
|
|
108
112
|
decoration: Decoration;
|
|
@@ -167,6 +171,11 @@ class DecorationGroup {
|
|
|
167
171
|
return this._hoverable;
|
|
168
172
|
}
|
|
169
173
|
|
|
174
|
+
/** True if this group has at least one item rendered via the DOM overlay path (not CSS Highlight API). */
|
|
175
|
+
get hasOverlayItems(): boolean {
|
|
176
|
+
return !this.experimentalHighlights || (this.notTextFlag?.size ?? 0) > 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
170
179
|
set hoverable(value: boolean) {
|
|
171
180
|
this._hoverable = value;
|
|
172
181
|
if (!value && this.hoveredItem) {
|
|
@@ -337,8 +346,7 @@ class DecorationGroup {
|
|
|
337
346
|
this.wnd.document.removeEventListener("pointermove", this.hoverHandler);
|
|
338
347
|
}
|
|
339
348
|
|
|
340
|
-
private clientRectsToDocCoords(rects: Rect[]): Rect[] {
|
|
341
|
-
const ctx = makeWritingContext(this.wnd);
|
|
349
|
+
private clientRectsToDocCoords(rects: Rect[], ctx: WritingContext = makeWritingContext(this.wnd)): Rect[] {
|
|
342
350
|
const dx = ctx.xDocOffset;
|
|
343
351
|
const dy = ctx.yDocOffset;
|
|
344
352
|
if (dx === 0 && dy === 0) return rects;
|
|
@@ -501,6 +509,32 @@ class DecorationGroup {
|
|
|
501
509
|
});
|
|
502
510
|
}
|
|
503
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Cheap scroll-driven pass: repositions existing overlay elements in place instead of
|
|
514
|
+
* requestLayout()'s full destroy-and-rebuild, only falling back to a full layout()
|
|
515
|
+
* per item when geometry has genuinely changed shape (a real reflow).
|
|
516
|
+
*/
|
|
517
|
+
repositionOverlays() {
|
|
518
|
+
let hasMaskItem = false;
|
|
519
|
+
const relaidOut: DecorationItem[] = [];
|
|
520
|
+
const ctx = makeWritingContext(this.wnd);
|
|
521
|
+
this.items.forEach(item => {
|
|
522
|
+
if (this.experimentalHighlights && !this.notTextFlag?.has(item.id)) return;
|
|
523
|
+
if (item.decoration.style?.type === DecorationStyleType.Mask) {
|
|
524
|
+
hasMaskItem = true;
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (!this.repositionItem(item, ctx)) {
|
|
528
|
+
item.container?.remove();
|
|
529
|
+
this.layout(item);
|
|
530
|
+
relaidOut.push(item);
|
|
531
|
+
}
|
|
532
|
+
item.hitRects = this.clientRectsToDocCoords(getClientRectsNoOverlap(item.range, false, false, ((item.decoration.style as BuiltinDecorationStyle).expand ?? 0) + this.hitGap()), ctx);
|
|
533
|
+
});
|
|
534
|
+
if (relaidOut.length) this.renderLayout(relaidOut);
|
|
535
|
+
if (hasMaskItem) this.updateSharedMask();
|
|
536
|
+
}
|
|
537
|
+
|
|
504
538
|
private experimentalLayout(item: DecorationItem) {
|
|
505
539
|
const stylesheet = this.requireContainer(true) as HTMLStyleElement;
|
|
506
540
|
const cssHighlights = (this.wnd as any).CSS.highlights as Map<string, any>;
|
|
@@ -655,6 +689,131 @@ class DecorationGroup {
|
|
|
655
689
|
stylesheet.innerHTML = rules.join("\n");
|
|
656
690
|
}
|
|
657
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Positions a single overlay element according to the decoration's width mode.
|
|
694
|
+
*/
|
|
695
|
+
private positionElement(item: DecorationItem, ctx: WritingContext, iz: number, element: HTMLElement, rect: Rect, boundingRect: DOMRect, inlineInset = 0) {
|
|
696
|
+
const w = item.decoration?.style?.width;
|
|
697
|
+
const r = rect;
|
|
698
|
+
switch (w) {
|
|
699
|
+
case DecorationWidth.Viewport: {
|
|
700
|
+
const snap = Math.floor(ctx.inlineStart(r) / ctx.viewportInlineSize) * ctx.viewportInlineSize;
|
|
701
|
+
ctx.applyPosition(element, snap + ctx.inlineScrollOffset + inlineInset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.viewportInlineSize - 2 * inlineInset, ctx.blockSize(r), iz);
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
case DecorationWidth.Page: {
|
|
705
|
+
const snap = Math.floor(ctx.inlineStart(r) / ctx.pageInlineSize) * ctx.pageInlineSize;
|
|
706
|
+
ctx.applyPosition(element, snap + ctx.inlineScrollOffset + inlineInset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.pageInlineSize - 2 * inlineInset, ctx.blockSize(r), iz);
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case DecorationWidth.Bounds: {
|
|
710
|
+
ctx.applyPosition(element, ctx.inlineStart(boundingRect) + ctx.inlineScrollOffset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.inlineSize(boundingRect), ctx.blockSize(r), iz);
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
default: {
|
|
714
|
+
ctx.applyPosition(element, ctx.inlineStart(r) + ctx.inlineScrollOffset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.inlineSize(r), ctx.blockSize(r), iz);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Computes the ordered list of rects an overlay item's elements should occupy
|
|
721
|
+
* (one rect for Bounds layout, one per merged client rect for Boxes layout).
|
|
722
|
+
* Shared between the full build path (layout) and the cheap reposition path.
|
|
723
|
+
*/
|
|
724
|
+
private computeOverlayRects(item: DecorationItem, ctx: WritingContext, boundingRect: DOMRect, expand: number): Rect[] {
|
|
725
|
+
if (item.decoration?.style?.layout === DecorationLayout.Bounds) {
|
|
726
|
+
const boundsRect: Rect = expand ? {
|
|
727
|
+
left: boundingRect.left - expand,
|
|
728
|
+
right: boundingRect.right + expand,
|
|
729
|
+
top: boundingRect.top - expand,
|
|
730
|
+
bottom: boundingRect.bottom + expand,
|
|
731
|
+
width: boundingRect.width + expand * 2,
|
|
732
|
+
height: boundingRect.height + expand * 2,
|
|
733
|
+
} : boundingRect;
|
|
734
|
+
return [boundsRect];
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Fall back to "boxes" value for layout.
|
|
738
|
+
// For underline/strikethrough, pre-filter to text-node rects only so Ruby
|
|
739
|
+
// (rt/rp) glyphs don't produce a separate decoration segment above the base text.
|
|
740
|
+
const decoStyle = item.decoration.style;
|
|
741
|
+
const decoType = (decoStyle as BuiltinDecorationStyle).type;
|
|
742
|
+
const isLineDecoration = decoType === DecorationStyleType.Underline
|
|
743
|
+
|| decoType === DecorationStyleType.Strikethrough;
|
|
744
|
+
const isStrikethrough = decoType === DecorationStyleType.Strikethrough;
|
|
745
|
+
const rectSource = isLineDecoration
|
|
746
|
+
? getTextClientRects(item.range, ["rt", "rp"])
|
|
747
|
+
: item.range;
|
|
748
|
+
// Line decorations (underline/strikethrough) don't expand in the block axis —
|
|
749
|
+
// expand extends endpoints along the inline axis only.
|
|
750
|
+
let clientRects = getClientRectsNoOverlap(
|
|
751
|
+
rectSource,
|
|
752
|
+
true, // doNotMergeHorizontallyAlignedRects
|
|
753
|
+
ctx.isVertical, // doNotMergeVerticallyAlignedRects
|
|
754
|
+
isLineDecoration ? 0 : expand
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
clientRects = clientRects.sort((r1, r2) => {
|
|
758
|
+
if (ctx.isVertical) {
|
|
759
|
+
// vertical-rl: rightmost column first; vertical-lr: leftmost first
|
|
760
|
+
const factor = ctx.isVertLR ? 1 : -1;
|
|
761
|
+
return factor * (r1.left - r2.left);
|
|
762
|
+
}
|
|
763
|
+
return r1.top - r2.top;
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
return clientRects.map(clientRect => {
|
|
767
|
+
let posRect: Rect = clientRect;
|
|
768
|
+
if (isStrikethrough) {
|
|
769
|
+
// Thin the rect to ~10% of block size, centred on the mid-line.
|
|
770
|
+
const thickness = ctx.blockSize(clientRect) * 0.1;
|
|
771
|
+
const blockMid = ctx.blockStart(clientRect) + ctx.blockSize(clientRect) / 2;
|
|
772
|
+
const bs = blockMid - thickness / 2;
|
|
773
|
+
posRect = ctx.isVertical
|
|
774
|
+
? { left: bs, right: bs + thickness, top: clientRect.top, bottom: clientRect.bottom, width: thickness, height: clientRect.height }
|
|
775
|
+
: { top: bs, bottom: bs + thickness, left: clientRect.left, right: clientRect.right, height: thickness, width: clientRect.width };
|
|
776
|
+
}
|
|
777
|
+
if (expand && isLineDecoration) {
|
|
778
|
+
posRect = ctx.isVertical
|
|
779
|
+
? { ...posRect, top: posRect.top - expand, bottom: posRect.bottom + expand, height: posRect.height + expand * 2 }
|
|
780
|
+
: { ...posRect, left: posRect.left - expand, right: posRect.right + expand, width: posRect.width + expand * 2 };
|
|
781
|
+
}
|
|
782
|
+
return posRect;
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Cheaply repositions an already-rendered overlay item's existing elements in place,
|
|
788
|
+
* without destroying and rebuilding them. Returns false if the element count no
|
|
789
|
+
* longer matches the current geometry (a genuine reflow happened), signalling the
|
|
790
|
+
* caller to fall back to a full layout().
|
|
791
|
+
*/
|
|
792
|
+
private repositionItem(item: DecorationItem, ctx: WritingContext): boolean {
|
|
793
|
+
if (!item.container) return true;
|
|
794
|
+
const decoStyle = item.decoration.style;
|
|
795
|
+
if (decoStyle.type !== DecorationStyleType.Template) {
|
|
796
|
+
const type = (decoStyle as BuiltinDecorationStyle).type ?? DecorationStyleType.Highlight;
|
|
797
|
+
if (type === DecorationStyleType.TextColor || type === DecorationStyleType.Mask) return true;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const iz = 1 / this.effectiveZoom();
|
|
801
|
+
const expand = (decoStyle as BuiltinDecorationStyle).expand ?? 0;
|
|
802
|
+
const boundingRect = item.range.getBoundingClientRect();
|
|
803
|
+
const outlineInset = (() => {
|
|
804
|
+
if ((decoStyle as BuiltinDecorationStyle).type !== DecorationStyleType.Outline) return 0;
|
|
805
|
+
const w = (decoStyle as BuiltinDecorationStyle).width;
|
|
806
|
+
return (w === DecorationWidth.Page || w === DecorationWidth.Viewport) ? 3 : 0;
|
|
807
|
+
})();
|
|
808
|
+
|
|
809
|
+
const rects = this.computeOverlayRects(item, ctx, boundingRect, expand);
|
|
810
|
+
const children = Array.from(item.container.children) as HTMLElement[];
|
|
811
|
+
if (children.length !== rects.length) return false;
|
|
812
|
+
|
|
813
|
+
children.forEach((el, i) => this.positionElement(item, ctx, iz, el, rects[i], boundingRect, outlineInset));
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
|
|
658
817
|
/**
|
|
659
818
|
* Layouts a single DecorationItem.
|
|
660
819
|
* @param item
|
|
@@ -677,29 +836,6 @@ class DecorationGroup {
|
|
|
677
836
|
const iz = 1 / this.effectiveZoom();
|
|
678
837
|
|
|
679
838
|
const expand = (item.decoration.style as BuiltinDecorationStyle).expand ?? 0;
|
|
680
|
-
const positionElement = (element: HTMLElement, rect: Rect, boundingRect: DOMRect, inlineInset = 0) => {
|
|
681
|
-
const w = item.decoration?.style?.width;
|
|
682
|
-
const r = rect;
|
|
683
|
-
switch (w) {
|
|
684
|
-
case DecorationWidth.Viewport: {
|
|
685
|
-
const snap = Math.floor(ctx.inlineStart(r) / ctx.viewportInlineSize) * ctx.viewportInlineSize;
|
|
686
|
-
ctx.applyPosition(element, snap + ctx.inlineScrollOffset + inlineInset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.viewportInlineSize - 2 * inlineInset, ctx.blockSize(r), iz);
|
|
687
|
-
break;
|
|
688
|
-
}
|
|
689
|
-
case DecorationWidth.Page: {
|
|
690
|
-
const snap = Math.floor(ctx.inlineStart(r) / ctx.pageInlineSize) * ctx.pageInlineSize;
|
|
691
|
-
ctx.applyPosition(element, snap + ctx.inlineScrollOffset + inlineInset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.pageInlineSize - 2 * inlineInset, ctx.blockSize(r), iz);
|
|
692
|
-
break;
|
|
693
|
-
}
|
|
694
|
-
case DecorationWidth.Bounds: {
|
|
695
|
-
ctx.applyPosition(element, ctx.inlineStart(boundingRect) + ctx.inlineScrollOffset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.inlineSize(boundingRect), ctx.blockSize(r), iz);
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
default: {
|
|
699
|
-
ctx.applyPosition(element, ctx.inlineStart(r) + ctx.inlineScrollOffset, ctx.blockStart(r) + ctx.blockScrollOffset, ctx.inlineSize(r), ctx.blockSize(r), iz);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
839
|
const boundingRect = item.range.getBoundingClientRect();
|
|
704
840
|
|
|
705
841
|
const decoStyle = item.decoration.style;
|
|
@@ -817,6 +953,8 @@ class DecorationGroup {
|
|
|
817
953
|
`mix-blend-mode: ${isDarkMode ? "exclusion" : "multiply"} !important`,
|
|
818
954
|
"opacity: 1 !important",
|
|
819
955
|
"box-sizing: border-box !important",
|
|
956
|
+
// Works around an iPadOS Safari bug where mix-blend-mode paints opaque instead of blending.
|
|
957
|
+
"transform: translateZ(0) !important",
|
|
820
958
|
].join("; ");
|
|
821
959
|
}
|
|
822
960
|
}
|
|
@@ -827,70 +965,12 @@ class DecorationGroup {
|
|
|
827
965
|
elementTemplate = template.content.firstElementChild!;
|
|
828
966
|
}
|
|
829
967
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
top: boundingRect.top - expand,
|
|
837
|
-
bottom: boundingRect.bottom + expand,
|
|
838
|
-
width: boundingRect.width + expand * 2,
|
|
839
|
-
height: boundingRect.height + expand * 2,
|
|
840
|
-
} : boundingRect;
|
|
841
|
-
positionElement(bounds, boundsRect, boundingRect, outlineInset);
|
|
842
|
-
itemContainer.append(bounds);
|
|
843
|
-
} else {
|
|
844
|
-
// Fall back to "boxes" value for layout.
|
|
845
|
-
// For underline/strikethrough, pre-filter to text-node rects only so Ruby
|
|
846
|
-
// (rt/rp) glyphs don't produce a separate decoration segment above the base text.
|
|
847
|
-
const decoType = (decoStyle as BuiltinDecorationStyle).type;
|
|
848
|
-
const isLineDecoration = decoType === DecorationStyleType.Underline
|
|
849
|
-
|| decoType === DecorationStyleType.Strikethrough;
|
|
850
|
-
const isStrikethrough = decoType === DecorationStyleType.Strikethrough;
|
|
851
|
-
const rectSource = isLineDecoration
|
|
852
|
-
? getTextClientRects(item.range, ["rt", "rp"])
|
|
853
|
-
: item.range;
|
|
854
|
-
// Line decorations (underline/strikethrough) don't expand in the block axis —
|
|
855
|
-
// expand extends endpoints along the inline axis only.
|
|
856
|
-
let clientRects = getClientRectsNoOverlap(
|
|
857
|
-
rectSource,
|
|
858
|
-
true, // doNotMergeHorizontallyAlignedRects
|
|
859
|
-
ctx.isVertical, // doNotMergeVerticallyAlignedRects
|
|
860
|
-
isLineDecoration ? 0 : expand
|
|
861
|
-
);
|
|
862
|
-
|
|
863
|
-
clientRects = clientRects.sort((r1, r2) => {
|
|
864
|
-
if (ctx.isVertical) {
|
|
865
|
-
// vertical-rl: rightmost column first; vertical-lr: leftmost first
|
|
866
|
-
const factor = ctx.isVertLR ? 1 : -1;
|
|
867
|
-
return factor * (r1.left - r2.left);
|
|
868
|
-
}
|
|
869
|
-
return r1.top - r2.top;
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
for (let clientRect of clientRects) {
|
|
873
|
-
const line = elementTemplate.cloneNode(true) as HTMLDivElement;
|
|
874
|
-
line.style.setProperty("pointer-events", "none");
|
|
875
|
-
let posRect: Rect = clientRect;
|
|
876
|
-
if (isStrikethrough) {
|
|
877
|
-
// Thin the rect to ~10% of block size, centred on the mid-line.
|
|
878
|
-
const thickness = ctx.blockSize(clientRect) * 0.1;
|
|
879
|
-
const blockMid = ctx.blockStart(clientRect) + ctx.blockSize(clientRect) / 2;
|
|
880
|
-
const bs = blockMid - thickness / 2;
|
|
881
|
-
posRect = ctx.isVertical
|
|
882
|
-
? { left: bs, right: bs + thickness, top: clientRect.top, bottom: clientRect.bottom, width: thickness, height: clientRect.height }
|
|
883
|
-
: { top: bs, bottom: bs + thickness, left: clientRect.left, right: clientRect.right, height: thickness, width: clientRect.width };
|
|
884
|
-
}
|
|
885
|
-
// Expand line decorations along the inline axis only (extend endpoints, not block size).
|
|
886
|
-
if (expand && isLineDecoration) {
|
|
887
|
-
posRect = ctx.isVertical
|
|
888
|
-
? { ...posRect, top: posRect.top - expand, bottom: posRect.bottom + expand, height: posRect.height + expand * 2 }
|
|
889
|
-
: { ...posRect, left: posRect.left - expand, right: posRect.right + expand, width: posRect.width + expand * 2 };
|
|
890
|
-
}
|
|
891
|
-
positionElement(line, posRect, boundingRect, outlineInset);
|
|
892
|
-
itemContainer.append(line);
|
|
893
|
-
}
|
|
968
|
+
const rects = this.computeOverlayRects(item, ctx, boundingRect, expand);
|
|
969
|
+
for (const posRect of rects) {
|
|
970
|
+
const el = elementTemplate.cloneNode(true) as HTMLDivElement;
|
|
971
|
+
el.style.setProperty("pointer-events", "none");
|
|
972
|
+
this.positionElement(item, ctx, iz, el, posRect, boundingRect, outlineInset);
|
|
973
|
+
itemContainer.append(el);
|
|
894
974
|
}
|
|
895
975
|
|
|
896
976
|
item.container = itemContainer;
|
|
@@ -975,8 +1055,11 @@ class DecorationGroup {
|
|
|
975
1055
|
this.maskSvg.remove();
|
|
976
1056
|
this.maskSvg = undefined;
|
|
977
1057
|
}
|
|
978
|
-
if
|
|
979
|
-
|
|
1058
|
+
// Only tear down the shared shadow host if no other decorations use it
|
|
1059
|
+
if (this.shadowHost && !this.container) {
|
|
1060
|
+
this.shadowHost.remove();
|
|
1061
|
+
this.shadowRoot = undefined;
|
|
1062
|
+
this.shadowHost = undefined;
|
|
980
1063
|
}
|
|
981
1064
|
return;
|
|
982
1065
|
}
|
|
@@ -1142,6 +1225,7 @@ export class Decorator extends Module {
|
|
|
1142
1225
|
private resizeObserver!: ResizeObserver;
|
|
1143
1226
|
private styleObserver!: MutationObserver;
|
|
1144
1227
|
private wnd!: Window;
|
|
1228
|
+
private observedResizeTargets = new Map<string, Element>();
|
|
1145
1229
|
/*private readonly lastSize = {
|
|
1146
1230
|
width: 0,
|
|
1147
1231
|
height: 0
|
|
@@ -1154,6 +1238,7 @@ export class Decorator extends Module {
|
|
|
1154
1238
|
private cleanup() {
|
|
1155
1239
|
this.groups.forEach(g => g.destroy());
|
|
1156
1240
|
this.groups.clear();
|
|
1241
|
+
this.observedResizeTargets.clear();
|
|
1157
1242
|
}
|
|
1158
1243
|
|
|
1159
1244
|
private updateHighlightStyles() {
|
|
@@ -1165,13 +1250,25 @@ export class Decorator extends Module {
|
|
|
1165
1250
|
private handleResize() {
|
|
1166
1251
|
this.wnd.clearTimeout(this.resizeFrame);
|
|
1167
1252
|
this.resizeFrame = this.wnd.setTimeout(() => {
|
|
1168
|
-
this.groups.forEach(g =>
|
|
1169
|
-
if(!g.experimentalHighlights) g.requestLayout();
|
|
1170
|
-
});
|
|
1253
|
+
this.groups.forEach(g => g.requestLayout());
|
|
1171
1254
|
}, 50);
|
|
1172
1255
|
}
|
|
1173
1256
|
private readonly handleResizer = this.handleResize.bind(this);
|
|
1174
1257
|
|
|
1258
|
+
private scrollScheduled = false;
|
|
1259
|
+
private handleScroll() {
|
|
1260
|
+
// rAF-throttled, not debounced: overlays must track every frame, not just settle after scroll stops.
|
|
1261
|
+
if (this.scrollScheduled) return;
|
|
1262
|
+
this.scrollScheduled = true;
|
|
1263
|
+
this.wnd.requestAnimationFrame(() => {
|
|
1264
|
+
this.scrollScheduled = false;
|
|
1265
|
+
this.groups.forEach(g => {
|
|
1266
|
+
if(g.hasOverlayItems) g.repositionOverlays();
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
private readonly handleScroller = this.handleScroll.bind(this);
|
|
1271
|
+
|
|
1175
1272
|
mount(wnd: Window, comms: IComms): boolean {
|
|
1176
1273
|
this.wnd = wnd;
|
|
1177
1274
|
|
|
@@ -1209,6 +1306,13 @@ export class Decorator extends Module {
|
|
|
1209
1306
|
ack(true);
|
|
1210
1307
|
});
|
|
1211
1308
|
|
|
1309
|
+
comms.register("decoration_resize", Decorator.moduleName, (data, ack) => {
|
|
1310
|
+
const req = data as DecorationResizeRequest;
|
|
1311
|
+
if (req.action === "watch") this.addDecorationResizeTarget(req.selector);
|
|
1312
|
+
else this.removeDecorationResizeTarget(req.selector);
|
|
1313
|
+
ack(true);
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1212
1316
|
comms.register("decoration_activatable", Decorator.moduleName, (data, ack) => {
|
|
1213
1317
|
const req = data as { group: string; activatable: boolean };
|
|
1214
1318
|
const group = this.groups.get(req.group);
|
|
@@ -1231,6 +1335,10 @@ export class Decorator extends Module {
|
|
|
1231
1335
|
this.resizeObserver.observe(wnd.document.documentElement);
|
|
1232
1336
|
wnd.addEventListener("orientationchange", this.handleResizer);
|
|
1233
1337
|
wnd.addEventListener("resize", this.handleResizer);
|
|
1338
|
+
// Capture phase: scroll events don't bubble, so a scrollable descendant
|
|
1339
|
+
// (e.g. an app shell's own reading-pane container) would never reach a
|
|
1340
|
+
// bubble-phase listener on wnd.
|
|
1341
|
+
wnd.addEventListener("scroll", this.handleScroller, { passive: true, capture: true });
|
|
1234
1342
|
|
|
1235
1343
|
// Watch for any style change on <html> — covers appearance, background color,
|
|
1236
1344
|
// font size, line height, margins, and anything else that reflows text.
|
|
@@ -1256,6 +1364,7 @@ export class Decorator extends Module {
|
|
|
1256
1364
|
unmount(wnd: Window, comms: IComms): boolean {
|
|
1257
1365
|
wnd.removeEventListener("orientationchange", this.handleResizer);
|
|
1258
1366
|
wnd.removeEventListener("resize", this.handleResizer);
|
|
1367
|
+
wnd.removeEventListener("scroll", this.handleScroller, { capture: true });
|
|
1259
1368
|
|
|
1260
1369
|
comms.unregisterAll(Decorator.moduleName);
|
|
1261
1370
|
this.resizeObserver.disconnect();
|
|
@@ -1265,4 +1374,25 @@ export class Decorator extends Module {
|
|
|
1265
1374
|
comms.log("Decorator Unmounted");
|
|
1266
1375
|
return true;
|
|
1267
1376
|
}
|
|
1377
|
+
|
|
1378
|
+
private addDecorationResizeTarget(selector: string): void {
|
|
1379
|
+
const existing = this.observedResizeTargets.get(selector);
|
|
1380
|
+
if (existing) this.resizeObserver.unobserve(existing);
|
|
1381
|
+
|
|
1382
|
+
const el = this.wnd.document.querySelector(selector);
|
|
1383
|
+
if (el) {
|
|
1384
|
+
this.resizeObserver.observe(el);
|
|
1385
|
+
this.observedResizeTargets.set(selector, el);
|
|
1386
|
+
} else {
|
|
1387
|
+
this.observedResizeTargets.delete(selector);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
private removeDecorationResizeTarget(selector: string): void {
|
|
1392
|
+
const el = this.observedResizeTargets.get(selector);
|
|
1393
|
+
if (el) {
|
|
1394
|
+
this.resizeObserver.unobserve(el);
|
|
1395
|
+
this.observedResizeTargets.delete(selector);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1268
1398
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type CommsEventKey = "_pong" | "_unhandled" | "_ack" | "log" | "error" | "click" | "tap" | "tap_more" | "no_more" | "no_less" | "swipe" | "scroll" | "progress" | "first_visible_locator" | "text_selected" | "context_menu" | "media_play" | "media_pause" | "content_protection" | "keyboard_peripherals" | "decoration_activated" | "decoration_pointer_enter" | "decoration_pointer_leave";
|
|
2
|
-
export type CommsCommandKey = "_ping" | "go_prev" | "go_next" | "go_id" | "go_text" | "go_end" | "go_start" | "go_progression" | "get_properties" | "update_properties" | "set_property" | "remove_property" | "first_visible_locator" | "decorate" | "decoration_activatable" | "decoration_hoverable" | "protect" | "unprotect" | "unfocus" | "focus" | "activate" | "shake" | "force_webkit_recalc" | "peripherals_protection" | "keyboard_peripherals" | "scroll_protection" | "print_protection" | "timeline_entries";
|
|
2
|
+
export type CommsCommandKey = "_ping" | "go_prev" | "go_next" | "go_id" | "go_text" | "go_end" | "go_start" | "go_progression" | "get_properties" | "update_properties" | "set_property" | "remove_property" | "first_visible_locator" | "decorate" | "decoration_resize" | "decoration_activatable" | "decoration_hoverable" | "protect" | "unprotect" | "unfocus" | "focus" | "activate" | "shake" | "force_webkit_recalc" | "peripherals_protection" | "keyboard_peripherals" | "scroll_protection" | "print_protection" | "timeline_entries";
|
|
3
3
|
export type SuspiciousActivityType = "developer_tools" | "select_all" | "save" | "suspicious_selection" | "bulk_copy" | "suspicious_scrolling" | "suspicious_snapping" | "drag_detected" | "drop_detected" | "print";
|
|
@@ -103,11 +103,19 @@ export type DecoratorRequest = {
|
|
|
103
103
|
group: string;
|
|
104
104
|
action: "clear";
|
|
105
105
|
};
|
|
106
|
+
export type DecorationResizeRequest = {
|
|
107
|
+
action: "watch";
|
|
108
|
+
selector: string;
|
|
109
|
+
} | {
|
|
110
|
+
action: "unwatch";
|
|
111
|
+
selector: string;
|
|
112
|
+
};
|
|
106
113
|
export declare class Decorator extends Module {
|
|
107
114
|
static readonly moduleName: ModuleName;
|
|
108
115
|
private resizeObserver;
|
|
109
116
|
private styleObserver;
|
|
110
117
|
private wnd;
|
|
118
|
+
private observedResizeTargets;
|
|
111
119
|
private resizeFrame;
|
|
112
120
|
private lastGroupId;
|
|
113
121
|
private groups;
|
|
@@ -115,7 +123,12 @@ export declare class Decorator extends Module {
|
|
|
115
123
|
private updateHighlightStyles;
|
|
116
124
|
private handleResize;
|
|
117
125
|
private readonly handleResizer;
|
|
126
|
+
private scrollScheduled;
|
|
127
|
+
private handleScroll;
|
|
128
|
+
private readonly handleScroller;
|
|
118
129
|
mount(wnd: Window, comms: IComms): boolean;
|
|
119
130
|
unmount(wnd: Window, comms: IComms): boolean;
|
|
131
|
+
private addDecorationResizeTarget;
|
|
132
|
+
private removeDecorationResizeTarget;
|
|
120
133
|
}
|
|
121
134
|
export {};
|