efficient-web-builder 38.1.0 → 38.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/editor/components/SectionWidthRows.js +1 -1
- package/dist/editor/components/SourceStatus.js +1 -1
- package/dist/editor/lib/puck/blocks/general/ButtonBlock/fields.js +1 -1
- package/dist/format/lib/source-rules/panel-controls-medium.d.ts +12 -0
- package/dist/format/lib/source-rules/panel-controls-medium.js +4 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 38.2.0 - 2026-10-23
|
|
6
|
+
|
|
7
|
+
A MINOR: settings-panel controls are 32 tall, and the new tree-wide `panel-controls-medium` rule that a host runs over its own files (#274). Width › Background is efficient-easel's row (#268).
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Width › Background is efficient-easel's row** (Lewis 2026-10-23, "Email Section panel split": one row for both editors). `components/SectionWidthRows` `BackgroundWidthRow` hands this editor's words to easel's `SettingsBackgroundWidth` (19.1.0) and draws nothing of its own; `WIDTH_CHOICES` is easel's `BACKGROUND_WIDTHS`. The email designer draws the same part. The Site styles and section panels look and write as before. `efficient-easel` peer and dev range `^19.1.0`. The guards that read the row's element tree open the easel part's level (`test-site-style-reset-clears`, `test-site-styles-visual` §4), and `test-two-ways-to-add` §1 pins `^19.1.0`.
|
|
12
|
+
- **Settings-panel controls are 32 tall** (harness FT-6; Lewis 2026-10-12: small 24, medium 32, large 40; a settings panel's controls are 32). The source-status block's "Refresh now" and Edit buttons draw medium. The Button panel's Icon position strip fills the rest of its row in equal shares (Lewis 2026-10-23) and keeps its small chips in a 32 track.
|
|
13
|
+
|
|
14
|
+
### Not changed, on purpose
|
|
15
|
+
|
|
16
|
+
- The colour row's swatch and hex box stay small: at medium the row ran 71px past a 220 panel's edge (`test-shape-choices-are-pictures` §4). The part panel's palette swatches stay small too: a palette is a picture of the theme, like the corner picker (Lewis 2026-10-23: "the corner picker is a picture, not a row of controls"). Both are asked of the owner, and both are named in the guard below.
|
|
17
|
+
|
|
18
|
+
### Guards
|
|
19
|
+
|
|
20
|
+
- New tree-wide rule `lib/source-rules/panel-controls-medium` and `scripts/test-panel-controls-medium.ts` (red first: five small controls): a shell control drawn at `size="sm"` in editor chrome fails, except a text link, a segmented strip (its 32 track) and a file or control named with its reason. The storefront blocks run the same rule over their own files.
|
|
21
|
+
- `scripts/test-source-status-is-shared.tsx` reads the refresh button at medium.
|
|
22
|
+
|
|
5
23
|
## 38.1.0 - 2026-10-23
|
|
6
24
|
|
|
7
25
|
A MINOR: `WebsiteBackend.geocode`, the optional backend group behind "Find coordinates" (#271), and the tree-wide `one-map-module` rule. A host whose backend has no `geocode` draws no "Find coordinates" button. EFFICIENT's client (efficient-storefront-blocks `createEfficientBackend`) implements it in the release that takes this one.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsx as
|
|
1
|
+
"use client";import{jsx as t,jsxs as C}from"react/jsx-runtime";import{BACKGROUND_WIDTHS as g,SettingsBackgroundWidth as E,SettingsDefaultReset as b,SettingsField as O,SettingsSegmented as H}from"efficient-easel/panel";import{WidthIcon as W}from"efficient-easel/icons";import{PAGE_WIDTH as _}from"../../format/lib/content/page-width.js";import{WIDTH_CHOICE_LABEL as w}from"../../format/lib/content/section-width.js";import{say as o,editorEaselWords as m}from"../../format/lib/editor-words.js";import{Slider as D}from"../lib/puck/controls.js";const K="Width",S="Background",B="Content",L=g,p=e=>o(w[e==="content"?"page":"full"]),R=400,P="Content cannot be wider than the background.",M="The width of every section's background, until the section sets its own.",F="The width of every section's content, until the section sets its own.",q="On phones, content always fills the screen.";function z({value:e,changed:r,detail:s,onChange:l,onReset:a,disabled:c=!1,hint:n,labelId:d}){return t(E,{value:e,changed:r,detail:s,onChange:l,onReset:a,disabled:c,hint:n?o(n):void 0,labelId:d,words:{label:o(S),full:p("full"),content:p("content")},resetWords:m()?.panel})}function J({mode:e,px:r,max:s=_.max,min:l=R,changed:a,detail:c,onMode:n,onPx:d,onReset:x,fullClosed:I=!1,disabled:u=!1,hint:h,labelId:N}){const i=o(B),T=e!=="full"?t(D,{min:l,max:s,step:_.step,value:Math.min(Math.max(r,l),s),onChange:d,suffix:"px",ariaLabel:i,disabled:u}):null;return t(O,{label:i,labelId:N,icon:t(W,{}),changed:a,hint:h?o(h):void 0,action:t(b,{words:m()?.panel,label:i,changed:a,detail:c,onReset:x}),children:e===void 0||!n?T:C("div",{className:"flex w-full min-w-0 flex-col gap-2",children:[t(H,{options:L.map(f=>({id:f,label:p(f),disabled:f==="full"&&I})),value:e,onChange:n,ariaLabel:i,disabled:u}),T]})})}export{S as BACKGROUND_LABEL,z as BackgroundWidthRow,P as CONTENT_INSIDE_BACKGROUND_HINT,B as CONTENT_LABEL,J as ContentWidthRow,R as SECTION_CONTENT_MIN,M as SITE_BACKGROUND_HINT,F as SITE_CONTENT_HINT,L as WIDTH_CHOICES,K as WIDTH_GROUP_LABEL,q as WIDTH_PHONE_HINT};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsx as s,jsxs as
|
|
1
|
+
"use client";import{jsx as s,jsxs as i}from"react/jsx-runtime";import{Button as a}from"efficient-shell/ui";import{say as u}from"../../format/lib/editor-words.js";function f({status:l,statusText:o,onRefresh:n,busy:r=!1,edit:e,hint:c,spaced:t=!0}){return i("div",{className:"sf-blogsource",style:t?{marginTop:9}:void 0,children:[s("p",{className:"sf-blogsource-status","data-state":l,children:o}),s(a,{variant:"secondary",className:"sf-blogsource-action",onClick:n,disabled:r,children:u("Refresh now")}),e?s(a,{variant:"secondary",className:"sf-blogsource-action",onClick:e.onClick,children:e.label}):null,s("p",{className:"es-field-hint sf-hint-keep",children:c})]})}export{f as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e,jsxs as I}from"react/jsx-runtime";import{ButtonIcon as d,ButtonIconIcon as h,GroundIcon as g,IconAfterTextIcon as x,IconBeforeTextIcon as
|
|
1
|
+
import{jsx as e,jsxs as I}from"react/jsx-runtime";import{ButtonIcon as d,ButtonIconIcon as h,GroundIcon as g,IconAfterTextIcon as x,IconBeforeTextIcon as b,StylesIcon as y,TextColorIcon as C,WidthIcon as S}from"efficient-easel/icons";import*as T from"efficient-easel/icons";import{SettingsField as _}from"efficient-easel/panel";import{useSelectedFields as a}from"efficient-easel/easel";import{IconPicker as B,Segmented as F}from"efficient-shell/ui";import{StorefrontTextLinkField as O}from"../../../../../components/StorefrontLinkField.js";import{BUTTON_ICONS as k,buttonIconChoice as v,buttonIconSide as N}from"../../../../../../format/lib/content/button-icon.js";import{say as l}from"../../../../../../format/lib/editor-words.js";import{FOLLOWS_SITE_BUTTON as z,FOLLOWS_SITE_BUTTON_INK as L,colorInput as m,group as w,inOwnBox as j,linkField as P,plainText as U,section as u,segmented as f}from"../../../controls.js";import{NoControlMark as W,noControlField as A}from"efficient-easel/screen/lib/puck/panel-no-control-mark";import{alignField as E}from"../../shared-fields.js";const s=o=>({...A({type:"custom",render:()=>e(W,{})},"always"),reachedBy:o});function G(o){const n=(o==="none"?"no-symbol":o).replace(/(^|-)([a-z])/g,(r,c,p)=>p.toUpperCase()),t=T[`${n}Icon`];return t?e(t,{size:16}):null}const K=k.map(o=>({value:o,icon:G(o)}));function M({value:o,onChange:i}){const n=a(),t=n.values.href,r=typeof o=="string"?o:o&&typeof o=="object"&&"text"in o?String(o.text??""):"";return e(O,{label:"Button",icon:e(d,{size:18}),text:r,onTextChange:i,link:typeof t=="string"?t:"",onLinkChange:c=>n.onChange("href",c)})}function R({value:o,onChange:i}){const n=a(),t=v(o,n.values.arrow),r=N(n.values.iconPosition);return e(_,{label:l("Icon"),icon:e(h,{size:18}),children:I("div",{className:"flex items-center gap-2",children:[e(B,{label:l("Icon"),value:t,emptyValue:"none",options:K,onChange:i}),t!=="none"&&e(F,{size:"sm",block:!0,label:l("Icon position"),value:r,onChange:c=>n.onChange("iconPosition",c),options:[{value:"before",icon:e(b,{size:16}),label:e("span",{className:"sr-only",children:l("Icon before text")}),title:l("Icon before text")},{value:"after",icon:e(x,{size:16}),label:e("span",{className:"sr-only",children:l("Icon after text")}),title:l("Icon after text")}]})]})})}const eo={fields:{label:j(U("Button"),o=>e(M,{value:o.value,onChange:o.onChange})),href:{...P("Button link"),...s("label")},_dSize:u("Size"),widthFit:f("Width",e(S,{}),[{label:"Fit content",value:"fit"},{label:"Fill",value:"fill"}],"fit"),_gStyle:w("Style"),variant:f("Style",e(y,{}),[{label:"Filled",value:"primary"},{label:"Outline",value:"ghost"}],"primary"),icon:{type:"custom",render:o=>e(R,{value:o.value,onChange:o.onChange})},iconPosition:s("icon"),arrow:s("icon"),buttonColor:m("Fill color",z,e(g,{})),buttonTextColor:m("Text color",L,e(C,{})),_dLayout:u("Layout"),align:E()}};export{eo as ButtonBlockFields};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type * as TS from "typescript";
|
|
2
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
3
|
+
export type PanelControlsMediumOptions = {
|
|
4
|
+
readonly notPanel?: Readonly<Record<string, string>>;
|
|
5
|
+
readonly keepSmall?: Readonly<Record<string, {
|
|
6
|
+
readonly count: number;
|
|
7
|
+
readonly why: string;
|
|
8
|
+
}>>;
|
|
9
|
+
readonly minFiles: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const LADDER_CONTROLS: ReadonlySet<string>;
|
|
12
|
+
export declare function panelControlsMedium(ts: typeof TS, files: readonly SourceFile[], options: PanelControlsMediumOptions): RuleReport;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{ruleChecks as k}from"./rule.js";const $=new Set(["Input","Select","NativeSelect","InputNumber","IconButton","Button","ColorPicker","ColorSwatch","HexInput","TextLinkField"]);function d(i,a){const o=i.createSourceFile(a.path,a.text,i.ScriptTarget.Latest,!0,i.ScriptKind.TSX),n=[],p=l=>{if(i.isJsxOpeningElement(l)||i.isJsxSelfClosingElement(l)){const m=l.tagName.getText(o);if($.has(m)){let r=!1,c=!1;for(const t of l.attributes.properties){if(!i.isJsxAttribute(t)||!t.initializer)continue;const e=t.name.getText(o),s=t.initializer.getText(o).replace(/^\{\s*|\s*\}$/g,"");e==="size"&&/^["'`]sm["'`]$/.test(s)&&(r=!0),e==="variant"&&/^["'`]link["'`]$/.test(s)&&(c=!0)}if(r&&!c){const t=o.getLineAndCharacterOfPosition(l.getStart(o)).line+1;n.push({file:a.path,line:t,tag:m})}}}i.forEachChild(l,p)};return p(o),n}function S(i,a,o){const{check:n,report:p}=k("panel controls are medium"),l=o.notPanel??{},m=o.keepSmall??{};{const t=['const a = <Input size="sm" value={v} />;','const b = <IconButton size={"sm"} aria-label="Bold" />;','const c = <Button variant="link" size="sm">Edit</Button>;','const d = <Segmented size="sm" options={o} />;','// a comment about <Input size="sm"> is not a use',"const e = <Input value={v} />;",'const f = <ColorPicker size="md" value={v} />;','const g = <TextLinkField size="sm" />;'].join(`
|
|
2
|
+
`),e=d(i,{path:"fixture.tsx",text:t}).map(s=>s.line);n('M1 flags <Input size="sm">',e.includes(1)),n('M1 flags <IconButton size={"sm"}>',e.includes(2)),n("a text link is not a control on the ladder",!e.includes(3)),n("a segmented strip keeps small chips in its 32 track",!e.includes(4)),n("a comment is not a use",!e.includes(5)),n("a control with no size (the shell's medium) passes",!e.includes(6)),n("a medium control passes",!e.includes(7)),n('M1 flags <TextLinkField size="sm">',e.includes(8))}const r=a.filter(t=>/\.tsx?$/.test(t.path)&&!t.path.endsWith(".d.ts")),c=[];for(const t of r){if(t.path in l)continue;const e=d(i,t);for(const s of e){const u=m[`${s.file}#${s.tag}`],h=e.filter(f=>f.tag===s.tag).length;(!u||h>u.count)&&c.push(s)}}n('every settings-panel control is medium (32): no shell control at size="sm"',c.length===0,`${c.length} hit(s):
|
|
3
|
+
`+c.map(t=>`${t.file}:${t.line} <${t.tag} size="sm">`).join(`
|
|
4
|
+
`)),n(`read the source (${r.length} TS/TSX files, at least ${o.minFiles})`,r.length>=o.minFiles);for(const[t,e]of Object.entries(l)){const s=a.find(h=>h.path===t),u=!!s&&d(i,s).length>0;n(`the exception is still real: ${t} (${e})`,u,"remove the stale entry")}for(const[t,{count:e,why:s}]of Object.entries(m)){const[u,h]=t.split("#"),f=a.find(g=>g.path===u),x=f?d(i,f).filter(g=>g.tag===h).length:0;n(`the kept small controls are still real: ${t} × ${e} (${s})`,x===e,`found ${x}; fix the entry`)}return p()}export{$ as LADDER_CONTROLS,S as panelControlsMedium};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "efficient-web-builder",
|
|
3
|
-
"version": "38.
|
|
3
|
+
"version": "38.2.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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@tabler/icons-react": "^3.46.0",
|
|
56
|
-
"efficient-easel": "^19.
|
|
56
|
+
"efficient-easel": "^19.1.0",
|
|
57
57
|
"efficient-shell": "^8.39.0",
|
|
58
58
|
"isomorphic-dompurify": "^3.18.0",
|
|
59
59
|
"next": ">=16",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@types/node": "^20",
|
|
73
73
|
"@types/react": "^19",
|
|
74
74
|
"@types/react-dom": "^19",
|
|
75
|
-
"efficient-easel": "^19.
|
|
75
|
+
"efficient-easel": "^19.1.0",
|
|
76
76
|
"efficient-shell": "^8.39.0",
|
|
77
77
|
"esbuild": "^0.28.0",
|
|
78
78
|
"happy-dom": "^20.14.5",
|