fewrd 0.3.0 → 1.0.1

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/index.js CHANGED
@@ -1 +1 @@
1
- const G=/[‐-―−]/g,H=/[‘-‟]/g,P=e=>"\u201C\u201D\u201E\u201F".includes(e)?'"':"'";function Q(e){let s="";const o=[];let r=!1,n=0;for(const p of e){if(/\s/.test(p))r||(o.push(n),s+=" "),r=!0;else{const c=p.normalize("NFKC").replace(G,"-").replace(H,P);for(const i of c)o.push(n),s+=i;r=!1}n+=p.length}return o.push(e.length),{text:s,at:o}}const U=/[\p{L}\p{N}]/u,y=e=>e!==void 0&&U.test(e),$=(e,s)=>y(e[s])&&y(e[s-1]),b=(e,s)=>y(e[s-1])&&y(e[s]),w=e=>e.replace(/[gy]/g,""),M=new WeakMap;function k(e,s){let o=M.get(e);return o||M.set(e,o={}),o[s]??=s==="left"?new RegExp(`(?:${e.source})$`,w(e.flags)):s==="right"?new RegExp(e.source,`${w(e.flags)}y`):new RegExp(e.source,`${w(e.flags).replace("d","")}gd`)}function Y(e,s,o,r,n){const p=[{part:"value",span:o}],c=new Set;let i=o[0];for(let u=!0;u;){u=!1;for(const t of s.left??[]){if(c.has(t.part))continue;const l=k(t.rx,"left").exec(e.slice(r,i));if(!l||l[0].length===0)continue;const f=r+l.index;if(!$(e,f)){p.unshift({part:t.part,span:[f,i]}),c.add(t.part),i=f,u=!0;break}}}i=o[1];const a=e.slice(0,n);for(let u=!0;u;){u=!1;for(const t of s.right??[]){if(c.has(t.part))continue;const l=k(t.rx,"right");l.lastIndex=i;const f=l.exec(a);if(!f||f[0].length===0)continue;const d=i+f[0].length;if(!b(e,d)){p.push({part:t.part,span:[i,d]}),c.add(t.part),i=d,u=!0;break}}}return p}function z(e,s,o,r,n){const p=[],c=e.slice(0,r);return s.forEach((i,a)=>{if(i.rest||!!i.weak!==n)return;const u=k(i.anchor,"scan");u.lastIndex=o;for(let t=u.exec(c);t;t=u.exec(c)){const l=[t.index,t.index+t[0].length];if(l[0]===l[1]){u.lastIndex=t.index+1;continue}if(!i.glued&&($(e,l[0])||b(e,l[1]))){u.lastIndex=t.index+1;continue}const f=Y(e,i,l,o,r),d=Object.fromEntries(f.map(h=>[h.part,e.slice(h.span[0],h.span[1])])),g=(i.requires??[]).every(h=>h in d)?i.resolve?i.resolve(d):d.value:null;if(g===null){u.lastIndex=t.index+1;continue}p.push({recipe:a,entity:i.entity,extent:[f[0].span[0],f.at(-1).span[1]],parts:f,value:g})}}),p}const B=(e,s)=>e[0]<s[1]&&s[0]<e[1],R=e=>e.extent[1]-e.extent[0];function _(e,s){const o=[...s];for(const r of[...e].sort((n,p)=>R(p)-R(n)||n.recipe-p.recipe))o.some(n=>B(n.extent,r.extent))||o.push(r);return o.sort((r,n)=>r.extent[0]-n.extent[0])}const E=/(?<sep>(?:\s|[,;:](?=\s|$)|(?<=^|\s)-(?=\s|$))+)|(?<open>[(\[])|(?<close>[)\]])/gu;function N(e,s,o,r,n){const p=(i,a,u)=>{a>i&&n.push({start:i,end:a,kind:u,...r!==void 0?{mention:r}:{}})};E.lastIndex=s;let c=s;for(let i=E.exec(e);i&&i.index<o;i=E.exec(e)){p(c,i.index,"text");const a=Math.min(i.index+i[0].length,o);p(i.index,a,i.groups.sep?"sep":i.groups.open?"open":"close"),c=a}p(c,o,"text")}function W(e,s,o,r,n,p,c){let i;s.forEach((l,f)=>{if(!l.rest)return;const d=k(l.anchor,"scan");d.lastIndex=o;for(let g=d.exec(e);g&&g.index<r;g=d.exec(e)){const h=[g.index,Math.min(g.index+g[0].length,r)];if(!(!l.glued&&($(e,h[0])||b(e,h[1])))){(!i||h[0]<i.at[0])&&(i={recipe:f,at:h});break}}});const a=i?i.at[0]:r,u=_(z(e,s,o,a,!0),_(z(e,s,o,a,!1),[]));let t=o;for(const l of u){N(e,t,l.extent[0],n,c);const f=p.push({entity:l.entity,recipe:l.recipe,extent:l.extent,parts:l.parts,value:l.value,...n!==void 0?{parent:n}:{}})-1;for(const d of l.parts)c.push({start:d.span[0],end:d.span[1],kind:"part",part:d.part,mention:f});t=l.extent[1]}if(N(e,t,a,n,c),i){const l=p.push({entity:s[i.recipe].entity,recipe:i.recipe,extent:[i.at[0],r],parts:[{part:"lead",span:i.at}],value:e.slice(i.at[1],r),...n!==void 0?{parent:n}:{}})-1;c.push({start:i.at[0],end:i.at[1],kind:"part",part:"lead",mention:l}),W(e,s,i.at[1],r,l,p,c)}}function J(e,s){const o=Q(e),r=[],n=[];W(o.text,s.recipes,0,o.text.length,void 0,r,n);const p=([a,u])=>({start:o.at[a],end:o.at[u]}),c=r.map(a=>({...a,extent:p(a.extent),parts:a.parts.map(u=>({part:u.part,span:p(u.span)}))})),i=n.map(a=>({...a,start:o.at[a.start],end:o.at[a.end]})).filter(a=>a.end>a.start);for(const a of c){const u=s.recipes[a.recipe];if(u.rest)a.value=e.slice(a.parts[0].span.end,a.extent.end);else if(!u.resolve){const t=a.parts.find(l=>l.part==="value").span;a.value=e.slice(t.start,t.end)}}return{text:e,book:s.version,mentions:c,leaves:i}}const Z=e=>e.includes("-")?4:e.includes(";")?3:e.includes(":")?2:e.includes(",")?1:0,V=/^[.,;:!?)\]]/;function C(e,s){return K(e,s).keep}function K(e,s){const{text:o,mentions:r,leaves:n}=e,p=r.map(()=>!1);r.forEach((t,l)=>p[l]=s(t,l)||t.parent!==void 0&&p[t.parent]);const c=n.map(t=>t.kind!=="sep"&&!(t.mention!==void 0&&p[t.mention])),i=t=>t.kind==="text"||t.kind==="part",a=[];n.forEach((t,l)=>{if(t.kind==="open")a.push(l);else if(t.kind==="close"&&a.length){const f=a.pop();n.slice(f+1,l).some((d,g)=>i(d)&&c[f+1+g])||(c[f]=c[l]=!1)}});let u=-1;for(let t=0;t<=n.length;t++){if(t<n.length&&(n[t].kind==="sep"||!c[t]))continue;const l=[...Array(t-u-1).keys()].map(g=>u+1+g),f=l.filter(g=>n[g].kind==="sep");if(!(l.length!==f.length))f.forEach(g=>c[g]=!0);else{const g=v=>o.slice(v.start,v.end),h=u<0||t===n.length,m=u>=0&&n[u].kind==="open"||t<n.length&&(n[t].kind==="close"||V.test(g(n[t])));if(!h&&!m&&f.length){const v=f.reduce((O,S)=>Z(g(n[S]))>Z(g(n[O]))?S:O);c[v]=!0}}u=t}return{keep:c,folded:p}}function X(e,s){const o=C(e,s);return e.leaves.map((r,n)=>o[n]?e.text.slice(r.start,r.end):"").join("")}const A=e=>e.replace(/[&<>"]/g,s=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[s]);function ee(e,s){const{keep:o,folded:r}=K(e,s),{mentions:n}=e,p=t=>t===void 0?[]:[...p(n[t].parent),t],c=t=>r[t]&&!(n[t].parent!==void 0&&r[n[t].parent]),i=t=>t?" data-fold":"";let a="";const u=[];return e.leaves.forEach((t,l)=>{const f=p(t.mention);let d=0;for(;d<u.length&&u[d]===f[d];)d++;for(;u.length>d;u.pop())a+="</span>";for(const m of f.slice(d))a+=`<span data-entity="${A(n[m].entity)}" data-mention="${m}"${i(c(m))}>`,u.push(m);const g=A(e.text.slice(t.start,t.end)),h=!o[l]&&!(t.mention!==void 0&&r[t.mention]);t.kind==="part"?a+=`<span data-part="${A(t.part)}">${g}</span>`:t.kind==="text"&&!h?a+=g:a+=`<span data-${t.kind}${i(h)}>${g}</span>`}),a+"</span>".repeat(u.length)}const q={book:{all:["$schema","version","defs","recipes"]},recipe:{all:["entity","anchor","left","right","requires","resolve","weak","rest","glued"]},neighbour:{all:["part","rx"]}},te=/^[A-Za-z_][A-Za-z0-9_]*$/,ne=/%\{([A-Za-z_][A-Za-z0-9_]*)\}/g,x=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),L=e=>typeof e=="string"&&e!=="",se=(e,s)=>e?`${e}.${s}`:s;function j(e,s,o,r){for(const n of Object.keys(e))s.includes(n)||r.push({path:se(o,n),message:`unknown key "${n}"`})}function T(e,s,o=[]){return e.replace(ne,(r,n)=>{const p=[...o,n];if(o.includes(n))throw new Error(`circular fragment ${p.join(" \u2192 ")}`);const c=s.get(n);if(c===void 0)throw new Error(`unknown fragment %{${n}}`);return`(?:${T(c,s,p)})`})}function D(e,s,o,r){if(e===void 0)return void r.push({path:s,message:"is required"});const n=typeof e=="string"&&e[0]==="/"?e.lastIndexOf("/"):-1;if(typeof e!="string"||n<1)return void r.push({path:s,message:"must be a pattern string like /source/flags"});try{return new RegExp(T(e.slice(1,n),o),e.slice(n+1))}catch(p){r.push({path:s,message:p instanceof Error?p.message:String(p)})}}function I(e,s,o){return e===void 0?void o.push({path:s,message:"is required"}):L(e)?e:void o.push({path:s,message:"must be a non-empty string"})}function F(e,s,o,r){if(e!==void 0)return Array.isArray(e)?e.flatMap((n,p)=>{const c=`${s}[${p}]`;if(!x(n))return r.push({path:c,message:"must be an object (a neighbour)"}),[];j(n,q.neighbour.all,c,r);const i=I(n.part,`${c}.part`,r),a=D(n.rx,`${c}.rx`,o,r);return i!==void 0&&a?[{part:i,rx:a}]:[]}):void r.push({path:s,message:"must be an array of neighbours"})}function oe(e,s,o,r,n){if(!x(e))return void n.push({path:s,message:"must be an object (a recipe)"});j(e,q.recipe.all,s,n);const p=I(e.entity,`${s}.entity`,n),c=D(e.anchor,`${s}.anchor`,o,n),i=F(e.left,`${s}.left`,o,n),a=F(e.right,`${s}.right`,o,n);e.requires!==void 0&&!(Array.isArray(e.requires)&&e.requires.every(t=>typeof t=="string"))&&n.push({path:`${s}.requires`,message:"must be an array of strings"});let u;e.resolve!==void 0&&(typeof e.resolve!="string"?n.push({path:`${s}.resolve`,message:"must be a string (a resolver name)"}):Object.hasOwn(r,e.resolve)?u=r[e.resolve]:n.push({path:`${s}.resolve`,message:`unknown resolver "${e.resolve}"`}));for(const t of["weak","rest","glued"])e[t]!==void 0&&typeof e[t]!="boolean"&&n.push({path:`${s}.${t}`,message:"must be a boolean"});if(!(p===void 0||!c))return{entity:p,anchor:c,...i&&{left:i},...a&&{right:a},...Array.isArray(e.requires)&&{requires:e.requires.filter(t=>typeof t=="string")},...u&&{resolve:u},...e.weak===!0&&{weak:!0},...e.rest===!0&&{rest:!0},...e.glued===!0&&{glued:!0}}}function ie(e,s={}){if(!x(e))return{book:{version:"",recipes:[]},errors:[{path:"",message:"must be an object (a book)"}]};const o=[];j(e,q.book.all,"",o);const r=I(e.version,"version",o)??"",n=new Map;if(e.defs!==void 0&&!x(e.defs))o.push({path:"defs",message:"must be an object of name \u2192 pattern source"});else if(e.defs)for(const[c,i]of Object.entries(e.defs))te.test(c)?typeof i!="string"?o.push({path:`defs.${c}`,message:"must be a string (pattern source)"}):n.set(c,i):o.push({path:`defs.${c}`,message:"a fragment name is letters, digits and _, not starting with a digit"});if(!Array.isArray(e.recipes))return o.push({path:"recipes",message:e.recipes===void 0?"is required":"must be an array of recipes"}),{book:{version:r,recipes:[]},errors:o};const p=[];return e.recipes.forEach((c,i)=>{const a=[],u=oe(c,`recipes[${i}]`,n,s.resolvers??{},a),t=x(c)&&L(c.entity)?c.entity:void 0;for(const l of a)o.push({...l,recipe:i,...t!==void 0&&{entity:t}});!a.length&&u&&p.push(u)}),{book:{version:r,recipes:p},errors:o}}export{ie as compile,X as gist,ee as html,J as read,C as shown};
1
+ const G=["^","$","*"],Ae=["doc","text"],Se=["value",...G],V={conf:["version","patterns","tags"],tag:["rx","search","resolve","weak","fate"],search:["from","back","forward"],atom:["tag","rx","as","optional"]},je=/^[A-Za-z_][A-Za-z0-9_]*$/,Ne=/%\{([A-Za-z_][A-Za-z0-9_]*)\}/g,T=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),H=e=>typeof e=="string"&&e!=="",Re=(e,t)=>e?`${e}.${t}`:t;function J(e,t,n,s){for(const r of Object.keys(e))t.includes(r)||s.push({path:Re(n,r),message:`unknown key "${r}"`})}function fe(e,t,n=[]){return e.replace(Ne,(s,r)=>{const a=[...n,r];if(n.includes(r))throw new Error(`circular pattern ${a.join(" \u2192 ")}`);const i=t.get(r);if(i===void 0)throw new Error(`unknown pattern %{${r}}`);return`(?:${fe(i,t,a)})`})}function le(e,t,n,s){if(e===void 0)return void s.push({path:t,message:"is required"});const r=typeof e=="string"&&e[0]==="/"?e.lastIndexOf("/"):-1;if(typeof e!="string"||r<1)return void s.push({path:t,message:"must be a pattern string like /source/flags"});try{return new RegExp(fe(e.slice(1,r),n.patterns),e.slice(r+1))}catch(a){s.push({path:t,message:a instanceof Error?a.message:String(a)})}}function ue(e,t,n,s){if(!H(e))return void s.push({path:t,message:"must be a non-empty string (a tag name)"});!G.includes(e)&&!n.declared.has(e)&&s.push({path:t,message:`unknown tag "${e}"`})}function Ie(e,t,n,s){if(!T(e))return void s.push({path:t,message:"must be an object (an atom)"});const r=s.length;J(e,V.atom,t,s),"tag"in e=="rx"in e&&s.push({path:t,message:"must have exactly one of tag and rx"}),e.as!==void 0&&(H(e.as)?Se.includes(e.as)&&s.push({path:`${t}.as`,message:`"${e.as}" is reserved and cannot name a role`}):s.push({path:`${t}.as`,message:"must be a non-empty string (a role name)"})),e.optional!==void 0&&typeof e.optional!="boolean"&&s.push({path:`${t}.optional`,message:"must be a boolean"});let a;if("rx"in e?a=le(e.rx,`${t}.rx`,n,s):"tag"in e&&(Array.isArray(e.tag)?(e.tag.length||s.push({path:`${t}.tag`,message:"must not be an empty list"}),e.tag.forEach((p,c)=>ue(p,`${t}.tag[${c}]`,n,s))):ue(e.tag,`${t}.tag`,n,s)),s.length>r)return;const i={...e.as!==void 0&&{as:e.as},...e.optional===!0&&{optional:!0}};return a?{rx:a,...i}:{tag:e.tag,...i}}function he(e,t,n,s){if(!Array.isArray(e)||!e.length)return void s.push({path:t,message:"must be a non-empty array of atoms"});const r=s.length;let a=!1;const i=e.map((c,w)=>{const $=`${t}[${w}]`,k=Ie(c,$,n,s),v=T(c)&&"rx"in c;return v&&a&&s.push({path:$,message:"two regex atoms in a row (optional atoms between do not separate them): merge them into one pattern"}),a=v||a&&T(c)&&c.optional===!0,k}),p=e[e.length-1];return T(p)&&p.optional===!0&&s.push({path:`${t}[${e.length-1}]`,message:"the outermost atom must not be optional"}),s.length>r?void 0:i}function ze(e,t,n,s){if(!T(e))return void s.push({path:t,message:"must be an object (a search)"});const r=s.length;J(e,V.search,t,s),H(e.from)?(G.includes(e.from)||!n.declared.has(e.from))&&s.push({path:`${t}.from`,message:`unknown tag "${e.from}"`}):s.push({path:`${t}.from`,message:"must be a non-empty string (a tag name)"}),"back"in e=="forward"in e&&s.push({path:t,message:"must have exactly one of back and forward"});const a="back"in e?he(e.back,`${t}.back`,n,s):void 0,i="forward"in e?he(e.forward,`${t}.forward`,n,s):void 0;if(!(s.length>r))return{from:e.from,...a&&{back:a},...i&&{forward:i}}}function Te(e,t,n,s){if(!T(e))return void s.push({path:t,message:"must be an object (a tag)"});const r=s.length;J(e,V.tag,t,s),"rx"in e=="search"in e&&s.push({path:t,message:"must have exactly one of rx and search"});const a="rx"in e?le(e.rx,`${t}.rx`,n,s):void 0;let i;"search"in e&&(!Array.isArray(e.search)||!e.search.length?s.push({path:`${t}.search`,message:"must be a non-empty array of searches"}):i=e.search.map((c,w)=>ze(c,`${t}.search[${w}]`,n,s)).filter(c=>c!==void 0));let p;if(e.resolve!==void 0&&(typeof e.resolve!="string"?s.push({path:`${t}.resolve`,message:"must be a string (a resolver name)"}):Object.hasOwn(n.resolvers,e.resolve)?p=n.resolvers[e.resolve]:s.push({path:`${t}.resolve`,message:`unknown resolver "${e.resolve}"`})),e.weak!==void 0&&typeof e.weak!="boolean"&&s.push({path:`${t}.weak`,message:"must be a boolean"}),e.fate!==void 0&&e.fate!=="separator"&&e.fate!=="connector"&&e.fate!=="bracket"&&s.push({path:`${t}.fate`,message:'must be "separator", "connector" or "bracket"'}),!(s.length>r))return{...a&&{rx:a},...i&&{search:i},...p&&{resolve:p},...e.weak===!0&&{weak:!0},...e.fate!==void 0&&{fate:e.fate}}}function We(e,t={}){const n=[];if(!T(e))return{conf:{version:"",tags:{}},errors:[{path:"",message:"must be an object (a conf)"}]};J(e,V.conf,"",n);let s="";H(e.version)?s=e.version:n.push({path:"version",message:e.version===void 0?"is required":"must be a non-empty string"});const r=new Map;if(e.patterns!==void 0&&!T(e.patterns))n.push({path:"patterns",message:"must be an object of name \u2192 pattern source"});else if(e.patterns)for(const[c,w]of Object.entries(e.patterns))je.test(c)?typeof w!="string"?n.push({path:`patterns.${c}`,message:"must be a string (pattern source)"}):r.set(c,w):n.push({path:`patterns.${c}`,message:"a pattern name is letters, digits and _, not starting with a digit"});const a=T(e.patterns)?{patterns:e.patterns}:{};if(!T(e.tags))return n.push({path:"tags",message:e.tags===void 0?"is required":"must be an object of tags"}),{conf:{version:s,...a,tags:{}},errors:n};const i={patterns:r,resolvers:t.resolvers??{},declared:new Set(Object.keys(e.tags))},p={};for(const[c,w]of Object.entries(e.tags)){const $=[];(G.includes(c)||Ae.includes(c))&&$.push({path:`tags.${c}`,message:`"${c}" is reserved and cannot be declared as a tag`});const k=Te(w,`tags.${c}`,i,$);for(const v of $)n.push({...v,tag:c});!$.length&&k&&(p[c]=k)}return{conf:{version:s,...a,tags:p},errors:n}}function ee(e){const t={starts:new Map,ends:new Map,all:[...e]};for(const n of t.all)for(const[s,r]of[[t.starts,n[1][0]],[t.ends,n[1][1]]]){const a=s.get(r);a?a.push(n):s.set(r,[n])}return t}const Z=(e,t)=>e[0]-t[0]||e[1]-t[1],ge=(e,t)=>e<t?-1:e>t?1:0,de=[];class q{#e;#s;constructor(t,n){this.#e=t,this.#s=n}static empty(t){return new q(new Map([["^",[[0,0]]],["$",[[t,t]]]]),t)}static from(t){const n=Object.entries(t).flatMap(([r,a])=>a.map(i=>[r,[i[0],i[1]]])),s=t.$?.[0]?.[1]??Math.max(0,...n.map(([,r])=>r[1]));return q.empty(s).with(n)}toJSON(){const t={};for(const n of[...this.#e.keys()].sort(ge))t[n]=this.#e.get(n).map(s=>[s[0],s[1]]);return t}with(t){const n=new Map;for(const[r,a]of t){const i=n.get(r);i?i.push(a):n.set(r,[a])}let s;for(const[r,a]of n){const i=this.#e.get(r)??de,p=[...i,...a].sort(Z),c=p.filter((w,$)=>$===0||Z(w,p[$-1])!==0);c.length!==i.length&&(s??=new Map(this.#e)).set(r,c)}return s?new q(s,this.#s):this}size(){let t=0;for(const n of this.#e.values())t+=n.length;return t}#t(t){if(t==="*")return this.#e.values();const n=this.#e.get(t);return n?[n]:[]}has(t,n,s){for(const r of this.#t(t))if(r.some(a=>a[0]===n&&a[1]===s))return!0;return!1}after(t,n){let s;for(const r of this.#t(t)){let a=0,i=r.length;for(;a<i;){const p=a+i>>1;r[p][0]<n?a=p+1:i=p}a<r.length&&(!s||Z(r[a],s)<0)&&(s=r[a])}return s}before(t,n){let s;for(const r of this.#t(t))for(let a=r.length-1;a>=0;a--)if(!(r[a][1]>n)){(!s||Z(r[a],s)>0)&&(s=r[a]);break}return s}spans(t){return this.#e.get(t)??de}all(){const t=[];for(const[n,s]of this.#e)for(const r of s)t.push([n,r]);return t.sort((n,s)=>Z(n[1],s[1])||ge(n[0],s[0]))}edges(){return ee(this.all())}}function pe(e,t){const[n,s]=e,[r,a]=t;return n===r&&s===a?"equals":s<r?"before":a<n?"after":s===r?"meets":a===n?"met-by":n<r?s<a?"overlaps":s===a?"finished-by":"contains":n===r?s<a?"starts":"started-by":s<a?"during":s===a?"finishes":"overlapped-by"}const Q=e=>e.replace(/[gyd]/g,""),me=new WeakMap;function U(e,t){let n=me.get(e);return n||me.set(e,n={}),n[t]??=t==="scan"?new RegExp(e.source,`${Q(e.flags)}g`):t==="back"?new RegExp(`(?:${e.source})$`,Q(e.flags)):t==="forward"?new RegExp(e.source,`${Q(e.flags)}y`):new RegExp(`^(?:${e.source})$`,Q(e.flags))}function ve(e,t,n,s,r,a){const i=s.forward!==void 0,p=s.forward??s.back??[],c=(k,v,M)=>v===void 0?k:{...k,[v]:M},w=(k,v,M)=>v===void 0?k:[...k,{text:M,as:v}],$=(k,v,M,N,h,m,u)=>{if(k===p.length){if(!u)return a(M,N,h,m);if(i){const b=U(u.rx,"forward");b.lastIndex=v;const A=b.exec(e);A&&a(M,v+A[0].length,c(h,u.as,A[0]),w(m,u.as,A[0]))}else{const b=U(u.rx,"back").exec(e.slice(0,v));b&&a(b.index,N,c(h,u.as,b[0]),w(m,u.as,b[0]))}return}const f=p[k];if(f.optional&&$(k+1,v,M,N,h,m,u),"rx"in f){u||$(k+1,v,M,N,h,m,{rx:f.rx,...f.as!==void 0&&{as:f.as}});return}const E=typeof f.tag=="string"?[f.tag]:f.tag,O=b=>E.includes("*")||E.includes(b),x=(b,A,z)=>{const[I,D]=b[1];$(k+1,i?D:I,i?M:I,i?D:N,c(A,f.as,n(b)),[...z,{row:b,...f.as!==void 0&&{as:f.as}}])};if(!u){for(const b of(i?t.starts:t.ends).get(v)??[])O(b[0])&&x(b,h,m);return}const R=U(u.rx,"whole");for(const b of t.all){if(!O(b[0])||(i?b[1][0]<v:b[1][1]>v))continue;const A=i?e.slice(v,b[1][0]):e.slice(b[1][1],v);R.test(A)&&x(b,c(h,u.as,A),w(m,u.as,A))}};$(0,i?r[1][1]:r[1][0],r[1][0],r[1][1],{},[{row:r}])}const Pe=/[‐-―−]/g,qe=/[‘-‟]/g,De=e=>"\u201C\u201D\u201E\u201F".includes(e)?'"':"'";function we(e){let t="";const n=[];let s=!1;for(let r=0;r<e.length;){const a=e.charCodeAt(r);if(a<128){a===32||a>=9&&a<=13?(s||(n.push(r),t+=" "),s=!0):(n.push(r),t+=e[r],s=!1),r++;continue}const i=String.fromCodePoint(e.codePointAt(r));if(/\s/.test(i))s||(n.push(r),t+=" "),s=!0;else{const p=i.normalize("NFKC").replace(Pe,"-").replace(qe,De);for(let c=0;c<p.length;c++)n.push(r);t+=p,s=!1}r+=i.length}return n.push(e.length),{text:t,at:n}}const Ke=/[\p{L}\p{N}]/u,Y=(e,t)=>{const n=e.charCodeAt(t);return Number.isNaN(n)?!1:n<128?n>=48&&n<=57||n>=65&&n<=90||n>=97&&n<=122:Ke.test(e[t])},Ze=(e,t)=>Y(e,t)&&Y(e,t-1),_e=(e,t)=>Y(e,t-1)&&Y(e,t),Ce=(e,t)=>e<t?-1:e>t?1:0,be=(e,t)=>e[0]-t[0]||e[1]-t[1],B=(e,t)=>be(e[1],t[1])||Ce(e[0],t[0]);function te(e,t,n){if(!e.length||n(e[e.length-1],t)<=0)return void e.push(t);let s=0,r=e.length;for(;s<r;){const a=s+r>>1;n(e[a],t)<=0?s=a+1:r=a}e.splice(s,0,t)}const $e=new WeakMap;function Le(e){let t=$e.get(e);if(!t){const n=e.forward??e.back??[];$e.set(e,t=[e.from,...n.flatMap(s=>"tag"in s?s.tag:[])])}return t}function Fe(e,t,n,s){const r=[];for(const[a,i]of Object.entries(t.tags)){if(!i.rx)continue;const p=U(i.rx,"scan");p.lastIndex=0;for(let c=p.exec(e);c;c=p.exec(e)){const w=c.index,$=w+c[0].length;if(p.lastIndex=w+(e.codePointAt(w)>65535?2:1),!($===w||Ze(e,w)||_e(e,$))){if(i.resolve){const k=i.resolve({value:c[0]});if(k===null)continue;s.set(n(a,w,$),k)}r.push([a,[w,$]])}}}return r}function Ge(e,t){const{text:n,at:s}=we(e),r=n.length,a=Object.keys(t.tags).sort(),i=new Map(["^","$",...a].map((u,f)=>[u,f])),p=(u,f,E)=>(i.get(u)*(r+1)+f)*(r+1)+E,c=new Map,w=([u,[f,E]])=>c.get(p(u,f,E))??n.slice(f,E),$=new Set,k=new Map,v={starts:new Map,ends:new Map,all:[]},M=(u,f,E)=>{const O=u.get(f);O?te(O,E,B):u.set(f,[E])},N=u=>{const[f,[E,O]]=u,x=p(f,E,O);if($.has(x))return!1;$.add(x);const R=k.get(f);return R?te(R,u[1],be):k.set(f,[u[1]]),te(v.all,u,B),M(v.starts,E,u),M(v.ends,O,u),!0},h=[["^",[0,0]],["$",[r,r]],...Fe(n,t,p,c)];for(const u of h.sort(B))N(u);let m=new Set(k.keys());for(;m.size;){const u=[];for(const f of a){const E=t.tags[f];for(const O of E.search??[])if(Le(O).some(x=>x==="*"||m.has(x)))for(const x of k.get(O.from)??[])ve(n,v,w,O,[O.from,x],(R,b,A)=>{if(E.resolve){if(c.has(p(f,R,b)))return;const z=E.resolve({...A,value:n.slice(R,b)});if(z===null)return;c.set(p(f,R,b),z)}u.push([f,[R,b]])})}m=new Set;for(const f of u.sort(B))N(f)&&m.add(f[0])}return q.empty(e.length).with(v.all.map(([u,[f,E]])=>[u,[s[f],s[E]]]))}const Ve=(e,t)=>e[1][0]-t[1][0]||e[1][1]-t[1][1]||(e[0]<t[0]?-1:e[0]>t[0]?1:0),He=(e,t)=>{const n=pe(e,t);return n==="starts"||n==="during"||n==="finishes"},Je=e=>e[0]==="^"||e[0]==="$";function Qe(e,t,n){const{text:s,at:r}=we(e),a=s.length,i=new Int32Array(e.length+1).fill(-1),p=new Int32Array(e.length+1).fill(-1);for(let o=0;o<r.length;o++)i[r[o]]<0&&(i[r[o]]=o),p[r[o]]=o;const c=new Map(Object.keys(n.tags).map((o,l)=>[o,l])),w=[],$=[];for(const[o,[l,g]]of t.all())if(o==="^")w.push(["^",[0,0]]);else if(o==="$")w.push(["$",[a,a]]);else{const d=c.get(o);if(d===void 0)throw new Error(`the chart has rows of "${o}", which the conf does not declare`);const y=i[l]??-1,j=p[g]??-1;if(y<0||j<0)throw new Error(`${o} (${l},${g}) is not on a boundary of the text: is this the chart of another text?`);const S=[o,[y,j]];w.push(S),$.push({row:S,weak:n.tags[o].weak===!0,rank:d})}w.sort(Ve);const k=o=>`${o[0]} (${r[o[1][0]]},${r[o[1][1]]})`,v=new Map,M=new Map,N=new Set,h=new Set;function m(o){const l=v.get(o);if(l)return l;if(N.has(o))return[];N.add(o);const[g,[d,y]]=o,j=n.tags[g],S=w.filter(P=>d<=P[1][0]&&P[1][1]<=y&&P!==o),F=ee(S),K=[];for(const P of j.search??[])for(const ae of S)ae[0]===P.from&&ve(s,F,u,P,ae,(Ee,Me,Oe,ie)=>{if(Ee!==d||Me!==y)return;if(!j.resolve)return void K.push({steps:ie});const ce=j.resolve({...Oe,value:s.slice(d,y)});ce===null?h.add(o):K.push({steps:ie,value:ce})});if(N.delete(o),!K.length)throw new Error(h.has(o)?`resolver of "${g}" refused (${r[d]},${r[y]}) at dom time`:`no derivation of ${k(o)}: is this the chart of this conf?`);return v.set(o,K),K}function u(o){const l=n.tags[o[0]];if(!l?.resolve)return s.slice(o[1][0],o[1][1]);let g=M.get(o);if(g===void 0){if(l.search)g=m(o)[0].value;else{const d=l.resolve({value:s.slice(o[1][0],o[1][1])});if(d===null)throw new Error(`resolver of "${o[0]}" refused (${r[o[1][0]]},${r[o[1][1]]}) at dom time`);g=d}M.set(o,g)}return g}$.sort((o,l)=>Number(o.weak)-Number(l.weak)||l.row[1][1]-l.row[1][0]-(o.row[1][1]-o.row[1][0])||o.rank-l.rank||o.row[1][0]-l.row[1][0]);const f=[],E=new Map,O=new Map,x=new Int32Array(a+1).fill(-1),R=new Int32Array(a+1).fill(a+1),b=new Map,A=new Map,z=new Map,I=[],D=o=>{for(;I.length>o;)I.pop()()},_=([,[o,l]])=>o*(a+1)+l,se=([o,l])=>{for(let g=o+1;g<l;g++)if(x[g]>l||R[g]<o)return!0;return!1};function ke(o){const[l,[g,d]]=o;let y=O.get(l);y||O.set(l,y=[]);const[j,S]=[x[g],R[d]];f.push(o),y.push(o),E.set(_(o),o),d>j&&(x[g]=d),g<S&&(R[d]=g),I.push(()=>{f.pop(),y.pop(),E.delete(_(o)),x[g]=j,R[d]=S})}function xe(o,l){const g=b.get(l);g?g.includes(o[0])||(g.push(o[0]),I.push(()=>g.pop())):(b.set(l,[o[0]]),I.push(()=>b.delete(l)));const d=z.get(o);return d!==l&&(z.set(o,l),I.push(()=>d===void 0?z.delete(o):z.set(o,d))),!0}function ne(o,l){if(l!==void 0&&o[0]===l)return oe(o,l,!1);const g=E.get(_(o));if(g)return g[0]===o[0]||xe(o,g);if(se(o[1]))return!1;const d=I.length;return ke(o),!n.tags[o[0]].search||oe(o,o[0],!0)?!0:(D(d),!1)}function oe(o,l,g){for(const d of m(o)){const y=I.length;if(d.steps.every(j=>!("row"in j)||Je(j.row)||ne(j.row,l)))return g&&(A.set(o,d),I.push(()=>A.delete(o))),!0;D(y)}return!1}for(const{row:o}of $)E.get(_(o))?.[0]!==o[0]&&(se(o[1])||O.get(o[0])?.some(l=>He(o[1],l[1]))||ne(o));const C={tag:"doc",start:0,end:e.length,attrs:{},text:e,children:[]},L=new Map,W=[{node:C,lo:0,hi:a}];for(const o of[...f].sort((l,g)=>l[1][0]-g[1][0]||g[1][1]-l[1][1])){const[l,[g,d]]=o;for(;W[W.length-1].lo>g||W[W.length-1].hi<d;)W.pop();const y=n.tags[l],j=y.resolve?y.search?A.get(o).value:u(o):void 0,S={tag:l,start:r[g],end:r[d]};j!==void 0&&(S.value=j),S.attrs={};const F=b.get(o);F&&(S.also=F),y.fate!==void 0&&(S.fate=y.fate),S.children=[],W[W.length-1].node.children.push(S),W.push({node:S,lo:g,hi:d}),L.set(o,S)}for(const o of f){const l=A.get(o);if(!l)continue;const g=L.get(o);for(const d of l.steps)if(d.as!==void 0)if("row"in d){const y=z.get(d.row);g.attrs[d.as]=L.get(d.row)??(y&&L.get(y))??s.slice(d.row[1][0],d.row[1][1])}else g.attrs[d.as]=d.text}const re=o=>{if(!o.children.length&&(o!==C||!o.end))return;const l=[];let g=o.start;const d=y=>{y>g&&l.push({tag:"text",start:g,end:y,attrs:{},children:[]})};for(const y of o.children)d(y.start),l.push(y),g=y.end,re(y);d(o.end),o.children=l};return re(C),C}const Ue=/^[.,;:!?)\]}]/;function X(e){return e.children.length?e.children.flatMap(X):[e]}function ye(e,t){const n=e.text;if(n===void 0)throw new Error("the tree has no text: build it with dom, or keep `text` on the root when you parse it back");const s=new Set,r=h=>{s.add(h),h.children.forEach(r)},a=h=>h.fate==="separator",i=h=>n.slice(h.start,h.end),p=h=>{for(const m of h.children)t(m)?r(m):p(m)};p(e);const c=e.children.length?X(e):[];for(let h=c.length-1;h>=0;h--){if(c[h].fate!=="connector"||s.has(c[h]))continue;const m=c.slice(h+1).find(u=>!a(u));m&&s.has(m)&&s.add(c[h])}const w=new Set,$=h=>{if(h.children.forEach($),h.fate!=="bracket"||s.has(h)||h.children.length<2)return;const m=h.children,u=x=>x.tag==="text"&&x.end-x.start===1,f=u(m[0])?m[0]:void 0,E=u(m[m.length-1])?m[m.length-1]:void 0,O=X(h).filter(x=>x!==f&&x!==E&&!a(x));O.some(x=>s.has(x))&&O.every(x=>s.has(x))?r(h):f&&w.add(f)};$(e);const k=h=>{const m=i(h);return m.includes("-")?4:m.includes(";")?3:m.includes(":")?2:m.includes(",")?1:0};let v,M=[];const N=h=>{if(M.some(m=>s.has(m))){const m=M.filter(f=>a(f)&&!s.has(f));let u;for(const f of m)(!u||k(f)>k(u))&&(u=f);for(const f of m)f!==u&&s.add(f);u&&(!v||!h||w.has(v)||Ue.test(i(h)))&&s.add(u)}M=[]};for(const h of c)a(h)||s.has(h)?M.push(h):(N(h),v=h);return N(void 0),s}function Ye(e,t){const n=ye(e,t),s=e.text;return(e.children.length?X(e):[]).filter(r=>!n.has(r)).map(r=>s.slice(r.start,r.end)).join("")}export{q as Chart,We as compile,Qe as dom,ee as edges,Ge as find,Ye as gist,ye as hidden,pe as rel};
@@ -1,39 +1,157 @@
1
- import{read as A,compile as C,html as H,gist as F}from"./index.js";function h(e){const t=new Set;for(const r of e.recipes)t.add(r.entity);return[...t]}function J(e,t,r){if(!r)return[];const i=new Set;for(const c of t)A(c.text,e).mentions.forEach((o,u)=>{r(o,u)&&i.add(o.entity)});return h(e).filter(c=>i.has(c))}const l=e=>e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t]),b=e=>e instanceof Error?e.message:String(e);function B(e){let t=0;for(let r=0;r<e.length;r++)t=t*31+e.charCodeAt(r)|0;return Math.abs(t)}function D(e){return`<table><thead><tr><th>span</th><th>leaf</th><th>mention</th><th>text</th></tr></thead><tbody>${e.leaves.map(r=>{const i=r.mention===void 0?"":`${r.mention} ${e.mentions[r.mention].entity}`;return`<tr><td>${r.start}\u2013${r.end}</td><td>${r.kind}${r.part?`:${r.part}`:""}</td><td>${i}</td><td><code>${l(JSON.stringify(e.text.slice(r.start,r.end)))}</code></td></tr>`}).join("")}</tbody></table>`}function x(e,t,r,i){try{const c=performance.now(),d=A(t,r),o=(performance.now()-c).toFixed(2);e.innerHTML=`
2
- <p class="fewrd-tagged">${H(d,i)}</p>
3
- <p class="fewrd-gist"><span class="fewrd-k">gist</span> ${l(F(d,i))}</p>
4
- <details><summary>${d.mentions.length} mentions \xB7 ${d.leaves.length} leaves \xB7 ${o} ms</summary>
5
- <pre>${l(JSON.stringify(d.mentions,null,1))}</pre>
6
- ${D(d)}
7
- </details>`}catch(c){e.innerHTML=`<p class="fewrd-error">${l(b(c))}</p>`}}function I(e,t){const{cases:r}=t,i="data"in t?t.resolvers:void 0,c="data"in t?t.save:void 0;let d="data"in t?JSON.stringify(t.data,null,2):"",o,u=[];"book"in t?o=t.book:{book:o,errors:u}=C(t.data,{resolvers:i});const p=new Set(J(o,r,t.fold)),w=n=>p.has(n.entity),a=`fewrd-pg-${Math.random().toString(36).slice(2)}`;e.classList.add(a,"condensed");const S=document.createElement("style");document.head.appendChild(S);function k(){S.textContent=`
8
- .${a}.condensed [data-fold] { display: none; }
9
- .${a}:not(.condensed) [data-fold] { text-decoration: line-through; text-decoration-color: rgba(0,0,0,.35); }
10
- .${a} [data-entity] { background: var(--h); border-radius: 3px; padding: 0 1px; }
11
- .${a} .fewrd-tagged [data-entity] [data-entity] { box-shadow: inset 0 -2px 0 rgba(0,0,0,.12); }
12
- .${a} [data-part="value"] { font-weight: 600; }
13
- .${a} [data-part="label"], .${a} [data-part="channel"], .${a} [data-part="lead"] { opacity: .7; }
14
- .${a} [data-part="date"] { font-style: italic; }
15
- .${a} [data-sep] { white-space: pre-wrap; }
16
- .${a} .fewrd-controls { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 12px; }
17
- .${a} .fewrd-folds { min-width: 160px; }
18
- .${a} .fewrd-editor { margin-bottom: 12px; }
19
- .${a} .fewrd-book { display: block; width: 100%; box-sizing: border-box; min-height: 18em; resize: vertical; font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; tab-size: 2; }
20
- .${a} .fewrd-errors { margin: 6px 0; padding-left: 18px; font-size: 12px; }
21
- .${a} .fewrd-errors:empty { display: none; }
22
- .${a} .fewrd-errors, .${a} .fewrd-error { color: #b3261e; }
23
- .${a} .fewrd-status { font-size: 12px; opacity: .7; }
24
- ${h(o).map(n=>`.${a} [data-entity=${JSON.stringify(n)}] { --h: hsl(${B(n)%360}, 70%, 88%); }`).join(`
25
- `)}
26
- `}const T="data"in t?`<details class="fewrd-editor" open><summary>recipes <button type="button" class="fewrd-reset">reset</button>${c?' <button type="button" class="fewrd-save">save</button> <span class="fewrd-status"></span>':""}</summary>
27
- <textarea class="fewrd-book" spellcheck="false"></textarea>
28
- <ul class="fewrd-errors"></ul>
29
- </details>`:"";e.innerHTML=`
30
- <div class="fewrd-controls">
31
- <label><input type="checkbox" class="fewrd-condensed" checked /> condensed</label>
32
- <select class="fewrd-folds" multiple></select>
33
- </div>
34
- ${T}
35
- <section class="fewrd-trial">
36
- <textarea class="fewrd-custom" rows="3" spellcheck="false"></textarea>
37
- <div class="fewrd-trial-out"></div>
38
- </section>
39
- <div class="fewrd-cases"></div>`;const L=e.querySelector(".fewrd-condensed"),m=e.querySelector(".fewrd-folds"),$=e.querySelector(".fewrd-custom"),q=e.querySelector(".fewrd-trial-out"),E=e.querySelector(".fewrd-cases");E.innerHTML=r.map((n,s)=>`<section class="fewrd-case"><h2>${l(r[s].name)}</h2><div class="fewrd-out" data-i="${s}"></div></section>`).join("");const j=Array.from(E.querySelectorAll(".fewrd-out"));function M(){const n=h(o);for(const s of Array.from(p))n.includes(s)||p.delete(s);m.size=Math.max(n.length,1),m.innerHTML=n.map(s=>`<option value="${l(s)}"${p.has(s)?" selected":""}>${l(s)}</option>`).join("")}function y(){j.forEach(n=>x(n,r[Number(n.getAttribute("data-i"))].text,o,w)),x(q,$.value,o,w)}m.addEventListener("change",()=>{p.clear();for(const n of Array.from(m.selectedOptions))p.add(n.value);y()}),L.addEventListener("change",()=>e.classList.toggle("condensed",L.checked)),$.addEventListener("input",()=>x(q,$.value,o,w));const f=e.querySelector(".fewrd-book"),O=e.querySelector(".fewrd-errors");function g(n){O&&(O.innerHTML=n.map(s=>`<li>${l([s.path,s.entity&&`(${s.entity})`].filter(Boolean).join(" "))}${s.path?": ":""}${l(s.message)}</li>`).join(""))}function N(){if(!f)return;let n;try{n=JSON.parse(f.value)}catch(s){return g([{path:"",message:`invalid JSON, still showing the last good book: ${b(s)}`}])}({book:o,errors:u}=C(n,{resolvers:i})),g(u),k(),M(),y()}if(f){f.value=d,f.addEventListener("input",N),e.querySelector(".fewrd-reset").addEventListener("click",s=>{s.preventDefault(),f.value=d,N()});const n=e.querySelector(".fewrd-status");e.querySelector(".fewrd-save")?.addEventListener("click",async s=>{s.preventDefault();const v=f.value;try{JSON.parse(v),await c(v),d=v,n.textContent="saved"}catch(z){n.textContent=`not saved: ${b(z)}`}}),f.addEventListener("input",()=>n&&(n.textContent=""))}g(u),k(),M(),y()}export{h as entities,J as initialSelection,I as mount};
1
+ import{hidden as mr,compile as xr,find as Lr,dom as Mr,gist as Sr}from"./index.js";function Ar(d){const w=[],i=[];for(const g of d){let a=i.findIndex(b=>b<=g[1][0]);a<0&&(a=w.length,w.push([]),i.push(0)),w[a].push(g),i[a]=g[1][1]}return w}function zr(d,w){const i=[0];for(let g=0;d.length-g>w;i.push(g)){const a=d.lastIndexOf(" ",g+w-1);g=a>g?a+1:g+w}return i}function ue([d,w],i){const g=[];return i.forEach((a,b)=>{const C=i[b+1]??1/0;d<C&&w>a&&g.push({line:b,from:Math.max(d,a)-a,to:Math.min(w,C)-a})}),g}function Or(d,w){const i=[],g=(a,b)=>{const C=a.also?` also=${a.also.join(",")}`:"",re=a.value!==void 0?` value=${JSON.stringify(a.value)}`:"";i.push({node:a,depth:b,line:`${" ".repeat(b)}${a.tag} (${a.start}, ${a.end})${C}${re} ${JSON.stringify(w.slice(a.start,a.end))}`});for(const te of a.children)g(te,b+1)};for(const a of d.children)g(a,0);return i}function Ir(d,w){const i=mr(d,w),g=[],a=b=>{if(b.children.length)return b.children.forEach(a);if(!i.has(b))return;const C=g[g.length-1];C&&C[1]===b.start?C[1]=b.end:g.push([b.start,b.end])};return d.children.forEach(a),g}const Je=[20,50,80,110,140,170,200,230,260,290,320,350],Br=d=>(Math.max(0,d)*5+Math.floor(Math.max(0,d)/Je.length))%Je.length,ee=(d,w,i)=>`${d}:${w}:${i}`,je=d=>d instanceof Error?d.message:String(d);function t(d,w,i){const g=document.createElement(d);return w&&(g.className=w),i!==void 0&&(g.textContent=i),g}const Nr="--bg:oklch(.965 .008 85);--surface:oklch(.977 .007 85);--raised:oklch(.99 .005 85);--sunken:oklch(.948 .009 85);--text:oklch(.21 .015 265);--muted:oklch(.45 .015 85);--faint:oklch(.6 .012 85);--line:oklch(.91 .009 85);--line-strong:oklch(.84 .011 85);--hover:oklch(.935 .02 270);--accent:oklch(.5 .13 275);--accent-fg:oklch(.985 .005 85);--ok:oklch(.5 .09 155);--warn:oklch(.52 .09 70);--err:oklch(.5 .12 27);--err-bg:oklch(.962 .02 27);--grey:oklch(.7 .008 85);--band-l:.64;--band-c:.105;--wash:18%;--shadow-sm:0 1px 2px oklch(.3 .02 85/.07);--shadow-pop:0 1px 2px oklch(.3 .02 85/.08),0 10px 28px oklch(.3 .02 85/.16);",kr="--bg:oklch(.22 .012 265);--surface:oklch(.25 .013 265);--raised:oklch(.29 .015 265);--sunken:oklch(.19 .011 265);--text:oklch(.94 .006 265);--muted:oklch(.74 .01 265);--faint:oklch(.58 .012 265);--line:oklch(.31 .013 265);--line-strong:oklch(.4 .015 265);--hover:oklch(.3 .03 270);--accent:oklch(.76 .09 275);--accent-fg:oklch(.2 .03 275);--ok:oklch(.78 .1 155);--warn:oklch(.8 .09 75);--err:oklch(.75 .11 25);--err-bg:oklch(.27 .04 25);--grey:oklch(.5 .01 265);--band-l:.74;--band-c:.1;--wash:24%;--shadow-sm:0 1px 2px oklch(0 0 0/.3);--shadow-pop:0 1px 2px oklch(0 0 0/.4),0 10px 28px oklch(0 0 0/.45);",Tr=Je.map((d,w)=>`--band-${w}:oklch(var(--band-l) var(--band-c) ${d});`).join(""),be="fewrd-pg-style",Hr=`
2
+ .fewrd-pg { --ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; --icon: "Material Symbols Rounded";
3
+ --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 40px; --t-xs: 12px; --t-sm: 13px; --t-md: 15px; --t-lg: 18px; --t-xl: 26px; --w-reg: 400; --w-med: 500; --w-semi: 600;
4
+ --r-s: 4px; --r-m: 8px; --r-l: 14px; --r-pill: 999px; --grid-size: 15px; --grid-lh: 24px; --band-h: 5px; --band-h-lost: 3px; --lane: 8px; --bar: 73px; --ic: 20px; --ic-s: 16px; --dur: 130ms; --dur-drawer: 200ms; --drawer-w: min(34rem, calc(100vw - var(--s6))); --pane: 38%; --scrim: color-mix(in oklch, var(--text) 30%, transparent);
5
+ ${Tr} ${Nr} color-scheme: light dark; background: var(--bg); color: var(--text); font: var(--w-reg) var(--t-md)/1.5 var(--ui); -webkit-font-smoothing: antialiased; }
6
+ @media (prefers-color-scheme: dark) { .fewrd-pg:not([data-theme="light"]) { ${kr} } }
7
+ .fewrd-pg[data-theme="dark"] { ${kr} color-scheme: dark; }
8
+ .fewrd-pg[data-theme="light"] { color-scheme: light; }
9
+ .fewrd-pg *, .fewrd-pg ::before, .fewrd-pg ::after { box-sizing: border-box; }
10
+ .fewrd-pg [hidden] { display: none !important; }
11
+ .fewrd-pg :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
12
+ .fewrd-pg h2 { margin: 0; font: var(--w-semi) var(--t-md)/1.3 var(--ui); letter-spacing: -.005em; }
13
+ .fewrd-pg button, .fewrd-pg select, .fewrd-pg textarea { font: inherit; color: inherit; }
14
+ .fewrd-pg .fewrd-dim { color: var(--muted); font-size: var(--t-sm); font-variant-numeric: tabular-nums; }
15
+ .fewrd-pg .fewrd-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
16
+
17
+ /* Icons: a ligature name inside, a text glyph in data-fb until the icon font has loaded (the root then carries data-icons). */
18
+ .fewrd-pg .fewrd-ic { display: inline-grid; place-items: center; flex: none; width: var(--ic); height: var(--ic); overflow: hidden; font-size: 0; line-height: 1; user-select: none; transition: transform var(--dur); }
19
+ .fewrd-pg .fewrd-ic::before { content: attr(data-fb); font: var(--w-med) calc(var(--ic) * .9)/1 var(--ui); }
20
+ .fewrd-pg[data-icons] .fewrd-ic { font: var(--w-reg) var(--ic)/1 var(--icon); font-variation-settings: "wght" 500; font-feature-settings: "liga"; letter-spacing: normal; text-transform: none; white-space: nowrap; direction: ltr; }
21
+ .fewrd-pg[data-icons] .fewrd-ic::before { content: none; }
22
+
23
+ .fewrd-pg .fewrd-bar { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: var(--s3); min-height: var(--bar); padding: var(--s3) var(--s4); background: color-mix(in oklch, var(--bg) 88%, transparent); backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); }
24
+ .fewrd-pg .fewrd-brand { font: var(--w-semi) var(--t-lg)/1 var(--ui); letter-spacing: -.02em; }
25
+ .fewrd-pg .fewrd-nav { display: flex; align-items: center; gap: var(--s2); }
26
+ .fewrd-pg .fewrd-step { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border-radius: var(--r-m); border: 1px solid var(--line); background: var(--raised); color: var(--accent); box-shadow: var(--shadow-sm); cursor: pointer; transition: background var(--dur), color var(--dur), border-color var(--dur), transform var(--dur); }
27
+ .fewrd-pg .fewrd-step .fewrd-ic { --ic: 26px; }
28
+ .fewrd-pg .fewrd-step:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
29
+ .fewrd-pg .fewrd-step:active { transform: scale(.94); }
30
+ .fewrd-pg .fewrd-count { min-width: 7ch; text-align: center; font: var(--w-semi) var(--t-xl)/1 var(--ui); font-variant-numeric: tabular-nums; letter-spacing: -.02em; white-space: nowrap; }
31
+ .fewrd-pg .fewrd-pick { flex: 0 1 30rem; min-width: 0; height: 40px; padding: 0 var(--s3); border-radius: var(--r-m); border: 1px solid var(--line); background: var(--raised); font-weight: var(--w-med); text-overflow: ellipsis; }
32
+ .fewrd-pg .fewrd-theme { display: inline-flex; align-items: center; gap: var(--s2); margin-left: auto; height: 40px; padding: 0 var(--s3); border-radius: var(--r-m); border: 1px solid var(--line); background: var(--raised); font-size: var(--t-sm); font-weight: var(--w-med); cursor: pointer; white-space: nowrap; transition: border-color var(--dur); }
33
+ .fewrd-pg .fewrd-theme:hover { border-color: var(--line-strong); }
34
+
35
+ .fewrd-pg .fewrd-layout { display: grid; gap: var(--s4); grid-template-columns: minmax(0, 1fr); grid-template-areas: "draw" "gist" "fold" "tree"; max-width: 1400px; margin: 0 auto; padding: var(--s4); align-items: start; }
36
+ .fewrd-pg .fewrd-card { min-width: 0; padding: var(--s4); border: 1px solid var(--line); border-radius: var(--r-l); background: var(--surface); }
37
+ .fewrd-pg .fewrd-drawcard { grid-area: draw; }
38
+ .fewrd-pg .fewrd-gistcard { grid-area: gist; min-width: 0; padding: 0 var(--s2); }
39
+ .fewrd-pg .fewrd-foldcard { grid-area: fold; }
40
+ .fewrd-pg .fewrd-treebox { grid-area: tree; }
41
+ @media (min-width: 720px) { .fewrd-pg .fewrd-layout { padding: var(--s5); gap: var(--s5); } }
42
+ @media (max-width: 719px) { .fewrd-pg { --grid-size: 14px; --grid-lh: 22px; } .fewrd-pg .fewrd-brand, .fewrd-pg .fewrd-theme .fewrd-lbl { display: none; } .fewrd-pg .fewrd-bar { gap: var(--s2); padding-inline: var(--s3); } .fewrd-pg .fewrd-count { min-width: 6ch; font-size: var(--t-lg); } .fewrd-pg .fewrd-theme { padding: 0 var(--s2); } .fewrd-pg .fewrd-step { width: 40px; height: 40px; } }
43
+
44
+ /* The conf: a drawer under the sticky bar below 1700px, the left pane from there on. */
45
+ .fewrd-pg .fewrd-scrim { position: fixed; inset: 0; z-index: 20; background: var(--scrim); opacity: 0; pointer-events: none; transition: opacity var(--dur-drawer); }
46
+ .fewrd-pg .fewrd-drawer { position: fixed; top: 0; bottom: 0; left: 0; z-index: 25; display: flex; flex-direction: column; gap: var(--s3); width: var(--drawer-w); padding: calc(var(--bar) + var(--s3)) var(--s4) var(--s4); border-right: 1px solid var(--line); background: var(--surface); box-shadow: var(--shadow-pop); transform: translateX(-100%); opacity: 0; visibility: hidden; transition: transform var(--dur-drawer) ease, opacity var(--dur-drawer) ease, visibility 0s linear var(--dur-drawer); }
47
+ .fewrd-pg[data-drawer] .fewrd-drawer { transform: none; opacity: 1; visibility: visible; transition-delay: 0s; }
48
+ .fewrd-pg[data-drawer] .fewrd-scrim { opacity: 1; pointer-events: auto; }
49
+ .fewrd-pg .fewrd-confhead { display: flex; align-items: baseline; gap: var(--s2); }
50
+ .fewrd-pg .fewrd-drawer textarea { display: block; flex: 1; min-height: 12rem; width: 100%; padding: var(--s3); border: 1px solid var(--line); border-radius: var(--r-m); background: var(--sunken); resize: none; font: var(--w-reg) var(--t-xs)/1.6 var(--mono); font-variant-ligatures: none; tab-size: 2; }
51
+ .fewrd-pg .fewrd-drawer textarea[aria-invalid="true"] { border-color: var(--err); }
52
+ .fewrd-pg .fewrd-errors { flex: none; max-height: 40%; overflow: auto; padding: var(--s3); border: 1px solid color-mix(in oklch, var(--err) 35%, transparent); border-radius: var(--r-m); background: var(--err-bg); color: var(--err); font-size: var(--t-sm); overflow-wrap: anywhere; }
53
+ .fewrd-pg .fewrd-errors ul { margin: var(--s2) 0 0; padding-left: 1.2em; font-size: var(--t-xs); }
54
+ .fewrd-pg .fewrd-errors code { display: block; margin-top: var(--s1); font: inherit; }
55
+ .fewrd-pg .fewrd-confbtn { position: relative; display: grid; place-items: center; flex: none; width: 40px; height: 40px; padding: 0; border-radius: var(--r-m); border: 1px solid var(--line); background: var(--raised); color: var(--muted); cursor: pointer; transition: border-color var(--dur), color var(--dur), background var(--dur); }
56
+ .fewrd-pg .fewrd-confbtn:hover, .fewrd-pg .fewrd-confbtn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
57
+ .fewrd-pg .fewrd-confbtn[aria-expanded="true"] { background: color-mix(in oklch, var(--accent) 10%, var(--raised)); }
58
+ .fewrd-pg .fewrd-confbtn .fewrd-badge { position: absolute; top: -6px; right: -6px; min-width: 18px; padding: 1px 5px; border: 1px solid var(--bg); background: var(--err); color: var(--bg); font-size: 11px; line-height: 14px; text-align: center; }
59
+ @media (min-width: 1700px) {
60
+ .fewrd-pg .fewrd-shell { display: grid; grid-template-columns: var(--pane) minmax(0, 1fr); align-items: start; }
61
+ .fewrd-pg .fewrd-drawer { position: sticky; top: var(--bar); z-index: auto; width: auto; height: calc(100vh - var(--bar)); padding-top: var(--s4); box-shadow: none; transform: none; opacity: 1; visibility: visible; transition: none; }
62
+ .fewrd-pg .fewrd-scrim, .fewrd-pg .fewrd-confbtn { display: none; }
63
+ }
64
+
65
+ .fewrd-pg .fewrd-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s1) var(--s3); }
66
+ .fewrd-pg .fewrd-head .fewrd-grow { margin-left: auto; }
67
+
68
+ /* The grid: the one place that is monospace, so every character is one 1ch cell and a span is a distance. */
69
+ .fewrd-pg .fewrd-draw { font: var(--w-reg) var(--grid-size)/var(--grid-lh) var(--mono); font-variant-ligatures: none; font-feature-settings: "liga" 0, "calt" 0; letter-spacing: 0; transition: opacity .15s; }
70
+ .fewrd-pg [data-stale] .fewrd-draw { opacity: .5; }
71
+ .fewrd-pg .fewrd-probe { display: block; width: 1ch; height: 0; }
72
+ .fewrd-pg .fewrd-line { position: relative; margin-bottom: var(--s4); }
73
+ .fewrd-pg .fewrd-text { position: relative; white-space: pre; height: var(--grid-lh); }
74
+ .fewrd-pg .fewrd-wash { position: absolute; top: 0; height: var(--grid-lh); border-radius: var(--r-s); background: color-mix(in oklch, var(--band) var(--wash), transparent); box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--band) calc(var(--wash) * 1.6), transparent); pointer-events: none; }
75
+ .fewrd-pg .fewrd-wash.mark { background: color-mix(in oklch, var(--band) calc(var(--wash) * .7), transparent); box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--band) calc(var(--wash) * 1.1), transparent); }
76
+ .fewrd-pg .fewrd-lane { position: relative; height: var(--lane); }
77
+ .fewrd-pg .fewrd-lane:first-of-type { margin-top: 2px; }
78
+ .fewrd-pg .fewrd-band { position: absolute; top: 1px; height: var(--band-h); background: var(--band); outline: none; transition: opacity .15s, box-shadow var(--dur), filter var(--dur); }
79
+ .fewrd-pg .fewrd-band.lost { top: 2px; height: var(--band-h-lost); }
80
+ .fewrd-pg .fewrd-band.gone { opacity: .25; }
81
+ .fewrd-pg .fewrd-band.hot { z-index: 1; opacity: 1; filter: saturate(1.15) brightness(1.06); box-shadow: 0 0 0 2px color-mix(in oklch, var(--band) 30%, transparent); }
82
+ .fewrd-pg .fewrd-band.gone.hot { opacity: .6; }
83
+ /* Chip highlights: lit bands get a soft ring; while anything is lit the others recede. */
84
+ .fewrd-pg .fewrd-band.mark { z-index: 1; opacity: 1; box-shadow: 0 0 0 2px color-mix(in oklch, var(--band) 26%, transparent); filter: saturate(1.1); }
85
+ .fewrd-pg .fewrd-band.gone.mark { opacity: .6; }
86
+ .fewrd-pg .fewrd-draw[data-marking] .fewrd-band:not(.mark):not(.hot) { opacity: .45; }
87
+ .fewrd-pg .fewrd-draw[data-marking] .fewrd-band.gone:not(.mark):not(.hot) { opacity: .18; }
88
+ .fewrd-pg .fewrd-band:focus-visible { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--band); }
89
+ .fewrd-pg .fewrd-band.open { border-top-left-radius: 2px; border-bottom-left-radius: 2px; }
90
+ .fewrd-pg .fewrd-band.close { border-top-right-radius: 2px; border-bottom-right-radius: 2px; }
91
+ .fewrd-pg .fewrd-band.open::before, .fewrd-pg .fewrd-band.close::after { content: ""; position: absolute; top: -1px; width: 2px; height: 7px; background: color-mix(in oklch, var(--band) 85%, black); }
92
+ .fewrd-pg .fewrd-band.lost.open::before, .fewrd-pg .fewrd-band.lost.close::after { height: 5px; }
93
+ .fewrd-pg .fewrd-band.open::before { left: 0; }
94
+ .fewrd-pg .fewrd-band.close::after { right: 0; }
95
+ .fewrd-pg .fewrd-band .fewrd-hit { position: absolute; inset: -2px 0; }
96
+ .fewrd-pg .fewrd-grey { color: var(--grey); text-decoration: line-through; }
97
+ .fewrd-pg .fewrd-hint { margin: var(--s3) 0 0; color: var(--muted); font-size: var(--t-xs); }
98
+ .fewrd-pg .fewrd-failed { margin: 0; color: var(--err); font-size: var(--t-sm); }
99
+
100
+ /* The popover: above the text line, or below the last lane; the arrow points at the band. */
101
+ .fewrd-pg .fewrd-tip { position: fixed; z-index: 20; display: grid; gap: 2px; max-width: min(90vw, 26rem); padding: var(--s2) var(--s3); border: 1px solid var(--line-strong); border-radius: var(--r-m); background: var(--raised); color: var(--text); font: var(--w-reg) var(--t-xs)/1.45 var(--ui); box-shadow: var(--shadow-pop); pointer-events: none; animation: fewrd-pop var(--dur) ease-out; }
102
+ .fewrd-pg .fewrd-tip::after { content: ""; position: absolute; left: var(--ax, 50%); width: 10px; height: 10px; margin-left: -5px; background: var(--raised); border: 1px solid var(--line-strong); transform: rotate(45deg); }
103
+ .fewrd-pg .fewrd-tip[data-side="above"]::after { bottom: -6px; border-left: 0; border-top: 0; }
104
+ .fewrd-pg .fewrd-tip[data-side="below"]::after { top: -6px; border-right: 0; border-bottom: 0; }
105
+ .fewrd-pg .fewrd-tip-head { display: flex; align-items: center; gap: var(--s2); }
106
+ .fewrd-pg .fewrd-tip-head i { width: 12px; height: var(--band-h); border-radius: 2px; background: var(--band); }
107
+ .fewrd-pg .fewrd-tip-head b { font-weight: var(--w-semi); font-size: var(--t-sm); }
108
+ .fewrd-pg .fewrd-tip-head span { color: var(--muted); font-variant-numeric: tabular-nums; }
109
+ .fewrd-pg .fewrd-tip-value { overflow-wrap: anywhere; }
110
+ .fewrd-pg .fewrd-tip-note { color: var(--muted); }
111
+ @keyframes fewrd-pop { from { opacity: 0; } }
112
+
113
+ .fewrd-pg .fewrd-gistcard h2 { color: var(--muted); font-size: var(--t-xs); font-weight: var(--w-semi); letter-spacing: .08em; text-transform: uppercase; }
114
+ .fewrd-pg .fewrd-gist { margin: var(--s2) 0 0; font: var(--w-reg) 17px/1.6 var(--ui); white-space: pre-wrap; overflow-wrap: anywhere; }
115
+ .fewrd-pg .fewrd-gist.empty { color: var(--muted); font-style: italic; }
116
+ .fewrd-pg .fewrd-verdict { display: inline-flex; align-items: center; gap: var(--s1); font-size: var(--t-xs); font-weight: var(--w-med); }
117
+ .fewrd-pg .fewrd-verdict .fewrd-ic { --ic: 14px; }
118
+ .fewrd-pg .fewrd-verdict.ok { color: var(--ok); }
119
+ .fewrd-pg .fewrd-verdict.warn { color: var(--warn); }
120
+ .fewrd-pg .fewrd-reset { padding: var(--s1) var(--s3); border-radius: var(--r-pill); border: 1px solid var(--line); background: var(--raised); font-size: var(--t-xs); font-weight: var(--w-med); cursor: pointer; }
121
+ .fewrd-pg .fewrd-reset:hover { border-color: var(--accent); color: var(--accent); }
122
+
123
+ .fewrd-pg .fewrd-chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s3); }
124
+ .fewrd-pg .fewrd-cap { flex-basis: 100%; margin-top: var(--s1); color: var(--muted); font-size: var(--t-xs); }
125
+ .fewrd-pg .fewrd-chip { --band: var(--faint); display: inline-flex; align-items: center; border: 1px solid var(--line); border-radius: var(--r-pill); background: var(--raised); font: var(--w-med) var(--t-sm)/1 var(--ui); transition: background var(--dur), border-color var(--dur); }
126
+ .fewrd-pg .fewrd-chip:hover { border-color: color-mix(in oklch, var(--band) 60%, var(--line)); }
127
+ .fewrd-pg .fewrd-chip.on { border-color: color-mix(in oklch, var(--band) 55%, var(--line)); background: color-mix(in oklch, var(--band) 16%, var(--raised)); }
128
+ .fewrd-pg .fewrd-chip button { display: inline-flex; align-items: center; margin: 0; border: 0; background: none; cursor: pointer; }
129
+ .fewrd-pg .fewrd-chip-main { gap: 6px; padding: 7px 4px 7px 10px; border-radius: var(--r-pill) 0 0 var(--r-pill); font-weight: inherit; }
130
+ .fewrd-pg .fewrd-chip i, .fewrd-pg .fewrd-tag i { width: 12px; height: var(--band-h); border-radius: 2px; background: var(--band); }
131
+ .fewrd-pg .fewrd-eye { justify-content: center; width: 28px; height: 28px; margin-right: 2px; padding: 0; border-radius: var(--r-pill); color: var(--muted); transition: background var(--dur), color var(--dur); }
132
+ .fewrd-pg .fewrd-eye .fewrd-ic { --ic: var(--ic-s); }
133
+ .fewrd-pg .fewrd-eye:hover { background: color-mix(in oklch, var(--band) 16%, transparent); color: var(--text); }
134
+ .fewrd-pg .fewrd-chip.folded .fewrd-name { color: var(--muted); text-decoration: line-through; }
135
+ .fewrd-pg .fewrd-chip.folded .fewrd-eye { color: var(--faint); }
136
+ .fewrd-pg .fewrd-tag { display: inline-flex; align-items: center; gap: 6px; padding: 7px 10px; border: 1px solid transparent; border-radius: var(--r-pill); color: var(--muted); font: var(--w-reg) var(--t-sm)/1 var(--ui); cursor: default; }
137
+ .fewrd-pg .fewrd-tag i { opacity: .5; }
138
+
139
+ .fewrd-pg summary { display: flex; align-items: center; gap: var(--s2); padding: var(--s1) 0; cursor: pointer; list-style: none; }
140
+ .fewrd-pg summary::-webkit-details-marker { display: none; }
141
+ .fewrd-pg summary .fewrd-ic { color: var(--muted); transform: rotate(-90deg); }
142
+ .fewrd-pg details[open] > summary .fewrd-ic { transform: none; }
143
+ .fewrd-pg .fewrd-badge { padding: 2px 8px; border-radius: var(--r-pill); background: var(--err-bg); color: var(--err); font-size: var(--t-xs); font-weight: var(--w-semi); }
144
+
145
+ .fewrd-pg .fewrd-tree { margin-top: var(--s3); font: var(--w-reg) var(--t-sm)/1.75 var(--ui); }
146
+ .fewrd-pg .fewrd-node { --d: 0; display: flex; align-items: baseline; gap: var(--s2); min-width: 0; padding: 0 var(--s2) 0 calc(var(--s2) + var(--d) * 1.25rem); border-radius: var(--r-s); background-image: repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px 1.25rem); background-size: calc(var(--d) * 1.25rem) 100%; background-repeat: no-repeat; background-position: var(--s2) 0; transition: background-color var(--dur); }
147
+ .fewrd-pg .fewrd-node:hover, .fewrd-pg .fewrd-node.on { background-color: var(--hover); }
148
+ .fewrd-pg .fewrd-node.mark { background-color: color-mix(in oklch, var(--band) 14%, transparent); }
149
+ .fewrd-pg .fewrd-node i { flex: none; width: 10px; height: var(--band-h); align-self: center; border-radius: 2px; background: var(--band); }
150
+ .fewrd-pg .fewrd-node b { font-weight: var(--w-semi); }
151
+ .fewrd-pg .fewrd-node .sp { flex: none; color: var(--muted); font-variant-numeric: tabular-nums; }
152
+ .fewrd-pg .fewrd-node .pill { flex: none; padding: 0 6px; border-radius: var(--r-pill); background: color-mix(in oklch, var(--muted) 16%, transparent); font-size: var(--t-xs); }
153
+ .fewrd-pg .fewrd-node .q { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
154
+ .fewrd-pg .fewrd-node.water { color: var(--muted); }
155
+ .fewrd-pg .fewrd-node.water b { font-weight: var(--w-reg); }
156
+ .fewrd-pg .fewrd-node.gone b, .fewrd-pg .fewrd-node.gone .q { color: var(--grey); text-decoration: line-through; }
157
+ @media (prefers-reduced-motion: reduce) { .fewrd-pg *, .fewrd-pg ::before { transition: none !important; animation: none !important; } }`,me=["auto","light","dark"],U=(d,w)=>{const i=t("span","fewrd-ic",d);return i.dataset.fb=w,i.setAttribute("aria-hidden","true"),i},Rr=(d,w,i)=>{d.textContent=w,d.dataset.fb=i},Jr={auto:["contrast","\u25D0"],light:["light_mode","\u2600"],dark:["dark_mode","\u263E"]};function jr(d,w){const{cases:i}=w;if(!document.getElementById(be)){const r=t("style");r.id=be,r.textContent=Hr,document.head.appendChild(r)}d.classList.add("fewrd-pg");const g=new Map(Object.entries(w.confs).map(([r,{conf:n,resolvers:p}])=>{const{conf:o,errors:$}=xr(n,{resolvers:p});return[r,{resolvers:p,compiled:o,errors:$,text:JSON.stringify(n,null,2)}]}));let a=0,b=80;const C=new Map,re=new Map,te=(r,n,p)=>{const o=t("button","fewrd-step");return o.type="button",o.setAttribute("aria-label",r),o.append(U(n,p)),o},Pe=te("previous case","chevron_left","\u2039"),_e=te("next case","chevron_right","\u203A"),Ke=t("span","fewrd-count"),De=t("div","fewrd-nav");De.append(Pe,Ke,_e);const J=t("select","fewrd-pick");J.setAttribute("aria-label","jump to a case");const Fe=new Map;i.forEach((r,n)=>{let p=Fe.get(r.conf);p||(p=t("optgroup"),p.label=r.conf,Fe.set(r.conf,p),J.append(p));const o=t("option",void 0,r.name);o.value=String(n),p.append(o)});const W=t("button","fewrd-theme");W.type="button";const xe=t("div","fewrd-sr");xe.setAttribute("aria-live","polite");const z=t("button","fewrd-confbtn");z.type="button",z.setAttribute("aria-controls","fewrd-drawer");const ke=t("span","fewrd-badge"),ye=t("header","fewrd-bar");ye.append(z,t("span","fewrd-brand","fewrd"),De,J,W,xe);let A="auto";try{const r=localStorage.getItem(be);me.includes(r)&&(A=r)}catch{}const qe=()=>{A==="auto"?delete d.dataset.theme:d.dataset.theme=A;const[r,n]=Jr[A];W.replaceChildren(U(r,n),t("span","fewrd-lbl",A)),W.setAttribute("aria-label",`theme: ${A}, switch`);try{localStorage.setItem(be,A)}catch{}};W.addEventListener("click",()=>{A=me[(me.indexOf(A)+1)%me.length],qe()}),qe(),document.fonts?.load('20px "Material Symbols Rounded"',"chevron_left").then(r=>r.length&&d.setAttribute("data-icons",""),()=>{});const Ue=t("span","fewrd-probe"),ae=t("div"),G=t("div","fewrd-draw");G.append(Ue,ae);const $e=t("p","fewrd-hint"),ne=t("section","fewrd-card fewrd-drawcard");ne.setAttribute("aria-label","the case, with its rows"),ne.append(G,$e);const j=t("p","fewrd-gist");j.setAttribute("aria-live","polite"),j.setAttribute("aria-atomic","true");const Ee=t("span","fewrd-dim"),P=t("span","fewrd-verdict"),We=t("section","fewrd-gistcard"),Ge=t("div","fewrd-head");Ge.append(t("h2",void 0,"Gist"),Ee,P),We.append(Ge,j);const Y=t("div","fewrd-chips");Y.setAttribute("role","group"),Y.setAttribute("aria-label","tags");const _=t("button","fewrd-reset","Reset");_.type="button";const Ye=t("section","fewrd-card fewrd-foldcard"),Xe=t("div","fewrd-head");Xe.append(t("h2",void 0,"Tags"),t("span","fewrd-dim","Hover a tag to find it, click to keep it lit, the eye drops it from the gist."),_),_.classList.add("fewrd-grow"),Ye.append(Xe,Y);const Qe=t("span","fewrd-dim"),oe=t("span","fewrd-badge"),M=t("textarea");M.spellcheck=!1,M.setAttribute("aria-label","conf, as JSON");const O=t("div","fewrd-errors");O.setAttribute("aria-live","polite");const Ve=t("div","fewrd-confhead");Ve.append(t("h2",void 0,"Conf"),Qe,oe);const de=t("aside","fewrd-drawer");de.id="fewrd-drawer",de.setAttribute("aria-label","conf editor"),de.append(Ve,M,O);const Ze=t("div","fewrd-scrim"),Ce=t("span","fewrd-dim"),Le=t("div","fewrd-tree"),er=t("summary");er.append(U("expand_more","\u25BE"),t("h2",void 0,"Tree"),Ce);const Me=t("details");Me.append(er,Le);const rr=t("section","fewrd-card fewrd-treebox");rr.append(Me);const tr=t("div","fewrd-layout");tr.append(ne,We,Ye,rr);const ar=t("div","fewrd-shell");ar.append(de,tr);const x=t("div","fewrd-tip");x.hidden=!0,d.replaceChildren(ye,ar,Ze,x);const nr=matchMedia("(min-width: 720px)"),or=()=>Me.open=nr.matches;nr.addEventListener("change",or),or();const dr=matchMedia("(min-width: 1700px)");let I=0;const Se=()=>{const r=d.hasAttribute("data-drawer");z.setAttribute("aria-expanded",String(r)),z.setAttribute("aria-label",`conf editor${I&&!r?`, ${I===1/0?"invalid JSON":`${I} error${I===1?"":"s"}`}`:""}`),ke.textContent=I===1/0?"!":String(I),ke.hidden=r||!I},ie=r=>{d.toggleAttribute("data-drawer",r&&!dr.matches),Se()},yr=U("data_object","{}");z.append(yr,ke),z.addEventListener("click",()=>{const r=!d.hasAttribute("data-drawer");ie(r),r&&M.focus({preventScroll:!0})}),Ze.addEventListener("click",()=>ie(!1)),dr.addEventListener("change",()=>ie(!1)),d.addEventListener("keydown",r=>{r.key!=="Escape"||!d.hasAttribute("data-drawer")||(ie(!1),z.focus())}),Se();let k={starts:[0],lines:[],order:[],head:new Map},Ae=[],se=[],ce;const ir=(r,n)=>{ze();for(const{line:p,from:o,to:$}of ue(r,k.starts)){const y=t("div","fewrd-wash");y.style.cssText=`left:${o}ch;width:${$-o}ch`,y.style.setProperty("--band",n),k.lines[p]?.prepend(y),Ae.push(y)}},ze=()=>{Ae.forEach(r=>r.remove()),Ae=[]},X=()=>{ce=void 0,x.hidden=!0,ze(),se.forEach(r=>r.classList.remove("on","hot")),se=[]},sr=r=>{ce=r,x.hidden=!1;const n=r.getBoundingClientRect(),p=(r.closest(".fewrd-line")??r).getBoundingClientRect(),o=x.offsetWidth,$=10,y=p.top-$-x.offsetHeight>=ye.getBoundingClientRect().bottom+4,fe=n.left+n.width/2,E=Math.max(8,Math.min(fe-o/2,innerWidth-o-8));x.dataset.side=y?"above":"below",x.style.left=`${E}px`,x.style.top=`${y?p.top-$-x.offsetHeight:p.bottom+$}px`,x.style.setProperty("--ax",`${Math.max(14,Math.min(fe-E,o-14))}px`)},cr=()=>ce&&sr(ce);addEventListener("scroll",cr,{passive:!0}),addEventListener("resize",cr);function Oe(r){X(),ae.replaceChildren(t("p","fewrd-failed",r)),$e.textContent="",Y.replaceChildren(),Le.replaceChildren(),Ce.textContent="",j.textContent="",Ee.textContent="",P.hidden=_.hidden=!0}function le(r){X();const n=g.get(r.conf);if(!n)return Oe(`No conf named \u201C${r.conf}\u201D.`);let p;try{p=Lr(r.text,n.compiled)}catch(e){return Oe(`Could not find: ${je(e)}`)}let o,$="";try{o=Mr(r.text,p,n.compiled)}catch(e){$=`Could not build the tree: ${je(e)}`}const y=[...p.all()].filter(([,[e,c]])=>c>e),fe=Object.keys(n.compiled.tags),E=e=>`var(--band-${Br(fe.indexOf(e))})`,we=o?Or(o,r.text):[],Q=new Map;for(const{node:e}of we)if(e.tag!=="text"){Q.set(ee(e.tag,e.start,e.end),{node:e});for(const c of e.also??[])Q.set(ee(c,e.start,e.end),{node:e,twinOf:e.tag})}const lr=new Map,pr=new Map,V=new Map,Te=new Map,ge=new Map,B=zr(r.text,b),K=B.map((e,c)=>{const s=t("div","fewrd-line"),l=t("div","fewrd-text",r.text.slice(e,B[c+1]));return s.append(l),{line:s,text:l,lanes:[]}});k={starts:B,lines:K.map(e=>e.line),order:[],head:new Map};const fr=[];let wr=0;const $r=(e,c)=>{const s=Q.get(ee(e,...c)),l=t("i");l.style.setProperty("--band",E(e));const f=t("div","fewrd-tip-head");f.append(l,t("b",void 0,e),t("span",void 0,`(${c[0]}, ${c[1]})`)),x.replaceChildren(f);const h=s&&!s.twinOf?s.node.value:void 0;h!==void 0&&x.append(t("div","fewrd-tip-value",`value ${JSON.stringify(h)}`));const u=o?s?s.twinOf?`twin of ${s.twinOf}`:"":"not kept by the tree":"";u&&x.append(t("div","fewrd-tip-note",u))};Ar(y).forEach((e,c)=>{for(const[s,l]of e){const f=ee(s,...l),h=o!==void 0&&!Q.has(f);h&&wr++;const u=ue(l,B);let v;u.forEach(({line:Z,from:R,to:q},T)=>{const S=K[Z];for(;S.lanes.length<=c;){const H=t("div","fewrd-lane");S.lanes.push(H),S.line.append(H)}const m=t("span",`fewrd-band${T===0?" open":""}${T===u.length-1?" close":""}${h?" lost":""}`);m.style.cssText=`left:${R}ch;width:${q-R}ch`,m.style.setProperty("--band",E(s)),m.tabIndex=-1,v?k.head.set(m,v):fr.push({band:m,span:l}),v??=m,m.setAttribute("role","img"),m.setAttribute("aria-label",`${s}, ${l[0]} to ${l[1]}${h?", not in the tree":""}`),m.append(t("span","fewrd-hit"));const br=()=>{X(),ir(l,E(s));const H=lr.get(f);for(const Cr of V.get(f)??[])Cr.classList.add("hot");se.push(...V.get(f)??[]),H&&(H.classList.add("on"),se.push(H)),$r(s,l),sr(m)};m.addEventListener("mouseenter",br),m.addEventListener("mouseleave",X),m.addEventListener("focus",()=>{k.order.forEach(H=>H.tabIndex=-1),(k.head.get(m)??m).tabIndex=0,br()}),m.addEventListener("blur",X),S.lanes[c].append(m),V.set(f,[...V.get(f)??[],m]),Te.set(s,[...Te.get(s)??[],m])})}}),k.order=fr.sort((e,c)=>e.span[0]-c.span[0]||c.span[1]-e.span[1]).map(e=>e.band),k.order[0]&&(k.order[0].tabIndex=0),ae.replaceChildren(...K.map(e=>e.line));const gr=Math.max(0,...K.map(e=>e.lanes.length));$e.textContent=y.length?`${y.length} rows in ${gr} lane${gr===1?"":"s"}. Hover a band, or Tab to the bands and use \u2191 \u2193, to read it.${wr?" Thin bands are rows the tree did not keep.":""}`:"Nothing found in this text with this conf.",Ce.textContent=o?`${we.length} nodes`:"",Le.replaceChildren(...o?we.map(({node:e,depth:c,line:s})=>{const l=e.tag==="text",f=t("div",`fewrd-node${l?" water":""}`);if(f.style.setProperty("--d",String(c)),f.title=s.trim(),!l){const h=t("i");h.style.setProperty("--band",E(e.tag)),f.append(h)}if(f.append(t("b",void 0,e.tag),t("span","sp",`${e.start}\u2013${e.end}`)),e.also&&f.append(t("span","pill",`also ${e.also.join(", ")}`)),e.value!==void 0&&f.append(t("span","pill",`= ${JSON.stringify(e.value)}`)),f.append(t("span","q",JSON.stringify(r.text.slice(e.start,e.end)))),f.addEventListener("mouseenter",()=>ir([e.start,e.end],l?"var(--faint)":E(e.tag))),f.addEventListener("mouseleave",ze),pr.set(e,f),!l)for(const h of[e.tag,...e.also??[]])lr.set(ee(h,e.start,e.end),f),ge.set(h,[...ge.get(h)??[],f]);return f}):[t("p","fewrd-failed",$)]);const he=Object.keys(n.compiled.tags),L=C.get(a)??new Set(r.fold??[]);C.set(a,L);for(const e of L)he.includes(e)||L.delete(e);const ve=new Set(we.flatMap(({node:e})=>e.tag==="text"?[]:[e.tag,...e.also??[]])),N=re.get(a)??new Set;re.set(a,N);for(const e of N)ve.has(e)||N.delete(e);let D,He=[];const F=()=>{He.forEach(c=>c.remove()),He=[];const e=new Set(N);D&&e.add(D),G.toggleAttribute("data-marking",e.size>0);for(const[c,s]of Te)s.forEach(l=>l.classList.toggle("mark",e.has(c)));ge.forEach(c=>c.forEach(s=>s.classList.remove("mark")));for(const c of e){for(const[s,l]of y)if(s===c)for(const{line:f,from:h,to:u}of ue(l,B)){const v=t("div","fewrd-wash mark");v.style.cssText=`left:${h}ch;width:${u-h}ch`,v.style.setProperty("--band",E(c)),K[f].line.prepend(v),He.push(v)}for(const s of ge.get(c)??[])s.classList.add("mark"),s.style.setProperty("--band",E(c))}},hr=new Map,vr=e=>{const c=t("i");if(!ve.has(e)){const v=t("span","fewrd-tag");return v.style.setProperty("--band",E(e)),v.append(c,e),v}const s=t("button","fewrd-chip-main");s.type="button",s.title=`Highlight ${e} in the preview`,s.append(c,t("span","fewrd-name",e));const l=t("button","fewrd-eye");l.type="button";const f=U("visibility","\u25C9");l.append(f);const h=t("span","fewrd-chip");h.style.setProperty("--band",E(e)),h.append(s,l);const u=()=>{const v=L.has(e);h.classList.toggle("on",N.has(e)),h.classList.toggle("folded",v),s.setAttribute("aria-pressed",String(N.has(e))),l.setAttribute("aria-pressed",String(v)),l.setAttribute("aria-label",`Fold ${e} out of the gist`),l.title=v?`${e} is folded out of the gist`:`Fold ${e} out of the gist`,Rr(f,v?"visibility_off":"visibility",v?"\u25CC":"\u25C9")};return hr.set(e,u),u(),h.addEventListener("mouseenter",()=>(D=e,F())),h.addEventListener("mouseleave",()=>(D=void 0,F())),h.addEventListener("focusin",v=>v.target instanceof Element&&v.target.matches(":focus-visible")&&(D=e,F())),h.addEventListener("focusout",()=>(D=void 0,F())),s.addEventListener("click",()=>{N.delete(e)||N.add(e),u(),F()}),l.addEventListener("click",()=>{L.delete(e)||L.add(e),u(),Re()}),h},Er=he.filter(e=>ve.has(e)),ur=he.filter(e=>!ve.has(e));Y.replaceChildren(...Er.map(vr),...ur.length?[t("span","fewrd-cap","not in this tree")]:[],...ur.map(vr)),_.onclick=()=>{L.clear(),(r.fold??[]).forEach(e=>he.includes(e)&&L.add(e)),hr.forEach(e=>e()),Re()};function Re(){const e=u=>L.has(u.tag),c=o?Sr(o,e):"";j.textContent=o?c||"(nothing left)":"No gist: the tree could not be built.",j.classList.toggle("empty",!o||!c),Ee.textContent=o?`${r.text.length} \u2192 ${c.length} characters`:"";const s=(r.fold??[]).length===L.size&&(r.fold??[]).every(u=>L.has(u));P.hidden=!o||r.gist===void 0||r.fold===void 0||!s;const l=c===r.gist;if(P.replaceChildren(...l?[U("check","\u2713")]:[],l?"matches the case's gist":"differs from the case's gist"),P.title=l?"":`the case's gist: ${r.gist}`,P.className=`fewrd-verdict ${l?"ok":"warn"}`,_.hidden=s,!o)return;const f=mr(o,e);for(const[u,{node:v}]of Q)V.get(u)?.forEach(Z=>Z.classList.toggle("gone",f.has(v)));for(const[u,v]of pr)v.classList.toggle("gone",f.has(u));const h=[];for(const u of Ir(o,e))h.push(...ue(u,B));K.forEach(({text:u},v)=>{const Z=B[v],R=r.text.slice(Z,B[v+1]),q=[];let T=0;for(const S of h.filter(m=>m.line===v))S.from>T&&q.push(R.slice(T,S.from)),q.push(t("span","fewrd-grey",R.slice(S.from,S.to))),T=S.to;T<R.length&&q.push(R.slice(T)),u.replaceChildren(...q)})}Re(),F()}function Ie(r){const n=r?.errors??[];if(O.replaceChildren(),r?.invalid)O.append(t("b",void 0,"Invalid JSON.")," Showing the last good chart.",t("code",void 0,r.invalid));else if(n.length){const p=t("ul");p.append(...n.map(o=>t("li",void 0,`${[o.path,o.tag&&`(${o.tag})`].filter(Boolean).join(" ")}${o.path?": ":""}${o.message}`))),O.append(t("b",void 0,`${n.length} error${n.length===1?"":"s"} in the conf.`)," The tags that compiled are drawn.",p)}O.hidden=!O.hasChildNodes(),M.setAttribute("aria-invalid",String(!O.hidden)),oe.textContent=r?.invalid?"invalid JSON":n.length?`${n.length} error${n.length===1?"":"s"}`:"",oe.hidden=!oe.textContent,I=r?.invalid?1/0:n.length,Se(),ne.toggleAttribute("data-stale",!!r?.invalid)}function Be(){const r=i[a];if(Ke.textContent=`${i.length?a+1:0} / ${i.length}`,!r)return Oe("No cases to show.");J.value=String(a),xe.textContent=`Case ${a+1} of ${i.length}: ${r.name}`;const n=g.get(r.conf);Qe.textContent=r.conf,M.value=n?.text??"",M.disabled=!n,Ie(n),le(r)}const pe=r=>{i.length&&(a=(a+r+i.length)%i.length,Be())};Pe.addEventListener("click",()=>pe(-1)),_e.addEventListener("click",()=>pe(1)),J.addEventListener("change",()=>{a=Number(J.value),Be()}),document.addEventListener("keydown",r=>{!d.isConnected||r.altKey||r.ctrlKey||r.metaKey||r.shiftKey||r.target instanceof Element&&r.target.closest("input, textarea, select")||(r.key==="ArrowLeft"?pe(-1):r.key==="ArrowRight"&&pe(1))}),M.addEventListener("input",()=>{const r=i[a],n=g.get(r.conf);n.text=M.value;let p;try{p=JSON.parse(M.value)}catch(y){return n.invalid=je(y),Ie(n)}n.invalid=void 0;const{conf:o,errors:$}=xr(p,{resolvers:n.resolvers});Object.assign(n,{compiled:o,errors:$}),Ie(n),le(r)}),ae.addEventListener("keydown",r=>{const n=r.target instanceof HTMLElement?k.head.get(r.target)??r.target:null,p=n?k.order.indexOf(n):-1,o=r.key==="ArrowDown"?p+1:r.key==="ArrowUp"?p-1:r.key==="Home"?0:r.key==="End"?k.order.length-1:-1;p<0||o<0||o>=k.order.length||(r.preventDefault(),k.order[o].focus())});const Ne=()=>{const r=Ue.getBoundingClientRect().width,n=r?Math.max(20,Math.floor(G.clientWidth/r)):b,p=n!==b;return b=n,p};Ne(),new ResizeObserver(()=>Ne()&&i[a]&&le(i[a])).observe(G),document.fonts?.ready.then(()=>Ne()&&i[a]&&le(i[a])),Be()}export{jr as mount};
@@ -0,0 +1,44 @@
1
+ export type Span = readonly [start: number, end: number];
2
+ export type AllenRelation = 'before' | 'meets' | 'overlaps' | 'starts' | 'during' | 'finishes' | 'equals' | 'after' | 'met-by' | 'overlapped-by' | 'started-by' | 'contains' | 'finished-by';
3
+ export type Row = readonly [tag: string, span: Span];
4
+ /** Rows laid out by edge, what the matcher asks for: where rows start, where they end, and all of them in order. */
5
+ export interface Edges {
6
+ starts: Map<number, Row[]>;
7
+ ends: Map<number, Row[]>;
8
+ all: Row[];
9
+ }
10
+ /** `rows` must come in position order; `Chart.edges()` for a whole chart, this for a subset of its rows. */
11
+ export declare function edges(rows: Iterable<Row>): Edges;
12
+ /**
13
+ * ponytail: `has` and `before` scan a tag's sorted list and `after` binary-searches
14
+ * it; `*` visits every tag. Enough at subject length; index by position if subjects grow.
15
+ */
16
+ export declare class Chart {
17
+ #private;
18
+ private constructor();
19
+ /** A chart of a text of length `n`: only `^` (0,0) and `$` (n,n). */
20
+ static empty(n: number): Chart;
21
+ /** The inverse of `toJSON`. The text length is where `$` sits (else the furthest row end). */
22
+ static from(json: Record<string, readonly Span[]>): Chart;
23
+ toJSON(): Record<string, Span[]>;
24
+ /** A new chart with these rows added; triples already present are ignored, untouched tag lists are shared. */
25
+ with(rows: Iterable<Row>): Chart;
26
+ /** Total rows, `^` and `$` included: the fixpoint test. */
27
+ size(): number;
28
+ has(tag: string, start: number, end: number): boolean;
29
+ /** The first span of `tag` with start ≥ `pos`. */
30
+ after(tag: string, pos: number): Span | undefined;
31
+ /** The last span of `tag` (latest in position order) with end ≤ `pos`. */
32
+ before(tag: string, pos: number): Span | undefined;
33
+ /** The tag's rows, sorted; empty for an absent tag. */
34
+ spans(tag: string): readonly Span[];
35
+ /** Every row of every tag in position order: start, then end, then tag name. */
36
+ all(): Iterable<Row>;
37
+ /** Every row by edge, for the matcher: `has`, `after` and `before` answer one row at a time, this answers "every row at this edge". */
38
+ edges(): Edges;
39
+ }
40
+ /**
41
+ * The Allen relation of `a` to `b`. Tested in this order, so for zero-length
42
+ * spans the text's edges meet the rows that touch them.
43
+ */
44
+ export declare function rel(a: Span, b: Span): AllenRelation;
@@ -0,0 +1,45 @@
1
+ export type Conf<P = string, R = string> = {
2
+ version: string;
3
+ patterns?: Record<string, string>;
4
+ tags: Record<string, Tag<P, R>>;
5
+ };
6
+ export type Fate = 'separator' | 'connector' | 'bracket';
7
+ export type Tag<P = string, R = string> = {
8
+ rx?: P;
9
+ search?: Search<P>[];
10
+ resolve?: R;
11
+ weak?: boolean;
12
+ fate?: Fate;
13
+ };
14
+ export type Search<P = string> = {
15
+ from: string;
16
+ back?: Atom<P>[];
17
+ forward?: Atom<P>[];
18
+ };
19
+ export type Atom<P = string> = {
20
+ tag: string | string[];
21
+ as?: string;
22
+ optional?: boolean;
23
+ } | {
24
+ rx: P;
25
+ as?: string;
26
+ optional?: boolean;
27
+ };
28
+ export type Resolve = (row: Readonly<Record<string, string>>) => string | null;
29
+ export interface CompileError {
30
+ path: string;
31
+ tag?: string;
32
+ message: string;
33
+ }
34
+ /**
35
+ * Compile a conf written as data (typically `JSON.parse` output). `resolve`
36
+ * names are looked up in `options.resolvers`; nothing in `data` is ever
37
+ * evaluated as code. A tag with any error is left out; the others keep their
38
+ * key order.
39
+ */
40
+ export declare function compile(data: unknown, options?: {
41
+ resolvers?: Readonly<Record<string, Resolve>>;
42
+ }): {
43
+ conf: Conf<RegExp, Resolve>;
44
+ errors: CompileError[];
45
+ };
@@ -0,0 +1,31 @@
1
+ import type { Edges, Row } from './chart.ts';
2
+ import type { Search } from './conf.ts';
3
+ export type { Row };
4
+ export type Roles = Readonly<Record<string, string>>;
5
+ /** One thing a derivation took: a row for a tag atom, the matched text for a regex atom (kept only when it has `as`). */
6
+ export type Step = {
7
+ row: Row;
8
+ as?: string;
9
+ } | {
10
+ text: string;
11
+ as: string;
12
+ };
13
+ type Kind = 'scan' | 'back' | 'forward' | 'whole';
14
+ /**
15
+ * A regex pinned for one job, cached per RegExp: `scan` global, `back` anchored at
16
+ * the end of what it is run on, `forward` sticky, `whole` anchored at both ends.
17
+ * ponytail: `back` is run on the prefix, so it rescans it (O(n) per attempt) and a
18
+ * lookahead in it cannot see past the cursor; fine at subject length.
19
+ */
20
+ export declare function pinned(rx: RegExp, kind: Kind): RegExp;
21
+ /**
22
+ * One search from one row: every way its atoms can be taken, outward from the
23
+ * row's edge, calling `emit` with the extent, the roles and the steps of each
24
+ * derivation (the `from` row first). `valueOf` is the value a role takes from a row.
25
+ *
26
+ * ponytail: a depth-first enumeration, exponential in the number of optional
27
+ * atoms of one search; a `*` atom on a search that grows from its own tag makes
28
+ * the chart quadratic in the text. Candidate rows are looked up by edge; a regex
29
+ * atom between two atoms scans every row on its far side. Fine at subject length.
30
+ */
31
+ export declare function derive(text: string, ix: Edges, valueOf: (row: Row) => string, search: Search<RegExp>, from: Row, emit: (lo: number, hi: number, roles: Roles, steps: readonly Step[]) => void): void;
@@ -0,0 +1,33 @@
1
+ import { type Chart } from './chart.ts';
2
+ import type { Conf, Fate, Resolve } from './conf.ts';
3
+ export type Node = {
4
+ /** A conf tag, or reserved: 'doc' (the root), 'text' (water). */
5
+ tag: string;
6
+ /** Into the original string. */
7
+ start: number;
8
+ end: number;
9
+ /** The resolver's answer, only on tags that resolve. */
10
+ value?: string;
11
+ /** Roles bound by `as`: a Node for a tag atom, the text for a regex atom (or for a row the node absorbed). */
12
+ attrs: Record<string, Node | string>;
13
+ /** Tags of twins folded into this node, in selection order. */
14
+ also?: string[];
15
+ /** The conf tag's fate, copied here so the fold reads the tree alone. */
16
+ fate?: Fate;
17
+ /** The original string, on `doc` only. */
18
+ text?: string;
19
+ /** By containment, in text order, water included. */
20
+ children: Node[];
21
+ };
22
+ type Compiled = Conf<RegExp, Resolve>;
23
+ /**
24
+ * The tree of `text`: a `doc` node over the whole text, one node per chosen
25
+ * row, water as `text` nodes. Throws when the chart holds a row of a tag the
26
+ * conf does not declare, or when a resolver refuses a row it accepted in `find`.
27
+ *
28
+ * ponytail: the crossing test walks the boundaries inside the row (O(its length))
29
+ * and the same-tag test scans the chosen rows of that tag; a composed row's
30
+ * derivations are all enumerated, as in `find`. An interval tree if subjects grow.
31
+ */
32
+ export declare function dom(text: string, chart: Chart, conf: Compiled): Node;
33
+ export {};
@@ -0,0 +1,12 @@
1
+ import { Chart } from './chart.ts';
2
+ import type { Conf, Resolve } from './conf.ts';
3
+ type Compiled = Conf<RegExp, Resolve>;
4
+ /**
5
+ * Every row the chart has for `text` under `conf`: complete, neutral, in original coordinates.
6
+ *
7
+ * The chart grows in place, sorted: a pass adds its rows once it is over, and
8
+ * the next pass runs only the searches that read a tag the last one added to.
9
+ * ponytail: each insert is a splice, O(rows); fine at subject length.
10
+ */
11
+ export declare function find(text: string, conf: Compiled): Chart;
12
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { Node } from './dom.ts';
2
+ /** True for a node the reader wants folded away. Asked about every node but `doc`. */
3
+ export type Fold = (node: Node) => boolean;
4
+ /**
5
+ * Every node the condensed view drops. In this order, each rule seeing what the
6
+ * earlier ones hid: the policy (a folded node takes its whole subtree), the
7
+ * connector fate, emptied brackets, the separator fate.
8
+ */
9
+ export declare function hidden(doc: Node, fold: Fold): Set<Node>;
10
+ /** The condensed view as text: every leaf `hidden` does not drop, in order. */
11
+ export declare function gist(doc: Node, fold: Fold): string;
@@ -0,0 +1,27 @@
1
+ import type { Row, Span } from './chart.ts';
2
+ import type { Node } from './dom.ts';
3
+ import { type Fold } from './fold.ts';
4
+ export type { Row };
5
+ /** Stack rows into lanes: each row on the first lane whose last row ends at or before its start. */
6
+ export declare function lanes(rows: Iterable<Row>): Row[][];
7
+ /** Where each line of a text wrapped to `cols` characters starts: after the last space that fits, or hard at `cols` when there is none. */
8
+ export declare function wrap(text: string, cols: number): number[];
9
+ /** Where a row falls on lines that start at `starts`: one piece per line it touches, columns within the line. */
10
+ export declare function segments([a, b]: Span, starts: readonly number[]): {
11
+ line: number;
12
+ from: number;
13
+ to: number;
14
+ }[];
15
+ /** The tree, one entry per node but `doc`, in text order: the node, its depth and a one-line description (tag, span, twins, value, the node's text). */
16
+ export declare function walk(doc: Node, text: string): {
17
+ node: Node;
18
+ depth: number;
19
+ line: string;
20
+ }[];
21
+ export declare const treeLines: (doc: Node, text: string) => string[];
22
+ /** The spans of the leaves the fold hides, adjacent ones merged. */
23
+ export declare function greyed(doc: Node, fold: Fold): Span[];
24
+ /** The twelve band hues, one lightness and chroma per theme (set in the palette). */
25
+ export declare const HUES: number[];
26
+ /** The band slot of the tag at place `i` in its conf: a step of 5 slots is 150° of hue, so neighbours differ; the second lap shifts by one. */
27
+ export declare const slot: (i: number) => number;
@@ -1,4 +1,5 @@
1
- export type { Book, BookData, CompileError, CompileOptions, Cuts, Leaf, LeafKind, Mention, Neighbour, NeighbourData, Recipe, RecipeData, Resolver, Span, } from './types.ts';
2
- export { read } from './read.ts';
3
- export { gist, html, shown, type Fold } from './render.ts';
4
- export { compile } from './compile.ts';
1
+ export * from './conf.ts';
2
+ export * from './chart.ts';
3
+ export * from './find.ts';
4
+ export * from './dom.ts';
5
+ export * from './fold.ts';
@@ -1,6 +1,6 @@
1
1
  export interface Normal {
2
2
  text: string;
3
- /** Original boundary of each copy boundary, `text.length + 1` entries. */
3
+ /** Original boundary of each copy boundary, per UTF-16 unit: `text.length + 1` entries. */
4
4
  at: number[];
5
5
  }
6
6
  export declare function normalise(original: string): Normal;