efficient-web-builder 23.0.0 → 23.1.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/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 23.1.0 - 2026-10-19
6
+
7
+ **A list item's rows read the section's settings, and a rule that hides a whole tab keeps the tab strip** (SSOT batch 1, GG 2026-10-19). Nothing a merchant sees moves: no rule uses either yet. They let the last two hand-written hides in the storefront blocks move (DetailRows' Image shape, ProductHero's dynamic-page cut).
8
+
9
+ ### Changed for hosts
10
+
11
+ - **`SiteFields` hands easel's Inspector `showWhenContext` with the node's own props as `node`.** easel reads an array item's (or an object's) rows against that item's values, so a rule on such a row reads the SECTION with a context check: `{ context: "node.layout", is: "narrow" }`. The shell's existing rule shapes only; no new rule type and no reader here.
12
+ - **The one show-when reader re-seats the tab strip.** When a rule drops a tab header (`BUCKET_KEY`), `rowsShownForNode` runs `reseatBucketTabs`, so the first surviving tab draws the strip (card 66151). Once, for every panel; a block no longer has to re-seat after its own cut.
13
+ - The efficient-easel peer and dev range rises to `^13.1.1` (the Inspector's `showWhenContext` is 13.1.0; 13.1.1 takes its field helpers from the shell, which 23.0.x already requires at `^7.74.0`).
14
+
15
+ ### Guard
16
+
17
+ - `scripts/test-show-when-rows.tsx` §11: a rule that hides a whole tab, its header included, re-seats the surviving header (it fails without the re-seat). §12: an item row's rule reads `node`, and `SiteFields` passes it.
18
+
19
+ **`withShowWhen` and `showWhenOf` are the shell's** (SSOT batch 1, GG 2026-10-19; efficient-shell #552, 7.74.0). The same names, the same behaviour.
20
+
21
+ ### Changed for hosts
22
+
23
+ - `lib/puck/show-when-attach` (and `lib/puck/show-when`, which re-exports it) re-exports `withShowWhen`, `showWhenOf` and the `WithShowWhen` type from `efficient-shell/format` and deletes its own copy. `format` is the shell's leaf (no React, no kit), so the modules a published page loads still take no editor code: `scripts/test-render-closure.ts` (W4) passes.
24
+ - The efficient-shell peer and dev range rises to `^7.74.0`, the first release with the two helpers in `format`. A narrowed range: hosts move their shell in the same pull request.
25
+
26
+ ### Guard
27
+
28
+ - `lib/source-rules/one-show-when` R6: a second definition of either helper (a function, or a const set to one) fails, in this repository and in every host that runs the rule. A re-export passes.
29
+ - `scripts/test-show-when-rows.tsx` §10: the builder's helpers ARE the shell's functions.
30
+
5
31
  ## 23.0.0 - 2026-10-19
6
32
 
7
33
  **A host block declares its show-when facts in its field module** (SSOT batch 1, for the storefront blocks). A type change only: nothing a merchant sees moves.
@@ -1 +1 @@
1
- "use client";import{jsx as E}from"react/jsx-runtime";import{useLayoutEffect as P,useMemo as C,useRef as q,useState as T}from"react";import{useEasel as R,useSelectedFields as U}from"efficient-easel/easel";import{Inspector as X,planFields as F}from"efficient-easel/fields";import{BUCKET_ICONS as G,entriesHiddenForTab as H}from"../lib/puck/controls.js";import{BUCKETS as W}from"../lib/puck/field-groups.js";import{say as Y}from"../lib/editor-words.js";import{usePreviewDevice as z}from"efficient-easel/draw/client";import{showWhenContext as J,visibleFields as Q}from"../lib/puck/show-when.js";import{fieldsOfPart as V}from"../lib/puck/hero-media-parts.js";import{bandIsDropped as Z,droppedBands as $,sameKeys as ee,TEXT_BAND_KEY as v,TEXT_BAND_TITLE as te,withoutBands as oe}from"../lib/puck/panel-bands.js";const g=new Map,p=new Set;function ne(o,r){const c=new Map;for(const s of o.querySelectorAll(".es-insp-row[data-field]"))c.set(s.getAttribute("data-field")??"",s);const n=[];for(const[s,a]of r)n.push({key:s,rows:a.map(w=>{const l=c.get(w),f=!!l&&[...l.children].some(b=>!b.hidden),u=l?.querySelector(".es-field-label, .es-list-label")?.textContent?.trim();return{shown:f,labelled:f&&!!u}})});return n}const se=Object.fromEntries(W.map(o=>{const r=G[o];return[o,E(r,{},o)]}));function be({entries:o,entryCount:r=0,part:c=null}){const{id:n,fields:s,values:a,readOnly:w,onChange:l}=U(),f=z(),{appState:u,config:b}=R(e=>e),_=J(f,b,u.data.content??[],u.ui.itemSelector),A=V(s,c),d=Q(A,a,_),[I,M]=T(()=>new Set(p)),[x,O]=T(()=>new Set),B=q(null),D=C(()=>{const e=new Map;for(const t of F(d).rows){if(!t.fold)continue;const i=e.get(t.fold.key)??[];i.push(t.name),e.set(t.fold.key,i)}return e},[d]);P(()=>{const e=B.current?.parentElement;if(!e)return;const t=new Set($(ne(e,D)));O(i=>ee(i,t)?i:t)});const N=C(()=>oe(d,x),[d,x]),k={key:v,rows:Array.from({length:r},()=>({shown:!0,labelled:!0}))},K=Z(k)?void 0:{key:v,title:te},[h,j]=T(()=>g.get(n)),y=g.get(n),m=F(d).buckets,S=y&&m.includes(y)?y:h&&m.includes(h)?h:m[0],L=S===void 0||H(m,S)!==!0;return E(X,{fields:N,values:a,onChange:l,readOnly:w,tab:S,onTab:e=>{g.set(n,e),j(e)},tabIcons:se,idPrefix:n,ariaLabel:Y("Section settings"),lead:L?o:null,leadFold:K,closedFolds:I,onFold:(e,t)=>{t?p.delete(e):p.add(e),M(new Set(p))},children:E("span",{ref:B,hidden:!0})})}export{be as SiteFields,se as TAB_ICONS};
1
+ "use client";import{jsx as E}from"react/jsx-runtime";import{useLayoutEffect as P,useMemo as F,useRef as q,useState as T}from"react";import{useEasel as R,useSelectedFields as U}from"efficient-easel/easel";import{Inspector as W,planFields as v}from"efficient-easel/fields";import{BUCKET_ICONS as X,entriesHiddenForTab as G}from"../lib/puck/controls.js";import{BUCKETS as H}from"../lib/puck/field-groups.js";import{say as Y}from"../lib/editor-words.js";import{usePreviewDevice as z}from"efficient-easel/draw/client";import{showWhenContext as J,visibleFields as Q}from"../lib/puck/show-when.js";import{fieldsOfPart as V}from"../lib/puck/hero-media-parts.js";import{bandIsDropped as Z,droppedBands as $,sameKeys as ee,TEXT_BAND_KEY as _,TEXT_BAND_TITLE as te,withoutBands as oe}from"../lib/puck/panel-bands.js";const x=new Map,p=new Set;function ne(o,r){const a=new Map;for(const s of o.querySelectorAll(".es-insp-row[data-field]"))a.set(s.getAttribute("data-field")??"",s);const n=[];for(const[s,l]of r)n.push({key:s,rows:l.map(w=>{const d=a.get(w),f=!!d&&[...d.children].some(b=>!b.hidden),u=d?.querySelector(".es-field-label, .es-list-label")?.textContent?.trim();return{shown:f,labelled:f&&!!u}})});return n}const se=Object.fromEntries(H.map(o=>{const r=X[o];return[o,E(r,{},o)]}));function be({entries:o,entryCount:r=0,part:a=null}){const{id:n,fields:s,values:l,readOnly:w,onChange:d}=U(),f=z(),{appState:u,config:b}=R(e=>e),g=J(f,b,u.data.content??[],u.ui.itemSelector),A=V(s,a),i=Q(A,l,g),[I,M]=T(()=>new Set(p)),[C,O]=T(()=>new Set),B=q(null),D=F(()=>{const e=new Map;for(const t of v(i).rows){if(!t.fold)continue;const c=e.get(t.fold.key)??[];c.push(t.name),e.set(t.fold.key,c)}return e},[i]);P(()=>{const e=B.current?.parentElement;if(!e)return;const t=new Set($(ne(e,D)));O(c=>ee(c,t)?c:t)});const N=F(()=>oe(i,C),[i,C]),k={key:_,rows:Array.from({length:r},()=>({shown:!0,labelled:!0}))},K=Z(k)?void 0:{key:_,title:te},[h,j]=T(()=>x.get(n)),y=x.get(n),m=v(i).buckets,S=y&&m.includes(y)?y:h&&m.includes(h)?h:m[0],L=S===void 0||G(m,S)!==!0;return E(W,{fields:N,values:l,onChange:d,readOnly:w,showWhenContext:{...g,node:l},tab:S,onTab:e=>{x.set(n,e),j(e)},tabIcons:se,idPrefix:n,ariaLabel:Y("Section settings"),lead:L?o:null,leadFold:K,closedFolds:I,onFold:(e,t)=>{t?p.delete(e):p.add(e),M(new Set(p))},children:E("span",{ref:B,hidden:!0})})}export{be as SiteFields,se as TAB_ICONS};
@@ -1,10 +1,6 @@
1
- import type { ShowWhenRule } from "efficient-shell/format";
2
- export type { ShowWhenRule };
3
- export type WithShowWhen = {
4
- showWhen?: ShowWhenRule;
5
- };
6
- export declare function withShowWhen<F extends object>(field: F, rule: ShowWhenRule): F;
7
- export declare function showWhenOf(field: unknown): ShowWhenRule | undefined;
1
+ import { showWhenOf, withShowWhen, type ShowWhenRule, type WithShowWhen } from "efficient-shell/format";
2
+ export { showWhenOf, withShowWhen };
3
+ export type { ShowWhenRule, WithShowWhen };
8
4
  export declare function withRowsShownWhen<T extends Record<string, unknown>>(fields: T, keys: readonly string[], rule: ShowWhenRule): T;
9
5
  export type RowRule = readonly [keys: readonly string[], rule: ShowWhenRule];
10
6
  export declare function withRowRules<T extends Record<string, unknown>>(fields: T, table: readonly RowRule[]): T;
@@ -1 +1 @@
1
- function p(t,n){return{...t,showWhen:n}}function f(t){return t&&typeof t=="object"?t.showWhen:void 0}const a=new WeakMap;function W(t,n,o){if(!t||!n.some(r=>r in t))return t;let e=a.get(t);e||a.set(t,e=new WeakMap);let c=e.get(o);c||e.set(o,c=new Map);const u=n.join("\0"),i=c.get(u);if(i)return i;const h={...t};for(const r of n){const s=h[r];if(!s||typeof s!="object")continue;const w=f(s);h[r]=p(s,w?{all:[w,o]}:o)}return c.set(u,h),h}function x(t,n){return n.reduce((o,[e,c])=>W(o,e,c),t)}function b(t,n,o,e){const c={field:t,in:e};return e.includes(o)?{any:[c,{not:{field:t,in:n}}]}:c}function y(t,n){const o=t;o.showWhenFacts={...n,...o.showWhenFacts??{}}}export{y as addShowWhenFacts,f as showWhenOf,b as shownForOption,x as withRowRules,W as withRowsShownWhen,p as withShowWhen};
1
+ import{showWhenOf as u,withShowWhen as p}from"efficient-shell/format";const f=new WeakMap;function W(t,o,n){if(!t||!o.some(i=>i in t))return t;let e=f.get(t);e||f.set(t,e=new WeakMap);let c=e.get(n);c||e.set(n,c=new Map);const r=o.join("\0"),a=c.get(r);if(a)return a;const h={...t};for(const i of o){const s=h[i];if(!s||typeof s!="object")continue;const w=u(s);h[i]=p(s,w?{all:[w,n]}:n)}return c.set(r,h),h}function b(t,o){return o.reduce((n,[e,c])=>W(n,e,c),t)}function y(t,o,n,e){const c={field:t,in:e};return e.includes(n)?{any:[c,{not:{field:t,in:o}}]}:c}function R(t,o){const n=t;n.showWhenFacts={...o,...n.showWhenFacts??{}}}export{R as addShowWhenFacts,u as showWhenOf,y as shownForOption,b as withRowRules,W as withRowsShownWhen,p as withShowWhen};
@@ -1 +1 @@
1
- import{isShown as b}from"efficient-shell/format";import{containerOffersPositions as j}from"../content/grid-span.js";import{heroGroupNestDepth as S}from"../hero-group-placement.js";import{readsContext as k,resolverContext as x,showWhenOf as y}from"./show-when.js";import{findParentNode as G}from"./passes/document-position.js";const a=new WeakMap;function M(o,f,n,d={}){if(!o)return o;const u=f?.props??{},s=u.id,w=Object.fromEntries(Object.entries(d).map(([t,e])=>[t,()=>e(f,n)])),O=x(n,{...w,parentOffersPositions:()=>{const t=G(n.appState,s);return t===void 0?void 0:j(t)},heroGroupDepth:()=>S(n.appState?.data,typeof s=="string"?s:"")}),r=[];for(const[t,e]of Object.entries(o)){const c=y(e);!c||k(c,"device")||b(c,u,O)||r.push(t)}if(r.length===0)return o;let i=a.get(o);i||a.set(o,i=new Map);const h=r.join("\0"),m=i.get(h);if(m)return m;const g=new Set(r),p={};for(const[t,e]of Object.entries(o))g.has(t)||(p[t]=e);return i.set(h,p),p}export{M as rowsShownForNode};
1
+ import{isShown as S}from"efficient-shell/format";import{containerOffersPositions as g}from"../content/grid-span.js";import{heroGroupNestDepth as j}from"../hero-group-placement.js";import{BUCKET_KEY as k,reseatBucketTabs as E}from"./field-groups.js";import{readsContext as x,resolverContext as y,showWhenOf as v}from"./show-when.js";import{findParentNode as B}from"./passes/document-position.js";const m=new WeakMap,C=new Set(Object.values(k));function A(e,p,s,w={}){if(!e)return e;const u=p?.props??{},c=u.id,d=Object.fromEntries(Object.entries(w).map(([t,r])=>[t,()=>r(p,s)])),O=y(s,{...d,parentOffersPositions:()=>{const t=B(s.appState,c);return t===void 0?void 0:g(t)},heroGroupDepth:()=>j(s.appState?.data,typeof c=="string"?c:"")}),o=[];for(const[t,r]of Object.entries(e)){const f=v(r);!f||x(f,"device")||S(f,u,O)||o.push(t)}if(o.length===0)return e;let i=m.get(e);i||m.set(e,i=new Map);const h=o.join("\0"),a=i.get(h);if(a)return a;const b=new Set(o);let n={};for(const[t,r]of Object.entries(e))b.has(t)||(n[t]=r);return o.some(t=>C.has(t))&&(n=E(n)),i.set(h,n),n}export{A as rowsShownForNode};
@@ -1,4 +1,4 @@
1
- import{ruleChecks as F}from"./rule.js";const k=["lib/puck/show-when.ts","lib/puck/show-when-resolve.ts"],y=(t,o=[])=>/(^|\/)fields\.tsx?$/.test(t)||t.includes("lib/puck/passes/")||o.includes(t),E=new Set(["SettingRow","SettingsField","SettingsSwitch","SettingsSegmented","SettingsDrill","SettingsAction","SettingsGroupSub","InspectorField","FieldText","FieldNumber","FieldSelect","FieldSlider","ListField"]),S=/\b(?:props|values|settings|data\.props)(?:\.|\[|\?\.)/;function x(t,o,h=[]){const s=t.createSourceFile(o.path,o.text,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),d=e=>s.getLineAndCharacterOfPosition(e.getStart(s)).line+1,r=[];let p=0;const f=k.some(e=>o.path.endsWith(e)),m=e=>t.isPropertyAssignment(e)&&e.name.getText(s)==="resolveFields"||t.isBinaryExpression(e)&&e.operatorToken.kind===t.SyntaxKind.EqualsToken&&t.isPropertyAccessExpression(e.left)&&e.left.name.text==="resolveFields",u=(e,n)=>{const c=n||m(e);if(t.isDeleteExpression(e)&&(t.isPropertyAccessExpression(e.expression)||t.isElementAccessExpression(e.expression))&&(c?r.push({file:o.path,line:d(e),what:"R1 a `delete` in a resolveFields — give the row a showWhen rule"}):y(o.path,h)&&r.push({file:o.path,line:d(e),what:"R2 a `delete` in a field file or a pass — give the row a showWhen rule"})),c&&t.isObjectBindingPattern(e)&&e.elements.some(l=>l.dotDotDotToken)&&r.push({file:o.path,line:d(e),what:"R1 a destructure that leaves a key out, in a resolveFields — give the row a showWhen rule"}),c&&t.isCallExpression(e)&&t.isPropertyAccessExpression(e.expression)&&e.expression.name.text==="filter"&&/^Object\.entries\(/.test(e.expression.expression.getText(s))&&r.push({file:o.path,line:d(e),what:"R1 a filter over Object.entries in a resolveFields — give the row a showWhen rule"}),c&&t.isForOfStatement(e)&&/^Object\.entries\(/.test(e.expression.getText(s))&&r.push({file:o.path,line:d(e),what:"R4 a loop over Object.entries in a resolveFields — give the row a showWhen rule"}),t.isJsxExpression(e)&&e.expression){const l=e.expression,w=a=>t.isParenthesizedExpression(a)?w(a.expression):a,g=a=>{if(!a)return!1;const v=t.isJsxElement(a)?a.openingElement:t.isJsxSelfClosingElement(a)?a:t.isJsxFragment(a)?null:void 0;return v===null?a.getChildren(s).some(b=>b.getChildren(s).some(R=>g(R))):!!v&&E.has(v.tagName.getText(s))};(t.isBinaryExpression(l)&&l.operatorToken.kind===t.SyntaxKind.AmpersandAmpersandToken&&S.test(l.left.getText(s))&&g(w(l.right))||t.isConditionalExpression(l)&&S.test(l.condition.getText(s))&&(g(w(l.whenTrue))||g(w(l.whenFalse))))&&r.push({file:o.path,line:d(e),what:"R5 a settings row drawn by a stored value — use the row's showWhen (SettingRow / ShowWhen)"})}if(t.isCallExpression(e)&&t.isIdentifier(e.expression)){const l=e.expression.text;l==="isShown"&&!f&&r.push({file:o.path,line:d(e),what:"R3 a second reader of the rule — `isShown(` lives in lib/puck/show-when"}),(l==="withShowWhen"||l==="withRowsShownWhen"||l==="withRowRules")&&p++}t.forEachChild(e,l=>u(l,c))};return u(s,!1),{bad:r,attached:p}}function T(t,o,h){const{check:s,report:d}=F("one show-when rule"),r=h.kept??{};{const e=["export const A = { resolveFields: (data, { fields }) => { const f = { ...fields }; delete f.alt; return f; } };","export const B = { resolveFields: (d, { fields }) => { const { align: _x, ...rest } = fields; return rest; } };",'comp.resolveFields = (d, p) => Object.fromEntries(Object.entries(p.fields).filter(([k]) => k !== "x"));',"function cull(f) { delete f[key]; }","const shown = isShown(rule, values);","// delete f.alt in a comment is not a use",'export const ok = { fields: { alt: withShowWhen(text("Alt"), { field: "src", set: true }) } };',"export const C = { resolveFields: (d, { fields }) => { const out = {}; for (const [k, v] of Object.entries(fields)) if (keep.includes(k)) out[k] = v; return out; } };",'const p = <div>{settings.logo_position === "center" && <SettingRow label="Left items" />}</div>;','const q = <div>{props.on ? (<SettingsField label="x" />) : null}</div>;','const r = <div>{style ? <SettingsField label="Style" /> : null}</div>;'].join(`
2
- `),n=x(t,{path:"lib/puck/blocks/general/X/fields.tsx",text:e}),c=n.bad.map(i=>i.line);s("R1 flags a delete in a resolveFields",n.bad.some(i=>i.line===1&&i.what.startsWith("R1"))),s("R1 flags a destructure that leaves a key out",n.bad.some(i=>i.line===2&&i.what.startsWith("R1"))),s("R1 flags a filter over Object.entries",n.bad.some(i=>i.line===3&&i.what.startsWith("R1"))),s("R2 flags a delete in a field file's helper",n.bad.some(i=>i.line===4&&i.what.startsWith("R2"))),s("R3 flags a second isShown(",n.bad.some(i=>i.line===5&&i.what.startsWith("R3"))),s("a comment is not a use, and a rule passes and is counted",!c.includes(6)&&!c.includes(7)&&n.attached===1),s("R4 flags an allow-list loop over Object.entries",n.bad.some(i=>i.line===8&&i.what.startsWith("R4"))),s("R5 flags a row drawn by a stored value (&&)",n.bad.some(i=>i.line===9&&i.what.startsWith("R5"))),s("R5 flags a row drawn by a stored value (? :)",n.bad.some(i=>i.line===10&&i.what.startsWith("R5"))),s("R5 lets a part the host passed through",!c.includes(11));const l=x(t,{path:"lib/puck/cull.ts",text:"function cull(f) { delete f.x; }"},["lib/puck/cull.ts"]);s("R2 reads a file a repository names as a field file",l.bad.length===1);const w=x(t,{path:"lib/puck/part-styles.ts",text:"function f(next) { delete next[k]; }"});s("R2 reads only field files and passes (a delete of a VALUE elsewhere is not a hide)",w.bad.length===0);const g=x(t,{path:"lib/puck/show-when.ts",text:"isShown(rule, values);"});s("R3 lets the reader read",g.bad.length===0)}const p=o.filter(e=>/\.tsx?$/.test(e.path)&&!e.path.endsWith(".d.ts")),f=[],m=new Map;let u=0;for(const e of p){const n=x(t,e,h.alsoFieldFiles);u+=n.attached,m.set(e.path,n.bad.length),e.path in r||f.push(...n.bad)}s("no settings row hidden by hand: no delete or key-dropping filter in a resolver, field file or pass; one reader",f.length===0,`${f.length} hit(s):
3
- `+f.map(e=>`${e.file}:${e.line} ${e.what}`).join(`
4
- `)),s(`read the source (${p.length} TS/TSX files, at least ${h.minFiles})`,p.length>=h.minFiles),s(`found >= ${h.ruleFloor} rows given a rule (found ${u})`,u>=h.ruleFloor,"fewer means rows went back to a hand-written hide, or the reader broke");for(const e of h.alsoFieldFiles??[])s(`read the named field file ${e}`,p.some(n=>n.path===e));for(const[e,n]of Object.entries(r))s(`the exception is still real: ${e} (${n})`,(m.get(e)??0)>0,"remove the stale entry");return d()}export{T as oneShowWhen};
1
+ import{ruleChecks as F}from"./rule.js";const W=["lib/puck/show-when.ts","lib/puck/show-when-resolve.ts"],k=(t,l=[])=>/(^|\/)fields\.tsx?$/.test(t)||t.includes("lib/puck/passes/")||l.includes(t),y=new Set(["SettingRow","SettingsField","SettingsSwitch","SettingsSegmented","SettingsDrill","SettingsAction","SettingsGroupSub","InspectorField","FieldText","FieldNumber","FieldSelect","FieldSlider","ListField"]),R=/\b(?:props|values|settings|data\.props)(?:\.|\[|\?\.)/;function g(t,l,c=[]){const i=t.createSourceFile(l.path,l.text,t.ScriptTarget.Latest,!0,t.ScriptKind.TSX),h=e=>i.getLineAndCharacterOfPosition(e.getStart(i)).line+1,r=[];let f=0;const p=W.some(e=>l.path.endsWith(e)),m=e=>t.isPropertyAssignment(e)&&e.name.getText(i)==="resolveFields"||t.isBinaryExpression(e)&&e.operatorToken.kind===t.SyntaxKind.EqualsToken&&t.isPropertyAccessExpression(e.left)&&e.left.name.text==="resolveFields",u=(e,o)=>{const d=o||m(e);if(t.isDeleteExpression(e)&&(t.isPropertyAccessExpression(e.expression)||t.isElementAccessExpression(e.expression))&&(d?r.push({file:l.path,line:h(e),what:"R1 a `delete` in a resolveFields — give the row a showWhen rule"}):k(l.path,c)&&r.push({file:l.path,line:h(e),what:"R2 a `delete` in a field file or a pass — give the row a showWhen rule"})),d&&t.isObjectBindingPattern(e)&&e.elements.some(n=>n.dotDotDotToken)&&r.push({file:l.path,line:h(e),what:"R1 a destructure that leaves a key out, in a resolveFields — give the row a showWhen rule"}),d&&t.isCallExpression(e)&&t.isPropertyAccessExpression(e.expression)&&e.expression.name.text==="filter"&&/^Object\.entries\(/.test(e.expression.expression.getText(i))&&r.push({file:l.path,line:h(e),what:"R1 a filter over Object.entries in a resolveFields — give the row a showWhen rule"}),d&&t.isForOfStatement(e)&&/^Object\.entries\(/.test(e.expression.getText(i))&&r.push({file:l.path,line:h(e),what:"R4 a loop over Object.entries in a resolveFields — give the row a showWhen rule"}),t.isJsxExpression(e)&&e.expression){const n=e.expression,w=a=>t.isParenthesizedExpression(a)?w(a.expression):a,s=a=>{if(!a)return!1;const S=t.isJsxElement(a)?a.openingElement:t.isJsxSelfClosingElement(a)?a:t.isJsxFragment(a)?null:void 0;return S===null?a.getChildren(i).some(v=>v.getChildren(i).some(b=>s(b))):!!S&&y.has(S.tagName.getText(i))};(t.isBinaryExpression(n)&&n.operatorToken.kind===t.SyntaxKind.AmpersandAmpersandToken&&R.test(n.left.getText(i))&&s(w(n.right))||t.isConditionalExpression(n)&&R.test(n.condition.getText(i))&&(s(w(n.whenTrue))||s(w(n.whenFalse))))&&r.push({file:l.path,line:h(e),what:"R5 a settings row drawn by a stored value — use the row's showWhen (SettingRow / ShowWhen)"})}const x=n=>!!n&&t.isIdentifier(n)&&(n.text==="withShowWhen"||n.text==="showWhenOf");if((t.isFunctionDeclaration(e)&&x(e.name)||t.isVariableDeclaration(e)&&x(e.name)&&e.initializer&&(t.isArrowFunction(e.initializer)||t.isFunctionExpression(e.initializer)))&&r.push({file:l.path,line:h(e),what:"R6 a second copy of the shell's withShowWhen / showWhenOf — import them from efficient-shell/format"}),t.isCallExpression(e)&&t.isIdentifier(e.expression)){const n=e.expression.text;n==="isShown"&&!p&&r.push({file:l.path,line:h(e),what:"R3 a second reader of the rule — `isShown(` lives in lib/puck/show-when"}),(n==="withShowWhen"||n==="withRowsShownWhen"||n==="withRowRules")&&f++}t.forEachChild(e,n=>u(n,d))};return u(i,!1),{bad:r,attached:f}}function T(t,l,c){const{check:i,report:h}=F("one show-when rule"),r=c.kept??{};{const e=["export const A = { resolveFields: (data, { fields }) => { const f = { ...fields }; delete f.alt; return f; } };","export const B = { resolveFields: (d, { fields }) => { const { align: _x, ...rest } = fields; return rest; } };",'comp.resolveFields = (d, p) => Object.fromEntries(Object.entries(p.fields).filter(([k]) => k !== "x"));',"function cull(f) { delete f[key]; }","const shown = isShown(rule, values);","// delete f.alt in a comment is not a use",'export const ok = { fields: { alt: withShowWhen(text("Alt"), { field: "src", set: true }) } };',"export const C = { resolveFields: (d, { fields }) => { const out = {}; for (const [k, v] of Object.entries(fields)) if (keep.includes(k)) out[k] = v; return out; } };",'const p = <div>{settings.logo_position === "center" && <SettingRow label="Left items" />}</div>;','const q = <div>{props.on ? (<SettingsField label="x" />) : null}</div>;','const r = <div>{style ? <SettingsField label="Style" /> : null}</div>;',"export function withShowWhen(field, rule) { return { ...field, showWhen: rule }; }","const showWhenOf = (f) => f.showWhen;",'export { showWhenOf, withShowWhen } from "efficient-shell/format";'].join(`
2
+ `),o=g(t,{path:"lib/puck/blocks/general/X/fields.tsx",text:e}),d=o.bad.map(s=>s.line);i("R1 flags a delete in a resolveFields",o.bad.some(s=>s.line===1&&s.what.startsWith("R1"))),i("R1 flags a destructure that leaves a key out",o.bad.some(s=>s.line===2&&s.what.startsWith("R1"))),i("R1 flags a filter over Object.entries",o.bad.some(s=>s.line===3&&s.what.startsWith("R1"))),i("R2 flags a delete in a field file's helper",o.bad.some(s=>s.line===4&&s.what.startsWith("R2"))),i("R3 flags a second isShown(",o.bad.some(s=>s.line===5&&s.what.startsWith("R3"))),i("a comment is not a use, and a rule passes and is counted",!d.includes(6)&&!d.includes(7)&&o.attached===1),i("R4 flags an allow-list loop over Object.entries",o.bad.some(s=>s.line===8&&s.what.startsWith("R4"))),i("R5 flags a row drawn by a stored value (&&)",o.bad.some(s=>s.line===9&&s.what.startsWith("R5"))),i("R5 flags a row drawn by a stored value (? :)",o.bad.some(s=>s.line===10&&s.what.startsWith("R5"))),i("R5 lets a part the host passed through",!d.includes(11)),i("R6 flags a second withShowWhen (a function)",o.bad.some(s=>s.line===12&&s.what.startsWith("R6"))),i("R6 flags a second showWhenOf (a const arrow)",o.bad.some(s=>s.line===13&&s.what.startsWith("R6"))),i("R6 lets a re-export from the shell through",!d.includes(14));const x=g(t,{path:"lib/puck/cull.ts",text:"function cull(f) { delete f.x; }"},["lib/puck/cull.ts"]);i("R2 reads a file a repository names as a field file",x.bad.length===1);const n=g(t,{path:"lib/puck/part-styles.ts",text:"function f(next) { delete next[k]; }"});i("R2 reads only field files and passes (a delete of a VALUE elsewhere is not a hide)",n.bad.length===0);const w=g(t,{path:"lib/puck/show-when.ts",text:"isShown(rule, values);"});i("R3 lets the reader read",w.bad.length===0)}const f=l.filter(e=>/\.tsx?$/.test(e.path)&&!e.path.endsWith(".d.ts")),p=[],m=new Map;let u=0;for(const e of f){const o=g(t,e,c.alsoFieldFiles);u+=o.attached,m.set(e.path,o.bad.length),e.path in r||p.push(...o.bad)}i("no settings row hidden by hand: no delete or key-dropping filter in a resolver, field file or pass; one reader",p.length===0,`${p.length} hit(s):
3
+ `+p.map(e=>`${e.file}:${e.line} ${e.what}`).join(`
4
+ `)),i(`read the source (${f.length} TS/TSX files, at least ${c.minFiles})`,f.length>=c.minFiles),i(`found >= ${c.ruleFloor} rows given a rule (found ${u})`,u>=c.ruleFloor,"fewer means rows went back to a hand-written hide, or the reader broke");for(const e of c.alsoFieldFiles??[])i(`read the named field file ${e}`,f.some(o=>o.path===e));for(const[e,o]of Object.entries(r))i(`the exception is still real: ${e} (${o})`,(m.get(e)??0)>0,"remove the stale entry");return h()}export{T as oneShowWhen};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "efficient-web-builder",
3
- "version": "23.0.0",
3
+ "version": "23.1.0",
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",
@@ -44,8 +44,8 @@
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@tabler/icons-react": "^3.46.0",
47
- "efficient-easel": "^13.0.0",
48
- "efficient-shell": "^7.73.0",
47
+ "efficient-easel": "^13.1.1",
48
+ "efficient-shell": "^7.74.0",
49
49
  "isomorphic-dompurify": "^3.18.0",
50
50
  "next": ">=16",
51
51
  "react": ">=19",
@@ -64,8 +64,8 @@
64
64
  "@types/node": "^20",
65
65
  "@types/react": "^19",
66
66
  "@types/react-dom": "^19",
67
- "efficient-easel": "^13.0.0",
68
- "efficient-shell": "^7.73.0",
67
+ "efficient-easel": "^13.1.1",
68
+ "efficient-shell": "^7.74.0",
69
69
  "esbuild": "^0.28.0",
70
70
  "happy-dom": "^20.14.5",
71
71
  "isomorphic-dompurify": "^3.18.0",