twiddle-panel 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Panel used to be published as `@theoldpixel/twiddle`; that package is deprecated
18
18
  npx twiddle-panel init
19
19
  ```
20
20
 
21
- It shows the line to add and where (your entry file, or `index.html` for plain pages), and changes nothing. Add `--write` to add it, `--dev-only` to load the panel only while developing. For Next.js it prints a small client component to use instead.
21
+ It shows the line to add and where (your entry file, or `index.html` for plain pages), and changes nothing. Add `--write` to add it. With a build tool it loads the panel only while developing (the tokens always); add `--always` to keep it in production builds too, for example on shared preview links. For Next.js it prints a small client component to use instead.
22
22
 
23
23
  **Projects with a build tool (Vite, Next.js…)**
24
24
 
@@ -43,7 +43,7 @@ if (import.meta.env.DEV) import('twiddle-panel');
43
43
  **Plain HTML prototypes** (no install needed)
44
44
 
45
45
  ```html
46
- <script src="https://cdn.jsdelivr.net/npm/twiddle-panel@0.8.0/dist/twiddle.js" defer></script>
46
+ <script src="https://cdn.jsdelivr.net/npm/twiddle-panel@0.8.2/dist/twiddle.js" defer></script>
47
47
  ```
48
48
 
49
49
  The prototype's CSS must use the `--twd-*` token variables
package/bin/init.js CHANGED
@@ -7,8 +7,10 @@
7
7
 
8
8
  npx twiddle-panel init show what to add, and where
9
9
  npx twiddle-panel init --write add it
10
- npx twiddle-panel init --dev-only build tools: load the panel only
11
- in development (tokens always)
10
+ npx twiddle-panel init --always build tools: load the panel in every
11
+ build, e.g. for shared preview links
12
+ (default: development only, tokens
13
+ always)
12
14
  npx twiddle-panel init --file <path> use this entry / HTML file
13
15
  --------------------------------------------------------- */
14
16
 
@@ -20,7 +22,6 @@ const NAME = pkgSelf.name;
20
22
  const VERSION = pkgSelf.version;
21
23
  const SCRIPT_TAG = `<script src="https://cdn.jsdelivr.net/npm/${NAME}@${VERSION}/dist/twiddle.js" defer></script>`;
22
24
  const IMPORT = `import '${NAME}';`;
23
- const IMPORT_DEV = `import '${NAME}/tokens.css';\nif (import.meta.env.DEV) import('${NAME}');`;
24
25
  const DOCS = 'https://panel.twiddle.in';
25
26
 
26
27
  const args = process.argv.slice(2);
@@ -34,13 +35,17 @@ const say = (s = '') => process.stdout.write(`${s}\n`);
34
35
  const indent = (s) => s.split('\n').map((l) => ` ${l}`).join('\n');
35
36
 
36
37
  if (cmd !== 'init') {
37
- say(`Panel by Twiddle ${VERSION}\n\nUsage:\n npx ${NAME} init show the line to add, and where\n npx ${NAME} init --write add it\n npx ${NAME} init --dev-only load the panel only in development\n npx ${NAME} init --file <path> use this entry or HTML file\n\nGuide: ${DOCS}`);
38
+ say(`Panel by Twiddle ${VERSION}\n\nUsage:\n npx ${NAME} init show the line to add, and where\n npx ${NAME} init --write add it\n npx ${NAME} init --always load the panel in every build (shared previews)\n npx ${NAME} init --file <path> use this entry or HTML file\n\nGuide: ${DOCS}`);
38
39
  process.exit(cmd && !['help', '--help', '-h'].includes(cmd) ? 1 : 0);
39
40
  }
40
41
 
41
42
  const readJSON = (p) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch (e) { return null; } };
42
43
  const pkg = readJSON(join(cwd, 'package.json'));
43
44
  const deps = pkg ? { ...pkg.dependencies, ...pkg.devDependencies } : {};
45
+ // Development only (the default for build tools). import.meta.env.DEV exists only
46
+ // in Vite; other bundlers (webpack, Create React App…) replace process.env.NODE_ENV.
47
+ const devCheck = deps.vite ? 'import.meta.env.DEV' : "process.env.NODE_ENV !== 'production'";
48
+ const IMPORT_DEV = `import '${NAME}/tokens.css';\nif (${devCheck}) import('${NAME}');`;
44
49
  const first = (list) => list.map((p) => join(cwd, p)).find((p) => existsSync(p)) || null;
45
50
 
46
51
  /** The JS file a Vite-style index.html loads (<script type="module" src="/src/main.ts">). */
@@ -81,13 +86,14 @@ if (plan.kind === 'next') {
81
86
  }
82
87
 
83
88
  if (plan.kind === 'unknown') {
84
- say(`Couldn't find an entry file or index.html here.\n\nWith a build tool, add this to your app's entry file:\n${indent(IMPORT)}\n\nIn a plain HTML page, add this inside <head>:\n${indent(SCRIPT_TAG)}\n\nOr point at a file: npx ${NAME} init --file <path>\nGuide: ${DOCS}`);
89
+ say(`Couldn't find an entry file or index.html here.\n\nWith a build tool, add this to your app's entry file (the panel loads only while developing):\n${indent(IMPORT_DEV)}\n (In a Vite project, use: if (import.meta.env.DEV) import('${NAME}');)\n\nIn a plain HTML page, add this inside <head>:\n${indent(SCRIPT_TAG)}\n\nOr point at a file: npx ${NAME} init --file <path>\nGuide: ${DOCS}`);
85
90
  process.exit(1);
86
91
  }
87
92
 
88
93
  // ---- Show, then (with --write) add ----
89
94
  const src = readFileSync(plan.file, 'utf8');
90
- const lines = plan.kind === 'html' ? SCRIPT_TAG : (flag('--dev-only') ? IMPORT_DEV : IMPORT);
95
+ // --dev-only (0.8.0/0.8.1) is now the default and still accepted.
96
+ const lines = plan.kind === 'html' ? SCRIPT_TAG : (flag('--always') ? IMPORT : IMPORT_DEV);
91
97
  const where = plan.kind === 'html' ? `inside <head> of ${rel(plan.file)}` : `at the top of ${rel(plan.file)}`;
92
98
 
93
99
  if (src.includes(NAME) || src.includes('twiddle.js')) {
@@ -98,7 +104,8 @@ if (src.includes(NAME) || src.includes('twiddle.js')) {
98
104
  if (!flag('--write')) {
99
105
  say(`Add this ${where}:\n\n${indent(lines)}\n`);
100
106
  if (plan.kind === 'build' && !deps[NAME]) say(`Install first:\n npm install -D ${NAME}\n`);
101
- say(`Run again with --write to add it for you.${plan.kind === 'build' && !flag('--dev-only') ? ' Add --dev-only to load the panel only while developing.' : ''}\nGuide: ${DOCS}`);
107
+ if (plan.kind === 'build') say(flag('--always') ? 'This loads the panel in every build, including production.\n' : 'This loads the panel only while developing (the tokens always). Add --always to keep it in production builds too, e.g. for shared preview links.\n');
108
+ say(`Run again with --write to add it for you.\nGuide: ${DOCS}`);
102
109
  process.exit(0);
103
110
  }
104
111
 
@@ -114,5 +121,6 @@ if (plan.kind === 'html') {
114
121
  }
115
122
  writeFileSync(plan.file, out);
116
123
  say(`Added Panel ${where}.`);
124
+ if (plan.kind === 'build' && !flag('--always')) say('It loads only while developing. For shared preview links, use --always instead.');
117
125
  if (plan.kind === 'build' && !deps[NAME]) say(`\nNow install it:\n npm install -D ${NAME}`);
118
126
  say(`\nYour CSS needs the --twd-* tokens for the panel to change anything. Guide and agent prompt: ${DOCS}`);
package/dist/twiddle.js CHANGED
@@ -762,7 +762,7 @@ ${Object.entries(r).map(([i,l])=>` ${i}: ${l} !important;`).join(`
762
762
  .summary-swatch[hidden] { display: none; }
763
763
  .summary-row .chev { display: grid; color: var(--p-muted); }
764
764
  `,L=(e,t,n)=>`<svg width="${e}" height="${e}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${t}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${n}</svg>`,A={dial:L(20,1.8,'<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),link:L(15,2,'<path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1 1"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1-1"/>'),reset:L(15,2,'<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v6h6"/>'),close:L(15,2,'<path d="M18 6L6 18M6 6l12 12"/>'),chevron:L(12,2.4,'<path d="M9 6l6 6-6 6"/>'),pick:L(15,2,'<circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/>'),compare:L(15,2,'<circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/>'),undo:L(15,2,'<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-3"/>'),redo:L(15,2,'<path d="M15 14l5-5-5-5"/><path d="M20 9H9a5 5 0 0 0 0 10h3"/>'),back:L(15,2,'<path d="M15 18l-6-6 6-6"/>'),down:L(12,2.4,'<path d="M6 9l6 6 6-6"/>'),minimise:L(15,2,'<path d="M5 12h14"/>'),copy:L(13,2,'<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/>'),warn:L(13,2,'<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>'),panelMark:'<svg class="mark" width="44" height="44" viewBox="0 0 120 120" aria-hidden="true"><path class="mark-disc" fill-rule="evenodd" clip-rule="evenodd" d="M60 120C93.1371 120 120 93.1371 120 60C120 26.8629 93.1371 0 60 0C26.8629 0 0 26.8629 0 60C0 93.1371 26.8629 120 60 120ZM86 76C86 90.3594 74.3594 102 60 102C45.6406 102 34 90.3594 34 76V44C34 29.6406 45.6406 18 60 18C74.3594 18 86 29.6406 86 44V76ZM60 22C72.1503 22 82 31.8497 82 44V76C82 88.1503 72.1503 98 60 98C47.8497 98 38 88.1503 38 76V44C38 31.8497 47.8497 22 60 22Z"/><circle class="mark-t2" cx="60" cy="80.46" r="4.28"/><circle class="mark-t1" cx="60" cy="66.04" r="7.14"/><circle class="mark-knob" cx="60" cy="44" r="11.9"/></svg>',tabOverview:L(18,1.8,'<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),tabColour:L(18,2,'<path d="M12 3c-3.5 4.5-6 7.8-6 11a6 6 0 0 0 12 0c0-3.2-2.5-6.5-6-11z"/>'),tabType:L(18,2,'<path d="M5 7V5h14v2"/><path d="M12 5v14"/><path d="M9 19h6"/>'),tabSpacing:L(18,2,'<path d="M4 5v14M20 5v14"/><path d="M8 12h8"/><path d="M10 9l-3 3 3 3M14 9l3 3-3 3"/>'),tabShape:L(18,2,'<path d="M20 4h-9a7 7 0 0 0-7 7v9"/><path d="M20 10h-5a5 5 0 0 0-5 5v5"/>'),tabShadows:L(18,2,'<rect x="4" y="4" width="12" height="12" rx="2"/><path d="M20 8v10a2 2 0 0 1-2 2H8"/>'),sliders:L(15,2,'<path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/>')},Ut="twiddle:";function W(e){try{return localStorage.getItem(Ut+e)}catch{return null}}function ne(e,t){try{localStorage.setItem(Ut+e,t)}catch{}}const ae=44,oe=8,ua=4,qt={corner:"br",x:16,y:16};function ha(){try{const e=JSON.parse(W("position"));if(e&&/^[tb][lr]$/.test(e.corner)&&isFinite(e.x)&&isFinite(e.y))return e}catch{}return{...qt}}function Ht(e){const t=Math.max(oe,innerWidth-ae-oe),n=Math.max(oe,innerHeight-ae-oe);return{corner:e.corner,x:Math.round(Math.min(Math.max(e.x,oe),t)),y:Math.round(Math.min(Math.max(e.y,oe),n))}}function fa({wrapEl:e,launcherEl:t}){let n=ha();function a(){const d=Ht(n),p=d.corner[1]==="l",u=d.corner[0]==="t";Object.assign(e.style,{left:p?`${d.x}px`:"auto",right:p?"auto":`${d.x}px`,top:u?`${d.y}px`:"auto",bottom:u?"auto":`${d.y}px`}),e.style.setProperty("--off-x",`${d.x}px`),e.style.setProperty("--off-y",`${d.y}px`),e.classList.toggle("at-left",p),e.classList.toggle("at-top",u)}function o(d,p=!0){n=Ht(d),a(),p&&ne("position",JSON.stringify(n))}function s(d,p){const u=d+ae/2<innerWidth/2,v=p+ae/2<innerHeight/2,f=!v&&e.classList.contains("is-open")?10:0;return{corner:(v?"t":"b")+(u?"l":"r"),x:u?d:innerWidth-d-ae,y:v?p:innerHeight-p-ae-f}}let r=null,i=!1;t.addEventListener("pointerdown",d=>{if(d.button!==0)return;const p=t.getBoundingClientRect();r={id:d.pointerId,sx:d.clientX,sy:d.clientY,dx:d.clientX-p.left,dy:d.clientY-p.top,moving:!1}}),t.addEventListener("pointermove",d=>{if(!(!r||d.pointerId!==r.id)){if(!r.moving){if(Math.hypot(d.clientX-r.sx,d.clientY-r.sy)<ua)return;r.moving=!0,t.setPointerCapture(d.pointerId),e.classList.add("dragging")}o(s(d.clientX-r.dx,d.clientY-r.dy),!1)}});const l=d=>{!r||d.pointerId!==r.id||(r.moving&&(i=!0,e.classList.remove("dragging"),o(n)),r=null)};return t.addEventListener("pointerup",l),t.addEventListener("pointercancel",l),t.addEventListener("keydown",d=>{const p={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]}[d.key];if(!p||d.altKey||d.metaKey||d.ctrlKey)return;d.preventDefault();const u=d.shiftKey?10:1,v=n.corner[1]==="l"?1:-1,f=n.corner[0]==="t"?1:-1;o({corner:n.corner,x:n.x+p[0]*u*v,y:n.y+p[1]*u*f})}),window.addEventListener("resize",a),a(),{consumeClick(){const d=i;return i=!1,d},reset(){o({...qt})}}}function re({label:e,options:t,onSelect:n}){const a=document.createElement("div");a.className="seg",a.setAttribute("role","radiogroup"),a.setAttribute("aria-label",e);const o=t.map(l=>{const d=document.createElement("button");return d.type="button",d.setAttribute("role","radio"),d.dataset.value=l.value,d.textContent=l.label,l.title&&(d.title=l.title),d.addEventListener("click",()=>i(l.value)),d});a.append(...o);let s=null;function r(l){s=l,o.forEach(p=>p.setAttribute("aria-checked",String(p.dataset.value===l)));const d=Math.max(0,o.findIndex(p=>p.dataset.value===l));o.forEach((p,u)=>{p.tabIndex=u===d?0:-1})}function i(l,d){l!==s&&(r(l),n(l)),d&&o.find(p=>p.dataset.value===l).focus()}return a.addEventListener("keydown",l=>{const d=o.indexOf(l.target);if(d<0)return;const p={ArrowRight:1,ArrowDown:1,ArrowLeft:-1,ArrowUp:-1}[l.key];let u;if(p)u=(d+p+o.length)%o.length;else if(l.key==="Home")u=0;else if(l.key==="End")u=o.length-1;else return;l.preventDefault(),i(o[u].dataset.value,!0)}),r(null),{el:a,setValue:r}}const Vt=/var\(\s*(--twd-[\w-]+)/g,ga=/::?[\w-]+(\([^)]*\))?/g;function*Me(e){for(const t of e)t.selectorText!=null&&(yield t),t.cssRules&&(yield*Me(t.cssRules))}function ma(){const e=[];for(const t of document.styleSheets){if(t.ownerNode?.id==="twiddle-scoped")continue;let n;try{n=t.cssRules}catch{continue}for(const a of Me(n)){const o=[...a.style.cssText.matchAll(Vt)].map(s=>s[1]);o.length&&e.push({selectors:a.selectorText.split(","),vars:o})}}return e}function ba(e,t){const n=t.replace(ga,"").trim()||"*";try{return e.matches(n)}catch{return!1}}function _t(e){const t=new Set,n=[];return ma().forEach(({selectors:a,vars:o})=>{const s=a.find(r=>ba(e,r));s&&(o.forEach(r=>t.add(r)),n.push(s.trim()))}),[...(e.getAttribute("style")||"").matchAll(Vt)].forEach(a=>t.add(a[1])),{names:t,selectors:n}}const et=e=>window.CSS&&CSS.escape?CSS.escape(e):e;function xa(e){const t=[];let n=e;for(;n&&n.nodeType===1&&n!==document.body&&n!==document.documentElement;){if(n.id&&document.querySelectorAll(`#${et(n.id)}`).length===1)return t.unshift(`#${et(n.id)}`),t.join(" > ");const a=n.localName,o=[...n.parentElement.children].filter(s=>s.localName===a);t.unshift(o.length>1?`${a}:nth-of-type(${o.indexOf(n)+1})`:a),n=n.parentElement}return["body",...t].join(" > ")}function wa(e,t=_t(e).selectors){const n=e.getAttribute("data-twd");if(n)return`[data-twd="${n.replace(/"/g,"")}"]`;const a=[...e.classList],s=a.find(r=>t.some(i=>new RegExp(`\\.${r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?![\\w-])`).test(i)))||a[0];return s?`.${et(s)}`:null}function ya(e){const t=_t(e),n=wa(e,t.selectors),a=(e.getAttribute("aria-label")||e.placeholder||e.textContent||"").trim().replace(/\s+/g," ");return{el:e,tag:e.localName,classes:[...e.classList],text:a.length>40?`${a.slice(0,40)}…`:a,tokens:t.names,elementSelector:xa(e),classSelector:n,classCount:n?document.querySelectorAll(n).length:0}}const tt=e=>e.localName+[...e.classList].map(t=>`.${t}`).join(""),va={radius:"px",intensity:"%",base:"px"},Kt=e=>e.charAt(0).toUpperCase()+e.slice(1),ka=(e,t)=>typeof t=="number"||/^\d+$/.test(t)?`${t}${va[e]||""}`:Kt(String(t));function Wt(e,t,n){const a=Bt(()=>e.tokens({[e.id]:t})||{}),o=Object.keys(a);let s={};if(o.length){const r=n.selector?document.querySelector(n.selector):void 0;s=la(o,r||void 0)}return{...n,sectionId:e.id,title:e.title,settings:Object.entries(t).map(([r,i])=>({key:r,label:Kt(r),value:ka(r,i),...e.describe?.(r,i)||{}})),tokens:o.map(r=>({name:r,from:s[r]||"",to:a[r]}))}}function Gt(e,t){const n=[];return t.forEach(a=>{e[a.id]&&n.push(Wt(a,e[a.id],{kind:"global",selector:null}))}),Object.entries(e.scopes||{}).forEach(([a,o])=>{const s=Je(a)?"class":"element";t.forEach(r=>{o[r.id]&&n.push(Wt(r,o[r.id],{kind:s,selector:a}))})}),n}function Sa(e){const t=o=>Object.values(o||{}).reduce((s,r)=>s+Object.keys(r||{}).length,0),{scopes:n,...a}=e;return t(a)+Object.values(n||{}).reduce((o,s)=>o+t(s),0)}const se=e=>`\`${e}\``,nt=e=>e.map(t=>` - ${se(t.name)}: ${t.from?`${se(t.from)} → `:""}${se(t.to)}`),at=e=>`${e.title} › ${e.settings.map(t=>`${t.label}: ${t.value}`).join(", ")}`;function $a(e){let t=null;try{t=document.querySelector(e)}catch{}if(!t)return"not found on the current page";const n=(t.getAttribute("aria-label")||t.placeholder||t.textContent||"").trim().replace(/\s+/g," ").slice(0,60);return`${se(tt(t))}${n?` with text “${n}”`:""}`}function Yt(e,t){const n=new Map;return e.forEach(a=>{const o=a[t];n.has(o)||n.set(o,[]),n.get(o).push(a)}),[...n]}function Ea(e,{link:t=location.href,warnings:n=[]}={}){if(!e.length)return"";const a=e.filter(i=>i.kind==="global"),o=e.filter(i=>i.kind==="class"),s=e.filter(i=>i.kind==="element"),r=[];if(r.push("# Apply design tweaks from Panel","","I explored design changes to this prototype with Panel (by Twiddle), a tweak panel that overrides the prototype's `--twd-*` CSS custom properties (design tokens) at runtime. Make these changes permanent in the source code.","",`Preview of the tweaked version: ${t}`),a.length&&(r.push("","## 1. Global token changes","","Update the default value of each token where it is defined (usually a `:root` rule or a `tokens.css` / `twiddle-tokens.css` file). Change the definition, not the places that use it.",""),a.forEach(i=>r.push(`- **${at(i)}**`,...nt(i.tokens)))),o.length&&(r.push("",`## ${a.length?2:1}. Component-wide changes`,"","These apply to every element matching the selector. In the CSS rule for that component, redeclare the token, e.g. `.input { --twd-radius-control: 999px; }`. Don't replace the `var(--twd-…)` usages with fixed values.",""),Yt(o,"selector").forEach(([i,l])=>{r.push(`- ${se(i)} (${document.querySelectorAll(i).length} on this page)`),l.forEach(d=>r.push(` - **${at(d)}**`,...nt(d.tokens).map(p=>` ${p}`)))})),s.length){const i=1+(a.length?1:0)+(o.length?1:0);r.push("",`## ${i}. Single-element changes`,"","These apply to one element only. Add a modifier class to that element (e.g. `input--pill`) and redeclare the token in a rule for that class. The selectors below are runtime paths to help you find the element; don't copy them into the stylesheet.",""),Yt(s,"selector").forEach(([l,d])=>{r.push(`- ${$a(l)}, found at ${se(l)}`),d.forEach(p=>r.push(` - **${at(p)}**`,...nt(p.tokens).map(u=>` ${u}`)))})}return n.length&&r.push("","## Contrast to check","","Panel flagged these colour pairs as below WCAG AA with the new values:","",...n.map(i=>`- ${i.text}`)),r.push("","## Constraints","","- Keep styling through the `--twd-*` tokens; don't hard-code the new values in component rules.","- Change only what is listed above.","- If a value uses `color-mix()` and the project targets older browsers, ask before converting it.","- When done, list the files you changed."),r.join(`
765
- `)}const Te=4.5,Ca=3,Ma=[["--twd-text","--twd-bg","Text on background"],["--twd-text","--twd-surface","Text on surface"],["--twd-text-muted","--twd-surface","Muted text on surface"],["--twd-on-primary","--twd-primary","Text on brand"]];let G;function ot(e){if(!e||(G||=Object.assign(document.createElement("canvas"),{width:1,height:1}).getContext("2d",{willReadFrequently:!0}),!G))return null;G.clearRect(0,0,1,1),G.fillStyle="#000",G.fillStyle=e,G.fillRect(0,0,1,1);const[t,n,a,o]=G.getImageData(0,0,1,1).data;return[t,n,a,o/255]}function Xt(e){const t=document.createElement("i");t.style.cssText=`position:absolute;visibility:hidden;color:var(${e})`,document.documentElement.appendChild(t);const n=getComputedStyle(t).color;return t.remove(),ot(n)}const Zt=([e,t,n])=>{const a=o=>(o/=255,o<=.03928?o/12.92:((o+.055)/1.055)**2.4);return .2126*a(e)+.7152*a(t)+.0722*a(n)},Jt=(e,t)=>e.slice(0,3).map((n,a)=>n*e[3]+t[a]*(1-e[3])).concat(1);function Qt(e,t){const n=Zt(e[3]<1?Jt(e,t):e),a=Zt(t);return(Math.max(n,a)+.05)/(Math.min(n,a)+.05)}const en=e=>`${e.toFixed(1)}:1`;function rt(){const e=[];return Ma.forEach(([t,n,a])=>{const o=Xt(t),s=Xt(n);if(!o||!s||s[3]===0)return;const r=Qt(o,s);r<Te&&e.push({label:a,ratio:r,needed:Te,text:`${a}: ${en(r)} · AA needs ${Te}`})}),e}function Ta(e){const t=[];for(let n=e;n&&n.nodeType===1;n=n.parentElement){const a=ot(getComputedStyle(n).backgroundColor);if(a&&a[3]>0&&(t.push(a),a[3]>=1))break}return t.reduceRight((n,a)=>Jt(a,n),[255,255,255,1])}function Aa(e){if(!e.textContent.trim()&&!e.placeholder)return null;const t=getComputedStyle(e),n=ot(t.color);if(!n)return null;const a=parseFloat(t.fontSize),s=a>=24||a>=18.66&&parseInt(t.fontWeight,10)>=700?Ca:Te,r=Qt(n,Ta(e));return r<s?{label:"This element",ratio:r,needed:s,text:`This element’s text: ${en(r)} · AA needs ${s}`}:null}const tn="twiddle-panel",La="0.8.0",nn="https://panel.twiddle.in",Pa="--twd-radius-control",Oa=/var\(\s*--twd-/;function Fa(e){return e.style.cssText.split(";").some(t=>{const n=t.slice(0,t.indexOf(":")).trim();return n&&!n.startsWith("--")&&Oa.test(t)})}function Na(){if(!getComputedStyle(document.documentElement).getPropertyValue(Pa).trim())return"no-tokens";let e=!1;for(const t of document.styleSheets){if(t.ownerNode?.id==="twiddle-scoped")continue;let n;try{n=t.cssRules}catch{e=!0;continue}for(const a of Me(n))if(Fa(a))return"ok"}return document.querySelector('[style*="var(--twd-"]')?"ok":e?"unknown":"unused"}function ja(){const e=new Set,t=n=>{for(const a of n.matchAll(/var\(\s*(--twd-[\w-]+)/g))e.add(a[1])};for(const n of document.styleSheets){if(n.ownerNode?.id==="twiddle-scoped")continue;let a;try{a=n.cssRules}catch{continue}for(const o of Me(a))o.style.cssText.split(";").forEach(s=>{const r=s.slice(0,s.indexOf(":")).trim();r&&!r.startsWith("--")&&t(s)})}return document.querySelectorAll('[style*="var(--twd-"]').forEach(n=>t(n.getAttribute("style"))),e}const an=[{label:"With a build tool, in your entry file",code:`import '${tn}/tokens.css';`},{label:"Plain HTML, in <head>",code:`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${tn}@${La}/dist/twiddle-tokens.css">`}],Ra=`Convert this prototype's styles to use the Panel design tokens (the --twd-* CSS custom properties from twiddle-panel), so the panel can tweak them live.
765
+ `)}const Te=4.5,Ca=3,Ma=[["--twd-text","--twd-bg","Text on background"],["--twd-text","--twd-surface","Text on surface"],["--twd-text-muted","--twd-surface","Muted text on surface"],["--twd-on-primary","--twd-primary","Text on brand"]];let G;function ot(e){if(!e||(G||=Object.assign(document.createElement("canvas"),{width:1,height:1}).getContext("2d",{willReadFrequently:!0}),!G))return null;G.clearRect(0,0,1,1),G.fillStyle="#000",G.fillStyle=e,G.fillRect(0,0,1,1);const[t,n,a,o]=G.getImageData(0,0,1,1).data;return[t,n,a,o/255]}function Xt(e){const t=document.createElement("i");t.style.cssText=`position:absolute;visibility:hidden;color:var(${e})`,document.documentElement.appendChild(t);const n=getComputedStyle(t).color;return t.remove(),ot(n)}const Zt=([e,t,n])=>{const a=o=>(o/=255,o<=.03928?o/12.92:((o+.055)/1.055)**2.4);return .2126*a(e)+.7152*a(t)+.0722*a(n)},Jt=(e,t)=>e.slice(0,3).map((n,a)=>n*e[3]+t[a]*(1-e[3])).concat(1);function Qt(e,t){const n=Zt(e[3]<1?Jt(e,t):e),a=Zt(t);return(Math.max(n,a)+.05)/(Math.min(n,a)+.05)}const en=e=>`${e.toFixed(1)}:1`;function rt(){const e=[];return Ma.forEach(([t,n,a])=>{const o=Xt(t),s=Xt(n);if(!o||!s||s[3]===0)return;const r=Qt(o,s);r<Te&&e.push({label:a,ratio:r,needed:Te,text:`${a}: ${en(r)} · AA needs ${Te}`})}),e}function Ta(e){const t=[];for(let n=e;n&&n.nodeType===1;n=n.parentElement){const a=ot(getComputedStyle(n).backgroundColor);if(a&&a[3]>0&&(t.push(a),a[3]>=1))break}return t.reduceRight((n,a)=>Jt(a,n),[255,255,255,1])}function Aa(e){if(!e.textContent.trim()&&!e.placeholder)return null;const t=getComputedStyle(e),n=ot(t.color);if(!n)return null;const a=parseFloat(t.fontSize),s=a>=24||a>=18.66&&parseInt(t.fontWeight,10)>=700?Ca:Te,r=Qt(n,Ta(e));return r<s?{label:"This element",ratio:r,needed:s,text:`This element’s text: ${en(r)} · AA needs ${s}`}:null}const tn="twiddle-panel",La="0.8.2",nn="https://panel.twiddle.in",Pa="--twd-radius-control",Oa=/var\(\s*--twd-/;function Fa(e){return e.style.cssText.split(";").some(t=>{const n=t.slice(0,t.indexOf(":")).trim();return n&&!n.startsWith("--")&&Oa.test(t)})}function Na(){if(!getComputedStyle(document.documentElement).getPropertyValue(Pa).trim())return"no-tokens";let e=!1;for(const t of document.styleSheets){if(t.ownerNode?.id==="twiddle-scoped")continue;let n;try{n=t.cssRules}catch{e=!0;continue}for(const a of Me(n))if(Fa(a))return"ok"}return document.querySelector('[style*="var(--twd-"]')?"ok":e?"unknown":"unused"}function ja(){const e=new Set,t=n=>{for(const a of n.matchAll(/var\(\s*(--twd-[\w-]+)/g))e.add(a[1])};for(const n of document.styleSheets){if(n.ownerNode?.id==="twiddle-scoped")continue;let a;try{a=n.cssRules}catch{continue}for(const o of Me(a))o.style.cssText.split(";").forEach(s=>{const r=s.slice(0,s.indexOf(":")).trim();r&&!r.startsWith("--")&&t(s)})}return document.querySelectorAll('[style*="var(--twd-"]').forEach(n=>t(n.getAttribute("style"))),e}const an=[{label:"With a build tool, in your entry file",code:`import '${tn}/tokens.css';`},{label:"Plain HTML, in <head>",code:`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${tn}@${La}/dist/twiddle-tokens.css">`}],Ra=`Convert this prototype's styles to use the Panel design tokens (the --twd-* CSS custom properties from twiddle-panel), so the panel can tweak them live.
766
766
 
767
767
  1. Keep the current look. In a :root rule loaded after twiddle-tokens.css, set each token below to the value this project already uses (for example, if buttons use #0f766e, set --twd-primary: #0f766e). Only set the tokens the project actually needs.
768
768
  2. Replace fixed values in component styles with var(--twd-…). Map each value to the closest token by role:
package/dist/twiddle.mjs CHANGED
@@ -1266,7 +1266,7 @@ function Da(e) {
1266
1266
  const a = parseFloat(t.fontSize), s = a >= 24 || a >= 18.66 && parseInt(t.fontWeight, 10) >= 700 ? Ra : Ue, r = qn(n, Ia(e));
1267
1267
  return r < s ? { label: "This element", ratio: r, needed: s, text: `This element’s text: ${Hn(r)} · AA needs ${s}` } : null;
1268
1268
  }
1269
- const un = "twiddle-panel", Ua = "0.8.0", hn = "https://panel.twiddle.in", Ba = "--twd-radius-control", qa = /var\(\s*--twd-/;
1269
+ const un = "twiddle-panel", Ua = "0.8.2", hn = "https://panel.twiddle.in", Ba = "--twd-radius-control", qa = /var\(\s*--twd-/;
1270
1270
  function Ha(e) {
1271
1271
  return e.style.cssText.split(";").some((t) => {
1272
1272
  const n = t.slice(0, t.indexOf(":")).trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twiddle-panel",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "A design-system tweak panel you drop into any prototype.",
5
5
  "license": "MIT",
6
6
  "author": "Ravi",
@@ -24,7 +24,7 @@
24
24
  "module": "./dist/twiddle.mjs",
25
25
  "types": "./dist/twiddle.d.ts",
26
26
  "bin": {
27
- "twiddle-panel": "./bin/init.js"
27
+ "twiddle-panel": "bin/init.js"
28
28
  },
29
29
  "unpkg": "dist/twiddle.js",
30
30
  "jsdelivr": "dist/twiddle.js",