efficient-web-builder 26.1.0 → 26.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 26.1.1 - 2026-10-20
6
+
7
+ **Site styles rows line up with the panel again, and an unset row says Default** (Lewis's screenshot of the INOVIT editor, 2026-10-20). A PATCH: no export changes.
8
+
9
+ ### Fixed
10
+
11
+ - **Every Site styles row sat on the panel's left border.** The one text panel (#104, 17.0.0) moved the settings body's side padding from `.sf-insp-body` to easel's `.es-insp-body` and gave the new class to the right inspector only. The Site styles body (`StylesPanel`) had no side padding: rows started 0px from the border, 16px left of the panel head, and the Corners handle at 0 touched the border. It wears `es-insp-body` now: 16px, 8px under a 284px panel. `scripts/test-panel-body-wears-the-inset.ts` ties each body's classes to the rules the padding model reads.
12
+ - **The rows' right side was 11px wider than the left**, in both settings panels. The body holds the scrollbar's lane at all times (`scrollbar-gutter: stable`), outside its padding. The body's own scrollbar is 8px now where a sheet can size it (`::-webkit-scrollbar`), and the right padding is the inset less that lane (`--sf-insp-pad-end`), so both sides and the head read one number. Firefox keeps its own scrollbar and the full inset. The full-bleed group rule cancels the right side by the new token, and the `panel-no-divider-lines` rule checks that.
13
+ - **An unset Block gap or Corners showed nothing at the end of its label line.** Since option E the reset slot is empty while a row is unset, and the row's grey value says it is the default. These two rows have no single built-in to print, so they print the word "Default", in the same grey.
14
+
5
15
  ## 26.1.0 - 2026-10-20
6
16
 
7
17
  This release carries three changes: Logo color (#203, first), efficient-easel 14 (#201) and the Box (#151), which was waiting under Unreleased.
@@ -1 +1 @@
1
- "use client";import{jsx as t}from"react/jsx-runtime";import{SettingsDefaultReset as r,SettingsField as c}from"efficient-easel/panel";import{AlignSeparateIcon as d}from"efficient-easel/icons";import{CONTAINER_GAP_BUILT_IN as p,SITE_BLOCK_GAP as o}from"../../format/lib/content/container-gap.js";import{say as e,editorEaselWords as m}from"../../format/lib/editor-words.js";import{Slider as f}from"../lib/puck/controls.js";const u="The space between the blocks inside a row, a grid or a box. Default means each one keeps the spacing it already has.",i="Block gap",_=p.Grid;function g({value:n,onChange:l,showHint:s=!0}){const a=n===null;return t(c,{label:e(i),labelId:"sf-block-gap",icon:t(d,{}),value:a?void 0:`${n}px`,changed:!a,action:t(r,{words:m()?.panel,label:e(i),changed:!a,detail:e("each block's own spacing"),onReset:()=>l(null)}),hint:s?e(u):void 0,children:t(f,{min:o.min,max:o.max,step:o.step,value:a?_:n,readout:!1,ariaLabel:e(i),onChange:l})})}export{g as default};
1
+ "use client";import{jsx as t}from"react/jsx-runtime";import{SettingsDefaultReset as r,SettingsField as c}from"efficient-easel/panel";import{AlignSeparateIcon as d}from"efficient-easel/icons";import{CONTAINER_GAP_BUILT_IN as p,SITE_BLOCK_GAP as n}from"../../format/lib/content/container-gap.js";import{say as e,editorEaselWords as m}from"../../format/lib/editor-words.js";import{Slider as f}from"../lib/puck/controls.js";const u="The space between the blocks inside a row, a grid or a box. Default means each one keeps the spacing it already has.",i="Block gap",_=p.Grid;function g({value:o,onChange:l,showHint:s=!0}){const a=o===null;return t(c,{label:e(i),labelId:"sf-block-gap",icon:t(d,{}),value:a?e("Default"):`${o}px`,changed:!a,action:t(r,{words:m()?.panel,label:e(i),changed:!a,detail:e("each block's own spacing"),onReset:()=>l(null)}),hint:s?e(u):void 0,children:t(f,{min:n.min,max:n.max,step:n.step,value:a?_:o,readout:!1,ariaLabel:e(i),onChange:l})})}export{g as default};
@@ -1 +1 @@
1
- "use client";import{jsx as t}from"react/jsx-runtime";import{SettingsDefaultReset as l,SettingsField as d}from"efficient-easel/panel";import{SquareIcon as u}from"efficient-easel/icons";import{CORNER_RADIUS_MAX as c,CORNER_RADIUS_MIN as i}from"../../format/lib/theme.js";import{say as e,editorEaselWords as p}from"../../format/lib/editor-words.js";import{Slider as m}from"../lib/puck/controls.js";const R="Applies to panels, cards, inputs and buttons. Round things — color swatches, switches, status dots — stay round.",s="Corners";function f({value:n,onChange:r,showHint:a=!0}){const o=n===null;return t(d,{label:e(s),icon:t(u,{}),value:o?void 0:`${n}px`,changed:!o,action:t(l,{words:p()?.panel,label:e(s),changed:!o,detail:e("the built-in corners"),onReset:()=>r(null)}),hint:a?e(R):void 0,children:t(m,{min:i,max:c,step:1,value:n??i,readout:!1,ariaLabel:e(s),onChange:r})})}export{s as CORNERS_LABEL,f as default};
1
+ "use client";import{jsx as e}from"react/jsx-runtime";import{SettingsDefaultReset as l,SettingsField as d}from"efficient-easel/panel";import{SquareIcon as u}from"efficient-easel/icons";import{CORNER_RADIUS_MAX as c,CORNER_RADIUS_MIN as i}from"../../format/lib/theme.js";import{say as t,editorEaselWords as p}from"../../format/lib/editor-words.js";import{Slider as m}from"../lib/puck/controls.js";const R="Applies to panels, cards, inputs and buttons. Round things — color swatches, switches, status dots — stay round.",s="Corners";function f({value:o,onChange:r,showHint:a=!0}){const n=o===null;return e(d,{label:t(s),icon:e(u,{}),value:n?t("Default"):`${o}px`,changed:!n,action:e(l,{words:p()?.panel,label:t(s),changed:!n,detail:t("the built-in corners"),onReset:()=>r(null)}),hint:a?t(R):void 0,children:e(m,{min:i,max:c,step:1,value:o??i,readout:!1,ariaLabel:t(s),onChange:r})})}export{s as CORNERS_LABEL,f as default};
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import c,{useWebsiteName as f}from"./WebsiteSettingsPanel.js";import d from"./SidePanelHead.js";import{say as s}from"../../format/lib/editor-words.js";import{StylesIcon as p}from"efficient-easel/rail";import{Tooltip as S}from"efficient-shell/ui";function x({tokenRef:n,siteId:o,onClose:i,onNotify:a,tab:l,onTabChange:m}){const t=f();return r("div",{className:"sf-styles",children:[e(d,{icon:e(p,{}),title:s("Site styles"),context:e(S,{content:s("{name} — renamed in the Console: {place}.",{name:t,place:s("Storefront ▸ Sites ▸ Settings")}),className:"sf-name-tip",children:e("span",{className:"sf-name-text",children:t})}),onHide:i}),e("div",{className:"sf-insp-body sf-scroll",children:e(c,{tokenRef:n,siteId:o,onNotify:a,tab:l,onTabChange:m})})]})}export{x as default};
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import c,{useWebsiteName as f}from"./WebsiteSettingsPanel.js";import d from"./SidePanelHead.js";import{say as s}from"../../format/lib/editor-words.js";import{StylesIcon as p}from"efficient-easel/rail";import{Tooltip as y}from"efficient-shell/ui";function S({tokenRef:n,siteId:o,onClose:i,onNotify:a,tab:l,onTabChange:m}){const t=f();return r("div",{className:"sf-styles",children:[e(d,{icon:e(p,{}),title:s("Site styles"),context:e(y,{content:s("{name} — renamed in the Console: {place}.",{name:t,place:s("Storefront ▸ Sites ▸ Settings")}),className:"sf-name-tip",children:e("span",{className:"sf-name-text",children:t})}),onHide:i}),e("div",{className:"sf-insp-body es-insp-body sf-scroll",children:e(c,{tokenRef:n,siteId:o,onNotify:a,tab:l,onTabChange:m})})]})}export{S as default};
@@ -877,6 +877,17 @@
877
877
  that inset, because rules below read it to reach the panel's edge. */
878
878
  --sf-insp-pad-x: var(--es-insp-pad-x, var(--space-16));
879
879
  padding-block: var(--space-4) var(--space-20);
880
+ /* THE RIGHT INSET COUNTS THE SCROLLBAR'S LANE (2026-10-20). The gutter above
881
+ is held whether or not the scrollbar is drawn, so the rows' right side was
882
+ the inset PLUS the lane: 16 + 11 at a 320px panel, against 16 on the left
883
+ and 16 in the head. Now the lane is part of the inset: the end padding is
884
+ the inset less the lane, so both sides and the head read one number — 16,
885
+ and 8 under a 284px panel. The lane is 8px for that reason: the narrow
886
+ inset must hold all of it (`@supports` below). A browser that draws its
887
+ own scrollbar (Firefox) keeps a 0px lane here and the full inset. */
888
+ --sf-insp-bar-w: 0px;
889
+ --sf-insp-pad-end: calc(var(--sf-insp-pad-x) - var(--sf-insp-bar-w));
890
+ padding-inline-end: var(--sf-insp-pad-end);
880
891
 
881
892
  /* THE DROPDOWN ARROW, WRITTEN ONCE — card 66399.c.
882
893
  Read by `.sf-insp-body select` at rest AND by the hover rule beside it. The
@@ -944,6 +955,16 @@
944
955
  /* The narrow inset (8px under a 284px panel) is easel's `.es-insp-body` rule
945
956
  now; `--sf-insp-pad-x` follows it, so every reader narrows with it. */
946
957
 
958
+ /* The lane the right inset counts (see `.sf-insp-body`): the body's own
959
+ scrollbar, 8px where this sheet can size it. easel's `.sf-scroll` draws 11px,
960
+ which the 8px narrow inset could not hold. The thumb keeps its transparent
961
+ frame, 2px a side instead of 3. */
962
+ @supports selector(::-webkit-scrollbar) {
963
+ .sf-insp-body { --sf-insp-bar-w: var(--space-8); }
964
+ .sf-insp-body::-webkit-scrollbar { width: var(--sf-insp-bar-w); }
965
+ .sf-insp-body::-webkit-scrollbar-thumb { border-width: var(--space-2); }
966
+ }
967
+
947
968
  /* Floating-styled form controls for Puck's native fields — and ONLY those.
948
969
 
949
970
  ⚠️ A FIELD THE SHELL DRAWS IS SKIPPED: `:not(:where(.rosh-focus-ring))` on
@@ -1756,9 +1777,9 @@
1756
1777
  6px OUTSIDE the panel at every width under 280, where card 66212 narrows
1757
1778
  the padding to 8 — measured at 260 and at 220 before this was a `var()`. */
1758
1779
  margin-left: calc(var(--sf-insp-pad-x) * -1);
1759
- margin-right: calc(var(--sf-insp-pad-x) * -1);
1780
+ margin-right: calc(var(--sf-insp-pad-end) * -1);
1760
1781
  padding-left: var(--sf-insp-pad-x);
1761
- padding-right: var(--sf-insp-pad-x);
1782
+ padding-right: var(--sf-insp-pad-end);
1762
1783
  }
1763
1784
  /* The heading's own top margin is the wrapper's job now. */
1764
1785
  .sf-insp-body .es-insp-row:has(.es-group-sub) .es-group-sub {
@@ -1,10 +1,10 @@
1
- import{ruleChecks as E}from"./rule.js";function v(r){const s=r.split(`
2
- `),n=[];return s.forEach((g,a)=>{if(!/border-(top|bottom|left|right)[a-z-]*\s*:[^;]*\bsolid\b/.test(g))return;let $=0,c="?";for(let i=a-1;i>=0;i-=1)if($+=(s[i].match(/\}/g)??[]).length-(s[i].match(/\{/g)??[]).length,$<0){c=s[i].split("{")[0].trim();let h=i-1;for(;h>=0&&s[h].trim().endsWith(",");)c=`${s[h].trim()} ${c}`,h-=1;break}n.push({line:a+1,selector:c,decl:g.trim()})}),n}const y=r=>r.split(/[\s,+>~]/)[0].trim(),j=(r,s)=>s in r||y(s)in r,O=/\bzinc-|--sf-line\b|--sf-divider\b|--sf-input-border\b|--sf-chip-bg\b|--es-line\b|--es-divider\b|--sf-text-secondary\b/,k=[["utility",/(^|["'`\s])border-(t|b|l|r)($|["'`\s])/],["style object",/border(Top|Bottom|Left|Right)\s*:/],["css in a string",/border-(top|bottom|left|right)\s*:/]];function S(r){return/radius/i.test(r)||!O.test(r)?null:k.find(([,s])=>s.test(r))?.[0]??null}function N(r,s){const{check:n,report:g}=E("panel divider lines"),a=s.allowed??{},$=s.pendingElsewhere??{},c=s.allowedTsx??[];{const e=u=>["border",u].join("-"),t=[".a,",".b + .c {",` ${e("top")}: 1px solid red;`,"}",`.d { ${e("radius")}: 4px; }`].join(`
3
- `),l=v(t);n("the census finds a directional solid border, with its whole multi-line selector",l.length===1&&l[0].selector===".a, .b + .c"&&y(l[0].selector)===".a",JSON.stringify(l));const d=["zinc","200"].join("-"),f=[["a utility",`<div className="${e("t")} border-${d}" />`,"utility"],["a style object",`style={{ ${["border","Top"].join("")}: "1px solid var(--sf-line)" }}`,"style object"],["CSS in a string",`const s = "${e("left")}: 1px solid var(--es-divider)";`,"css in a string"],["a border in the storefront's own ink",`<div className="${e("t")} border-line" />`,null],["a radius",`<div className="${e("t")} border-${d} rounded" style={{ borderRadius: 4 }} />`,null]];for(const[u,o,p]of f)n(`the sweep reads ${u} as ${p??"no hairline"}`,S(o)===p,o)}const i=[];for(const e of s.panelSheets??[]){const t=r.find(o=>o.path===e)?.text??"",l=v(t);n(`${e} was read and parsed`,t.length>1e4&&l.length>0,`${t.length} bytes, ${l.length} directional border(s)`),i.push(...l.map(o=>({...o,sheet:e})));const f=(t.replace(/\/\*[\s\S]*?\*\//g,"").match(/\[class\*="_PuckFields[^"]*"\][^{]*\{/g)??[]).length;n(`${e} reaches for no Puck field wrapper`,f===0,`${f} rule(s) still select \`[class*="_PuckFields…"]\` — the rows are \`.es-insp-row\` now`);const u=t.replace(/,\s*\n\s*/g,", ");for(const o of s.fullBleed??[]){if(!(o in a))continue;const p=u.indexOf(`
4
- ${o} {`);if(!n(`the allowed full-bleed rule is still in ${e}`,p>=0,o))continue;const x=u.slice(p,u.indexOf("}",p));for(const w of["left","right"])n(`the full-bleed rule cancels the panel's ${w} padding, so it reaches the edge`,new RegExp(`margin-${w}:\\s*calc\\(var\\(--sf-insp-pad-x\\)\\s*\\*\\s*-1\\)`).test(x),"inset, this is the line the owner rejected — full bleed is the exemption"),n(`…and puts that ${w} padding back inside, so the words stay on the rows' edge`,new RegExp(`padding-${w}:\\s*var\\(--sf-insp-pad-x\\)`).test(x))}}for(const e of s.fullBleed??[])n(`the full-bleed selector is an ALLOWED entry: ${e}`,e in a);if(s.panelSheets?.length){const e=i.filter(t=>!j(a,t.selector)&&!j($,t.selector));n(`no unlisted directional border survives in a panel stylesheet (${i.length} censused)`,e.length===0,`${e.length} neither removed, allowed, nor owed to another card — delete it and let the rhythm tokens separate, or add it to the allow-list with a reason if it is a control edge:
1
+ import{ruleChecks as O}from"./rule.js";function j(r){const s=r.split(`
2
+ `),n=[];return s.forEach((g,a)=>{if(!/border-(top|bottom|left|right)[a-z-]*\s*:[^;]*\bsolid\b/.test(g))return;let $=0,c="?";for(let i=a-1;i>=0;i-=1)if($+=(s[i].match(/\}/g)??[]).length-(s[i].match(/\{/g)??[]).length,$<0){c=s[i].split("{")[0].trim();let h=i-1;for(;h>=0&&s[h].trim().endsWith(",");)c=`${s[h].trim()} ${c}`,h-=1;break}n.push({line:a+1,selector:c,decl:g.trim()})}),n}const y=r=>r.split(/[\s,+>~]/)[0].trim(),k=(r,s)=>s in r||y(s)in r,L=/\bzinc-|--sf-line\b|--sf-divider\b|--sf-input-border\b|--sf-chip-bg\b|--es-line\b|--es-divider\b|--sf-text-secondary\b/,S=[["utility",/(^|["'`\s])border-(t|b|l|r)($|["'`\s])/],["style object",/border(Top|Bottom|Left|Right)\s*:/],["css in a string",/border-(top|bottom|left|right)\s*:/]];function E(r){return/radius/i.test(r)||!L.test(r)?null:S.find(([,s])=>s.test(r))?.[0]??null}function T(r,s){const{check:n,report:g}=O("panel divider lines"),a=s.allowed??{},$=s.pendingElsewhere??{},c=s.allowedTsx??[];{const e=u=>["border",u].join("-"),t=[".a,",".b + .c {",` ${e("top")}: 1px solid red;`,"}",`.d { ${e("radius")}: 4px; }`].join(`
3
+ `),l=j(t);n("the census finds a directional solid border, with its whole multi-line selector",l.length===1&&l[0].selector===".a, .b + .c"&&y(l[0].selector)===".a",JSON.stringify(l));const d=["zinc","200"].join("-"),f=[["a utility",`<div className="${e("t")} border-${d}" />`,"utility"],["a style object",`style={{ ${["border","Top"].join("")}: "1px solid var(--sf-line)" }}`,"style object"],["CSS in a string",`const s = "${e("left")}: 1px solid var(--es-divider)";`,"css in a string"],["a border in the storefront's own ink",`<div className="${e("t")} border-line" />`,null],["a radius",`<div className="${e("t")} border-${d} rounded" style={{ borderRadius: 4 }} />`,null]];for(const[u,o,p]of f)n(`the sweep reads ${u} as ${p??"no hairline"}`,E(o)===p,o)}const i=[];for(const e of s.panelSheets??[]){const t=r.find(o=>o.path===e)?.text??"",l=j(t);n(`${e} was read and parsed`,t.length>1e4&&l.length>0,`${t.length} bytes, ${l.length} directional border(s)`),i.push(...l.map(o=>({...o,sheet:e})));const f=(t.replace(/\/\*[\s\S]*?\*\//g,"").match(/\[class\*="_PuckFields[^"]*"\][^{]*\{/g)??[]).length;n(`${e} reaches for no Puck field wrapper`,f===0,`${f} rule(s) still select \`[class*="_PuckFields…"]\` — the rows are \`.es-insp-row\` now`);const u=t.replace(/,\s*\n\s*/g,", ");for(const o of s.fullBleed??[]){if(!(o in a))continue;const p=u.indexOf(`
4
+ ${o} {`);if(!n(`the allowed full-bleed rule is still in ${e}`,p>=0,o))continue;const v=u.slice(p,u.indexOf("}",p));for(const[w,x]of[["left","--sf-insp-pad-x"],["right","--sf-insp-pad-end"]])n(`the full-bleed rule cancels the panel's ${w} padding, so it reaches the edge`,new RegExp(`margin-${w}:\\s*calc\\(var\\(${x}\\)\\s*\\*\\s*-1\\)`).test(v),"inset, this is the line the owner rejected — full bleed is the exemption"),n(`…and puts that ${w} padding back inside, so the words stay on the rows' edge`,new RegExp(`padding-${w}:\\s*var\\(${x}\\)`).test(v))}}for(const e of s.fullBleed??[])n(`the full-bleed selector is an ALLOWED entry: ${e}`,e in a);if(s.panelSheets?.length){const e=i.filter(t=>!k(a,t.selector)&&!k($,t.selector));n(`no unlisted directional border survives in a panel stylesheet (${i.length} censused)`,e.length===0,`${e.length} neither removed, allowed, nor owed to another card — delete it and let the rhythm tokens separate, or add it to the allow-list with a reason if it is a control edge:
5
5
  `+e.map(t=>`${t.sheet}:${t.line} ${t.selector} — ${t.decl}`).join(`
6
6
  `))}const h=e=>i.some(t=>t.selector===e||y(t.selector)===e);for(const[e,t]of Object.entries(a))n(`ALLOWED entry ${e} still names a real rule (${t.slice(0,48)}…)`,h(e),"the rule is gone: delete this entry with it");for(const[e,t]of Object.entries($))n(`PENDING entry ${e} is still owed — ${t}`,h(e),"that card has landed and the rule is gone: delete this entry");const b=r.filter(e=>/\.tsx?$/.test(e.path));n(`the repository's own TS/TSX files were swept (${b.length}, at least ${s.minFiles})`,b.length>=s.minFiles);const m=[];for(const e of b)e.text.split(`
7
- `).forEach((t,l)=>{const d=S(t);d&&(c.some(f=>f.file===e.path&&t.includes(f.util))||m.push(`${e.path}:${l+1} (${d}) ${t.trim().slice(0,80)}`))});n("no editor panel file draws an unlisted directional border",m.length===0,`${m.length} found:
7
+ `).forEach((t,l)=>{const d=E(t);d&&(c.some(f=>f.file===e.path&&t.includes(f.util))||m.push(`${e.path}:${l+1} (${d}) ${t.trim().slice(0,80)}`))});n("no editor panel file draws an unlisted directional border",m.length===0,`${m.length} found:
8
8
  ${m.join(`
9
9
  `)}`);for(const e of c){const t=b.find(l=>l.path===e.file)?.text??"";n(`ALLOWED_TSX entry ${e.file} ${e.util} still names a real line (${e.why.slice(0,40)}…)`,t.split(`
10
- `).some(l=>k.some(([,d])=>d.test(l))&&l.includes(e.util)),"the line is gone: delete this entry")}return g()}export{N as panelNoDividerLines};
10
+ `).some(l=>S.some(([,d])=>d.test(l))&&l.includes(e.util)),"the line is gone: delete this entry")}return g()}export{T as panelNoDividerLines};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "efficient-web-builder",
3
- "version": "26.1.0",
3
+ "version": "26.1.1",
4
4
  "description": "The EFFICIENT website builder: the website editor, the general sections and the renderer that draws them, compiled. Hostable by any EFFICIENT product; nothing here imports a host.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Efficient Pty Ltd",