@squinch/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts CHANGED
@@ -36,6 +36,17 @@ export declare function iconsUsedBy(files: ProjectFile[] | string): {
36
36
  * them and a result of only `azure/key-vaults` reads as proof that
37
37
  * `azure/key-vault` doesn't exist) can look them up via `packInfo`. */
38
38
  export declare function searchIcons(query: string, pack?: string): string[];
39
+ /** The same search, saying how it matched. `relaxed: true` means no icon
40
+ * matched every word, so the hits are the closest partial matches instead —
41
+ * callers that print for an agent loop label them (`no exact match —
42
+ * closest:`), because near-misses answer the question and an empty list
43
+ * forces a second pass with different words. Measured before ranking
44
+ * existed: `rag` returned eighteen sto*rag*e rows with `sys/rag` dead last,
45
+ * and "message queue" / "object storage" returned nothing at all. */
46
+ export declare function searchIconsDetailed(query: string, pack?: string): {
47
+ hits: string[];
48
+ relaxed: boolean;
49
+ };
39
50
  export type { ProjectFile };
40
51
  export type * from "./model/types.js";
41
52
  export { HUES } from "./model/types.js";
package/dist/api.js CHANGED
@@ -69,6 +69,16 @@ export function iconsUsedBy(files) {
69
69
  * them and a result of only `azure/key-vaults` reads as proof that
70
70
  * `azure/key-vault` doesn't exist) can look them up via `packInfo`. */
71
71
  export function searchIcons(query, pack) {
72
+ return searchIconsDetailed(query, pack).hits;
73
+ }
74
+ /** The same search, saying how it matched. `relaxed: true` means no icon
75
+ * matched every word, so the hits are the closest partial matches instead —
76
+ * callers that print for an agent loop label them (`no exact match —
77
+ * closest:`), because near-misses answer the question and an empty list
78
+ * forces a second pass with different words. Measured before ranking
79
+ * existed: `rag` returned eighteen sto*rag*e rows with `sys/rag` dead last,
80
+ * and "message queue" / "object storage" returned nothing at all. */
81
+ export function searchIconsDetailed(query, pack) {
72
82
  // Vendors are inconsistent about number — Azure has "Container Registries"
73
83
  // and "Data Factories" where everyone searches "container registry" and
74
84
  // "data factory" — so both sides are singularized before comparing.
@@ -97,20 +107,46 @@ export function searchIcons(query, pack) {
97
107
  }
98
108
  }
99
109
  const words = rawWords.map(stem);
100
- const hits = [];
110
+ const joined = words.join(" ");
111
+ const scored = [];
101
112
  for (const name of allPackNames()) {
102
113
  if (packFilter && name !== packFilter)
103
114
  continue;
104
- const aliases = packInfo(name)?.aliases ?? {};
105
115
  const haystack = (id) => norm(`${id} ${iconTitle(name, id) ?? ""}`);
106
- const matched = new Set(iconIds(name).filter((id) => words.every((w) => haystack(id).includes(w))));
107
- for (const id of [...matched].sort()) {
108
- if (aliases[id] && matched.has(aliases[id]))
109
- continue; // canonical covers it
110
- hits.push(`${name}/${id}`);
116
+ const matchedIds = new Set(iconIds(name).filter((id) => words.some((w) => haystack(id).includes(w)) || !words.length));
117
+ for (const id of matchedIds) {
118
+ const hay = haystack(id);
119
+ const hayWords = new Set(hay.split(" "));
120
+ scored.push({
121
+ hit: `${name}/${id}`,
122
+ exact: joined && norm(id) === joined ? 1 : 0,
123
+ whole: words.length && words.every((w) => hayWords.has(w)) ? 1 : 0,
124
+ matched: words.filter((w) => hay.includes(w)).length,
125
+ });
111
126
  }
112
127
  }
113
- return hits.sort();
128
+ // One row per icon: an alias row is dropped only when its canonical is in
129
+ // the SAME result list. Checking the broader matched set instead deduped
130
+ // `sys/vector-search` (a full match) against a canonical that had only
131
+ // matched one word and was not being returned at all.
132
+ const dedupe = (list) => {
133
+ const present = new Set(list.map((s) => s.hit));
134
+ return list.filter((s) => {
135
+ const name = s.hit.slice(0, s.hit.indexOf("/"));
136
+ const id = s.hit.slice(s.hit.indexOf("/") + 1);
137
+ const canonical = (packInfo(name)?.aliases ?? {})[id];
138
+ return !(canonical && present.has(`${name}/${canonical}`)); // canonical covers it
139
+ });
140
+ };
141
+ const rank = (list) => dedupe(list)
142
+ .sort((a, b) => b.exact - a.exact || b.whole - a.whole || b.matched - a.matched || (a.hit < b.hit ? -1 : 1))
143
+ .map((s) => s.hit);
144
+ const full = scored.filter((s) => s.matched === words.length);
145
+ if (full.length || !words.length)
146
+ return { hits: rank(full), relaxed: false };
147
+ // Nothing matched every word: fall back to partial matches, best first.
148
+ // Capped — the point is a next move, not a directory listing.
149
+ return { hits: rank(scored).slice(0, 12), relaxed: true };
114
150
  }
115
151
  // the one colour vocabulary, for editors that complete it and tests that sweep it
116
152
  export { HUES } from "./model/types.js";
@@ -169,7 +205,11 @@ export async function renderProject(input, opts = {}) {
169
205
  loc: { from: 0, to: 0, line: 1, col: 1 },
170
206
  };
171
207
  }
172
- const themeName = opts.theme ?? view.theme ?? built.model.fileTheme ?? "light";
208
+ // Dark is the default render. The one exception is an adaptive render with
209
+ // no theme named anywhere: an adaptive file is a light render carrying a
210
+ // dark override (the media query is `prefers-color-scheme: dark`), so its
211
+ // base has to be light — naming `dark` for it is still the error below.
212
+ const themeName = opts.theme ?? view.theme ?? built.model.fileTheme ?? (opts.adaptive ? "light" : "dark");
173
213
  const theme = themes[themeName];
174
214
  const diagnostics = [...built.diagnostics];
175
215
  if (!theme)
@@ -1 +1 @@
1
- export declare const RUNTIME_JS = "\"use strict\";(()=>{var Q={ms:460,ease:\"cubic-bezier(.32,.72,0,1)\"},X={ms:240,ease:\"cubic-bezier(.4,0,.2,1)\"},K=e=>({x:e.x+e.w/2,y:e.y+e.h/2}),Y=(e,n,i)=>Math.min(i,Math.max(n,e)),Z=(e,n)=>Y(Math.min(e.w/n.w,e.h/n.h),1.15,3.2);function U(e){let{view:n,ghostBox:i,liveBox:a,anchor:p,dir:o}=e,{ms:l,ease:d}=p?Q:X;if(!p)return{ms:l,ease:d,gOrigin:\"50% 50%\",lOrigin:\"50% 50%\",gEnd:\"scale(.97)\",lStart:\"scale(1.03)\"};let f=Z(n,p),T=1+(f-1)*.62,E=K(p),h=K(n),y=h.x-E.x,w=h.y-E.y,x=o===\"in\",g=x?E:h,k=x?h:E;return{ms:l,ease:d,gOrigin:`${g.x-i.x}px ${g.y-i.y}px`,lOrigin:`${k.x-a.x}px ${k.y-a.y}px`,gEnd:x?`translate(${y}px, ${w}px) scale(${f})`:`translate(${-y}px, ${-w}px) scale(${1/f})`,lStart:x?`translate(${-y}px, ${-w}px) scale(${1/T})`:`translate(${y}px, ${w}px) scale(${T})`}}function W(e,n){if(n){if(!e)return n.split(\".\")[0];if(!(n===e||!n.startsWith(`${e}.`)))return`${e}.${n.slice(e.length+1).split(\".\")[0]}`}}function ee(e){if(!e)return[];let n=e.split(\".\");return n.map((i,a)=>n.slice(0,a+1).join(\".\"))}function O(e,n,i){return e.find(a=>a.scope===i&&a.name!==n)}function _(e,n,i){let a=e.find(d=>d.name===n)?.scope,p=e.find(d=>d.name===i)?.scope,o=W(a,p);if(o)return{dir:\"in\",anchor:o};let l=W(p,a);return l?{dir:\"out\",anchor:l}:{dir:\"in\"}}function te(e,n){let i=[],a=e.find(o=>!o.scope);if(a&&i.push({label:\"landscape\",view:a.name}),!n)return i;let p=n.split(\".\");for(let[o,l]of ee(n).entries())i.push({label:p[o],view:e.find(d=>d.scope===l)?.name});return i}function S(e,n,i){return[...te(e,i)].reverse().find(a=>a.view&&a.view!==n)?.view}var L=e=>document.querySelector(e);function j(){let e=JSON.parse(L(\"#sq-data\").textContent||\"{}\"),n=L(\"#sq-live\"),i=L(\"#sq-ghost\"),a=L(\"#sq-stage\"),p=()=>matchMedia(\"(prefers-reduced-motion: reduce)\").matches,o=e.entry,l=e.themes[0],d=0,f=!1;if(e.themes.length>1&&matchMedia(\"(prefers-color-scheme: dark)\").matches){let t=e.themes.find(s=>s.includes(\"dark\"));t&&(l=t)}let T=`${e.entry}|${e.themes[0]}`,E=n.firstElementChild?.cloneNode(!0)??null,h=(t,s,c=0)=>{let r=c?`${t}|${s}|${c}`:`${t}|${s}`;if(r===T)return E?.cloneNode(!0)??null;let u=document.querySelector(`template[data-key=\"${CSS.escape(r)}\"]`);return u?u.content.cloneNode(!0):null},y=t=>e.views.find(s=>s.name===t)?.scope,w=t=>{let s=t.getBoundingClientRect(),c=a.getBoundingClientRect();return{x:s.left-c.left,y:s.top-c.top,w:s.width,h:s.height}};function x(){document.title=e.views.find(r=>r.name===o)?.title??document.title;let t=document.querySelector(\"#sq-tabs\");if(t){t.replaceChildren();for(let r of e.views){let u=document.createElement(\"button\");u.type=\"button\",u.className=r.name===o?\"on\":\"\",u.textContent=r.name,u.title=r.title??r.name,u.onclick=()=>g(r.name),t.append(u)}t.querySelector(\".on\")?.scrollIntoView?.({block:\"nearest\",inline:\"nearest\"})}for(let r of n.querySelectorAll(\"[data-path]\")){let u=r.getAttribute(\"data-path\");r.classList.toggle(\"sq-zoom\",!!(u&&O(e.views,o,u)))}let s=document.querySelector(\"#sq-step\"),c=e.flows[o]??0;s&&(s.textContent=f&&c?`${d||1} / ${c}`:\"\")}function g(t,s=!1){if(!t||t===o)return;let c=f&&e.flows[t]?s?e.flows[t]:1:0,r=h(t,l,c);if(!r)return;d=c;let{dir:u,anchor:v}=_(e.views,o,t),C=w(n),H=n.firstElementChild;if(p()||!H){n.replaceChildren(r),o=t,x();return}i.replaceChildren(H.cloneNode(!0)),i.style.cssText=`position:absolute;left:${C.x}px;top:${C.y}px;width:${C.w}px;height:${C.h}px;z-index:1;pointer-events:none`,n.replaceChildren(r),o=t,x();let G=w(n),P=v?(u===\"in\"?i:n).querySelector(`[data-path=\"${CSS.escape(v)}\"]`):null,m=U({view:{x:0,y:0,w:a.clientWidth,h:a.clientHeight},ghostBox:C,liveBox:G,anchor:P?w(P):void 0,dir:u}),b=i.style,$=n.style;b.transition=\"none\",b.transformOrigin=m.gOrigin,b.transform=\"none\",b.opacity=\"1\",$.transition=\"none\",$.transformOrigin=m.lOrigin,$.transform=m.lStart,$.opacity=\"0\",n.offsetHeight,b.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.55)}ms ${m.ease}`,$.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.6)}ms ${m.ease} ${Math.round(m.ms*.25)}ms`,b.transform=m.gEnd,b.opacity=\"0\",$.transform=\"none\",$.opacity=\"1\";let I=!1,q=0,R=()=>{I||(I=!0,cancelAnimationFrame(q),i.replaceChildren(),i.removeAttribute(\"style\"),n.removeAttribute(\"style\"))},z=()=>{getComputedStyle(n).transform===\"none\"?R():q=requestAnimationFrame(z)};q=requestAnimationFrame(z),setTimeout(R,m.ms+1e3)}function k(t){let s=t===l?null:h(o,t,d);s&&(l=t,document.documentElement.dataset.theme=t,n.replaceChildren(s))}function M(t){let s=e.flows[o]??0,c=d+t;if(s&&c>=1&&c<=s){let v=h(o,l,c);v&&(d=c,n.replaceChildren(v),x());return}let r=e.views.findIndex(v=>v.name===o),u=e.views[r+t];u&&g(u.name,t<0)}n.addEventListener(\"click\",t=>{let c=t.target.closest?.(\"[data-path]\")?.getAttribute(\"data-path\");if(c){let r=O(e.views,o,c);if(r)return g(r.name)}if(!c){let r=S(e.views,o,y(o));r&&g(r)}});let A=document.querySelector(\"#sq-theme\");A&&(A.onclick=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]));function B(t){if(t!==f){if(f=t,document.body.classList.toggle(\"presenting\",t),t){if(document.documentElement.requestFullscreen?.().catch(()=>{}),(e.flows[o]??0)&&!d){let c=h(o,l,1);c&&(d=1,n.replaceChildren(c))}}else if(document.fullscreenElement&&document.exitFullscreen?.().catch(()=>{}),d){let s=h(o,l,0);s&&(d=0,n.replaceChildren(s))}x()}}let N,V=()=>{document.body.classList.remove(\"idle\"),clearTimeout(N),f&&(N=setTimeout(()=>document.body.classList.add(\"idle\"),3500))};addEventListener(\"mousemove\",V),addEventListener(\"keydown\",V);let J=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]);addEventListener(\"keydown\",t=>{if(!(t.metaKey||t.ctrlKey||t.altKey))switch(t.key){case\"ArrowRight\":case\"PageDown\":case\" \":case\"Enter\":t.preventDefault(),M(1);break;case\"ArrowLeft\":case\"PageUp\":t.preventDefault(),M(-1);break;case\"ArrowUp\":case\"Backspace\":{let s=S(e.views,o,y(o));s&&(t.preventDefault(),g(s));break}case\"Home\":t.preventDefault(),g(e.views[0].name);break;case\"End\":t.preventDefault(),g(e.views[e.views.length-1].name,!0);break;case\"Escape\":f&&(t.preventDefault(),B(!1));break;case\"p\":case\"P\":B(!f);break;case\"f\":case\"F\":document.fullscreenElement?document.exitFullscreen?.().catch(()=>{}):document.documentElement.requestFullscreen?.().catch(()=>{});break;case\"t\":case\"T\":e.themes.length>1&&J();break}}),addEventListener(\"fullscreenchange\",()=>{!document.fullscreenElement&&f&&B(!1)});let D=document.querySelector(\"#sq-present\");D&&(D.onclick=()=>B(!f));let F=()=>{let t=decodeURIComponent(location.hash.slice(1));t&&t!==o&&e.views.some(s=>s.name===t)&&g(t)};if(addEventListener(\"hashchange\",F),document.documentElement.dataset.theme=l,l!==e.themes[0]){let t=h(o,l);t&&n.replaceChildren(t)}x(),F()}document.readyState===\"loading\"?addEventListener(\"DOMContentLoaded\",j):j();})();";
1
+ export declare const RUNTIME_JS = "\"use strict\";(()=>{var Q={ms:460,ease:\"cubic-bezier(.32,.72,0,1)\"},X={ms:240,ease:\"cubic-bezier(.4,0,.2,1)\"},K=e=>({x:e.x+e.w/2,y:e.y+e.h/2}),Y=(e,n,c)=>Math.min(c,Math.max(n,e)),Z=(e,n)=>Y(Math.min(e.w/n.w,e.h/n.h),1.15,3.2);function U(e){let{view:n,ghostBox:c,liveBox:a,anchor:p,dir:o}=e,{ms:l,ease:d}=p?Q:X;if(!p)return{ms:l,ease:d,gOrigin:\"50% 50%\",lOrigin:\"50% 50%\",gEnd:\"scale(.97)\",lStart:\"scale(1.03)\"};let f=Z(n,p),T=1+(f-1)*.62,E=K(p),h=K(n),y=h.x-E.x,w=h.y-E.y,x=o===\"in\",g=x?E:h,k=x?h:E;return{ms:l,ease:d,gOrigin:`${g.x-c.x}px ${g.y-c.y}px`,lOrigin:`${k.x-a.x}px ${k.y-a.y}px`,gEnd:x?`translate(${y}px, ${w}px) scale(${f})`:`translate(${-y}px, ${-w}px) scale(${1/f})`,lStart:x?`translate(${-y}px, ${-w}px) scale(${1/T})`:`translate(${y}px, ${w}px) scale(${T})`}}function W(e,n){if(n){if(!e)return n.split(\".\")[0];if(!(n===e||!n.startsWith(`${e}.`)))return`${e}.${n.slice(e.length+1).split(\".\")[0]}`}}function ee(e){if(!e)return[];let n=e.split(\".\");return n.map((c,a)=>n.slice(0,a+1).join(\".\"))}function O(e,n,c){return e.find(a=>a.scope===c&&a.name!==n)}function _(e,n,c){let a=e.find(d=>d.name===n)?.scope,p=e.find(d=>d.name===c)?.scope,o=W(a,p);if(o)return{dir:\"in\",anchor:o};let l=W(p,a);return l?{dir:\"out\",anchor:l}:{dir:\"in\"}}function te(e,n){let c=[],a=e.find(o=>!o.scope);if(a&&c.push({label:\"landscape\",view:a.name}),!n)return c;let p=n.split(\".\");for(let[o,l]of ee(n).entries())c.push({label:p[o],view:e.find(d=>d.scope===l)?.name});return c}function S(e,n,c){return[...te(e,c)].reverse().find(a=>a.view&&a.view!==n)?.view}var L=e=>document.querySelector(e);function j(){let e=JSON.parse(L(\"#sq-data\").textContent||\"{}\"),n=L(\"#sq-live\"),c=L(\"#sq-ghost\"),a=L(\"#sq-stage\"),p=()=>matchMedia(\"(prefers-reduced-motion: reduce)\").matches,o=e.entry,l=e.themes[0],d=0,f=!1;if(e.themes.length>1){let t=matchMedia(\"(prefers-color-scheme: dark)\").matches,s=e.themes.find(r=>r.includes(\"dark\")===t);s&&(l=s)}let T=`${e.entry}|${e.themes[0]}`,E=n.firstElementChild?.cloneNode(!0)??null,h=(t,s,r=0)=>{let i=r?`${t}|${s}|${r}`:`${t}|${s}`;if(i===T)return E?.cloneNode(!0)??null;let u=document.querySelector(`template[data-key=\"${CSS.escape(i)}\"]`);return u?u.content.cloneNode(!0):null},y=t=>e.views.find(s=>s.name===t)?.scope,w=t=>{let s=t.getBoundingClientRect(),r=a.getBoundingClientRect();return{x:s.left-r.left,y:s.top-r.top,w:s.width,h:s.height}};function x(){document.title=e.views.find(i=>i.name===o)?.title??document.title;let t=document.querySelector(\"#sq-tabs\");if(t){t.replaceChildren();for(let i of e.views){let u=document.createElement(\"button\");u.type=\"button\",u.className=i.name===o?\"on\":\"\",u.textContent=i.name,u.title=i.title??i.name,u.onclick=()=>g(i.name),t.append(u)}t.querySelector(\".on\")?.scrollIntoView?.({block:\"nearest\",inline:\"nearest\"})}for(let i of n.querySelectorAll(\"[data-path]\")){let u=i.getAttribute(\"data-path\");i.classList.toggle(\"sq-zoom\",!!(u&&O(e.views,o,u)))}let s=document.querySelector(\"#sq-step\"),r=e.flows[o]??0;s&&(s.textContent=f&&r?`${d||1} / ${r}`:\"\")}function g(t,s=!1){if(!t||t===o)return;let r=f&&e.flows[t]?s?e.flows[t]:1:0,i=h(t,l,r);if(!i)return;d=r;let{dir:u,anchor:v}=_(e.views,o,t),C=w(n),H=n.firstElementChild;if(p()||!H){n.replaceChildren(i),o=t,x();return}c.replaceChildren(H.cloneNode(!0)),c.style.cssText=`position:absolute;left:${C.x}px;top:${C.y}px;width:${C.w}px;height:${C.h}px;z-index:1;pointer-events:none`,n.replaceChildren(i),o=t,x();let G=w(n),P=v?(u===\"in\"?c:n).querySelector(`[data-path=\"${CSS.escape(v)}\"]`):null,m=U({view:{x:0,y:0,w:a.clientWidth,h:a.clientHeight},ghostBox:C,liveBox:G,anchor:P?w(P):void 0,dir:u}),b=c.style,$=n.style;b.transition=\"none\",b.transformOrigin=m.gOrigin,b.transform=\"none\",b.opacity=\"1\",$.transition=\"none\",$.transformOrigin=m.lOrigin,$.transform=m.lStart,$.opacity=\"0\",n.offsetHeight,b.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.55)}ms ${m.ease}`,$.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.6)}ms ${m.ease} ${Math.round(m.ms*.25)}ms`,b.transform=m.gEnd,b.opacity=\"0\",$.transform=\"none\",$.opacity=\"1\";let I=!1,q=0,R=()=>{I||(I=!0,cancelAnimationFrame(q),c.replaceChildren(),c.removeAttribute(\"style\"),n.removeAttribute(\"style\"))},z=()=>{getComputedStyle(n).transform===\"none\"?R():q=requestAnimationFrame(z)};q=requestAnimationFrame(z),setTimeout(R,m.ms+1e3)}function k(t){let s=t===l?null:h(o,t,d);s&&(l=t,document.documentElement.dataset.theme=t,n.replaceChildren(s))}function M(t){let s=e.flows[o]??0,r=d+t;if(s&&r>=1&&r<=s){let v=h(o,l,r);v&&(d=r,n.replaceChildren(v),x());return}let i=e.views.findIndex(v=>v.name===o),u=e.views[i+t];u&&g(u.name,t<0)}n.addEventListener(\"click\",t=>{let r=t.target.closest?.(\"[data-path]\")?.getAttribute(\"data-path\");if(r){let i=O(e.views,o,r);if(i)return g(i.name)}if(!r){let i=S(e.views,o,y(o));i&&g(i)}});let A=document.querySelector(\"#sq-theme\");A&&(A.onclick=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]));function B(t){if(t!==f){if(f=t,document.body.classList.toggle(\"presenting\",t),t){if(document.documentElement.requestFullscreen?.().catch(()=>{}),(e.flows[o]??0)&&!d){let r=h(o,l,1);r&&(d=1,n.replaceChildren(r))}}else if(document.fullscreenElement&&document.exitFullscreen?.().catch(()=>{}),d){let s=h(o,l,0);s&&(d=0,n.replaceChildren(s))}x()}}let N,V=()=>{document.body.classList.remove(\"idle\"),clearTimeout(N),f&&(N=setTimeout(()=>document.body.classList.add(\"idle\"),3500))};addEventListener(\"mousemove\",V),addEventListener(\"keydown\",V);let J=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]);addEventListener(\"keydown\",t=>{if(!(t.metaKey||t.ctrlKey||t.altKey))switch(t.key){case\"ArrowRight\":case\"PageDown\":case\" \":case\"Enter\":t.preventDefault(),M(1);break;case\"ArrowLeft\":case\"PageUp\":t.preventDefault(),M(-1);break;case\"ArrowUp\":case\"Backspace\":{let s=S(e.views,o,y(o));s&&(t.preventDefault(),g(s));break}case\"Home\":t.preventDefault(),g(e.views[0].name);break;case\"End\":t.preventDefault(),g(e.views[e.views.length-1].name,!0);break;case\"Escape\":f&&(t.preventDefault(),B(!1));break;case\"p\":case\"P\":B(!f);break;case\"f\":case\"F\":document.fullscreenElement?document.exitFullscreen?.().catch(()=>{}):document.documentElement.requestFullscreen?.().catch(()=>{});break;case\"t\":case\"T\":e.themes.length>1&&J();break}}),addEventListener(\"fullscreenchange\",()=>{!document.fullscreenElement&&f&&B(!1)});let D=document.querySelector(\"#sq-present\");D&&(D.onclick=()=>B(!f));let F=()=>{let t=decodeURIComponent(location.hash.slice(1));t&&t!==o&&e.views.some(s=>s.name===t)&&g(t)};if(addEventListener(\"hashchange\",F),document.documentElement.dataset.theme=l,l!==e.themes[0]){let t=h(o,l);t&&n.replaceChildren(t)}x(),F()}document.readyState===\"loading\"?addEventListener(\"DOMContentLoaded\",j):j();})();";
@@ -3,4 +3,4 @@
3
3
  // output cannot drift with an esbuild upgrade; CI re-runs the generator and
4
4
  // diffs. It bundles view/dive.ts and view/navigate.ts, which is what makes
5
5
  // "the export moves exactly like the playground" true by construction.
6
- export const RUNTIME_JS = "\"use strict\";(()=>{var Q={ms:460,ease:\"cubic-bezier(.32,.72,0,1)\"},X={ms:240,ease:\"cubic-bezier(.4,0,.2,1)\"},K=e=>({x:e.x+e.w/2,y:e.y+e.h/2}),Y=(e,n,i)=>Math.min(i,Math.max(n,e)),Z=(e,n)=>Y(Math.min(e.w/n.w,e.h/n.h),1.15,3.2);function U(e){let{view:n,ghostBox:i,liveBox:a,anchor:p,dir:o}=e,{ms:l,ease:d}=p?Q:X;if(!p)return{ms:l,ease:d,gOrigin:\"50% 50%\",lOrigin:\"50% 50%\",gEnd:\"scale(.97)\",lStart:\"scale(1.03)\"};let f=Z(n,p),T=1+(f-1)*.62,E=K(p),h=K(n),y=h.x-E.x,w=h.y-E.y,x=o===\"in\",g=x?E:h,k=x?h:E;return{ms:l,ease:d,gOrigin:`${g.x-i.x}px ${g.y-i.y}px`,lOrigin:`${k.x-a.x}px ${k.y-a.y}px`,gEnd:x?`translate(${y}px, ${w}px) scale(${f})`:`translate(${-y}px, ${-w}px) scale(${1/f})`,lStart:x?`translate(${-y}px, ${-w}px) scale(${1/T})`:`translate(${y}px, ${w}px) scale(${T})`}}function W(e,n){if(n){if(!e)return n.split(\".\")[0];if(!(n===e||!n.startsWith(`${e}.`)))return`${e}.${n.slice(e.length+1).split(\".\")[0]}`}}function ee(e){if(!e)return[];let n=e.split(\".\");return n.map((i,a)=>n.slice(0,a+1).join(\".\"))}function O(e,n,i){return e.find(a=>a.scope===i&&a.name!==n)}function _(e,n,i){let a=e.find(d=>d.name===n)?.scope,p=e.find(d=>d.name===i)?.scope,o=W(a,p);if(o)return{dir:\"in\",anchor:o};let l=W(p,a);return l?{dir:\"out\",anchor:l}:{dir:\"in\"}}function te(e,n){let i=[],a=e.find(o=>!o.scope);if(a&&i.push({label:\"landscape\",view:a.name}),!n)return i;let p=n.split(\".\");for(let[o,l]of ee(n).entries())i.push({label:p[o],view:e.find(d=>d.scope===l)?.name});return i}function S(e,n,i){return[...te(e,i)].reverse().find(a=>a.view&&a.view!==n)?.view}var L=e=>document.querySelector(e);function j(){let e=JSON.parse(L(\"#sq-data\").textContent||\"{}\"),n=L(\"#sq-live\"),i=L(\"#sq-ghost\"),a=L(\"#sq-stage\"),p=()=>matchMedia(\"(prefers-reduced-motion: reduce)\").matches,o=e.entry,l=e.themes[0],d=0,f=!1;if(e.themes.length>1&&matchMedia(\"(prefers-color-scheme: dark)\").matches){let t=e.themes.find(s=>s.includes(\"dark\"));t&&(l=t)}let T=`${e.entry}|${e.themes[0]}`,E=n.firstElementChild?.cloneNode(!0)??null,h=(t,s,c=0)=>{let r=c?`${t}|${s}|${c}`:`${t}|${s}`;if(r===T)return E?.cloneNode(!0)??null;let u=document.querySelector(`template[data-key=\"${CSS.escape(r)}\"]`);return u?u.content.cloneNode(!0):null},y=t=>e.views.find(s=>s.name===t)?.scope,w=t=>{let s=t.getBoundingClientRect(),c=a.getBoundingClientRect();return{x:s.left-c.left,y:s.top-c.top,w:s.width,h:s.height}};function x(){document.title=e.views.find(r=>r.name===o)?.title??document.title;let t=document.querySelector(\"#sq-tabs\");if(t){t.replaceChildren();for(let r of e.views){let u=document.createElement(\"button\");u.type=\"button\",u.className=r.name===o?\"on\":\"\",u.textContent=r.name,u.title=r.title??r.name,u.onclick=()=>g(r.name),t.append(u)}t.querySelector(\".on\")?.scrollIntoView?.({block:\"nearest\",inline:\"nearest\"})}for(let r of n.querySelectorAll(\"[data-path]\")){let u=r.getAttribute(\"data-path\");r.classList.toggle(\"sq-zoom\",!!(u&&O(e.views,o,u)))}let s=document.querySelector(\"#sq-step\"),c=e.flows[o]??0;s&&(s.textContent=f&&c?`${d||1} / ${c}`:\"\")}function g(t,s=!1){if(!t||t===o)return;let c=f&&e.flows[t]?s?e.flows[t]:1:0,r=h(t,l,c);if(!r)return;d=c;let{dir:u,anchor:v}=_(e.views,o,t),C=w(n),H=n.firstElementChild;if(p()||!H){n.replaceChildren(r),o=t,x();return}i.replaceChildren(H.cloneNode(!0)),i.style.cssText=`position:absolute;left:${C.x}px;top:${C.y}px;width:${C.w}px;height:${C.h}px;z-index:1;pointer-events:none`,n.replaceChildren(r),o=t,x();let G=w(n),P=v?(u===\"in\"?i:n).querySelector(`[data-path=\"${CSS.escape(v)}\"]`):null,m=U({view:{x:0,y:0,w:a.clientWidth,h:a.clientHeight},ghostBox:C,liveBox:G,anchor:P?w(P):void 0,dir:u}),b=i.style,$=n.style;b.transition=\"none\",b.transformOrigin=m.gOrigin,b.transform=\"none\",b.opacity=\"1\",$.transition=\"none\",$.transformOrigin=m.lOrigin,$.transform=m.lStart,$.opacity=\"0\",n.offsetHeight,b.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.55)}ms ${m.ease}`,$.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.6)}ms ${m.ease} ${Math.round(m.ms*.25)}ms`,b.transform=m.gEnd,b.opacity=\"0\",$.transform=\"none\",$.opacity=\"1\";let I=!1,q=0,R=()=>{I||(I=!0,cancelAnimationFrame(q),i.replaceChildren(),i.removeAttribute(\"style\"),n.removeAttribute(\"style\"))},z=()=>{getComputedStyle(n).transform===\"none\"?R():q=requestAnimationFrame(z)};q=requestAnimationFrame(z),setTimeout(R,m.ms+1e3)}function k(t){let s=t===l?null:h(o,t,d);s&&(l=t,document.documentElement.dataset.theme=t,n.replaceChildren(s))}function M(t){let s=e.flows[o]??0,c=d+t;if(s&&c>=1&&c<=s){let v=h(o,l,c);v&&(d=c,n.replaceChildren(v),x());return}let r=e.views.findIndex(v=>v.name===o),u=e.views[r+t];u&&g(u.name,t<0)}n.addEventListener(\"click\",t=>{let c=t.target.closest?.(\"[data-path]\")?.getAttribute(\"data-path\");if(c){let r=O(e.views,o,c);if(r)return g(r.name)}if(!c){let r=S(e.views,o,y(o));r&&g(r)}});let A=document.querySelector(\"#sq-theme\");A&&(A.onclick=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]));function B(t){if(t!==f){if(f=t,document.body.classList.toggle(\"presenting\",t),t){if(document.documentElement.requestFullscreen?.().catch(()=>{}),(e.flows[o]??0)&&!d){let c=h(o,l,1);c&&(d=1,n.replaceChildren(c))}}else if(document.fullscreenElement&&document.exitFullscreen?.().catch(()=>{}),d){let s=h(o,l,0);s&&(d=0,n.replaceChildren(s))}x()}}let N,V=()=>{document.body.classList.remove(\"idle\"),clearTimeout(N),f&&(N=setTimeout(()=>document.body.classList.add(\"idle\"),3500))};addEventListener(\"mousemove\",V),addEventListener(\"keydown\",V);let J=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]);addEventListener(\"keydown\",t=>{if(!(t.metaKey||t.ctrlKey||t.altKey))switch(t.key){case\"ArrowRight\":case\"PageDown\":case\" \":case\"Enter\":t.preventDefault(),M(1);break;case\"ArrowLeft\":case\"PageUp\":t.preventDefault(),M(-1);break;case\"ArrowUp\":case\"Backspace\":{let s=S(e.views,o,y(o));s&&(t.preventDefault(),g(s));break}case\"Home\":t.preventDefault(),g(e.views[0].name);break;case\"End\":t.preventDefault(),g(e.views[e.views.length-1].name,!0);break;case\"Escape\":f&&(t.preventDefault(),B(!1));break;case\"p\":case\"P\":B(!f);break;case\"f\":case\"F\":document.fullscreenElement?document.exitFullscreen?.().catch(()=>{}):document.documentElement.requestFullscreen?.().catch(()=>{});break;case\"t\":case\"T\":e.themes.length>1&&J();break}}),addEventListener(\"fullscreenchange\",()=>{!document.fullscreenElement&&f&&B(!1)});let D=document.querySelector(\"#sq-present\");D&&(D.onclick=()=>B(!f));let F=()=>{let t=decodeURIComponent(location.hash.slice(1));t&&t!==o&&e.views.some(s=>s.name===t)&&g(t)};if(addEventListener(\"hashchange\",F),document.documentElement.dataset.theme=l,l!==e.themes[0]){let t=h(o,l);t&&n.replaceChildren(t)}x(),F()}document.readyState===\"loading\"?addEventListener(\"DOMContentLoaded\",j):j();})();";
6
+ export const RUNTIME_JS = "\"use strict\";(()=>{var Q={ms:460,ease:\"cubic-bezier(.32,.72,0,1)\"},X={ms:240,ease:\"cubic-bezier(.4,0,.2,1)\"},K=e=>({x:e.x+e.w/2,y:e.y+e.h/2}),Y=(e,n,c)=>Math.min(c,Math.max(n,e)),Z=(e,n)=>Y(Math.min(e.w/n.w,e.h/n.h),1.15,3.2);function U(e){let{view:n,ghostBox:c,liveBox:a,anchor:p,dir:o}=e,{ms:l,ease:d}=p?Q:X;if(!p)return{ms:l,ease:d,gOrigin:\"50% 50%\",lOrigin:\"50% 50%\",gEnd:\"scale(.97)\",lStart:\"scale(1.03)\"};let f=Z(n,p),T=1+(f-1)*.62,E=K(p),h=K(n),y=h.x-E.x,w=h.y-E.y,x=o===\"in\",g=x?E:h,k=x?h:E;return{ms:l,ease:d,gOrigin:`${g.x-c.x}px ${g.y-c.y}px`,lOrigin:`${k.x-a.x}px ${k.y-a.y}px`,gEnd:x?`translate(${y}px, ${w}px) scale(${f})`:`translate(${-y}px, ${-w}px) scale(${1/f})`,lStart:x?`translate(${-y}px, ${-w}px) scale(${1/T})`:`translate(${y}px, ${w}px) scale(${T})`}}function W(e,n){if(n){if(!e)return n.split(\".\")[0];if(!(n===e||!n.startsWith(`${e}.`)))return`${e}.${n.slice(e.length+1).split(\".\")[0]}`}}function ee(e){if(!e)return[];let n=e.split(\".\");return n.map((c,a)=>n.slice(0,a+1).join(\".\"))}function O(e,n,c){return e.find(a=>a.scope===c&&a.name!==n)}function _(e,n,c){let a=e.find(d=>d.name===n)?.scope,p=e.find(d=>d.name===c)?.scope,o=W(a,p);if(o)return{dir:\"in\",anchor:o};let l=W(p,a);return l?{dir:\"out\",anchor:l}:{dir:\"in\"}}function te(e,n){let c=[],a=e.find(o=>!o.scope);if(a&&c.push({label:\"landscape\",view:a.name}),!n)return c;let p=n.split(\".\");for(let[o,l]of ee(n).entries())c.push({label:p[o],view:e.find(d=>d.scope===l)?.name});return c}function S(e,n,c){return[...te(e,c)].reverse().find(a=>a.view&&a.view!==n)?.view}var L=e=>document.querySelector(e);function j(){let e=JSON.parse(L(\"#sq-data\").textContent||\"{}\"),n=L(\"#sq-live\"),c=L(\"#sq-ghost\"),a=L(\"#sq-stage\"),p=()=>matchMedia(\"(prefers-reduced-motion: reduce)\").matches,o=e.entry,l=e.themes[0],d=0,f=!1;if(e.themes.length>1){let t=matchMedia(\"(prefers-color-scheme: dark)\").matches,s=e.themes.find(r=>r.includes(\"dark\")===t);s&&(l=s)}let T=`${e.entry}|${e.themes[0]}`,E=n.firstElementChild?.cloneNode(!0)??null,h=(t,s,r=0)=>{let i=r?`${t}|${s}|${r}`:`${t}|${s}`;if(i===T)return E?.cloneNode(!0)??null;let u=document.querySelector(`template[data-key=\"${CSS.escape(i)}\"]`);return u?u.content.cloneNode(!0):null},y=t=>e.views.find(s=>s.name===t)?.scope,w=t=>{let s=t.getBoundingClientRect(),r=a.getBoundingClientRect();return{x:s.left-r.left,y:s.top-r.top,w:s.width,h:s.height}};function x(){document.title=e.views.find(i=>i.name===o)?.title??document.title;let t=document.querySelector(\"#sq-tabs\");if(t){t.replaceChildren();for(let i of e.views){let u=document.createElement(\"button\");u.type=\"button\",u.className=i.name===o?\"on\":\"\",u.textContent=i.name,u.title=i.title??i.name,u.onclick=()=>g(i.name),t.append(u)}t.querySelector(\".on\")?.scrollIntoView?.({block:\"nearest\",inline:\"nearest\"})}for(let i of n.querySelectorAll(\"[data-path]\")){let u=i.getAttribute(\"data-path\");i.classList.toggle(\"sq-zoom\",!!(u&&O(e.views,o,u)))}let s=document.querySelector(\"#sq-step\"),r=e.flows[o]??0;s&&(s.textContent=f&&r?`${d||1} / ${r}`:\"\")}function g(t,s=!1){if(!t||t===o)return;let r=f&&e.flows[t]?s?e.flows[t]:1:0,i=h(t,l,r);if(!i)return;d=r;let{dir:u,anchor:v}=_(e.views,o,t),C=w(n),H=n.firstElementChild;if(p()||!H){n.replaceChildren(i),o=t,x();return}c.replaceChildren(H.cloneNode(!0)),c.style.cssText=`position:absolute;left:${C.x}px;top:${C.y}px;width:${C.w}px;height:${C.h}px;z-index:1;pointer-events:none`,n.replaceChildren(i),o=t,x();let G=w(n),P=v?(u===\"in\"?c:n).querySelector(`[data-path=\"${CSS.escape(v)}\"]`):null,m=U({view:{x:0,y:0,w:a.clientWidth,h:a.clientHeight},ghostBox:C,liveBox:G,anchor:P?w(P):void 0,dir:u}),b=c.style,$=n.style;b.transition=\"none\",b.transformOrigin=m.gOrigin,b.transform=\"none\",b.opacity=\"1\",$.transition=\"none\",$.transformOrigin=m.lOrigin,$.transform=m.lStart,$.opacity=\"0\",n.offsetHeight,b.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.55)}ms ${m.ease}`,$.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.6)}ms ${m.ease} ${Math.round(m.ms*.25)}ms`,b.transform=m.gEnd,b.opacity=\"0\",$.transform=\"none\",$.opacity=\"1\";let I=!1,q=0,R=()=>{I||(I=!0,cancelAnimationFrame(q),c.replaceChildren(),c.removeAttribute(\"style\"),n.removeAttribute(\"style\"))},z=()=>{getComputedStyle(n).transform===\"none\"?R():q=requestAnimationFrame(z)};q=requestAnimationFrame(z),setTimeout(R,m.ms+1e3)}function k(t){let s=t===l?null:h(o,t,d);s&&(l=t,document.documentElement.dataset.theme=t,n.replaceChildren(s))}function M(t){let s=e.flows[o]??0,r=d+t;if(s&&r>=1&&r<=s){let v=h(o,l,r);v&&(d=r,n.replaceChildren(v),x());return}let i=e.views.findIndex(v=>v.name===o),u=e.views[i+t];u&&g(u.name,t<0)}n.addEventListener(\"click\",t=>{let r=t.target.closest?.(\"[data-path]\")?.getAttribute(\"data-path\");if(r){let i=O(e.views,o,r);if(i)return g(i.name)}if(!r){let i=S(e.views,o,y(o));i&&g(i)}});let A=document.querySelector(\"#sq-theme\");A&&(A.onclick=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]));function B(t){if(t!==f){if(f=t,document.body.classList.toggle(\"presenting\",t),t){if(document.documentElement.requestFullscreen?.().catch(()=>{}),(e.flows[o]??0)&&!d){let r=h(o,l,1);r&&(d=1,n.replaceChildren(r))}}else if(document.fullscreenElement&&document.exitFullscreen?.().catch(()=>{}),d){let s=h(o,l,0);s&&(d=0,n.replaceChildren(s))}x()}}let N,V=()=>{document.body.classList.remove(\"idle\"),clearTimeout(N),f&&(N=setTimeout(()=>document.body.classList.add(\"idle\"),3500))};addEventListener(\"mousemove\",V),addEventListener(\"keydown\",V);let J=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]);addEventListener(\"keydown\",t=>{if(!(t.metaKey||t.ctrlKey||t.altKey))switch(t.key){case\"ArrowRight\":case\"PageDown\":case\" \":case\"Enter\":t.preventDefault(),M(1);break;case\"ArrowLeft\":case\"PageUp\":t.preventDefault(),M(-1);break;case\"ArrowUp\":case\"Backspace\":{let s=S(e.views,o,y(o));s&&(t.preventDefault(),g(s));break}case\"Home\":t.preventDefault(),g(e.views[0].name);break;case\"End\":t.preventDefault(),g(e.views[e.views.length-1].name,!0);break;case\"Escape\":f&&(t.preventDefault(),B(!1));break;case\"p\":case\"P\":B(!f);break;case\"f\":case\"F\":document.fullscreenElement?document.exitFullscreen?.().catch(()=>{}):document.documentElement.requestFullscreen?.().catch(()=>{});break;case\"t\":case\"T\":e.themes.length>1&&J();break}}),addEventListener(\"fullscreenchange\",()=>{!document.fullscreenElement&&f&&B(!1)});let D=document.querySelector(\"#sq-present\");D&&(D.onclick=()=>B(!f));let F=()=>{let t=decodeURIComponent(location.hash.slice(1));t&&t!==o&&e.views.some(s=>s.name===t)&&g(t)};if(addEventListener(\"hashchange\",F),document.documentElement.dataset.theme=l,l!==e.themes[0]){let t=h(o,l);t&&n.replaceChildren(t)}x(),F()}document.readyState===\"loading\"?addEventListener(\"DOMContentLoaded\",j):j();})();";
@@ -25,11 +25,15 @@ function boot() {
25
25
  let step = 0;
26
26
  let presenting = false;
27
27
  // The reader's own preference wins over the author's, when the file carries
28
- // a palette that matches it — the same rule an adaptive SVG follows.
29
- if (data.themes.length > 1 && matchMedia("(prefers-color-scheme: dark)").matches) {
30
- const dark = data.themes.find((t) => t.includes("dark"));
31
- if (dark)
32
- theme = dark;
28
+ // a palette that matches it — the same rule an adaptive SVG follows. Both
29
+ // directions: a light-mode reader of a dark-entry file gets light, not just
30
+ // the reverse (the entry is dark by default now, so one-way promotion would
31
+ // have made the system setting a no-op for half of readers).
32
+ if (data.themes.length > 1) {
33
+ const wantDark = matchMedia("(prefers-color-scheme: dark)").matches;
34
+ const match = data.themes.find((t) => t.includes("dark") === wantDark);
35
+ if (match)
36
+ theme = match;
33
37
  }
34
38
  // The entry view is inline rather than in a <template>, so that a reader
35
39
  // whose browser never runs this script still sees a diagram. That means it is
@@ -66,8 +66,11 @@ export async function exportHTML(files, opts = {}) {
66
66
  // A document has one palette at a time and a button to change it, so the
67
67
  // theme is the document's rather than each view's — a per-view `theme` is
68
68
  // deliberately overridden here, the way `--theme` overrides it elsewhere.
69
- const base = opts.themes?.[0] ?? built.model.fileTheme ?? "light";
70
- const palette = opts.themes ?? [base, themes[base]?.pairsWith].filter(Boolean);
69
+ const base = opts.themes?.[0] ?? built.model.fileTheme ?? "dark";
70
+ // the counterpart pairs in either direction: light names dark, and dark is
71
+ // found by being named — so a dark entry still bundles light for the toggle
72
+ const mate = themes[base]?.pairsWith ?? Object.values(themes).find((t) => t.pairsWith === base)?.name;
73
+ const palette = opts.themes ?? [base, mate].filter(Boolean);
71
74
  for (const name of palette)
72
75
  if (!themes[name])
73
76
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squinch/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The Squinch engine: parse, model, layout and deterministic SVG rendering for architecture diagrams as code.",
5
5
  "keywords": [
6
6
  "architecture",
@@ -33,11 +33,11 @@
33
33
  "@lezer/lr": "^1.4.10",
34
34
  "elkjs": "^0.12.0",
35
35
  "fast-xml-parser": "^5.10.1",
36
- "@squinch/pack-aws": "0.1.0",
37
- "@squinch/pack-azure": "0.1.0",
38
- "@squinch/pack-k8s": "0.1.0",
39
- "@squinch/pack-logos": "0.1.0",
40
- "@squinch/pack-sys": "0.1.0"
36
+ "@squinch/pack-aws": "0.2.0",
37
+ "@squinch/pack-azure": "0.2.0",
38
+ "@squinch/pack-k8s": "0.2.0",
39
+ "@squinch/pack-logos": "0.2.0",
40
+ "@squinch/pack-sys": "0.2.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@fontsource/ibm-plex-mono": "^5.3.0",