@readium/helpers 1.2.0 → 1.3.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/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # @readium/helpers
2
2
 
3
- General-purpose browser helpers for Readium packages: color parsing/contrast utilities, OS/browser detection, and WICG Text Fragment parsing/resolution/generation.
3
+ General-purpose browser helpers for Readium packages: color parsing/contrast utilities, OS/browser detection, URI fragment decoding, and WICG Text Fragment parsing/resolution/generation.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```ts
8
- import { colorToRgba, checkContrast, isDarkColor, isLightColor, getContrastingTextColor, adjustColorForContrast, getLuminance, sML, sMLWithRequest, decodeTextFragmentDirective, processTextFragmentDirective, generateFragmentFromRange } from "@readium/helpers";
8
+ import { colorToRgba, checkContrast, isDarkColor, isLightColor, getContrastingTextColor, adjustColorForContrast, getLuminance, sML, sMLWithRequest, decodeFragmentComponent, decodeTextFragmentDirective, processTextFragmentDirective, generateFragmentFromRange } from "@readium/helpers";
9
9
  ```
10
10
 
11
11
  ## Color utilities
@@ -45,6 +45,15 @@ sMLWithRequest.iOSRequest // "mobile" | "desktop" | undefined — iPadOS "Reques
45
45
 
46
46
  `sML` is a bundled subset of [sML.js](https://github.com/satorumurmur/sML) by Satoru Matsushima, MIT licensed. Falls back safely when `navigator` is unavailable (SSR).
47
47
 
48
+ ## URI fragments
49
+
50
+ ```ts
51
+ // Percent-decodes one fragment component (e.g. an element id), keeping a malformed escape as-is
52
+ decodeFragmentComponent("caf%C3%A9%20noir"): string // "café noir"
53
+ ```
54
+
55
+ A structured fragment must be split on its delimiters before its components are decoded.
56
+
48
57
  ## Text Fragments
49
58
 
50
59
  ```ts
@@ -0,0 +1 @@
1
+ function r(n){return n.replace(/(?:%[0-9A-Fa-f]{2})+/g,e=>{try{return decodeURIComponent(e)}catch{return e}})}export{r as decodeFragmentComponent};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{adjustColorForContrast as r,checkContrast as o,colorToRgba as n,getContrastingTextColor as a,getLuminance as s,isDarkColor as i,isLightColor as g}from"./color.js";import{sML as c,sMLWithRequest as x}from"./sML.js";import{BINDING_PUNCT_CLASS as l,ssmlAttrEscape as p,ssmlTextEscape as C,startsWithBindingPunct as T}from"./text.js";import{decodeTextFragmentDirective as u}from"./textFragmentDirective.js";import{forTesting as L,internal as h,processTextFragmentDirective as D}from"./vendor/text-fragments-polyfill/textFragmentMatcher.js";import{GenerateFragmentStatus as R,generateFragment as N,generateFragmentFromRange as S,isValidRangeForFragmentGeneration as k,setFragmentGenerationTimeout as v}from"./vendor/text-fragments-polyfill/textFragmentGenerator.js";export{l as BINDING_PUNCT_CLASS,R as GenerateFragmentStatus,r as adjustColorForContrast,o as checkContrast,n as colorToRgba,u as decodeTextFragmentDirective,L as forTesting,N as generateFragment,S as generateFragmentFromRange,a as getContrastingTextColor,s as getLuminance,h as internal,i as isDarkColor,g as isLightColor,k as isValidRangeForFragmentGeneration,D as processTextFragmentDirective,c as sML,x as sMLWithRequest,v as setFragmentGenerationTimeout,p as ssmlAttrEscape,C as ssmlTextEscape,T as startsWithBindingPunct};
1
+ import{adjustColorForContrast as r,checkContrast as o,colorToRgba as n,getContrastingTextColor as a,getLuminance as s,isDarkColor as m,isLightColor as i}from"./color.js";import{decodeFragmentComponent as c}from"./fragment.js";import{sML as x,sMLWithRequest as F}from"./sML.js";import{BINDING_PUNCT_CLASS as l,ssmlAttrEscape as f,ssmlTextEscape as T,startsWithBindingPunct as d}from"./text.js";import{decodeTextFragmentDirective as L}from"./textFragmentDirective.js";import{forTesting as D,internal as G,processTextFragmentDirective as R}from"./vendor/text-fragments-polyfill/textFragmentMatcher.js";import{GenerateFragmentStatus as S,generateFragment as k,generateFragmentFromRange as v,isValidRangeForFragmentGeneration as A,setFragmentGenerationTimeout as B}from"./vendor/text-fragments-polyfill/textFragmentGenerator.js";export{l as BINDING_PUNCT_CLASS,S as GenerateFragmentStatus,r as adjustColorForContrast,o as checkContrast,n as colorToRgba,c as decodeFragmentComponent,L as decodeTextFragmentDirective,D as forTesting,k as generateFragment,v as generateFragmentFromRange,a as getContrastingTextColor,s as getLuminance,G as internal,m as isDarkColor,i as isLightColor,A as isValidRangeForFragmentGeneration,R as processTextFragmentDirective,x as sML,F as sMLWithRequest,B as setFragmentGenerationTimeout,f as ssmlAttrEscape,T as ssmlTextEscape,d as startsWithBindingPunct};
@@ -1 +1 @@
1
- const O=["ADDRESS","ARTICLE","ASIDE","BLOCKQUOTE","BR","DETAILS","DIALOG","DD","DIV","DL","DT","FIELDSET","FIGCAPTION","FIGURE","FOOTER","FORM","H1","H2","H3","H4","H5","H6","HEADER","HGROUP","HR","LI","MAIN","NAV","OL","P","PRE","SECTION","TABLE","UL","TR","TH","TD","COLGROUP","COL","CAPTION","THEAD","TBODY","TFOOT"],f=/[\t-\r -#%-\*,-\/:;\?@\[-\]_\{\}\x85\xA0\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u1680\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2000-\u200A\u2010-\u2029\u202F-\u2043\u2045-\u2051\u2053-\u205F\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E44\u3000-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/u,b=/[^\t-\r -#%-\*,-\/:;\?@\[-\]_\{\}\x85\xA0\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u1680\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2000-\u200A\u2010-\u2029\u202F-\u2043\u2045-\u2051\u2053-\u205F\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E44\u3000-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/u,U=(u,e,t)=>{const n=[],D=e.createRange();for(D.selectNodeContents(t??e);!D.collapsed&&n.length<2;){let F;if(u.prefix){const r=a(u.prefix,D);if(r==null)break;c(D,r.startContainer,r.startOffset);const i=e.createRange();if(i.setStart(r.endContainer,r.endOffset),i.setEnd(D.endContainer,D.endOffset),B(i),i.collapsed||(F=a(u.textStart,i),F==null))break;if(F.compareBoundaryPoints(Range.START_TO_START,i)!==0)continue}else{if(F=a(u.textStart,D),F==null)break;c(D,F.startContainer,F.startOffset)}if(u.textEnd){const r=e.createRange();r.setStart(F.endContainer,F.endOffset),r.setEnd(D.endContainer,D.endOffset);let i=!1;for(;!r.collapsed&&n.length<2;){const s=a(u.textEnd,r);if(s==null)break;if(c(r,s.startContainer,s.startOffset),F.setEnd(s.endContainer,s.endOffset),u.suffix){const o=R(u.suffix,F,D,e);if(o===E.NO_SUFFIX_MATCH)break;if(o===E.SUFFIX_MATCH){i=!0,n.push(F.cloneRange());continue}else if(o===E.MISPLACED_SUFFIX)continue}else i=!0,n.push(F.cloneRange())}if(!i)break}else if(u.suffix){const r=R(u.suffix,F,D,e);if(r===E.NO_SUFFIX_MATCH)break;if(r===E.SUFFIX_MATCH){n.push(F.cloneRange()),c(D,D.startContainer,D.startOffset);continue}else if(r===E.MISPLACED_SUFFIX)continue}else n.push(F.cloneRange())}return n},E={NO_SUFFIX_MATCH:0,SUFFIX_MATCH:1,MISPLACED_SUFFIX:2},R=(u,e,t,n)=>{const D=n.createRange();D.setStart(e.endContainer,e.endOffset),D.setEnd(t.endContainer,t.endOffset),B(D);const F=a(u,D);return F==null?E.NO_SUFFIX_MATCH:F.compareBoundaryPoints(Range.START_TO_START,D)!==0?E.MISPLACED_SUFFIX:E.SUFFIX_MATCH},c=(u,e,t)=>{try{u.setStart(e,t+1)}catch{u.setStartAfter(e)}},B=u=>{const e=g(u);let t=e.nextNode();for(;!u.collapsed&&t!=null;){if(t!==u.startContainer&&u.setStart(t,0),t.textContent.length>u.startOffset&&!t.textContent[u.startOffset].match(/\s/))return;try{u.setStart(t,u.startOffset+1)}catch{t=e.nextNode(),t==null?u.collapse():u.setStart(t,0)}}},g=u=>(u.commonAncestorContainer.ownerDocument??u.commonAncestorContainer).createTreeWalker(u.commonAncestorContainer,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT,{acceptNode:t=>N(t,u)}),H=u=>{if(u.hidden==="until-found")return!0;const e=u.attributes;return!!(e&&e.hidden&&e.hidden.value==="until-found")},T=u=>{let e=u;for(;e!=null&&!(e instanceof HTMLElement);)e=e.parentNode;const t=e?.ownerDocument?.defaultView;if(e!=null&&t!=null){if(H(e))return!0;const n=t.getComputedStyle(e);if(n.visibility==="hidden"||n.display==="none"||parseInt(n.height,10)===0&&n.overflowY!="visible"||parseInt(n.width,10)===0&&n.overflowX!="visible"||parseInt(n.opacity,10)===0)return!1}return!0},I=(u,e)=>e!=null&&!e.intersectsNode(u)?NodeFilter.FILTER_REJECT:T(u)?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_REJECT,N=(u,e)=>e!=null&&!e.intersectsNode(u)||!T(u)?NodeFilter.FILTER_REJECT:u.nodeType===Node.TEXT_NODE?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP,p=(u,e)=>{const t=[];let n=[];const D=Array.from(M(u,F=>I(F,e)));for(const F of D)F.nodeType===Node.TEXT_NODE?n.push(F):F instanceof HTMLElement&&O.includes(F.tagName.toUpperCase())&&n.length>0&&(t.push(n),n=[]);return n.length>0&&t.push(n),t},v=(u,e,t)=>{let n="";return u.length===1?n=u[0].textContent.substring(e,t):n=u[0].textContent.substring(e)+u.slice(1,-1).reduce((D,F)=>D+F.textContent,"")+u.slice(-1)[0].textContent.substring(0,t),n.replace(/[\t\n\r ]+/g," ")};function*M(u,e){const n=(u.ownerDocument??u).createTreeWalker(u,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,{acceptNode:e}),D=new Set;for(;h(n,D)!==null;)yield n.currentNode}const a=(u,e)=>{const t=p(e.commonAncestorContainer,e),n=S(e.commonAncestorContainer.ownerDocument??void 0);for(const D of t){const F=_(u,e,D,n);if(F!==void 0)return F}},_=(u,e,t,n)=>{if(!u||!e||!(t||[]).length)return;const D=t[0]===e.startContainer?e.startOffset:0,F=A(v(t,D,void 0)),r=A(u);let i=0,s,o;for(;i<F.length;){const C=F.indexOf(r,i);if(C===-1)return;if(m(F,C,r.length,n)){const l=A(t[0].data.slice(0,D)).length;s=d(l+C,t,!1),o=d(l+C+r.length,t,!0)}if(s!=null&&o!=null){const l=new Range;if(l.setStart(s.node,s.offset),l.setEnd(o.node,o.offset),e.compareBoundaryPoints(Range.START_TO_START,l)<=0&&e.compareBoundaryPoints(Range.END_TO_END,l)>=0)return l}i=C+1}},d=(u,e,t)=>{let n=0,D;for(let F=0;F<e.length;F++){const r=e[F];D||(D=A(r.data));let i=n+D.length;if(t&&(i+=1),i>u){const s=u-n;let o=Math.min(u-n,r.data.length);const C=t?D.substring(0,s):D.substring(s);let l=A(t?r.data.substring(0,o):r.data.substring(o));const L=(t?-1:1)*(C.length>l.length?-1:1);for(;o>=0&&o<=r.data.length;){if(l.length===C.length)return{node:r,offset:o};o+=L,l=A(t?r.data.substring(0,o):r.data.substring(o))}}if(n+=D.length,F+1<e.length){const s=A(e[F+1].data);D.slice(-1)===" "&&s.slice(0,1)===" "&&(n-=1),D=s}}},m=(u,e,t,n)=>{if(e<0||e>=u.length||t<=0||e+t>u.length)return!1;if(n){const D=n.segment(u),F=D.containing(e);if(!F||F.isWordLike&&F.index!=e)return!1;const r=e+t,i=D.containing(r);if(i&&i.isWordLike&&i.index!=r)return!1}else if(u[e].match(f)&&(++e,--t,!t)||u[e+t-1].match(f)&&(--t,!t)||e!==0&&!u[e-1].match(f)||e+t!==u.length&&!u[e+t].match(f))return!1;return!0},A=u=>(u||"").normalize("NFKD").replace(/\s+/g," ").replace(/[\u0300-\u036f]/g,"").toLowerCase(),S=u=>{if(Intl.Segmenter){const e=u?.documentElement?.lang||u?.defaultView?.navigator?.language||void 0;return new Intl.Segmenter(e,{granularity:"word"})}},h=(u,e)=>{if(!e.has(u.currentNode)){const D=u.firstChild();if(D!==null)return D}const t=u.nextSibling();if(t!==null)return t;const n=u.parentNode();return n!==null&&e.add(n),n},x=(u,e)=>{if(!e.has(u.currentNode)){const D=u.lastChild();if(D!==null)return D}const t=u.previousSibling();if(t!==null)return t;const n=u.parentNode();return n!==null&&e.add(n),n},w={advanceRangeStartPastOffset:c,advanceRangeStartToNonWhitespace:B,findRangeFromNodeList:_,findTextInRange:a,getBoundaryPointAtIndex:d,isWordBounded:m,makeNewSegmenter:S,normalizeString:A,forwardTraverse:h,backwardTraverse:x,getAllTextNodes:p,acceptTextNodeIfVisibleInRange:N},y={BLOCK_ELEMENTS:O,BOUNDARY_CHARS:f,NON_BOUNDARY_CHARS:b,acceptNodeIfVisibleInRange:I,normalizeString:A,makeNewSegmenter:S,forwardTraverse:h,backwardTraverse:x,makeTextNodeWalker:g,isNodeVisible:T};export{w as forTesting,y as internal,U as processTextFragmentDirective};
1
+ const N=["ADDRESS","ARTICLE","ASIDE","BLOCKQUOTE","BR","DETAILS","DIALOG","DD","DIV","DL","DT","FIELDSET","FIGCAPTION","FIGURE","FOOTER","FORM","H1","H2","H3","H4","H5","H6","HEADER","HGROUP","HR","LI","MAIN","NAV","OL","P","PRE","SECTION","TABLE","UL","TR","TH","TD","COLGROUP","COL","CAPTION","THEAD","TBODY","TFOOT"],f=/[\t-\r -#%-\*,-\/:;\?@\[-\]_\{\}\x85\xA0\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u1680\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2000-\u200A\u2010-\u2029\u202F-\u2043\u2045-\u2051\u2053-\u205F\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E44\u3000-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/u,b=/[^\t-\r -#%-\*,-\/:;\?@\[-\]_\{\}\x85\xA0\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u1680\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2000-\u200A\u2010-\u2029\u202F-\u2043\u2045-\u2051\u2053-\u205F\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E44\u3000-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/u,U=(u,e,t)=>{const D=[],n=e.createRange();for(n.selectNodeContents(t??e);!n.collapsed&&D.length<2;){let F;if(u.prefix){const r=a(u.prefix,n);if(r==null)break;c(n,r.startContainer,r.startOffset);const i=e.createRange();if(i.setStart(r.endContainer,r.endOffset),i.setEnd(n.endContainer,n.endOffset),B(i),i.collapsed||(F=a(u.textStart,i),F==null))break;if(F.compareBoundaryPoints(Range.START_TO_START,i)!==0)continue}else{if(F=a(u.textStart,n),F==null)break;c(n,F.startContainer,F.startOffset)}if(u.textEnd){const r=e.createRange();r.setStart(F.endContainer,F.endOffset),r.setEnd(n.endContainer,n.endOffset);let i=!1;for(;!r.collapsed&&D.length<2;){const s=a(u.textEnd,r);if(s==null)break;if(c(r,s.startContainer,s.startOffset),F.setEnd(s.endContainer,s.endOffset),u.suffix){const o=R(u.suffix,F,n,e);if(o===A.NO_SUFFIX_MATCH)break;if(o===A.SUFFIX_MATCH){i=!0,D.push(F.cloneRange());continue}else if(o===A.MISPLACED_SUFFIX)continue}else i=!0,D.push(F.cloneRange())}if(!i)break}else if(u.suffix){const r=R(u.suffix,F,n,e);if(r===A.NO_SUFFIX_MATCH)break;if(r===A.SUFFIX_MATCH){D.push(F.cloneRange()),c(n,n.startContainer,n.startOffset);continue}else if(r===A.MISPLACED_SUFFIX)continue}else D.push(F.cloneRange())}return D},A={NO_SUFFIX_MATCH:0,SUFFIX_MATCH:1,MISPLACED_SUFFIX:2},R=(u,e,t,D)=>{const n=D.createRange();n.setStart(e.endContainer,e.endOffset),n.setEnd(t.endContainer,t.endOffset),B(n);const F=a(u,n);return F==null?A.NO_SUFFIX_MATCH:F.compareBoundaryPoints(Range.START_TO_START,n)!==0?A.MISPLACED_SUFFIX:A.SUFFIX_MATCH},c=(u,e,t)=>{try{u.setStart(e,t+1)}catch{u.setStartAfter(e)}},B=u=>{const e=O(u);let t=e.nextNode();for(;!u.collapsed&&t!=null;){if(t!==u.startContainer&&u.setStart(t,0),t.textContent.length>u.startOffset&&!t.textContent[u.startOffset].match(/\s/))return;try{u.setStart(t,u.startOffset+1)}catch{t=e.nextNode(),t==null?u.collapse():u.setStart(t,0)}}},O=u=>(u.commonAncestorContainer.ownerDocument??u.commonAncestorContainer).createTreeWalker(u.commonAncestorContainer,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT,{acceptNode:t=>p(t,u)}),v=u=>{if(u.hidden==="until-found")return!0;const e=u.attributes;return!!(e&&e.hidden&&e.hidden.value==="until-found")},T=u=>{let e=u;for(;e!=null&&e.nodeType!==Node.ELEMENT_NODE;)e=e.parentNode;const t=e,D=t?.ownerDocument?.defaultView;if(t!=null&&D!=null){if(v(t))return!0;const n=D.getComputedStyle(t);if(n.visibility==="hidden"||n.display==="none"||parseInt(n.height,10)===0&&n.overflowY!="visible"||parseInt(n.width,10)===0&&n.overflowX!="visible"||parseInt(n.opacity,10)===0)return!1}return!0},g=(u,e)=>e!=null&&!e.intersectsNode(u)?NodeFilter.FILTER_REJECT:T(u)?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_REJECT,p=(u,e)=>e!=null&&!e.intersectsNode(u)||!T(u)?NodeFilter.FILTER_REJECT:u.nodeType===Node.TEXT_NODE?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP,I=(u,e)=>{const t=[];let D=[];const n=Array.from(M(u,F=>g(F,e)));for(const F of n)F.nodeType===Node.TEXT_NODE?D.push(F):F.nodeType===Node.ELEMENT_NODE&&N.includes(F.tagName.toUpperCase())&&D.length>0&&(t.push(D),D=[]);return D.length>0&&t.push(D),t},H=(u,e,t)=>{let D="";return u.length===1?D=u[0].textContent.substring(e,t):D=u[0].textContent.substring(e)+u.slice(1,-1).reduce((n,F)=>n+F.textContent,"")+u.slice(-1)[0].textContent.substring(0,t),D.replace(/[\t\n\r ]+/g," ")};function*M(u,e){const D=(u.ownerDocument??u).createTreeWalker(u,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,{acceptNode:e}),n=new Set;for(;h(D,n)!==null;)yield D.currentNode}const a=(u,e)=>{const t=I(e.commonAncestorContainer,e),D=S(e.commonAncestorContainer.ownerDocument??void 0);for(const n of t){const F=_(u,e,n,D);if(F!==void 0)return F}},_=(u,e,t,D)=>{if(!u||!e||!(t||[]).length)return;const n=t[0]===e.startContainer?e.startOffset:0,F=l(H(t,n,void 0)),r=l(u);let i=0,s,o;for(;i<F.length;){const C=F.indexOf(r,i);if(C===-1)return;if(m(F,C,r.length,D)){const E=l(t[0].data.slice(0,n)).length;s=d(E+C,t,!1),o=d(E+C+r.length,t,!0)}if(s!=null&&o!=null){const E=new Range;if(E.setStart(s.node,s.offset),E.setEnd(o.node,o.offset),e.compareBoundaryPoints(Range.START_TO_START,E)<=0&&e.compareBoundaryPoints(Range.END_TO_END,E)>=0)return E}i=C+1}},d=(u,e,t)=>{let D=0,n;for(let F=0;F<e.length;F++){const r=e[F];n||(n=l(r.data));let i=D+n.length;if(t&&(i+=1),i>u){const s=u-D;let o=Math.min(u-D,r.data.length);const C=t?n.substring(0,s):n.substring(s);let E=l(t?r.data.substring(0,o):r.data.substring(o));const L=(t?-1:1)*(C.length>E.length?-1:1);for(;o>=0&&o<=r.data.length;){if(E.length===C.length)return{node:r,offset:o};o+=L,E=l(t?r.data.substring(0,o):r.data.substring(o))}}if(D+=n.length,F+1<e.length){const s=l(e[F+1].data);n.slice(-1)===" "&&s.slice(0,1)===" "&&(D-=1),n=s}}},m=(u,e,t,D)=>{if(e<0||e>=u.length||t<=0||e+t>u.length)return!1;if(D){const n=D.segment(u),F=n.containing(e);if(!F||F.isWordLike&&F.index!=e)return!1;const r=e+t,i=n.containing(r);if(i&&i.isWordLike&&i.index!=r)return!1}else if(u[e].match(f)&&(++e,--t,!t)||u[e+t-1].match(f)&&(--t,!t)||e!==0&&!u[e-1].match(f)||e+t!==u.length&&!u[e+t].match(f))return!1;return!0},l=u=>(u||"").normalize("NFKD").replace(/\s+/g," ").replace(/[\u0300-\u036f]/g,"").toLowerCase(),S=u=>{if(Intl.Segmenter){const e=u?.documentElement?.lang||u?.defaultView?.navigator?.language||void 0;return new Intl.Segmenter(e,{granularity:"word"})}},h=(u,e)=>{if(!e.has(u.currentNode)){const n=u.firstChild();if(n!==null)return n}const t=u.nextSibling();if(t!==null)return t;const D=u.parentNode();return D!==null&&e.add(D),D},x=(u,e)=>{if(!e.has(u.currentNode)){const n=u.lastChild();if(n!==null)return n}const t=u.previousSibling();if(t!==null)return t;const D=u.parentNode();return D!==null&&e.add(D),D},w={advanceRangeStartPastOffset:c,advanceRangeStartToNonWhitespace:B,findRangeFromNodeList:_,findTextInRange:a,getBoundaryPointAtIndex:d,isWordBounded:m,makeNewSegmenter:S,normalizeString:l,forwardTraverse:h,backwardTraverse:x,getAllTextNodes:I,acceptTextNodeIfVisibleInRange:p},y={BLOCK_ELEMENTS:N,BOUNDARY_CHARS:f,NON_BOUNDARY_CHARS:b,acceptNodeIfVisibleInRange:g,normalizeString:l,makeNewSegmenter:S,forwardTraverse:h,backwardTraverse:x,makeTextNodeWalker:O,isNodeVisible:T};export{w as forTesting,y as internal,U as processTextFragmentDirective};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/helpers",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "General-purpose browser helpers for Readium packages",
6
6
  "author": "readium",
@@ -0,0 +1,11 @@
1
+ // Percent-decodes one component of a URI fragment, after any structured
2
+ // fragment has been split on its delimiters. A malformed escape is kept as-is.
3
+ export function decodeFragmentComponent(component: string): string {
4
+ return component.replace(/(?:%[0-9A-Fa-f]{2})+/g, (escapes) => {
5
+ try {
6
+ return decodeURIComponent(escapes);
7
+ } catch {
8
+ return escapes;
9
+ }
10
+ });
11
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./color.ts";
2
+ export * from "./fragment.ts";
2
3
  export * from "./sML.ts";
3
4
  export * from "./text.ts";
4
5
  export * from "./textFragmentDirective.ts";
@@ -364,8 +364,10 @@ const isHiddenUntilFound = (elt: Element): boolean => {
364
364
  const isNodeVisible = (node: Node): boolean => {
365
365
  // Find an HTMLElement (this node or an ancestor) so we can check
366
366
  // visibility.
367
- let elt: Node | null = node;
368
- while (elt != null && !(elt instanceof HTMLElement)) elt = elt.parentNode;
367
+ let parent: Node | null = node;
368
+ // nodeType, not instanceof: the document may belong to another realm (e.g. a frame).
369
+ while (parent != null && parent.nodeType !== Node.ELEMENT_NODE) parent = parent.parentNode;
370
+ const elt = parent as Element | null;
369
371
  // A document parsed via DOMParser (as opposed to one attached to a
370
372
  // real browsing context) has no defaultView, and therefore no
371
373
  // rendering/layout at all — nothing to check visibility against, so
@@ -455,8 +457,8 @@ const getAllTextNodes = (root: Node, range?: Range): Text[][] => {
455
457
  if (node.nodeType === Node.TEXT_NODE) {
456
458
  tmp.push(node as Text);
457
459
  } else if (
458
- node instanceof HTMLElement &&
459
- BLOCK_ELEMENTS.includes(node.tagName.toUpperCase()) && tmp.length > 0) {
460
+ node.nodeType === Node.ELEMENT_NODE &&
461
+ BLOCK_ELEMENTS.includes((node as Element).tagName.toUpperCase()) && tmp.length > 0) {
460
462
  // If this is a block element, the current set of text nodes in |tmp| is
461
463
  // complete, and we need to move on to a new one.
462
464
  blocks.push(tmp);
@@ -0,0 +1 @@
1
+ export declare function decodeFragmentComponent(component: string): string;
@@ -1,4 +1,5 @@
1
1
  export * from "./color.ts";
2
+ export * from "./fragment.ts";
2
3
  export * from "./sML.ts";
3
4
  export * from "./text.ts";
4
5
  export * from "./textFragmentDirective.ts";