alpinejs-highlight
Version:
Flexible highlighting of text matches with Alpine.js 🔦
2 lines (1 loc) • 6.48 kB
JavaScript
(()=>{function G(M){M.store("highlightConfig",{legacy:"auto"});let Y=H();M.store("__xHighlightRegistry",{instances:{},counts:{},elementMatchData:new Map,get useNativeApi(){let i=M.store("highlightConfig");if(i.legacy===!0)return!1;if(i.legacy===!1)return!0;if(Array.isArray(i.legacy)){let h=i.legacy.map(m=>String(m).trim().toLowerCase());return!(/^((?!chrome|android).)*safari/i.test(navigator.userAgent)&&h.includes("safari")||navigator.userAgent.toLowerCase().indexOf("firefox")>-1&&h.includes("firefox")||/chrome|chromium|crios/i.test(navigator.userAgent)&&!/edg|edge/i.test(navigator.userAgent)&&h.includes("chrome"))}return H()}}),M.magic("matches",()=>(i={})=>{let h=M.store("__xHighlightRegistry");if(!h.elementMatchData)return{count:0,matches:[]};let u=i.selector||null,y=i.set||null,d=i.bounds===!0,m=Array.from(h.elementMatchData.keys());u&&typeof u=="string"&&(m=Array.from(document.querySelectorAll(u)).filter(f=>h.elementMatchData.has(f)));let x=null;y&&(x=v(y));let b={count:0,matches:[]};return m.forEach(E=>{let f=h.elementMatchData.get(E);x?f[x]&&(b.count+=f[x].count,b.matches=[...b.matches,...f[x].matches]):Object.keys(f).forEach(C=>{b.count+=f[C].count,b.matches=[...b.matches,...f[C].matches]})}),d&&m.forEach(E=>{let f=h.elementMatchData.get(E);x?f[x]&&k(f[x].matches,E):Object.keys(f).forEach(C=>{k(f[C].matches,E)})}),b}),M.directive("highlight",(i,{modifiers:h,expression:u},{evaluate:y,evaluateLater:d,effect:m,cleanup:x})=>{let b=["all","nocase","min","fold"],E="",f=[],C=1;if(h.length>0){let e=h[0];b.includes(e)?f.push(e):E=e;for(let l=1;l<h.length;l++)h[l-1]==="min"&&!isNaN(parseInt(h[l]))?C=parseInt(h[l]):b.includes(h[l])&&f.push(h[l])}let L=f.includes("all"),O=f.includes("nocase"),_=f.includes("fold"),r=v(E),c=M.store("__xHighlightRegistry"),p=c.useNativeApi;p&&(c.instances[r]||(c.instances[r]=new Highlight,CSS.highlights.set(r,c.instances[r]),c.counts[r]=0),c.counts[r]++);let w=[],T=[],P=i.innerHTML,J=d(u);m(()=>{J(e=>{B(e)})});let $=i.textContent,F=new MutationObserver(e=>{i.textContent!==$&&(p||(P=i.innerHTML),B(y(u)),$=i.textContent)});F.observe(i,{childList:!0,characterData:!0}),x(()=>{if(F.disconnect(),p?(w.forEach(e=>{let l=c.instances[r];l&&l.delete(e)}),c.counts[r]--,c.counts[r]<=0&&(CSS.highlights.delete(r),delete c.instances[r],delete c.counts[r])):q(),c.elementMatchData.has(i)){let e=c.elementMatchData.get(i);delete e[r],Object.keys(e).length===0&&c.elementMatchData.delete(i)}});function B(e){p?(w.forEach(t=>{let n=c.instances[r];n&&n.delete(t)}),w.length=0):(q(),i.innerHTML=P);let l=i.textContent;c.elementMatchData.has(i)||c.elementMatchData.set(i,{}),c.elementMatchData.get(i)[r]={count:0,matches:[],text:l,element:i,set:r};let s=c.elementMatchData.get(i)[r];if(Array.isArray(e)&&e.length===2&&typeof e[0]=="number"&&typeof e[1]=="number"){z(e,l,s);return}else if(Array.isArray(e)&&e.length>0&&Array.isArray(e[0])){let t=!1;if(e.forEach(n=>{Array.isArray(n)&&n.length===2&&typeof n[0]=="number"&&typeof n[1]=="number"&&(z(n,l,s),t=!0)}),t)return}if(typeof e=="number"&&(e=String(e)),typeof e=="string"||e instanceof RegExp)e=[e];else if(!Array.isArray(e))return;e.forEach(t=>{let n=W(t,l);s.count+=n.length,p?U(n,l,s):j(n,l,s)})}function U(e,l,s){let t=i.firstChild;t&&e.forEach(n=>{try{let a=document.createRange();a.setStart(t,n[0]),a.setEnd(t,n[1]),c.instances[r].add(a),w.push(a);let g=l.substring(n[0],n[1]);s.matches.push({text:g,index:n[0],length:n[1]-n[0],set:r})}catch{}})}function j(e,l,s){e.sort((o,A)=>A[0]-o[0]);let t=i.innerHTML,a=new TextEncoder().encode(t),g=[];e.forEach((o,A)=>{try{let D=l.substring(o[0],o[1]),I=`${r}-${s.matches.length}`;s.matches.push({text:D,index:o[0],length:o[1]-o[0],set:r,id:I});let X=`<mark class="${r}" data-match-id="${I}">${D}</mark>`,R=0,S=0,Q=-1;for(let V=0;V<a.length;V++){if(t[S]==="<"){for(;S<t.length&&t[S]!==">";)S++;S++;continue}if(R===o[0]&&(Q=S),R===o[1]){g.push({startPos:Q,endPos:S,replacement:X,matchId:I,matchedText:D});break}R++,S++}}catch{}}),g.forEach(o=>{t=t.substring(0,o.startPos)+o.replacement+t.substring(o.endPos);let A=document.createElement("mark");A.className=r,A.dataset.matchId=o.matchId,A.textContent=o.matchedText,T.push(A)}),i.innerHTML=t}function q(){T.forEach(e=>{e.parentNode&&e.parentNode.replaceChild(document.createTextNode(e.textContent),e)}),T=[]}function W(e,l){let s=[];if(typeof e=="number"&&(e=String(e)),typeof e=="string"){if(e.length<C)return s;let t=l,n=e;if(_&&(t=N(t),n=N(n)),O&&(t=t.toLowerCase(),n=n.toLowerCase()),L){let a=0,g;for(;(g=t.indexOf(n,a))!==-1;)s.push([g,g+e.length]),a=g+e.length}else{let a=t.indexOf(n);a>=0&&s.push([a,a+e.length])}}else if(e instanceof RegExp){let t=e.flags;if(O&&!t.includes("i")&&(t+="i"),L&&!t.includes("g")&&(t+="g"),_){let n=N(l),a=new RegExp(e.source,t),g;for(;(g=a.exec(n))!==null&&(g[0].length>=C&&s.push([g.index,g.index+g[0].length]),!!a.global);)g.index===a.lastIndex&&a.lastIndex++}else{let n=new RegExp(e.source,t),a;for(;(a=n.exec(l))!==null&&(a[0].length>=C&&s.push([a.index,a.index+a[0].length]),!!n.global);)a.index===n.lastIndex&&n.lastIndex++}}return s}function z(e,l,s){try{let t=Math.max(0,e[0]),n=Math.min(l.length,e[1]);if(t>=n)return;let a=l.substring(t,n);if(s.count+=1,s.matches.push({text:a,index:t,length:n-t}),p){let g=i.firstChild;if(!g)return;let o=document.createRange();o.setStart(g,t),o.setEnd(g,n),c.instances[r].add(o),w.push(o)}else j([[t,n]],l,s)}catch{}}function N(e){let l=e.normalize("NFD").replace(/[\u0300-\u036f]/g,""),s={\u00E6:"a",\u00C6:"A",\u0153:"o",\u0152:"O",\u00F8:"o",\u00D8:"O",\u0142:"l",\u0141:"L",\u0111:"d",\u0110:"D",\u00F0:"d",\u00D0:"D"};return l.replace(/[æÆœŒøØłŁđĐðÐ]/g,t=>s[t]||t)}});function v(i){return i&&typeof i!="string"&&(i=String(i)),!i||i==="default"?"x-highlight":`x-highlight-${i}`}function k(i,h){i.forEach(u=>{if(!u.bounds)try{if(M.store("__xHighlightRegistry").useNativeApi){let y=document.createRange(),d=h.firstChild;if(d){y.setStart(d,u.index),y.setEnd(d,u.index+u.length);let m=y.getBoundingClientRect();u.bounds={top:m.top,right:m.right,bottom:m.bottom,left:m.left,width:m.width,height:m.height}}}else if(u.id){let y=h.querySelector(`mark[data-match-id="${u.id}"]`);if(y){let d=y.getBoundingClientRect();u.bounds={top:d.top,right:d.right,bottom:d.bottom,left:d.left,width:d.width,height:d.height}}}}catch{u.bounds=null}})}function H(){return typeof CSS<"u"&&typeof CSS.highlights<"u"&&typeof Highlight<"u"}}document.addEventListener("alpine:init",()=>window.Alpine.plugin(G));})();