@firsthandjs/devtools 0.9.1 → 0.10.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/chunk-B62UJE3O.js +7 -0
- package/dist/chunk-UEYJUDK5.js +84 -0
- package/dist/index.d.ts +41 -140
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/names.d.ts +12 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/panel.d.ts.map +1 -1
- package/dist/queries.d.ts +96 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/record.d.ts +23 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/recorded.d.ts +54 -0
- package/dist/recorded.d.ts.map +1 -0
- package/dist/style.d.ts +17 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views.d.ts +24 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/widgets.d.ts +28 -0
- package/dist/widgets.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-7MQSJYVB.js +0 -7
- package/dist/chunk-BCXJOY33.js +0 -86
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
var r={labels:new WeakMap,parts:new WeakMap,writers:new WeakMap,causes:new WeakMap,components:new WeakMap,cacheLog:[],updates:[],roots:new Set};function m(){r.updates.length=0,r.cacheLog.length=0,r.roots.clear()}function f(e,t,n){e.push(t),e.length>n&&e.shift()}function g(){let e=[];for(let t of r.roots){let n=t.deref();n===void 0?r.roots.delete(t):e.push(n)}return e}function i(e){let t=r.parts.get(e);if(t!==void 0){let o=t.node;return`${(o.tagName??o.nodeName??"node").toLowerCase()}.${t.property}`}let n=r.labels.get(e);return n===void 0?(e.flags&1)===0?"signal":"computed":n.name}function k(e){return r.parts.has(e)?"part":r.labels.get(e)?.kind??((e.flags&1)===0?"signal":"computed")}var a=null,u=null,p=null,d=null,l=null,L=/@firsthandjs|[\\/]node_modules[\\/]|[\\/]packages[\\/](core|dom|deep|devtools|jsx-runtime|data|router|styled|testing)[\\/]src[\\/]/;function N(){let e=(new Error().stack??"").split(`
|
|
2
|
+
`).slice(1),t=[];for(let n of e){if(L.test(n))continue;let o=n.trim().replace(/^at\s+/,"");if(o!==""&&t.push(o),t.length===6)break}return t}function j(){let t=(new Error().stack??"").split(`
|
|
3
|
+
`).slice(1).find(o=>!L.test(o))??"unknown",n=/\(?([^()\s]+:\d+:\d+)\)?$/.exec(t.trim());return n===null?t.trim():n[1]}function W(){if(l!==null)return!1;let e={attached:!0,label(t,n,o){r.labels.set(t,{kind:n,name:o===""?j():o})},cause:y,root(t){r.roots.add(new WeakRef(t))},component(t,n){r.components.set(t,n)},running:O,query(t,n,o){f(r.cacheLog,{event:t,key:n,tags:o},200)},part:C};return globalThis.__FIRSTHAND_DEVTOOLS__=e,l=e,!0}function y(e){d=e,a={update:{at:Math.round(performance.now()),source:i(e),ran:[],stack:N()},effects:[]},f(r.updates,a,100)}function O(e){if(u=e,e===null){p?.();return}d!==null&&(r.causes.set(e,d),a?.update.ran.push(i(e)),a?.effects.push(e))}function C(e,t){if(u===null)return;r.parts.set(u,{node:e,property:t});let n=r.writers.get(e);n===void 0&&(n=new Set,r.writers.set(e,n)),n.add(u)}function R(){l!==null&&(l.attached=!1),globalThis.__FIRSTHAND_DEVTOOLS__=void 0,l=null,u=null,d=null,a=null,p=null,m()}function A(e){p=e}function c(e,t,n){let o={kind:k(e),name:i(e),value:e.v,dependencies:[],dependents:[]};if(t>0)for(let s=e.deps;s!==void 0;s=s.nextDep)o.dependencies.push(c(s.dep,t-1,0));if(n>0)for(let s=e.subs;s!==void 0;s=s.nextSub)o.dependents.push(c(s.sub,0,n-1));return o}function b(e,t){e.cells!==null&&t.push(...e.cells);for(let n=e.head;n!==null;n=n.next)b(n,t)}function K(){let e=[];for(let t of g())b(t,e);return e.map(t=>c(t,1,1))}function T(e,t=8){let n=r.writers.get(e);return n===void 0?[]:[...n].map(o=>c(o,t,0))}function $(e){let t=T(e);return t.length===0?"Nothing reactive writes this node.":t.map(n=>E(w(n)).join(`
|
|
4
|
+
\u2193
|
|
5
|
+
`)).join(`
|
|
6
|
+
|
|
7
|
+
`)}function w(e){let t=[];for(let n of e.dependencies){let o=w(n);o.length>t.length&&(t=o)}return[...t,e]}function E(e){return e.map(t=>t.kind==="computed"?`computed(${t.name})`:t.name)}function F(e){let t=r.writers.get(e);if(t===void 0)return null;for(let n of t){let o=r.causes.get(n);if(o!==void 0)return i(o)}return null}function q(){return[...r.cacheLog]}function B(e){let t=r.writers.get(e);if(t===void 0)return[];let n=[];for(let o of t){let s=_(o.scope??null);s.length>n.length&&(n.length=0,n.push(...s))}return n}function _(e){let t=[];for(let n=e;n!==null;n=n.parent){let o=r.components.get(n);o!==void 0&&t.unshift(o)}return t}function P(e){if(e===void 0)return r.updates.map(n=>n.update);let t=r.writers.get(e);return t===void 0?[]:r.updates.filter(n=>n.effects.some(o=>t.has(o))).map(n=>n.update)}export{W as a,R as b,A as c,K as d,T as e,$ as f,w as g,F as h,q as i,B as j,P as k};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import{c as M,e as H,g as C,h as q,i as A,j as O,k as N}from"./chunk-B62UJE3O.js";var P=`:host { all: initial; }
|
|
2
|
+
.panel {
|
|
3
|
+
position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;
|
|
4
|
+
width: 460px; max-height: 78vh; display: flex; flex-direction: column;
|
|
5
|
+
font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
6
|
+
color: #e6e6e6; background: #1c1c1f; border: 1px solid #3a3a40;
|
|
7
|
+
border-radius: 10px; box-shadow: 0 10px 40px rgb(0 0 0 / 0.45);
|
|
8
|
+
}
|
|
9
|
+
header { display: flex; align-items: center; gap: 6px; padding: 8px 10px;
|
|
10
|
+
border-bottom: 1px solid #3a3a40; }
|
|
11
|
+
header strong { font-weight: 600; letter-spacing: 0.02em; flex: 1; }
|
|
12
|
+
button { font: inherit; color: inherit; background: #2a2a30; border: 1px solid #45454d;
|
|
13
|
+
border-radius: 5px; padding: 3px 8px; cursor: pointer; }
|
|
14
|
+
button:hover { background: #34343c; }
|
|
15
|
+
button[aria-pressed='true'] { background: #3d5afe; border-color: #3d5afe; color: #fff; }
|
|
16
|
+
.body { display: flex; flex-direction: column; overflow: hidden; padding: 12px; min-height: 0; }
|
|
17
|
+
/* The list scrolls; the detail stays where it can be read. Without this the
|
|
18
|
+
call stack sits below everything and is only reachable by scrolling past
|
|
19
|
+
the whole log \u2014 which is exactly when you least want to. */
|
|
20
|
+
/* Two independent scroll areas: the list above, the detail below. Each gets
|
|
21
|
+
its own, because a call stack that can only be reached by scrolling past
|
|
22
|
+
forty rows of log is out of reach exactly when it is wanted. */
|
|
23
|
+
.scroll { overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; min-height: 60px; }
|
|
24
|
+
.pinned { flex: 0 1 auto; min-height: 140px; max-height: 65%; overflow-y: auto;
|
|
25
|
+
overflow-x: hidden; margin-top: 8px; padding-top: 8px;
|
|
26
|
+
border-top: 1px solid #3a3a40; }
|
|
27
|
+
.detail-head { display: flex; align-items: center; gap: 8px; }
|
|
28
|
+
.detail-head .hint { margin: 0; flex: 1; }
|
|
29
|
+
.detail-head button { padding: 0 6px; line-height: 1.4; }
|
|
30
|
+
.empty { color: #8a8a94; }
|
|
31
|
+
.hint { color: #7c7c88; margin: 14px 0 6px; font-size: 10px; text-transform: uppercase;
|
|
32
|
+
letter-spacing: 0.1em; }
|
|
33
|
+
.hint:first-child { margin-top: 0; }
|
|
34
|
+
.section + .section { margin-top: 2px; }
|
|
35
|
+
.stack { margin: 4px 0 0; }
|
|
36
|
+
.stack div { color: #9a9aa6; padding-left: 10px; border-left: 1px solid #3a3a40; }
|
|
37
|
+
.stack div:first-child { color: #e6e6e6; }
|
|
38
|
+
.filters { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
|
39
|
+
.filters .chip { background: #2a2a30; border: 1px solid #45454d; border-radius: 999px;
|
|
40
|
+
padding: 1px 9px; cursor: pointer; color: #9ecbff; }
|
|
41
|
+
.filters .chip[aria-pressed='true'] { background: #3d5afe; border-color: #3d5afe; color: #fff; }
|
|
42
|
+
.count { color: #7c7c88; }
|
|
43
|
+
|
|
44
|
+
/* The path, as boxes and arrows rather than as three lines of text. */
|
|
45
|
+
.flow { display: flex; flex-direction: column; align-items: stretch; gap: 0; }
|
|
46
|
+
.box { border: 1px solid #45454d; border-radius: 7px; padding: 6px 9px; background: #232329;
|
|
47
|
+
display: flex; align-items: baseline; gap: 8px; }
|
|
48
|
+
.box .tag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em;
|
|
49
|
+
padding: 1px 5px; border-radius: 4px; background: #34343c; color: #b9b9c4; }
|
|
50
|
+
.box .label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
51
|
+
.box .val { color: #c3e88d; }
|
|
52
|
+
.box.signal { border-color: #3d5afe; }
|
|
53
|
+
.box.signal .tag { background: #23306b; color: #b6c4ff; }
|
|
54
|
+
.box.computed { border-color: #c792ea; }
|
|
55
|
+
.box.computed .tag { background: #3a2b47; color: #e6c8ff; }
|
|
56
|
+
.box.part { border-color: #ffcb6b; }
|
|
57
|
+
.box.part .tag { background: #4a3a1c; color: #ffdfa1; }
|
|
58
|
+
.box.trigger { box-shadow: 0 0 0 2px #3d5afe66; }
|
|
59
|
+
.arrow { align-self: center; color: #6a6a76; font-size: 14px; line-height: 1; padding: 3px 0; }
|
|
60
|
+
|
|
61
|
+
/* The component stack, as crumbs. */
|
|
62
|
+
.crumbs { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 10px; }
|
|
63
|
+
.crumb { background: #2a2a30; border: 1px solid #45454d; border-radius: 999px;
|
|
64
|
+
padding: 1px 8px; color: #c792ea; }
|
|
65
|
+
.crumb + .crumb::before { content: '\u203A'; color: #8a8a94; margin-right: 6px; margin-left: -4px; }
|
|
66
|
+
|
|
67
|
+
/* The timeline: one row per update, a bar for how much it woke. */
|
|
68
|
+
.track { display: flex; flex-direction: column; gap: 4px; }
|
|
69
|
+
.tick { display: grid; grid-template-columns: 52px minmax(0, 1fr) auto auto; gap: 8px;
|
|
70
|
+
align-items: center; padding: 3px 4px; border-radius: 5px; cursor: pointer; }
|
|
71
|
+
.tick:hover { background: #26262c; }
|
|
72
|
+
.tick[aria-selected='true'] { background: #23306b; }
|
|
73
|
+
.tick .when { color: #8a8a94; text-align: right; }
|
|
74
|
+
.tick .who { color: #9ecbff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
75
|
+
.tick .where { color: #7c7c88; white-space: nowrap; }
|
|
76
|
+
.tick .bar { height: 8px; border-radius: 4px; background: #3d5afe; min-width: 4px; }
|
|
77
|
+
.tick .bar.none { background: #4a4a54; }
|
|
78
|
+
.detail .ran { color: #ffdfa1; }
|
|
79
|
+
.cause { color: #ffcb6b; margin: 10px 0 0; }
|
|
80
|
+
.event { display: grid; grid-template-columns: 84px 1fr; gap: 8px; padding: 2px 0; }
|
|
81
|
+
.event .created { color: #c3e88d; }
|
|
82
|
+
.event .invalidated { color: #ffcb6b; }
|
|
83
|
+
.event .dropped { color: #f07178; }
|
|
84
|
+
.event .tags { color: #9ecbff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }`,m="firsthand-devtools-outline",U=`.${m} { outline: 2px solid #3d5afe !important; outline-offset: 1px; }`;var X="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function Z(e){let r=[],t=0,n=0;for(let o of e){let a=X.indexOf(o),i=a&32;if(n+=(a&31)<<t,i===0){let s=n&1;n>>=1,r.push(s===1?-n:n),t=0,n=0}else t+=5}return r}function ee(e){let r=[],t=0,n=0,o=0;for(let[a,i]of e.mappings.split(";").entries()){let s=0;for(let p of i.split(",")){if(p==="")continue;let u=Z(p);if(s+=u[0],u.length<4)continue;t+=u[1],n+=u[2],o+=u[3];let b=e.sources[t];b!=null&&r.push({generatedLine:a,generatedColumn:s,source:b,originalLine:n,originalColumn:o})}}return r}var I=new Map,te=/\/\/[#@]\s*sourceMappingURL=data:application\/json;(?:charset=[^;]+;)?base64,([\w+/=]+)/;async function ne(e){let r=I.get(e);if(r!==void 0)return r;let t=(async()=>{try{let o=await(await fetch(e)).text(),a=te.exec(o);if(a===null)return null;let i=JSON.parse(atob(a[1]));return ee(i)}catch{return null}})();return I.set(e,t),t}function re(e,r,t){let n=null;for(let o of e){if(o.generatedLine>r||o.generatedLine===r&&o.generatedColumn>t)break;o.generatedLine===r&&(n=o)}return n}async function F(e){let r=/^(.*?)\(?([^\s()]+):(\d+):(\d+)\)?$/.exec(e.trim().replace(/^at\s+/,""));if(r===null)return e;let[,t,n,o,a]=r;if(!/^https?:\/\//.test(n))return e;let i=await ne(n);if(i===null)return e;let s=re(i,Number(o)-1,Number(a)-1);if(s===null)return e;let u=`${s.source.slice(Math.max(s.source.lastIndexOf("/"),s.source.lastIndexOf("\\"))+1)}:${String(s.originalLine+1)}:${String(s.originalColumn+1)}`;return t.trim()===""?u:`${t.trim()} (${u})`}function v(e,r){let t=document.createElement("button");return t.textContent=e,t.setAttribute("aria-pressed","false"),t.addEventListener("click",r),t}function l(e,r,t){let n=document.createElement(e);return n.className=r,n.textContent=t,n}function G(e){let r=/^(.*?)\(?([^()\s]+):(\d+):(\d+)\)?$/.exec(e);if(r===null)return e;let[,t,n,o,a]=r,i=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\")),s=n.slice(i+1),p=s.indexOf("?"),b=`${(p===-1?s:s.slice(0,p)).trim()}:${o}:${a}`;return t.trim()===""?b:`${t.trim()} (${b})`}function j(e){let r=/\(([^()]+)\)\s*$/.exec(e);return(r===null?e:r[1]).trim()}function f(e,r,t,n=o=>o){let o=l(e,r,n(G(t)));return F(t).then(a=>{o.textContent=n(G(a))}),o}function E(e){return e.classList.add("pinned"),e}var c=null,k=new Map,w=null;function z(){return c!==null}function Y(){c=null,w=null,k.clear()}function oe(e,r){let t=document.createElement("div");t.className=`box ${e.kind}${r?" trigger":""}`,t.append(l("span","tag",e.kind)),t.append(f("span","label",e.name));let n=e.value;return n!==void 0&&(typeof n!="object"||n===null)&&t.append(l("span","val",JSON.stringify(n))),t}function ae(e,r,t){let n=C(e),o=document.createElement("div");o.className="flow";for(let[a,i]of n.entries())o.append(oe(i,i.name===r)),a<n.length-1&&o.append(l("div","arrow","\u2193"));t.append(o)}function B(e,r){let t=document.createElement("div");if(t.className="scroll",e.append(t),r===null){t.append(l("p","empty","Nothing selected."),l("p","hint","Press \u201CPick\u201D and click an element."));return}let n=O(r);if(n.length>0){t.append(l("p","hint","Component"));let s=document.createElement("div");s.className="crumbs";for(let p of n)s.append(l("span","crumb",p));t.append(s)}let o=H(r);if(o.length===0){t.append(l("p","empty","Nothing reactive writes this node."));return}let a=q(r);t.append(l("p","hint","Path"));for(let s of o)ae(s,a,t);t.append(l("p","cause",a===null?"Has not run since anything changed.":`Triggered by ${a}`));let i=N(r).slice(-6).reverse();i.length>0&&(t.append(l("p","hint",`Last ${String(i.length)} updates`)),t.append(Q(i,e))),c!==null&&e.append(E(S(c,e)))}function Q(e,r){k.clear();let t=Math.max(1,...e.map(o=>o.ran.length)),n=document.createElement("div");n.className="track";for(let o of e){let a=document.createElement("div");a.className="tick",a.setAttribute("aria-selected",String(o===c)),k.set(a,o),a.append(l("span","when",`${String(o.at)}ms`)),a.append(f("span","who",o.source));let i=document.createElement("span");i.className=o.ran.length===0?"bar none":"bar",i.style.width=`${String(Math.round(o.ran.length/t*60)+6)}px`,i.title=`woke ${String(o.ran.length)} of ${String(t)}`,a.append(i);let s=o.stack[0];s!==void 0&&a.append(f("span","where",s,j)),a.addEventListener("click",()=>{R(o,r)}),n.append(a)}return n}function S(e,r){let t=document.createElement("div");t.className="detail";let n=document.createElement("div");n.className="detail-head",n.append(f("p","hint",e.source,a=>`${a} woke ${String(e.ran.length)}`));let o=v("\xD7",()=>{R(e,r)});o.setAttribute("aria-label","Close detail"),n.append(o),t.append(n);for(let a of e.ran)t.append(f("div","ran",a));if(e.stack.length>0){t.append(l("p","hint","Written from"));let a=document.createElement("div");a.className="stack";for(let i of e.stack)a.append(f("div","",i));t.append(a)}return t}function R(e,r){c=c===e?null:e;for(let[t,n]of k)t.setAttribute("aria-selected",String(n===c));r.querySelector(".pinned")?.remove(),c!==null&&r.append(E(S(c,r)))}function W(e,r){let t=N();if(t.length===0){e.append(l("p","empty","Nothing has changed yet."));return}let n=document.createElement("div");n.className="scroll",e.append(n);let o=[...new Set(t.map(i=>i.source))];if(o.length>1){let i=document.createElement("div");i.className="filters";for(let s of o){let p=f("span","chip",s);p.setAttribute("aria-pressed",String(w===s)),p.addEventListener("click",()=>{w=w===s?null:s,c=null,r()}),i.append(p)}n.append(i)}let a=w===null?t:t.filter(i=>i.source===w);n.append(l("p","hint",`${String(a.length)} of ${String(t.length)} updates`)),n.append(Q([...a].reverse(),e)),c!==null&&e.append(E(S(c,e)))}function J(e){let r=document.createElement("div");r.className="scroll",e.append(r);let t=A();if(t.length===0){r.append(l("p","empty","No resource has done anything yet."));return}for(let n of[...t].reverse()){let o=document.createElement("div");o.className="event",o.append(l("span",n.event,n.event)),o.append(l("span","tags",n.tags.join(", "))),r.append(o)}}var d=null,L=null,T=null,x=!1,g="graph",y=null;function h(){if(L===null)return;let{body:e,pick:r,graphTab:t,queryTab:n,timelineTab:o}=L,a=e.querySelector(".scroll")?.scrollTop??0;t.setAttribute("aria-pressed",String(g==="graph")),n.setAttribute("aria-pressed",String(g==="queries")),o.setAttribute("aria-pressed",String(g==="timeline")),r.setAttribute("aria-pressed",String(x)),e.textContent="",g==="graph"?B(e,T):g==="queries"?J(e):W(e,h);let i=e.querySelector(".scroll");i!==null&&(i.scrollTop=a)}function V(e){!x||d===null||e.composedPath().includes(d)||(e.preventDefault(),e.stopPropagation(),T=e.target,x=!1,document.querySelector(`.${m}`)?.classList.remove(m),h())}function D(e){if(!x)return;document.querySelector(`.${m}`)?.classList.remove(m);let r=e.target;r!==null&&r!==d&&r.classList.add(m)}function _(){if(d!==null)return;d=document.createElement("div"),d.setAttribute("data-firsthand-devtools","");let e=d.attachShadow({mode:"open"}),r=document.createElement("style");r.textContent=P;let t=document.createElement("style");t.textContent=U,document.head.append(t);let n=document.createElement("div");n.className="panel";let o=document.createElement("div");o.className="body",n.append(ie(o),o),e.append(r,n),document.body.append(d),document.addEventListener("click",V,!0),document.addEventListener("mouseover",D,!0),M(se),h()}function ie(e){let r=document.createElement("header");r.append(l("strong","","Firsthand"));let t=v("Pick",()=>{x=!x,h()});t.dataset.pick="";let n=$("graph","Graph"),o=$("queries","Queries"),a=$("timeline","Timeline"),i=v("\xD7",K);return i.setAttribute("aria-label","Close"),r.append(t,n,o,a,i),L={body:e,pick:t,graphTab:n,queryTab:o,timelineTab:a},r}function $(e,r){let t=v(r,()=>{g=e,h()});return t.dataset.tab=e,t}function se(){z()||y!==null||(y=requestAnimationFrame(()=>{y=null,h()}))}function K(){M(null),y!==null&&(cancelAnimationFrame(y),y=null),document.removeEventListener("click",V,!0),document.removeEventListener("mouseover",D,!0),document.querySelector(`.${m}`)?.classList.remove(m),d?.remove(),d=null,L=null,x=!1,T=null,g="graph",Y()}function ve(){d===null?_():K()}function we(e){_(),T=e,g="graph",h()}function ye(){h()}export{K as close,_ as open,ye as refresh,we as show,ve as toggle};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,61 +1,42 @@
|
|
|
1
|
-
/** What a node of the graph is. */
|
|
2
|
-
export type NodeKind = 'signal' | 'computed' | 'effect' | 'part';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Devtools for Firsthand (ADR-0020).
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The reactive graph is not something this package records. It is already
|
|
5
|
+
* there, because propagation and disposal need it: every cell carries its
|
|
6
|
+
* dependencies and its subscribers as linked lists, and every owner carries
|
|
7
|
+
* its children and its cells. This package attaches names to those nodes and
|
|
8
|
+
* reads the structure when asked.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here runs unless `attach()` is called, and none of it exists in a
|
|
11
|
+
* production build of the framework: the hooks that feed it live in modules
|
|
12
|
+
* the build replaces with empty functions.
|
|
13
|
+
*
|
|
14
|
+
* | Module | What it does |
|
|
15
|
+
* | ------------- | ------------------------------------------------- |
|
|
16
|
+
* | `recorded.ts` | what has been observed, and nothing that writes it |
|
|
17
|
+
* | `record.ts` | the hook the core calls — the only writer |
|
|
18
|
+
* | `names.ts` | what to call a cell |
|
|
19
|
+
* | `queries.ts` | the questions, which is all the panel needs |
|
|
20
|
+
* | `panel.ts` | the answers, by pointing rather than by typing |
|
|
21
|
+
*
|
|
22
|
+
* This module is the entry point and nothing else: it wires the console API
|
|
23
|
+
* and the shortcut, and it is the only module that knows the panel exists.
|
|
24
|
+
* That is what lets the panel be loaded on demand — a module that imported it
|
|
25
|
+
* back would be a cycle, and a cycle would defeat the splitting.
|
|
8
26
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/** What was written. */
|
|
13
|
-
source: string;
|
|
14
|
-
/** What ran, in the order it ran. */
|
|
15
|
-
ran: string[];
|
|
16
|
-
/**
|
|
17
|
-
* Where the write came from, application frames only.
|
|
18
|
-
*
|
|
19
|
-
* "Which signal changed" is half an answer; the other half is which of your
|
|
20
|
-
* code changed it, and an event handler three files away is exactly the case
|
|
21
|
-
* where the graph cannot help.
|
|
22
|
-
*
|
|
23
|
-
* Kept exactly as the engine gave them, positions in the *compiled* module
|
|
24
|
-
* and all — browsers do not apply source maps to `error.stack`. The panel
|
|
25
|
-
* resolves them through the module's own map before showing them; anything
|
|
26
|
-
* else reading this should do the same.
|
|
27
|
-
*/
|
|
28
|
-
stack: string[];
|
|
29
|
-
};
|
|
30
|
-
/** Something a resource did. */
|
|
31
|
-
export type QueryEvent = {
|
|
32
|
-
event: 'created' | 'invalidated' | 'dropped';
|
|
33
|
-
/** The cache key: its tags and variables, as the client derived them. */
|
|
34
|
-
key: string;
|
|
35
|
-
/** The tags the entry carries, which is what an invalidation matches on. */
|
|
36
|
-
tags: readonly string[];
|
|
37
|
-
};
|
|
38
|
-
/** One node of the graph, as devtools describe it. */
|
|
39
|
-
export type GraphNode = {
|
|
40
|
-
kind: NodeKind;
|
|
41
|
-
/** `order.status`, `isEditable`, `button.disabled`, or a creation site. */
|
|
42
|
-
name: string;
|
|
43
|
-
/** The value the cell currently holds. */
|
|
44
|
-
value: unknown;
|
|
45
|
-
/** What this node reads. */
|
|
46
|
-
dependencies: GraphNode[];
|
|
47
|
-
/** What reads this node. */
|
|
48
|
-
dependents: GraphNode[];
|
|
49
|
-
};
|
|
27
|
+
import { causeOf, cells, chain, inspect, queries, stack, timeline } from './queries.js';
|
|
28
|
+
export { cells, inspect, chain, path, causeOf, queries, stack, timeline } from './queries.js';
|
|
29
|
+
export type { NodeKind, Update, QueryEvent, GraphNode } from './types.js';
|
|
50
30
|
/**
|
|
51
|
-
*
|
|
31
|
+
* Registers something to be told when the graph has settled.
|
|
52
32
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
33
|
+
* Used by the panel to redraw itself. Exported because the panel is a separate
|
|
34
|
+
* module, not because an application should need it — and re-exported rather
|
|
35
|
+
* than wrapped so that the panel and an outside caller reach the same function.
|
|
36
|
+
* A wrapper here would be a line only the panel could have covered, and the
|
|
37
|
+
* panel does not come through this module.
|
|
57
38
|
*/
|
|
58
|
-
export
|
|
39
|
+
export { setWatcher as watch } from './record.js';
|
|
59
40
|
/** What `attach()` puts on `globalThis` for the browser console to use. */
|
|
60
41
|
export type Console = {
|
|
61
42
|
chain: typeof chain;
|
|
@@ -69,95 +50,15 @@ export type Console = {
|
|
|
69
50
|
/** Opens the panel, or shows it for a node you already have. */
|
|
70
51
|
panel: (node?: Node) => void;
|
|
71
52
|
};
|
|
72
|
-
/** Stops recording and forgets everything. Mostly for tests. */
|
|
73
|
-
export declare function detach(): void;
|
|
74
|
-
/** Every computed and effect currently alive, in owner order. */
|
|
75
|
-
export declare function cells(): GraphNode[];
|
|
76
|
-
/**
|
|
77
|
-
* What feeds a DOM node, all the way up to the signals.
|
|
78
|
-
*
|
|
79
|
-
* This is the question the panel exists for: the node is on the screen, the
|
|
80
|
-
* value is wrong, and what you want to know is where it came from.
|
|
81
|
-
*/
|
|
82
|
-
export declare function inspect(node: Node, depth?: number): GraphNode[];
|
|
83
|
-
/**
|
|
84
|
-
* The chain from a DOM node up to its sources, as text.
|
|
85
|
-
*
|
|
86
|
-
* ```
|
|
87
|
-
* order.status
|
|
88
|
-
* ↓
|
|
89
|
-
* computed(isEditable)
|
|
90
|
-
* ↓
|
|
91
|
-
* button.disabled
|
|
92
|
-
* ```
|
|
93
|
-
*
|
|
94
|
-
* Only the deepest path is drawn, because a chain is a story and a tree is
|
|
95
|
-
* not. `inspect` returns the whole shape for anything that wants it.
|
|
96
|
-
*/
|
|
97
|
-
export declare function chain(node: Node): string;
|
|
98
|
-
/**
|
|
99
|
-
* The longest path through a node's dependencies, sources first.
|
|
100
|
-
*
|
|
101
|
-
* One path rather than the whole tree, because a chain is a story: it is what
|
|
102
|
-
* `chain` prints and what the panel draws as boxes. `inspect` has the shape
|
|
103
|
-
* for anything that wants all of it.
|
|
104
|
-
*/
|
|
105
|
-
export declare function path(node: GraphNode): GraphNode[];
|
|
106
53
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* The graph does not keep this — nothing needs it once the flush is over — so
|
|
110
|
-
* it is the one fact devtools record rather than read.
|
|
111
|
-
*/
|
|
112
|
-
export declare function causeOf(node: Node): string | null;
|
|
113
|
-
/**
|
|
114
|
-
* What the resources have done, oldest first.
|
|
115
|
-
*
|
|
116
|
-
* The cache is the one part of the framework whose behaviour is not in the
|
|
117
|
-
* reactive graph: a tag match is a decision rather than an edge, and an
|
|
118
|
-
* invalidation that matched nothing looks exactly like one that was never
|
|
119
|
-
* sent. Bounded to the last 200 events, because a long session should not
|
|
120
|
-
* become a memory leak in a debugging tool.
|
|
121
|
-
*
|
|
122
|
-
* ```ts
|
|
123
|
-
* queries().filter((e) => e.event === 'invalidated');
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
export declare function queries(): QueryEvent[];
|
|
127
|
-
/**
|
|
128
|
-
* Registers something to be told when the graph has settled.
|
|
129
|
-
*
|
|
130
|
-
* Used by the panel to redraw itself. Exported because the panel is a separate
|
|
131
|
-
* module, not because an application should need it.
|
|
132
|
-
*/
|
|
133
|
-
export declare function watch(onSettled: (() => void) | null): void;
|
|
134
|
-
/**
|
|
135
|
-
* The component stack a DOM node's part lives in, outermost first.
|
|
136
|
-
*
|
|
137
|
-
* The owner tree already has the shape — a component's scope is the parent of
|
|
138
|
-
* everything its setup created — so this is a walk, not a recording. What the
|
|
139
|
-
* DOM layer contributes is the name, which only it knows and only at the
|
|
140
|
-
* moment an instance is created.
|
|
141
|
-
*
|
|
142
|
-
* ```ts
|
|
143
|
-
* stack(button); // ['App', 'OrderPage', 'SaveButton']
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
|
-
export declare function stack(node: Node): string[];
|
|
147
|
-
/**
|
|
148
|
-
* Every update, oldest first: what was written, and what ran because of it.
|
|
149
|
-
*
|
|
150
|
-
* The graph answers "what depends on this". This answers "what happened", in
|
|
151
|
-
* order and with a time — which is the question when something updated and
|
|
152
|
-
* nobody expected it to.
|
|
153
|
-
*
|
|
154
|
-
* ```ts
|
|
155
|
-
* timeline(); // everything
|
|
156
|
-
* timeline(button); // only the updates that ran this node's part
|
|
157
|
-
* ```
|
|
54
|
+
* Starts recording.
|
|
158
55
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
56
|
+
* Call it before the application creates anything — an import at the top of
|
|
57
|
+
* the entry module is the usual place — because a cell created earlier has no
|
|
58
|
+
* name to record. It stays in the graph; it is simply labelled by its
|
|
59
|
+
* creation site rather than by what it is called.
|
|
161
60
|
*/
|
|
162
|
-
export declare function
|
|
61
|
+
export declare function attach(): void;
|
|
62
|
+
/** Stops recording and forgets everything. Mostly for tests. */
|
|
63
|
+
export declare function detach(): void;
|
|
163
64
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE9F,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,aAAa,CAAC;AAKlD,2EAA2E;AAC3E,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,MAAM,EAAE,OAAO,MAAM,CAAC;IACtB,gEAAgE;IAChE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,MAAM,IAAI,IAAI,CAwC7B;AAqBD,gEAAgE;AAChE,wBAAgB,MAAM,IAAI,IAAI,CAO7B"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a,b,c,d,e,f,g,h,i,j,k}from"./chunk-
|
|
1
|
+
import{a as p,b as d,c as f,d as t,e as i,f as n,g as u,h as l,i as s,j as a,k as r}from"./chunk-B62UJE3O.js";var o=null;function N(){p()&&(o=e=>{e.ctrlKey&&e.shiftKey&&e.key.toLowerCase()==="f"&&(e.preventDefault(),y())},globalThis.addEventListener("keydown",o),console.info("[firsthand] devtools attached \u2014 press Ctrl+Shift+F for the panel, or call __FIRSTHAND__.panel()"),globalThis.__FIRSTHAND__={chain:n,inspect:i,causeOf:l,stack:a,timeline:r,cells:t,queries:s,detach:_,panel:e=>{h(e)}})}async function h(e){let c=await import("./chunk-UEYJUDK5.js");e===void 0?c.open():c.show(e)}async function y(){(await import("./chunk-UEYJUDK5.js")).toggle()}function _(){d(),delete globalThis.__FIRSTHAND__,o!==null&&(globalThis.removeEventListener("keydown",o),o=null)}export{N as attach,l as causeOf,t as cells,n as chain,_ as detach,i as inspect,u as path,s as queries,a as stack,r as timeline,f as watch};
|
package/dist/names.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to call a cell.
|
|
3
|
+
*
|
|
4
|
+
* Its own module because both halves need it and neither should import the
|
|
5
|
+
* other: the hook names a cell as it records an update, and the query API names
|
|
6
|
+
* it again when somebody asks what the graph looks like.
|
|
7
|
+
*/
|
|
8
|
+
import type { CellLike, NodeKind } from './types.js';
|
|
9
|
+
/** A readable name for a cell, whatever devtools managed to learn about it. */
|
|
10
|
+
export declare function nameOf(cell: CellLike): string;
|
|
11
|
+
export declare function kindOf(cell: CellLike): NodeKind;
|
|
12
|
+
//# sourceMappingURL=names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErD,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAY7C;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAK/C"}
|
package/dist/panel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"AAiFA;;;;;;GAMG;AACH,wBAAgB,IAAI,IAAI,IAAI,CA4B3B;AA0DD,oEAAoE;AACpE,wBAAgB,KAAK,IAAI,IAAI,CAgB5B;AAED,2DAA2D;AAC3D,wBAAgB,MAAM,IAAI,IAAI,CAM7B;AAED,yDAAyD;AACzD,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAKrC;AAED,wEAAwE;AACxE,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The questions devtools answer, and the only things a panel needs.
|
|
3
|
+
*
|
|
4
|
+
* The reactive graph is not something this package records. It is already
|
|
5
|
+
* there, because propagation and disposal need it: every cell carries its
|
|
6
|
+
* dependencies and its subscribers as linked lists, and every owner carries its
|
|
7
|
+
* children and its cells. These functions read that structure and put names on
|
|
8
|
+
* it, using what `record.ts` wrote down.
|
|
9
|
+
*
|
|
10
|
+
* `panel.ts` imports from here rather than from the entry module, which is what
|
|
11
|
+
* keeps the entry module free to load the panel on demand.
|
|
12
|
+
*/
|
|
13
|
+
import type { GraphNode, QueryEvent, Update } from './types.js';
|
|
14
|
+
/** Every computed and effect currently alive, in owner order. */
|
|
15
|
+
export declare function cells(): GraphNode[];
|
|
16
|
+
/**
|
|
17
|
+
* What feeds a DOM node, all the way up to the signals.
|
|
18
|
+
*
|
|
19
|
+
* This is the question the panel exists for: the node is on the screen, the
|
|
20
|
+
* value is wrong, and what you want to know is where it came from.
|
|
21
|
+
*/
|
|
22
|
+
export declare function inspect(node: Node, depth?: number): GraphNode[];
|
|
23
|
+
/**
|
|
24
|
+
* The chain from a DOM node up to its sources, as text.
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
* order.status
|
|
28
|
+
* ↓
|
|
29
|
+
* computed(isEditable)
|
|
30
|
+
* ↓
|
|
31
|
+
* button.disabled
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Only the deepest path is drawn, because a chain is a story and a tree is
|
|
35
|
+
* not. `inspect` returns the whole shape for anything that wants it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function chain(node: Node): string;
|
|
38
|
+
/**
|
|
39
|
+
* The longest path through a node's dependencies, sources first.
|
|
40
|
+
*
|
|
41
|
+
* One path rather than the whole tree, because a chain is a story: it is what
|
|
42
|
+
* `chain` prints and what the panel draws as boxes. `inspect` has the shape
|
|
43
|
+
* for anything that wants all of it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function path(node: GraphNode): GraphNode[];
|
|
46
|
+
/**
|
|
47
|
+
* Why an effect last ran: the dependency whose change scheduled it.
|
|
48
|
+
*
|
|
49
|
+
* The graph does not keep this — nothing needs it once the flush is over — so
|
|
50
|
+
* it is the one fact devtools record rather than read.
|
|
51
|
+
*/
|
|
52
|
+
export declare function causeOf(node: Node): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* What the resources have done, oldest first.
|
|
55
|
+
*
|
|
56
|
+
* The cache is the one part of the framework whose behaviour is not in the
|
|
57
|
+
* reactive graph: a tag match is a decision rather than an edge, and an
|
|
58
|
+
* invalidation that matched nothing looks exactly like one that was never
|
|
59
|
+
* sent. Bounded to the last 200 events, because a long session should not
|
|
60
|
+
* become a memory leak in a debugging tool.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* queries().filter((e) => e.event === 'invalidated');
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function queries(): QueryEvent[];
|
|
67
|
+
/**
|
|
68
|
+
* The component stack a DOM node's part lives in, outermost first.
|
|
69
|
+
*
|
|
70
|
+
* The owner tree already has the shape — a component's scope is the parent of
|
|
71
|
+
* everything its setup created — so this is a walk, not a recording. What the
|
|
72
|
+
* DOM layer contributes is the name, which only it knows and only at the
|
|
73
|
+
* moment an instance is created.
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* stack(button); // ['App', 'OrderPage', 'SaveButton']
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function stack(node: Node): string[];
|
|
80
|
+
/**
|
|
81
|
+
* Every update, oldest first: what was written, and what ran because of it.
|
|
82
|
+
*
|
|
83
|
+
* The graph answers "what depends on this". This answers "what happened", in
|
|
84
|
+
* order and with a time — which is the question when something updated and
|
|
85
|
+
* nobody expected it to.
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* timeline(); // everything
|
|
89
|
+
* timeline(button); // only the updates that ran this node's part
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* The last 100 updates, so that a page left open overnight is still a
|
|
93
|
+
* debugging tool rather than a leak.
|
|
94
|
+
*/
|
|
95
|
+
export declare function timeline(node?: Node): Update[];
|
|
96
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAY,SAAS,EAAa,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AA0CrF,iEAAiE;AACjE,wBAAgB,KAAK,IAAI,SAAS,EAAE,CAMnC;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,SAAI,GAAG,SAAS,EAAE,CAM1D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAMxC;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,EAAE,CASjD;AAMD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAYjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,IAAI,UAAU,EAAE,CAEtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAc1C;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,MAAM,EAAE,CAW9C"}
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hook the core calls, and the only thing in this package that writes.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here runs unless `install()` is called, and none of it exists in a
|
|
5
|
+
* production build of the framework: the hooks that feed it live in modules the
|
|
6
|
+
* build replaces with empty functions (ADR-0020).
|
|
7
|
+
*
|
|
8
|
+
* It knows how to record and nothing about how anyone reads it back. That is
|
|
9
|
+
* what lets `queries.ts` sit beside it rather than above it, and it is why the
|
|
10
|
+
* panel can be loaded without loading this.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Installs the hook, unless one is already installed.
|
|
14
|
+
*
|
|
15
|
+
* Returns whether it did, so the caller knows whether the rest of attaching —
|
|
16
|
+
* the shortcut, the console API — still has to happen.
|
|
17
|
+
*/
|
|
18
|
+
export declare function install(): boolean;
|
|
19
|
+
/** Stops recording and forgets everything. */
|
|
20
|
+
export declare function uninstall(): void;
|
|
21
|
+
/** Who to tell when the graph has settled. The panel is the only caller. */
|
|
22
|
+
export declare function setWatcher(onSettled: (() => void) | null): void;
|
|
23
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuFH;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,OAAO,CAyBjC;AAwDD,8CAA8C;AAC9C,wBAAgB,SAAS,IAAI,IAAI,CAWhC;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE/D"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything devtools have written down, and nothing that writes it.
|
|
3
|
+
*
|
|
4
|
+
* One module holding the data so that the two halves above it — the hook that
|
|
5
|
+
* records and the API that reads — can both reach it without reaching each
|
|
6
|
+
* other. Kept as one object rather than eight exports because it is one thing:
|
|
7
|
+
* what this session has observed.
|
|
8
|
+
*
|
|
9
|
+
* All of it is bounded or weak. A tool that made a page leak while you debugged
|
|
10
|
+
* it would be worse than no tool.
|
|
11
|
+
*/
|
|
12
|
+
import type { CellLike, Label, OwnerLike, QueryEvent, Update } from './types.js';
|
|
13
|
+
/** `MUTABLE`, from the core's flags: distinguishes a computed from a signal. */
|
|
14
|
+
export declare const MUTABLE: number;
|
|
15
|
+
/** Bounded: a long session would otherwise keep every fetch it ever made. */
|
|
16
|
+
export declare const LOG_LIMIT = 200;
|
|
17
|
+
/** Bounded, for the same reason the cache log is. */
|
|
18
|
+
export declare const UPDATE_LIMIT = 100;
|
|
19
|
+
export declare const recorded: {
|
|
20
|
+
labels: WeakMap<object, Label>;
|
|
21
|
+
/** Which node and property each effect writes, learned while it runs. */
|
|
22
|
+
parts: WeakMap<object, {
|
|
23
|
+
node: object;
|
|
24
|
+
property: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** The effect a DOM node is written by, for looking a chain up from the page. */
|
|
27
|
+
writers: WeakMap<object, Set<object>>;
|
|
28
|
+
/** Why each effect last ran. */
|
|
29
|
+
causes: WeakMap<object, object>;
|
|
30
|
+
/** The component each scope belongs to, as the DOM layer reported it. */
|
|
31
|
+
components: WeakMap<object, string>;
|
|
32
|
+
/** What the resources have done, newest last. */
|
|
33
|
+
cacheLog: QueryEvent[];
|
|
34
|
+
/**
|
|
35
|
+
* Every update, newest last.
|
|
36
|
+
*
|
|
37
|
+
* The effects are kept beside the update rather than inside it: two parts
|
|
38
|
+
* writing two paragraphs are both called `p.text`, so asking "which updates
|
|
39
|
+
* ran *this* node" has to compare identities, not names.
|
|
40
|
+
*/
|
|
41
|
+
updates: {
|
|
42
|
+
update: Update;
|
|
43
|
+
effects: object[];
|
|
44
|
+
}[];
|
|
45
|
+
roots: Set<WeakRef<OwnerLike>>;
|
|
46
|
+
};
|
|
47
|
+
/** Forgets everything. The weak maps need no clearing: nothing holds them. */
|
|
48
|
+
export declare function forget(): void;
|
|
49
|
+
/** Appends to a log that must not grow without bound. */
|
|
50
|
+
export declare function append<T>(log: T[], entry: T, limit: number): void;
|
|
51
|
+
/** Every live root, with the dead references swept out on the way past. */
|
|
52
|
+
export declare function liveRoots(): OwnerLike[];
|
|
53
|
+
export type { CellLike };
|
|
54
|
+
//# sourceMappingURL=recorded.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorded.d.ts","sourceRoot":"","sources":["../src/recorded.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEjF,gFAAgF;AAChF,eAAO,MAAM,OAAO,QAAS,CAAC;AAE9B,6EAA6E;AAC7E,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,qDAAqD;AACrD,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,eAAO,MAAM,QAAQ;;IAEnB,yEAAyE;;cACtC,MAAM;kBAAY,MAAM;;IAC3D,iFAAiF;;IAEjF,gCAAgC;;IAEhC,yEAAyE;;IAEzE,iDAAiD;cACjC,UAAU,EAAE;IAC5B;;;;;;OAMG;aACY;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE;;CAEvD,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,MAAM,IAAI,IAAI,CAI7B;AAED,yDAAyD;AACzD,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAKjE;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,IAAI,SAAS,EAAE,CAWvC;AAED,YAAY,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/style.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the panel looks.
|
|
3
|
+
*
|
|
4
|
+
* Its own module because it changes for its own reasons — a colour, a width,
|
|
5
|
+
* a state nobody could see — and because ninety lines of CSS in the middle of
|
|
6
|
+
* the code that builds the panel makes both harder to read.
|
|
7
|
+
*
|
|
8
|
+
* `all: initial` at the top is the point of the shadow root: the page's
|
|
9
|
+
* stylesheet cannot reach in, and this cannot reach out. An inspector that
|
|
10
|
+
* changes what it is inspecting is worse than no inspector.
|
|
11
|
+
*/
|
|
12
|
+
export declare const STYLE = ":host { all: initial; }\n.panel {\n position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;\n width: 460px; max-height: 78vh; display: flex; flex-direction: column;\n font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #e6e6e6; background: #1c1c1f; border: 1px solid #3a3a40;\n border-radius: 10px; box-shadow: 0 10px 40px rgb(0 0 0 / 0.45);\n}\nheader { display: flex; align-items: center; gap: 6px; padding: 8px 10px;\n border-bottom: 1px solid #3a3a40; }\nheader strong { font-weight: 600; letter-spacing: 0.02em; flex: 1; }\nbutton { font: inherit; color: inherit; background: #2a2a30; border: 1px solid #45454d;\n border-radius: 5px; padding: 3px 8px; cursor: pointer; }\nbutton:hover { background: #34343c; }\nbutton[aria-pressed='true'] { background: #3d5afe; border-color: #3d5afe; color: #fff; }\n.body { display: flex; flex-direction: column; overflow: hidden; padding: 12px; min-height: 0; }\n/* The list scrolls; the detail stays where it can be read. Without this the\n call stack sits below everything and is only reachable by scrolling past\n the whole log \u2014 which is exactly when you least want to. */\n/* Two independent scroll areas: the list above, the detail below. Each gets\n its own, because a call stack that can only be reached by scrolling past\n forty rows of log is out of reach exactly when it is wanted. */\n.scroll { overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; min-height: 60px; }\n.pinned { flex: 0 1 auto; min-height: 140px; max-height: 65%; overflow-y: auto;\n overflow-x: hidden; margin-top: 8px; padding-top: 8px;\n border-top: 1px solid #3a3a40; }\n.detail-head { display: flex; align-items: center; gap: 8px; }\n.detail-head .hint { margin: 0; flex: 1; }\n.detail-head button { padding: 0 6px; line-height: 1.4; }\n.empty { color: #8a8a94; }\n.hint { color: #7c7c88; margin: 14px 0 6px; font-size: 10px; text-transform: uppercase;\n letter-spacing: 0.1em; }\n.hint:first-child { margin-top: 0; }\n.section + .section { margin-top: 2px; }\n.stack { margin: 4px 0 0; }\n.stack div { color: #9a9aa6; padding-left: 10px; border-left: 1px solid #3a3a40; }\n.stack div:first-child { color: #e6e6e6; }\n.filters { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }\n.filters .chip { background: #2a2a30; border: 1px solid #45454d; border-radius: 999px;\n padding: 1px 9px; cursor: pointer; color: #9ecbff; }\n.filters .chip[aria-pressed='true'] { background: #3d5afe; border-color: #3d5afe; color: #fff; }\n.count { color: #7c7c88; }\n\n/* The path, as boxes and arrows rather than as three lines of text. */\n.flow { display: flex; flex-direction: column; align-items: stretch; gap: 0; }\n.box { border: 1px solid #45454d; border-radius: 7px; padding: 6px 9px; background: #232329;\n display: flex; align-items: baseline; gap: 8px; }\n.box .tag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em;\n padding: 1px 5px; border-radius: 4px; background: #34343c; color: #b9b9c4; }\n.box .label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.box .val { color: #c3e88d; }\n.box.signal { border-color: #3d5afe; }\n.box.signal .tag { background: #23306b; color: #b6c4ff; }\n.box.computed { border-color: #c792ea; }\n.box.computed .tag { background: #3a2b47; color: #e6c8ff; }\n.box.part { border-color: #ffcb6b; }\n.box.part .tag { background: #4a3a1c; color: #ffdfa1; }\n.box.trigger { box-shadow: 0 0 0 2px #3d5afe66; }\n.arrow { align-self: center; color: #6a6a76; font-size: 14px; line-height: 1; padding: 3px 0; }\n\n/* The component stack, as crumbs. */\n.crumbs { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 10px; }\n.crumb { background: #2a2a30; border: 1px solid #45454d; border-radius: 999px;\n padding: 1px 8px; color: #c792ea; }\n.crumb + .crumb::before { content: '\u203A'; color: #8a8a94; margin-right: 6px; margin-left: -4px; }\n\n/* The timeline: one row per update, a bar for how much it woke. */\n.track { display: flex; flex-direction: column; gap: 4px; }\n.tick { display: grid; grid-template-columns: 52px minmax(0, 1fr) auto auto; gap: 8px;\n align-items: center; padding: 3px 4px; border-radius: 5px; cursor: pointer; }\n.tick:hover { background: #26262c; }\n.tick[aria-selected='true'] { background: #23306b; }\n.tick .when { color: #8a8a94; text-align: right; }\n.tick .who { color: #9ecbff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.tick .where { color: #7c7c88; white-space: nowrap; }\n.tick .bar { height: 8px; border-radius: 4px; background: #3d5afe; min-width: 4px; }\n.tick .bar.none { background: #4a4a54; }\n.detail .ran { color: #ffdfa1; }\n.cause { color: #ffcb6b; margin: 10px 0 0; }\n.event { display: grid; grid-template-columns: 84px 1fr; gap: 8px; padding: 2px 0; }\n.event .created { color: #c3e88d; }\n.event .invalidated { color: #ffcb6b; }\n.event .dropped { color: #f07178; }\n.event .tags { color: #9ecbff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }";
|
|
13
|
+
/** The element the picker is over, highlighted without touching its styles. */
|
|
14
|
+
export declare const OUTLINE = "firsthand-devtools-outline";
|
|
15
|
+
/** The one rule that has to live in the page, because the outline is on it. */
|
|
16
|
+
export declare const OUTLINE_STYLE = ".firsthand-devtools-outline { outline: 2px solid #3d5afe !important; outline-offset: 1px; }";
|
|
17
|
+
//# sourceMappingURL=style.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../src/style.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,KAAK,y6JAmF+E,CAAC;AAElG,+EAA+E;AAC/E,eAAO,MAAM,OAAO,+BAA+B,CAAC;AAEpD,+EAA+E;AAC/E,eAAO,MAAM,aAAa,gGAAgF,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What devtools say a graph is.
|
|
3
|
+
*
|
|
4
|
+
* The public shapes an application or a panel reads, and the internal ones
|
|
5
|
+
* describing what the core actually built. The internal three are read and
|
|
6
|
+
* never written: they are this package's view of another package's private
|
|
7
|
+
* structure, written down here so that the reading is in one place and the
|
|
8
|
+
* assumption is visible.
|
|
9
|
+
*/
|
|
10
|
+
/** What a node of the graph is. */
|
|
11
|
+
export type NodeKind = 'signal' | 'computed' | 'effect' | 'part';
|
|
12
|
+
/**
|
|
13
|
+
* One update: a write, and everything that ran because of it.
|
|
14
|
+
*
|
|
15
|
+
* This is the thing the graph cannot answer on its own. The graph says what
|
|
16
|
+
* depends on what; an update says what actually happened, in order, at a time.
|
|
17
|
+
*/
|
|
18
|
+
export type Update = {
|
|
19
|
+
/** Milliseconds since the page loaded, so entries can be read as a sequence. */
|
|
20
|
+
at: number;
|
|
21
|
+
/** What was written. */
|
|
22
|
+
source: string;
|
|
23
|
+
/** What ran, in the order it ran. */
|
|
24
|
+
ran: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Where the write came from, application frames only.
|
|
27
|
+
*
|
|
28
|
+
* "Which signal changed" is half an answer; the other half is which of your
|
|
29
|
+
* code changed it, and an event handler three files away is exactly the case
|
|
30
|
+
* where the graph cannot help.
|
|
31
|
+
*
|
|
32
|
+
* Kept exactly as the engine gave them, positions in the *compiled* module
|
|
33
|
+
* and all — browsers do not apply source maps to `error.stack`. The panel
|
|
34
|
+
* resolves them through the module's own map before showing them; anything
|
|
35
|
+
* else reading this should do the same.
|
|
36
|
+
*/
|
|
37
|
+
stack: string[];
|
|
38
|
+
};
|
|
39
|
+
/** Something a resource did. */
|
|
40
|
+
export type QueryEvent = {
|
|
41
|
+
event: 'created' | 'invalidated' | 'dropped';
|
|
42
|
+
/** The cache key: its tags and variables, as the client derived them. */
|
|
43
|
+
key: string;
|
|
44
|
+
/** The tags the entry carries, which is what an invalidation matches on. */
|
|
45
|
+
tags: readonly string[];
|
|
46
|
+
};
|
|
47
|
+
/** One node of the graph, as devtools describe it. */
|
|
48
|
+
export type GraphNode = {
|
|
49
|
+
kind: NodeKind;
|
|
50
|
+
/** `order.status`, `isEditable`, `button.disabled`, or a creation site. */
|
|
51
|
+
name: string;
|
|
52
|
+
/** The value the cell currently holds. */
|
|
53
|
+
value: unknown;
|
|
54
|
+
/** What this node reads. */
|
|
55
|
+
dependencies: GraphNode[];
|
|
56
|
+
/** What reads this node. */
|
|
57
|
+
dependents: GraphNode[];
|
|
58
|
+
};
|
|
59
|
+
/** Internal shape of a cell, as the core builds it. Read, never written. */
|
|
60
|
+
export type CellLike = {
|
|
61
|
+
flags: number;
|
|
62
|
+
v: unknown;
|
|
63
|
+
deps?: LinkLike;
|
|
64
|
+
subs?: LinkLike;
|
|
65
|
+
/** An effect's own scope, which is where the component stack starts. */
|
|
66
|
+
scope?: OwnerLike;
|
|
67
|
+
};
|
|
68
|
+
export type LinkLike = {
|
|
69
|
+
dep: CellLike;
|
|
70
|
+
sub: CellLike;
|
|
71
|
+
nextDep?: LinkLike;
|
|
72
|
+
nextSub?: LinkLike;
|
|
73
|
+
};
|
|
74
|
+
export type OwnerLike = {
|
|
75
|
+
parent: OwnerLike | null;
|
|
76
|
+
head: OwnerLike | null;
|
|
77
|
+
next: OwnerLike | null;
|
|
78
|
+
cells: CellLike[] | null;
|
|
79
|
+
};
|
|
80
|
+
export type Label = {
|
|
81
|
+
kind: NodeKind;
|
|
82
|
+
name: string;
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,mCAAmC;AACnC,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG;IACnB,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAC;IACX,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,GAAG,EAAE,MAAM,EAAE,CAAC;IACd;;;;;;;;;;;OAWG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,gCAAgC;AAChC,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;IAC7C,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,QAAQ,CAAC;IACf,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,KAAK,EAAE,OAAO,CAAC;IACf,4BAA4B;IAC5B,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,4BAA4B;IAC5B,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,OAAO,CAAC;IACX,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,QAAQ,CAAC;IACd,GAAG,EAAE,QAAQ,CAAC;IACd,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
|
package/dist/views.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three tabs, and the one piece of state they share.
|
|
3
|
+
*
|
|
4
|
+
* A view knows how to draw an answer and nothing about the panel around it:
|
|
5
|
+
* where a click has to redraw, the panel passes a function in. That is what
|
|
6
|
+
* lets `panel.ts` own the shell without either module importing the other.
|
|
7
|
+
*
|
|
8
|
+
* `chosen` is shared because both the graph and the timeline show the update
|
|
9
|
+
* list, and an entry opened in one is the same entry in the other.
|
|
10
|
+
*/
|
|
11
|
+
/** Whether an entry is open, which is when a redraw would move it. */
|
|
12
|
+
export declare function hasChosen(): boolean;
|
|
13
|
+
/** Forgets what was open and what was filtered. The panel calls it on close. */
|
|
14
|
+
export declare function resetViews(): void;
|
|
15
|
+
export declare function renderGraph(host: HTMLElement, selected: Node | null): void;
|
|
16
|
+
/**
|
|
17
|
+
* Every update, with a chip per source to narrow it.
|
|
18
|
+
*
|
|
19
|
+
* `redraw` rather than a call back into the panel: a filter changes what the
|
|
20
|
+
* whole panel shows, and the view does not know what else is on the screen.
|
|
21
|
+
*/
|
|
22
|
+
export declare function renderTimeline(host: HTMLElement, redraw: () => void): void;
|
|
23
|
+
export declare function renderQueries(host: HTMLElement): void;
|
|
24
|
+
//# sourceMappingURL=views.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"views.d.ts","sourceRoot":"","sources":["../src/views.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAeH,sEAAsE;AACtE,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED,gFAAgF;AAChF,wBAAgB,UAAU,IAAI,IAAI,CAIjC;AAqCD,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAqD1E;AA0FD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAkC1E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAgBrD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The small pieces every view is built from.
|
|
3
|
+
*
|
|
4
|
+
* Plain DOM, deliberately: this runs inside the page it is inspecting, so it
|
|
5
|
+
* must not create parts, own scopes or appear in the graph it is showing.
|
|
6
|
+
* Rendering it with the framework would put the inspector into its own results.
|
|
7
|
+
*/
|
|
8
|
+
export declare function button(label: string, onClick: () => void): HTMLButtonElement;
|
|
9
|
+
export declare function text(tag: string, className: string, content: string): HTMLElement;
|
|
10
|
+
/** Only the position of a frame: `main.tsx:12:11`, without who was running. */
|
|
11
|
+
export declare function position(frame: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Anything that carries a position, shown at once and corrected after.
|
|
14
|
+
*
|
|
15
|
+
* Two things here name a place: a stack frame, and a cell the compiler did not
|
|
16
|
+
* name, which is labelled by where it was created. Both come from the engine
|
|
17
|
+
* and both say where the *compiled* module has it, which is not a line anyone
|
|
18
|
+
* wrote. Resolving needs the module and its map, so it cannot happen while the
|
|
19
|
+
* write does — the position appears immediately and is replaced when the
|
|
20
|
+
* answer arrives.
|
|
21
|
+
*
|
|
22
|
+
* A name with no position in it — `h1.text`, or `v (main.tsx:9)`, which the
|
|
23
|
+
* compiler took from the source in the first place — passes through untouched.
|
|
24
|
+
*/
|
|
25
|
+
export declare function located(tag: string, className: string, name: string, format?: (shown: string) => string): HTMLElement;
|
|
26
|
+
/** Keeps a detail in view while the list above it scrolls. */
|
|
27
|
+
export declare function pin(detail: HTMLElement): HTMLElement;
|
|
28
|
+
//# sourceMappingURL=widgets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widgets.d.ts","sourceRoot":"","sources":["../src/widgets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,iBAAiB,CAM5E;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAKjF;AA0BD,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAyB,GACnD,WAAW,CAMb;AAED,8DAA8D;AAC9D,wBAAgB,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAGpD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firsthandjs/devtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Experimental devtools for Firsthand: see what updates what, why it ran, and when.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@firsthandjs/core": "0.
|
|
22
|
+
"@firsthandjs/core": "0.10.0"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=20.11.0"
|
package/dist/chunk-7MQSJYVB.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
var m=new WeakMap,L=new WeakMap,i=new WeakMap,N=new WeakMap,v=new WeakMap,d=[],r=[],T=100,l=null,j=200,p=new Set,a=null,b=null,u=null,f=null,c=null,w=1,_=/@firsthandjs|[\\/]node_modules[\\/]|[\\/]packages[\\/](core|dom|deep|devtools|jsx-runtime|data|router|styled|testing)[\\/]src[\\/]/;function D(){let t=(new Error().stack??"").split(`
|
|
2
|
-
`).slice(1),e=[];for(let n of t){if(_.test(n))continue;let o=n.trim().replace(/^at\s+/,"");if(o!==""&&e.push(o),e.length===6)break}return e}function E(){let e=(new Error().stack??"").split(`
|
|
3
|
-
`).slice(1).find(o=>!_.test(o))??"unknown",n=/\(?([^()\s]+:\d+:\d+)\)?$/.exec(e.trim());return n===null?e.trim():n[1]}function U(){if(c!==null)return;let t={attached:!0,label(e,n,o){m.set(e,{kind:n,name:o===""?E():o})},cause(e){f=e,l={update:{at:Math.round(performance.now()),source:h(e),ran:[],stack:D()},effects:[]},r.push(l),r.length>T&&r.shift()},root(e){p.add(new WeakRef(e))},component(e,n){v.set(e,n)},running(e){a=e,e===null&&b?.(),e!==null&&f!==null&&(N.set(e,f),l?.update.ran.push(h(e)),l?.effects.push(e))},query(e,n,o){d.push({event:e,key:n,tags:o}),d.length>j&&d.shift()},part(e,n){if(a===null)return;L.set(a,{node:e,property:n});let o=i.get(e);o===void 0&&(o=new Set,i.set(e,o)),o.add(a)}};globalThis.__FIRSTHAND_DEVTOOLS__=t,c=t,u=e=>{e.ctrlKey&&e.shiftKey&&e.key.toLowerCase()==="f"&&(e.preventDefault(),import("./chunk-BCXJOY33.js").then(n=>{n.toggle()}))},globalThis.addEventListener("keydown",u),console.info("[firsthand] devtools attached \u2014 press Ctrl+Shift+F for the panel, or call __FIRSTHAND__.panel()"),globalThis.__FIRSTHAND__={chain:A,inspect:C,causeOf:K,stack:F,timeline:W,cells:I,queries:M,detach:S,panel:e=>{import("./chunk-BCXJOY33.js").then(n=>{e===void 0?n.open():n.show(e)})}}}function S(){c!==null&&(c.attached=!1),globalThis.__FIRSTHAND_DEVTOOLS__=void 0,delete globalThis.__FIRSTHAND__,u!==null&&(globalThis.removeEventListener("keydown",u),u=null),c=null,a=null,f=null,l=null,r.length=0,b=null,d.length=0,p.clear()}function h(t){let e=L.get(t);if(e!==void 0){let o=e.node;return`${(o.tagName??o.nodeName??"node").toLowerCase()}.${e.property}`}let n=m.get(t);return n===void 0?(t.flags&w)===0?"signal":"computed":n.name}function G(t){return L.has(t)?"part":m.get(t)?.kind??((t.flags&w)===0?"signal":"computed")}function g(t,e,n){let o={kind:G(t),name:h(t),value:t.v,dependencies:[],dependents:[]};if(e>0)for(let s=t.deps;s!==void 0;s=s.nextDep)o.dependencies.push(g(s.dep,e-1,0));if(n>0)for(let s=t.subs;s!==void 0;s=s.nextSub)o.dependents.push(g(s.sub,0,n-1));return o}function R(){let t=[];for(let e of p){let n=e.deref();n===void 0?p.delete(e):t.push(n)}return t}function x(t,e){t.cells!==null&&e.push(...t.cells);for(let n=t.head;n!==null;n=n.next)x(n,e)}function I(){let t=[];for(let e of R())x(e,t);return t.map(e=>g(e,1,1))}function C(t,e=8){let n=i.get(t);return n===void 0?[]:[...n].map(o=>g(o,e,0))}function A(t){let e=C(t);return e.length===0?"Nothing reactive writes this node.":e.map(n=>H(O(n)).join(`
|
|
4
|
-
\u2193
|
|
5
|
-
`)).join(`
|
|
6
|
-
|
|
7
|
-
`)}function O(t){let e=[];for(let n of t.dependencies){let o=O(n);o.length>e.length&&(e=o)}return[...e,t]}function H(t){return t.map(e=>e.kind==="computed"?`computed(${e.name})`:e.name)}function K(t){let e=i.get(t);if(e===void 0)return null;for(let n of e){let o=N.get(n);if(o!==void 0)return h(o)}return null}function M(){return[...d]}function q(t){b=t}function F(t){let e=i.get(t);if(e===void 0)return[];let n=[];for(let o of e){let s=o.scope??null,k=[];for(;s!==null;){let y=v.get(s);y!==void 0&&k.unshift(y),s=s.parent}k.length>n.length&&(n.length=0,n.push(...k))}return n}function W(t){if(t===void 0)return r.map(n=>n.update);let e=i.get(t);return e===void 0?[]:r.filter(n=>n.effects.some(o=>e.has(o))).map(n=>n.update)}export{U as a,S as b,I as c,C as d,A as e,O as f,K as g,M as h,q as i,F as j,W as k};
|
package/dist/chunk-BCXJOY33.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import{d as $,f as H,g as q,h as C,i as L,j as A,k as T}from"./chunk-7MQSJYVB.js";var J="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function Q(e){let t=[],r=0,n=0;for(let o of e){let a=J.indexOf(o),i=a&32;if(n+=(a&31)<<r,i===0){let s=n&1;n>>=1,t.push(s===1?-n:n),r=0,n=0}else r+=5}return t}function W(e){let t=[],r=0,n=0,o=0;for(let[a,i]of e.mappings.split(";").entries()){let s=0;for(let u of i.split(",")){if(u==="")continue;let p=Q(u);if(s+=p[0],p.length<4)continue;r+=p[1],n+=p[2],o+=p[3];let m=e.sources[r];m!=null&&t.push({generatedLine:a,generatedColumn:s,source:m,originalLine:n,originalColumn:o})}}return t}var P=new Map,B=/\/\/[#@]\s*sourceMappingURL=data:application\/json;(?:charset=[^;]+;)?base64,([\w+/=]+)/;async function D(e){let t=P.get(e);if(t!==void 0)return t;let r=(async()=>{try{let o=await(await fetch(e)).text(),a=B.exec(o);if(a===null)return null;let i=JSON.parse(atob(a[1]));return W(i)}catch{return null}})();return P.set(e,r),r}function Y(e,t,r){let n=null;for(let o of e){if(o.generatedLine>t||o.generatedLine===t&&o.generatedColumn>r)break;o.generatedLine===t&&(n=o)}return n}async function O(e){let t=/^(.*?)\(?([^\s()]+):(\d+):(\d+)\)?$/.exec(e.trim().replace(/^at\s+/,""));if(t===null)return e;let[,r,n,o,a]=t;if(!/^https?:\/\//.test(n))return e;let i=await D(n);if(i===null)return e;let s=Y(i,Number(o)-1,Number(a)-1);if(s===null)return e;let p=`${s.source.slice(Math.max(s.source.lastIndexOf("/"),s.source.lastIndexOf("\\"))+1)}:${String(s.originalLine+1)}:${String(s.originalColumn+1)}`;return r.trim()===""?p:`${r.trim()} (${p})`}var K=`
|
|
2
|
-
:host { all: initial; }
|
|
3
|
-
.panel {
|
|
4
|
-
position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;
|
|
5
|
-
width: 460px; max-height: 78vh; display: flex; flex-direction: column;
|
|
6
|
-
font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
7
|
-
color: #e6e6e6; background: #1c1c1f; border: 1px solid #3a3a40;
|
|
8
|
-
border-radius: 10px; box-shadow: 0 10px 40px rgb(0 0 0 / 0.45);
|
|
9
|
-
}
|
|
10
|
-
header { display: flex; align-items: center; gap: 6px; padding: 8px 10px;
|
|
11
|
-
border-bottom: 1px solid #3a3a40; }
|
|
12
|
-
header strong { font-weight: 600; letter-spacing: 0.02em; flex: 1; }
|
|
13
|
-
button { font: inherit; color: inherit; background: #2a2a30; border: 1px solid #45454d;
|
|
14
|
-
border-radius: 5px; padding: 3px 8px; cursor: pointer; }
|
|
15
|
-
button:hover { background: #34343c; }
|
|
16
|
-
button[aria-pressed='true'] { background: #3d5afe; border-color: #3d5afe; color: #fff; }
|
|
17
|
-
.body { display: flex; flex-direction: column; overflow: hidden; padding: 12px; min-height: 0; }
|
|
18
|
-
/* The list scrolls; the detail stays where it can be read. Without this the
|
|
19
|
-
call stack sits below everything and is only reachable by scrolling past
|
|
20
|
-
the whole log \u2014 which is exactly when you least want to. */
|
|
21
|
-
/* Two independent scroll areas: the list above, the detail below. Each gets
|
|
22
|
-
its own, because a call stack that can only be reached by scrolling past
|
|
23
|
-
forty rows of log is out of reach exactly when it is wanted. */
|
|
24
|
-
.scroll { overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; min-height: 60px; }
|
|
25
|
-
.pinned { flex: 0 1 auto; min-height: 140px; max-height: 65%; overflow-y: auto;
|
|
26
|
-
overflow-x: hidden; margin-top: 8px; padding-top: 8px;
|
|
27
|
-
border-top: 1px solid #3a3a40; }
|
|
28
|
-
.detail-head { display: flex; align-items: center; gap: 8px; }
|
|
29
|
-
.detail-head .hint { margin: 0; flex: 1; }
|
|
30
|
-
.detail-head button { padding: 0 6px; line-height: 1.4; }
|
|
31
|
-
.empty { color: #8a8a94; }
|
|
32
|
-
.hint { color: #7c7c88; margin: 14px 0 6px; font-size: 10px; text-transform: uppercase;
|
|
33
|
-
letter-spacing: 0.1em; }
|
|
34
|
-
.hint:first-child { margin-top: 0; }
|
|
35
|
-
.section + .section { margin-top: 2px; }
|
|
36
|
-
.stack { margin: 4px 0 0; }
|
|
37
|
-
.stack div { color: #9a9aa6; padding-left: 10px; border-left: 1px solid #3a3a40; }
|
|
38
|
-
.stack div:first-child { color: #e6e6e6; }
|
|
39
|
-
.filters { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
|
40
|
-
.filters .chip { background: #2a2a30; border: 1px solid #45454d; border-radius: 999px;
|
|
41
|
-
padding: 1px 9px; cursor: pointer; color: #9ecbff; }
|
|
42
|
-
.filters .chip[aria-pressed='true'] { background: #3d5afe; border-color: #3d5afe; color: #fff; }
|
|
43
|
-
.count { color: #7c7c88; }
|
|
44
|
-
|
|
45
|
-
/* The path, as boxes and arrows rather than as three lines of text. */
|
|
46
|
-
.flow { display: flex; flex-direction: column; align-items: stretch; gap: 0; }
|
|
47
|
-
.box { border: 1px solid #45454d; border-radius: 7px; padding: 6px 9px; background: #232329;
|
|
48
|
-
display: flex; align-items: baseline; gap: 8px; }
|
|
49
|
-
.box .tag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em;
|
|
50
|
-
padding: 1px 5px; border-radius: 4px; background: #34343c; color: #b9b9c4; }
|
|
51
|
-
.box .label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
52
|
-
.box .val { color: #c3e88d; }
|
|
53
|
-
.box.signal { border-color: #3d5afe; }
|
|
54
|
-
.box.signal .tag { background: #23306b; color: #b6c4ff; }
|
|
55
|
-
.box.computed { border-color: #c792ea; }
|
|
56
|
-
.box.computed .tag { background: #3a2b47; color: #e6c8ff; }
|
|
57
|
-
.box.part { border-color: #ffcb6b; }
|
|
58
|
-
.box.part .tag { background: #4a3a1c; color: #ffdfa1; }
|
|
59
|
-
.box.trigger { box-shadow: 0 0 0 2px #3d5afe66; }
|
|
60
|
-
.arrow { align-self: center; color: #6a6a76; font-size: 14px; line-height: 1; padding: 3px 0; }
|
|
61
|
-
|
|
62
|
-
/* The component stack, as crumbs. */
|
|
63
|
-
.crumbs { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 10px; }
|
|
64
|
-
.crumb { background: #2a2a30; border: 1px solid #45454d; border-radius: 999px;
|
|
65
|
-
padding: 1px 8px; color: #c792ea; }
|
|
66
|
-
.crumb + .crumb::before { content: '\u203A'; color: #8a8a94; margin-right: 6px; margin-left: -4px; }
|
|
67
|
-
|
|
68
|
-
/* The timeline: one row per update, a bar for how much it woke. */
|
|
69
|
-
.track { display: flex; flex-direction: column; gap: 4px; }
|
|
70
|
-
.tick { display: grid; grid-template-columns: 52px minmax(0, 1fr) auto auto; gap: 8px;
|
|
71
|
-
align-items: center; padding: 3px 4px; border-radius: 5px; cursor: pointer; }
|
|
72
|
-
.tick:hover { background: #26262c; }
|
|
73
|
-
.tick[aria-selected='true'] { background: #23306b; }
|
|
74
|
-
.tick .when { color: #8a8a94; text-align: right; }
|
|
75
|
-
.tick .who { color: #9ecbff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
76
|
-
.tick .where { color: #7c7c88; white-space: nowrap; }
|
|
77
|
-
.tick .bar { height: 8px; border-radius: 4px; background: #3d5afe; min-width: 4px; }
|
|
78
|
-
.tick .bar.none { background: #4a4a54; }
|
|
79
|
-
.detail .ran { color: #ffdfa1; }
|
|
80
|
-
.cause { color: #ffcb6b; margin: 10px 0 0; }
|
|
81
|
-
.event { display: grid; grid-template-columns: 84px 1fr; gap: 8px; padding: 2px 0; }
|
|
82
|
-
.event .created { color: #c3e88d; }
|
|
83
|
-
.event .invalidated { color: #ffcb6b; }
|
|
84
|
-
.event .dropped { color: #f07178; }
|
|
85
|
-
.event .tags { color: #9ecbff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
86
|
-
`,b="firsthand-devtools-outline",d=null,E=null,h=null,v=!1,f="graph",k=null,c=null,M=new Map,w=null;function y(e,t){let r=document.createElement("button");return r.textContent=e,r.setAttribute("aria-pressed","false"),r.addEventListener("click",t),r}function l(e,t,r){let n=document.createElement(e);return n.className=t,n.textContent=r,n}function V(e,t){let r=document.createElement("div");r.className=`box ${e.kind}${t?" trigger":""}`,r.append(l("span","tag",e.kind)),r.append(x("span","label",e.name));let n=e.value;return n!==void 0&&(typeof n!="object"||n===null)&&r.append(l("span","val",JSON.stringify(n))),r}function X(e,t,r){let n=H(e),o=document.createElement("div");o.className="flow";for(let[a,i]of n.entries())o.append(V(i,i.name===t)),a<n.length-1&&o.append(l("div","arrow","\u2193"));r.append(o)}function Z(e){let t=document.createElement("div");if(t.className="scroll",e.append(t),h===null){t.append(l("p","empty","Nothing selected."),l("p","hint","Press \u201CPick\u201D and click an element."));return}let r=A(h);if(r.length>0){t.append(l("p","hint","Component"));let i=document.createElement("div");i.className="crumbs";for(let s of r)i.append(l("span","crumb",s));t.append(i)}let n=$(h);if(n.length===0){t.append(l("p","empty","Nothing reactive writes this node."));return}let o=q(h);t.append(l("p","hint","Path"));for(let i of n)X(i,o,t);t.append(l("p","cause",o===null?"Has not run since anything changed.":`Triggered by ${o}`));let a=T(h).slice(-6).reverse();a.length>0&&(t.append(l("p","hint",`Last ${String(a.length)} updates`)),t.append(F(a))),c!==null&&e.append(N(S(c)))}function F(e){M.clear();let t=Math.max(1,...e.map(n=>n.ran.length)),r=document.createElement("div");r.className="track";for(let n of e){let o=document.createElement("div");o.className="tick",o.setAttribute("aria-selected",String(n===c)),M.set(o,n),o.append(l("span","when",`${String(n.at)}ms`)),o.append(x("span","who",n.source));let a=document.createElement("span");a.className=n.ran.length===0?"bar none":"bar",a.style.width=`${String(Math.round(n.ran.length/t*60)+6)}px`,a.title=`woke ${String(n.ran.length)} of ${String(t)}`,o.append(a);let i=n.stack[0];i!==void 0&&o.append(x("span","where",i,_)),o.addEventListener("click",()=>{I(n)}),r.append(o)}return r}function U(e){let t=/^(.*?)\(?([^()\s]+):(\d+):(\d+)\)?$/.exec(e);if(t===null)return e;let[,r,n,o,a]=t,i=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\")),s=n.slice(i+1),u=s.indexOf("?"),m=`${(u===-1?s:s.slice(0,u)).trim()}:${o}:${a}`;return r.trim()===""?m:`${r.trim()} (${m})`}function _(e){let t=/\(([^()]+)\)\s*$/.exec(e);return(t===null?e:t[1]).trim()}function x(e,t,r,n=o=>o){let o=l(e,t,n(U(r)));return O(r).then(a=>{o.textContent=n(U(a))}),o}function N(e){return e.classList.add("pinned"),e}function S(e){let t=document.createElement("div");t.className="detail";let r=document.createElement("div");r.className="detail-head",r.append(x("p","hint",e.source,o=>`${o} woke ${String(e.ran.length)}`));let n=y("\xD7",()=>{I(e)});n.setAttribute("aria-label","Close detail"),r.append(n),t.append(r);for(let o of e.ran)t.append(x("div","ran",o));if(e.stack.length>0){t.append(l("p","hint","Written from"));let o=document.createElement("div");o.className="stack";for(let a of e.stack)o.append(x("div","",a));t.append(o)}return t}function I(e){c=c===e?null:e;for(let[r,n]of M)r.setAttribute("aria-selected",String(n===c));let t=E?.body;t!==void 0&&(t.querySelector(".pinned")?.remove(),c!==null&&t.append(N(S(c))))}function ee(e){let t=T();if(t.length===0){e.append(l("p","empty","Nothing has changed yet."));return}let r=document.createElement("div");r.className="scroll",e.append(r);let n=[...new Set(t.map(a=>a.source))];if(n.length>1){let a=document.createElement("div");a.className="filters";for(let i of n){let s=x("span","chip",i);s.setAttribute("aria-pressed",String(w===i)),s.addEventListener("click",()=>{w=w===i?null:i,c=null,g()}),a.append(s)}r.append(a)}let o=w===null?t:t.filter(a=>a.source===w);r.append(l("p","hint",`${String(o.length)} of ${String(t.length)} updates`)),r.append(F([...o].reverse())),c!==null&&e.append(N(S(c)))}function te(e){let t=document.createElement("div");t.className="scroll",e.append(t);let r=C();if(r.length===0){t.append(l("p","empty","No resource has done anything yet."));return}for(let n of[...r].reverse()){let o=document.createElement("div");o.className="event",o.append(l("span",n.event,n.event)),o.append(l("span","tags",n.tags.join(", "))),t.append(o)}}function g(){if(E===null)return;let{body:e,pick:t,graphTab:r,queryTab:n,timelineTab:o}=E,a=e.querySelector(".scroll")?.scrollTop??0;r.setAttribute("aria-pressed",String(f==="graph")),n.setAttribute("aria-pressed",String(f==="queries")),o.setAttribute("aria-pressed",String(f==="timeline")),t.setAttribute("aria-pressed",String(v)),e.textContent="",f==="graph"?Z(e):f==="queries"?te(e):ee(e);let i=e.querySelector(".scroll");i!==null&&(i.scrollTop=a)}function G(e){!v||d===null||e.composedPath().includes(d)||(e.preventDefault(),e.stopPropagation(),h=e.target,v=!1,document.querySelector(`.${b}`)?.classList.remove(b),g())}function z(e){if(!v)return;document.querySelector(`.${b}`)?.classList.remove(b);let t=e.target;t!==null&&t!==d&&t.classList.add(b)}function j(){if(d!==null)return;d=document.createElement("div"),d.setAttribute("data-firsthand-devtools","");let e=d.attachShadow({mode:"open"}),t=document.createElement("style");t.textContent=K;let r=document.createElement("style");r.textContent=`.${b} { outline: 2px solid #3d5afe !important; outline-offset: 1px; }`,document.head.append(r);let n=document.createElement("div");n.className="panel";let o=document.createElement("header");o.append(l("strong","","Firsthand"));let a=y("Pick",()=>{v=!v,g()});a.dataset.pick="";let i=y("Graph",()=>{f="graph",g()});i.dataset.tab="graph";let s=y("Queries",()=>{f="queries",g()});s.dataset.tab="queries";let u=y("Timeline",()=>{f="timeline",g()});u.dataset.tab="timeline";let p=y("\xD7",R);p.setAttribute("aria-label","Close"),o.append(a,i,s,u,p);let m=document.createElement("div");m.className="body",n.append(o,m),e.append(t,n),document.body.append(d),E={body:m,pick:a,graphTab:i,queryTab:s,timelineTab:u},document.addEventListener("click",G,!0),document.addEventListener("mouseover",z,!0),L(()=>{c!==null||k!==null||(k=requestAnimationFrame(()=>{k=null,g()}))}),g()}function R(){L(null),k!==null&&(cancelAnimationFrame(k),k=null),document.removeEventListener("click",G,!0),document.removeEventListener("mouseover",z,!0),document.querySelector(`.${b}`)?.classList.remove(b),d?.remove(),d=null,E=null,c=null,w=null,v=!1,h=null,f="graph"}function ae(){d===null?j():R()}function ie(e){j(),h=e,f="graph",g()}function se(){g()}export{R as close,j as open,se as refresh,ie as show,ae as toggle};
|