efficient-web-builder 4.0.0 → 5.0.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
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.0.1 - 2026-10-11
4
+
5
+ - **A custom face's Style box is read as it is seen.** For a text part whose
6
+ face is outside the site styles, the closed Style box shows "Custom" while
7
+ its list's first row is "No style". A screen reader read the row's label, so
8
+ the box was seen as "Custom" and heard as "No style". It is "Custom" for
9
+ both now (`PartStyleSelect` hands `FieldSelect` a `valueText`). Nothing
10
+ changes on screen.
11
+ - Takes effect with efficient-easel 2.1.0 and efficient-shell 4.180.0. The
12
+ peer ranges are unchanged (`^2.0.0`, `^4.171.0`): with an older easel or
13
+ shell the option's label is still what is read.
14
+
15
+ ## 5.0.0 - 2026-10-11
16
+
17
+ - **Breaking: efficient-easel ^2.0.0 and efficient-shell ^4.171.0** (peers;
18
+ were ^1.0.0 and ^4.165.0; Efficient-Pty-Ltd/efficient-easel#211, harness
19
+ UI-24 and UI-7). Easel 2.0.0 takes its glyphs from the shell's house set
20
+ (`efficient-shell/icons/efficient`) and its row and field corners from the
21
+ shell's role radius, and needs shell 4.171.0 for both. A host moves to this
22
+ release, easel 2.0.0 and a shell at or above 4.171.0 together.
23
+ - **Nothing in this package's own API changes.** The editor draws the same
24
+ glyphs (the set's data moved to the shell byte for byte) and the same
25
+ corners at the default theme; the theme's Corners setting now reaches the
26
+ editor's rows and fields through the role tokens. No guard had to change:
27
+ the 140 guards pass against easel 2.0.0 as they stood.
28
+
3
29
  ## 4.0.0 - 2026-10-11
4
30
 
5
31
  - **Breaking: the site's corner tokens are `--site-radius-*`.**
package/README.md CHANGED
@@ -45,27 +45,43 @@ Comments that name a file say `lib/puck/controls`; read them from `src/`.
45
45
 
46
46
  ## How the storefront uses it
47
47
 
48
- The storefront compiles these sources: its commerce and service blocks are
49
- written with the builder's control builders and passes, so it needs the source,
50
- not a bundle. It keeps a **vendored copy** of this repository's `src/` at
51
- `packages/web-builder/src`, pinned to a commit here, and resolves `@/…` in its
52
- own tree first and in the copy second.
53
-
54
- - A change to the builder is made HERE. The storefront then moves its pin
55
- (`node scripts/sync-web-builder.mjs <ref>` there), in a pull request that
56
- runs the storefront's whole suite over the new copy.
57
- - The copy is never edited in place: the storefront's check fails when that
58
- folder is not byte-for-byte the `src/` tree of the pinned commit.
48
+ The storefront installs this repository's npm package, like any host, at one
49
+ exact version in its `package.json` (efficient-storefront `doc/adr/0011`,
50
+ 2026-10-11). Its commerce and service blocks are written with the builder's
51
+ control builders and passes, which they import by path: there `@/…` resolves in
52
+ the storefront's own tree first, then in its blocks, then in
53
+ `node_modules/efficient-web-builder/dist`.
54
+
55
+ - **A change to the builder is made HERE and reaches the storefront as a
56
+ release.** Merge it, cut a GitHub Release `vX.Y.Z` (which publishes to npm),
57
+ move efficient-storefront-blocks to that release, then open one storefront
58
+ pull request that moves the version in its `package.json` and its blocks pin
59
+ together. The storefront's check fails while it and the blocks' repository
60
+ install two different builders.
61
+ - **To try a branch against the storefront's suite before a release**, install
62
+ its build there as a tarball:
63
+
64
+ ```bash
65
+ pnpm run build && pnpm pack --pack-destination /tmp/builder-trial
66
+ # in an efficient-storefront worktree
67
+ pnpm add "efficient-web-builder@file:/tmp/builder-trial/efficient-web-builder-<version>.tgz"
68
+ just check
69
+ ```
70
+
71
+ Exactly one storefront guard fails on a trial, the one that will not let an
72
+ unreleased builder merge.
73
+ - **What a host receives is compiled.** A function's own name is a letter in
74
+ `dist/`, so a host's test cannot find a component by that name: it imports
75
+ the component and compares, or asks what it draws. Export what a host has to
76
+ be able to find.
59
77
 
60
78
  So this repository's CI proves the builder stands alone, and the storefront's
61
- proves it still does what it did. Most of the builder's behavioural guards live
62
- in the storefront for now, because they run the editor assembled with a host.
63
-
64
- The copy is on its way out ([`doc/adr/0002`](doc/adr/0002-published-from-here-compiled.md)).
65
- Some 280 of the storefront's guards read these sources by path, and the package
66
- below carries no source. Once the guards that are the builder's alone live
67
- here, the storefront installs the package like any host and deletes
68
- `packages/web-builder`.
79
+ proves it still does what it did, on the package. The guards that run the
80
+ editor assembled with a host stay in the host's repository.
81
+
82
+ From 2026-10-07 to 2026-10-11 the storefront kept a vendored copy of `src/`
83
+ instead, pinned to a commit here ([`doc/adr/0001`](doc/adr/0001-extracted-from-the-storefront.md),
84
+ [`doc/adr/0002`](doc/adr/0002-published-from-here-compiled.md)).
69
85
 
70
86
  ## Published as `efficient-web-builder`
71
87
 
@@ -1 +1 @@
1
- "use client";import{jsx as t,jsxs as r,Fragment as O}from"react/jsx-runtime";import{useEffect as M,useId as _e,useRef as J,useState as Q}from"react";import{PartResetDot as fe}from"./PartResetDot.js";import{FieldSelect as pe,InspectorField as $e}from"efficient-easel/fields";import{SettingsField as me,SettingsGroup as Ae}from"efficient-easel/panel";import{InspectorUpLevel as G}from"efficient-easel/chrome";import{StylesIcon as Le,TextColorIcon as ze,TypeIcon as he}from"efficient-easel/icons";import{useEasel as Be}from"efficient-easel/easel";import{Tooltip as De}from"efficient-shell/ui";import{PartInkControl as Ve}from"./PartInkControl.js";import{PartTextBox as Me}from"./PartTextBox.js";import ye from"./UiIcon.js";import{usePreviewHeader as Ge}from"./PreviewHeaderContext.js";import{usePartStyle as We}from"./PreviewPartStyleContext.js";import{readHidable as W,textFieldOffPage as je}from"../lib/puck/controls.js";import{TEXT_FIELD_MARKER as He}from"../lib/puck/field-markers.js";import{useSectionOps as Ue}from"../lib/puck/use-section-ops.js";import{FONT_OPTIONS as Ke,FONT_ROLES as Z,FONT_ROLE_LABELS as ee,fontOption as _}from"../lib/fonts.js";import{isMultilineKind as Xe,partPlaceholder as Ye,typeInPlaceKind as qe}from"../lib/puck/part-text.js";import{partRowField as Je}from"../lib/puck/part-row-field.js";import{decodePartKey as Qe,partPropsWithRowValue as Ze,partRowValue as et}from"../lib/puck/part-rows.js";import{PART_WIDTH_MAX as tt,propsWithPartStyleValue as te,propsWithoutPartStyle as nt,readPartStyle as ge}from"../lib/puck/part-styles.js";import{partInks as ot}from"../lib/puck/part-inks.js";import{styleKeyForPart as st,TEXT_KIND_LABELS as lt}from"../lib/sections/section-text-kinds.js";import{commitPartText as rt}from"../lib/puck/part-typing.js";import{useEditorHost as it}from"../lib/editor-host.js";import{SOURCE_LINES as at,SOURCE_ACTIONS as ct,SOURCE_ACTION_LABEL as dt,partSourceOf as ut,sourceSlugOf as be}from"../lib/puck/source-bound-parts.js";import{encodePartId as ft,holdsAuthoredText as pt}from"../lib/puck/text-parts.js";import{regionsForSection as mt}from"../lib/sections/section-text-regions.js";import{textRegionOfPartKey as ht}from"../lib/sections/text-regions.js";const yt={400:"Regular",500:"Medium",600:"Semibold",700:"Bold"},Se=[{key:"font",label:"Font",group:"Style"},{key:"color",label:"Color",group:"Style"}],gt=[...new Set(Se.map(e=>e.group))],bt={font:t(he,{}),color:t(ze,{})};function $(e){return e.kind==="role"?ee[e.role]:e.kind==="custom"?"Custom":null}const St=e=>e.kind==="role"?`role:${e.role}`:e.kind;function xt(e){const n=e.role;return n&&Z.includes(n)?{kind:"role",role:n}:null}function Nt(e,n){if(!e)return{kind:"unread"};const{faceRole:s,base:l}=e;if(n[s]===l.fontKey)return{kind:"role",role:s};const a=Object.keys(n).filter(c=>n[c]===l.fontKey);return a.length===1?{kind:"role",role:a[0]}:{kind:"custom"}}function ne(e,n,s,l){return xt(ge(e,n))??Nt(s,l)}function un(e,n,s,l){return $(ne(e,n,s,l))}function oe(e,n){if(!e.length)return null;const s=n(e[0]);return e.every(l=>n(l)===s)?s:null}function vt(e){return Object.values(e).filter(n=>n!==void 0).length}function Pt(e,n){const s=e.style[n];return n==="color"?s??"":s!==void 0?String(s):e.base.fontKey}function kt({face:e,base:n,onPick:s}){const[l,a]=Q(!1),c=J(null),h=J(null),y=_e();M(()=>{if(!l)return;const i=T=>{c.current&&!c.current.contains(T.target)&&a(!1)};return document.addEventListener("mousedown",i),()=>document.removeEventListener("mousedown",i)},[l]);const u=e.kind==="role"?e.role:null,v=i=>{s(i),a(!1),h.current?.focus()};return r("div",{className:"sf-part-style-pick",ref:c,children:[r("button",{ref:h,type:"button",className:"sf-part-style-trigger","aria-expanded":l,"aria-controls":y,"aria-label":`Follows ${$(e)??"no style"} — change`,onClick:()=>a(i=>!i),onKeyDown:i=>{i.key==="Escape"&&l&&(i.preventDefault(),a(!1))},children:[t("span",{className:"sf-part-style-name",style:{fontFamily:n?.fontKey?`var(${_(n.fontKey)?.cssVar})`:void 0,fontWeight:n?.weight},children:$(e)}),t("span",{className:"sf-part-style-sign","aria-hidden":"true"})]}),l&&r("div",{className:"sf-multi-menu sf-part-style-menu",id:y,role:"listbox",onKeyDown:i=>{i.key==="Escape"&&(i.preventDefault(),a(!1),h.current?.focus())},children:[r("button",{type:"button",role:"option","aria-selected":u===null,className:`sf-part-style-option${u===null?" is-on":""}`,onClick:()=>v(null),children:[t("span",{className:"sf-part-style-option-name",children:"No style"}),t("span",{className:"sf-note",children:"Section’s own"})]}),Z.map(i=>t("button",{type:"button",role:"option","aria-selected":u===i,className:`sf-part-style-option${u===i?" is-on":""}`,onClick:()=>v(i),children:t("span",{className:"sf-part-style-option-name",style:{fontFamily:`var(--font-role-${i})`},children:ee[i]})},i))]})]})}const j="";function wt({face:e,base:n,onPick:s}){const l=e.kind==="role"?e.role:j,a=[{label:"No style",value:j},...Z.map(c=>({label:ee[c],value:c}))];return t(pe,{ariaLabel:"Style",value:l,options:a,inline:!0,onChange:c=>s(c===j?null:c),renderOption:c=>c.value===j?r(O,{children:[t("span",{className:"es-fsel-label sf-part-style-keep",children:c.label}),t("span",{className:"sf-part-style-aside","aria-hidden":"true",children:"Section’s own"})]}):t("span",{className:"es-fsel-label",style:{fontFamily:`var(--font-role-${c.value})`},children:c.label}),renderValue:()=>t("span",{style:{fontFamily:n?.fontKey?`var(${_(n.fontKey)?.cssVar})`:void 0,fontWeight:n?.weight},children:$(e)})})}function Tt({parts:e,onEditStyle:n,onClearAll:s,onFollowStyle:l,kindLabel:a}){const h=oe(e,T=>St(T.face))?e[0].face:null,y=h?$(h):null,u=h&&h.kind!=="unread"?e[0].base:null,v=e.reduce((T,A)=>T+vt(A.style),0),i=u?_(u.fontKey)?.label:void 0;return r("div",{className:`sf-part-style${v?" has-overrides":""}`,children:[t("div",{className:"sf-part-style-caption",children:"Style"}),e.length>1&&r("div",{className:"sf-part-style-count",children:[e.length," selected"]}),e.length===1&&a&&r("div",{className:"sf-part-style-count",children:[a," · every row"]}),y&&u?r(O,{children:[l&&h?t(kt,{face:h,base:u,onPick:l}):t("div",{className:"sf-part-style-name",style:{fontFamily:i?`var(${_(u.fontKey)?.cssVar})`:void 0,fontWeight:u.weight},children:y}),t("div",{className:"sf-part-style-meta",children:[i,`${Math.round(u.sizePx)} px`,yt[u.weight]].filter(Boolean).join(" · ")})]}):t("div",{className:"sf-part-style-name",children:"Mixed styles"}),r("div",{className:"sf-part-style-top",children:[y?v?r(O,{children:[r("span",{className:"sf-part-chip",children:[v," changed"]}),t("button",{type:"button",className:"sf-part-link",onClick:s,children:"Clear"})]}):t("span",{className:"sf-part-style-state",children:"This text follows the site style."}):t("span",{className:"sf-part-style-state",children:"Changes apply to all selected."}),t("span",{className:"sf-part-style-gap"}),y&&n&&t("button",{type:"button",className:"sf-part-link",onClick:n,children:"Edit"})]})]})}function Et({sectionName:e,onBack:n,textEditor:s,styleRow:l,children:a}){return r("div",{className:"sf-part-panel",children:[t(G,{inline:!0,name:e,onSelect:n}),s,l,a]})}function xe({source:e,slug:n}){const s=it(),l=ct[e],a=!l.needsSlug||!!n;return t("div",{className:"sf-note sf-note--source",children:r("p",{className:"es-field-hint sf-hint-keep",children:[at[e],a?r(O,{children:[" ",t("button",{type:"button",className:"sf-source-open",onClick:()=>{l.type==="storefront-editor:open-menus"?s.send(l.type):n&&s.send(l.type,{slug:n})},children:dt})]}):null]})})}const Ne=4,Rt=120;function Ct({region:e,sizePx:n,face:s,base:l,onFollowStyle:a,source:c,slug:h,onBack:y}){const u=e.follows;return r("div",{className:"sf-note",children:[t("p",{children:t("strong",{children:e.label})}),r("p",{children:["Its size follows Site styles ▸ ",u,n?` — ${Math.round(n)}px right now`:"","."]}),a?t(O,{children:t(me,{label:"Style",icon:t(Le,{}),hint:r(O,{children:["Every ",e.label.toLowerCase()," in this section follows it. The face and the size change; the letter case is the section’s own and stays as it is."]}),children:t(wt,{face:s,base:l,onPick:a})})}):t("p",{className:"es-field-hint sf-hint-keep",children:"Only the size follows a site style. This section sets its own font, weight and letter case."}),c?t(xe,{source:c,slug:h}):null,t(G,{inline:!0,name:"Page",onSelect:y})]})}function Ft({onBack:e}){return r("div",{className:"sf-note",children:[t("p",{children:t("strong",{children:"This part is no longer here."})}),t("p",{children:"The section it belonged to has been changed or removed."}),t(G,{inline:!0,name:"Page",onSelect:e})]})}function Ot({onBack:e,partId:n,why:s}){return r("div",{className:"sf-note",children:[t("p",{children:t("strong",{children:"This part's settings did not open."})}),t("p",{children:"The words are still on your page and nothing has been lost. The editor could not work out which control belongs to them, which is a fault on our side. Go back and click the section to open its settings instead."}),t("p",{className:"sf-note-detail",children:r("code",{children:[n," — ",s]})}),t(G,{inline:!0,name:"Page",onSelect:e})]})}function It({nodeId:e,propKey:n,onBack:s,onEditStyle:l}){const{getItemById:a,getSelectorForId:c,config:h}=Be(o=>o),y=Ue(),{partStyle:u}=We(),{fonts:v}=Ge(),[i,T]=Q({}),A=J(null),g=a(e),S=c(e),se=g?h.components[g.type]:void 0,{key:E,row:P}=Qe(n),le=se?.fields?.[E],k=P?Je(le,P):le,I=ft(e,n),p=g?.props??{},w=qe(k),ve=je(k),Pe=pt(k),H=g?ut(g.type):null,U=P?et(p[E],P):p[E],re=!!P&&!(Array.isArray(p[E])&&P.index<p[E].length);M(()=>{A.current?.querySelector(".es-copy [contenteditable='true'], .es-copy textarea, .sf-copybox textarea, .sf-copybox input")?.focus()},[I]);const[K,ie]=Q(0);M(()=>ie(0),[I]);const X=!!g&&!re&&(!S||!k);M(()=>{if(!X||K>=Ne)return;const o=setTimeout(()=>ie(d=>d+1),Rt);return()=>clearTimeout(o)},[X,K]);const ke=X&&K<Ne;if(!g||re)return t(Ft,{onBack:s});const L=mt(g.type),ae=L?ht(L,n):null;if(L&&ae){const o=u?.partId===I?u:null,d=L[ae],m=d.writable===!0&&!!S,b=f=>{S&&y.replaceComponent(S.zone,S.index,{...g,props:te(p,n,"role",f)})};return t(Ct,{region:d,sizePx:o?.base.sizePx??null,face:ne(p,n,o,v),base:o?.base??null,onFollowStyle:m?b:void 0,source:H,slug:be(p),onBack:s})}if(!S||!k)return ke?t("div",{className:"sf-note","aria-busy":"true"}):t(Ot,{onBack:s,partId:I,why:S?"no field":"no selector"});const z=o=>{y.replaceComponent(S.zone,S.index,{...g,props:o})},Y=o=>{if(W(o).hidden!==W(U).hidden||!w){const m=P?Ze(p,E,P,o):{...p,[E]:o};m&&z(m);return}rt({node:g,propKey:n,kind:w,zone:S.zone,index:S.index},W(o).text,y.replaceComponent)},we=U,B=st(g?.type,n),Te=ge(p,B),D=u?.partId===I?u:null,x=[{partKey:B,face:ne(p,B,D,v),sizeGroup:D?.sizeGroup??null,base:D?.base??_t,style:Te}],Ee=oe(x,o=>o.sizeGroup),ce=ot(D?.inks),V=(o,d)=>{let m=p;for(const b of x)m=te(m,b.partKey,o,d);z(m)},Re=o=>V("role",o),Ce=()=>{let o=p;for(const d of x)o=nt(o,d.partKey);z(o)},Fe=o=>{let d=p;for(const m of x)for(const[b,f]of Object.entries(o))d=te(d,m.partKey,b,f??null);z(d)},Oe=(o,d)=>{const m=x.some(N=>N.style[o]!==void 0),b=oe(x,N=>Pt(N,o)),f=b===null,F=`sf-part-${e}-${n}-${o}`;return o==="color"?t(Ve,{value:f?null:String(b),label:d,colors:ce,onChange:N=>V("color",N)},o):t(me,{label:d,icon:bt[o],className:"sf-part-field",changed:m,htmlFor:F,action:m?t(fe,{label:d,onClear:()=>V(o,null)}):void 0,children:o==="font"?t(pe,{id:F,ariaLabel:d,value:!f&&_(String(b))?String(b):void 0,placeholder:f?"Mixed":"Site style",options:Ke.map(N=>({label:N.label,value:N.key})),onChange:N=>V("font",N||null)}):null},o)},de=w==="text"||w==="textarea",{text:ue,hidden:R}=W(U),C=k?.[He]?.label??n,Ie=r("div",{className:"sf-field-head",children:[t(he,{}),t("span",{className:"sf-field-label sf-field-label--inline",children:C}),de&&t(De,{content:R?`Show ${C} on the slide`:`Hide ${C} on the slide`,children:t("button",{type:"button",className:"sf-field-eye","aria-pressed":R,"aria-label":R?`Show ${C} on the slide`:`Hide ${C} on the slide`,onClick:()=>Y({text:ue,hidden:!R}),children:R?t(ye,{name:"eye-off",size:14}):t(ye,{name:"eye",size:14})})})]}),q=!ve&&Pe;return r(Et,{sectionName:se?.label??g.type,onBack:s,textEditor:r("div",{ref:A,className:"sf-part-text",children:[w?t(Me,{text:ue,onTextChange:o=>Y(de?{text:o,hidden:R}:o),multiline:Xe(w),markup:w!=="plainText",styled:q,style:x[0].style,base:x[0].base,setValue:Fe,colors:ce.map(o=>({name:o.key,label:o.name,value:o.value})),placeholder:Ye(k),ariaLabel:C,head:Ie}):t($e,{field:k,name:n,value:we,onChange:Y,id:`sf-part-${e}-${n}`}),q&&w&&Ee===null&&t("p",{className:"es-field-hint sf-hint-keep",children:"Fixed size — the site’s Text setting doesn’t resize this."})]}),children:[H&&t(xe,{source:H,slug:be(p)}),q&&gt.map(o=>{const d=Se.filter(f=>f.group===o),m=d.filter(f=>x.some(F=>F.style[f.key]!==void 0)).length,b=`sf-part-g-${e}-${n}-${o.replace(/\W+/g,"-")}`;return r(Ae,{id:b,title:o,open:i[o]!==!0,onToggle:f=>T(F=>({...F,[o]:!f})),count:m,children:[t(Tt,{parts:x,onEditStyle:l,onClearAll:Ce,onFollowStyle:Re,kindLabel:lt[B]}),d.map(f=>Oe(f.key,f.label))]},o)})]})}const _t={fontKey:"",sizePx:16,weight:400,lineRatio:1.4,letterEm:0,align:"left",case:"none",paddingPx:0,widthPx:tt};export{Se as PART_FIELDS,Et as PartPanelBody,fe as PartResetDot,xe as PartSourceNote,Tt as PartStyleCard,wt as PartStyleSelect,It as default,ne as partFace,un as partStyleName};
1
+ "use client";import{jsx as t,jsxs as r,Fragment as O}from"react/jsx-runtime";import{useEffect as M,useId as _e,useRef as J,useState as Q}from"react";import{PartResetDot as fe}from"./PartResetDot.js";import{FieldSelect as pe,InspectorField as $e}from"efficient-easel/fields";import{SettingsField as me,SettingsGroup as Ae}from"efficient-easel/panel";import{InspectorUpLevel as G}from"efficient-easel/chrome";import{StylesIcon as Le,TextColorIcon as ze,TypeIcon as he}from"efficient-easel/icons";import{useEasel as Be}from"efficient-easel/easel";import{Tooltip as De}from"efficient-shell/ui";import{PartInkControl as Ve}from"./PartInkControl.js";import{PartTextBox as Me}from"./PartTextBox.js";import ye from"./UiIcon.js";import{usePreviewHeader as Ge}from"./PreviewHeaderContext.js";import{usePartStyle as We}from"./PreviewPartStyleContext.js";import{readHidable as W,textFieldOffPage as je}from"../lib/puck/controls.js";import{TEXT_FIELD_MARKER as He}from"../lib/puck/field-markers.js";import{useSectionOps as Ue}from"../lib/puck/use-section-ops.js";import{FONT_OPTIONS as Ke,FONT_ROLES as Z,FONT_ROLE_LABELS as ee,fontOption as $}from"../lib/fonts.js";import{isMultilineKind as Xe,partPlaceholder as Ye,typeInPlaceKind as qe}from"../lib/puck/part-text.js";import{partRowField as Je}from"../lib/puck/part-row-field.js";import{decodePartKey as Qe,partPropsWithRowValue as Ze,partRowValue as et}from"../lib/puck/part-rows.js";import{PART_WIDTH_MAX as tt,propsWithPartStyleValue as te,propsWithoutPartStyle as nt,readPartStyle as ge}from"../lib/puck/part-styles.js";import{partInks as ot}from"../lib/puck/part-inks.js";import{styleKeyForPart as st,TEXT_KIND_LABELS as lt}from"../lib/sections/section-text-kinds.js";import{commitPartText as rt}from"../lib/puck/part-typing.js";import{useEditorHost as it}from"../lib/editor-host.js";import{SOURCE_LINES as at,SOURCE_ACTIONS as ct,SOURCE_ACTION_LABEL as dt,partSourceOf as ut,sourceSlugOf as be}from"../lib/puck/source-bound-parts.js";import{encodePartId as ft,holdsAuthoredText as pt}from"../lib/puck/text-parts.js";import{regionsForSection as mt}from"../lib/sections/section-text-regions.js";import{textRegionOfPartKey as ht}from"../lib/sections/text-regions.js";const yt={400:"Regular",500:"Medium",600:"Semibold",700:"Bold"},Se=[{key:"font",label:"Font",group:"Style"},{key:"color",label:"Color",group:"Style"}],gt=[...new Set(Se.map(e=>e.group))],bt={font:t(he,{}),color:t(ze,{})};function I(e){return e.kind==="role"?ee[e.role]:e.kind==="custom"?"Custom":null}const St=e=>e.kind==="role"?`role:${e.role}`:e.kind;function xt(e){const n=e.role;return n&&Z.includes(n)?{kind:"role",role:n}:null}function vt(e,n){if(!e)return{kind:"unread"};const{faceRole:s,base:l}=e;if(n[s]===l.fontKey)return{kind:"role",role:s};const c=Object.keys(n).filter(i=>n[i]===l.fontKey);return c.length===1?{kind:"role",role:c[0]}:{kind:"custom"}}function ne(e,n,s,l){return xt(ge(e,n))??vt(s,l)}function un(e,n,s,l){return I(ne(e,n,s,l))}function oe(e,n){if(!e.length)return null;const s=n(e[0]);return e.every(l=>n(l)===s)?s:null}function Nt(e){return Object.values(e).filter(n=>n!==void 0).length}function Pt(e,n){const s=e.style[n];return n==="color"?s??"":s!==void 0?String(s):e.base.fontKey}function kt({face:e,base:n,onPick:s}){const[l,c]=Q(!1),i=J(null),h=J(null),y=_e();M(()=>{if(!l)return;const a=T=>{i.current&&!i.current.contains(T.target)&&c(!1)};return document.addEventListener("mousedown",a),()=>document.removeEventListener("mousedown",a)},[l]);const u=e.kind==="role"?e.role:null,N=a=>{s(a),c(!1),h.current?.focus()};return r("div",{className:"sf-part-style-pick",ref:i,children:[r("button",{ref:h,type:"button",className:"sf-part-style-trigger","aria-expanded":l,"aria-controls":y,"aria-label":`Follows ${I(e)??"no style"} — change`,onClick:()=>c(a=>!a),onKeyDown:a=>{a.key==="Escape"&&l&&(a.preventDefault(),c(!1))},children:[t("span",{className:"sf-part-style-name",style:{fontFamily:n?.fontKey?`var(${$(n.fontKey)?.cssVar})`:void 0,fontWeight:n?.weight},children:I(e)}),t("span",{className:"sf-part-style-sign","aria-hidden":"true"})]}),l&&r("div",{className:"sf-multi-menu sf-part-style-menu",id:y,role:"listbox",onKeyDown:a=>{a.key==="Escape"&&(a.preventDefault(),c(!1),h.current?.focus())},children:[r("button",{type:"button",role:"option","aria-selected":u===null,className:`sf-part-style-option${u===null?" is-on":""}`,onClick:()=>N(null),children:[t("span",{className:"sf-part-style-option-name",children:"No style"}),t("span",{className:"sf-note",children:"Section’s own"})]}),Z.map(a=>t("button",{type:"button",role:"option","aria-selected":u===a,className:`sf-part-style-option${u===a?" is-on":""}`,onClick:()=>N(a),children:t("span",{className:"sf-part-style-option-name",style:{fontFamily:`var(--font-role-${a})`},children:ee[a]})},a))]})]})}const j="";function wt({face:e,base:n,onPick:s}){const l=e.kind==="role"?e.role:j,c=[{label:"No style",value:j},...Z.map(i=>({label:ee[i],value:i}))];return t(pe,{ariaLabel:"Style",value:l,options:c,inline:!0,onChange:i=>s(i===j?null:i),renderOption:i=>i.value===j?r(O,{children:[t("span",{className:"es-fsel-label sf-part-style-keep",children:i.label}),t("span",{className:"sf-part-style-aside","aria-hidden":"true",children:"Section’s own"})]}):t("span",{className:"es-fsel-label",style:{fontFamily:`var(--font-role-${i.value})`},children:i.label}),renderValue:()=>t("span",{style:{fontFamily:n?.fontKey?`var(${$(n.fontKey)?.cssVar})`:void 0,fontWeight:n?.weight},children:I(e)}),valueText:i=>I(e)??i?.label??""})}function Tt({parts:e,onEditStyle:n,onClearAll:s,onFollowStyle:l,kindLabel:c}){const h=oe(e,T=>St(T.face))?e[0].face:null,y=h?I(h):null,u=h&&h.kind!=="unread"?e[0].base:null,N=e.reduce((T,A)=>T+Nt(A.style),0),a=u?$(u.fontKey)?.label:void 0;return r("div",{className:`sf-part-style${N?" has-overrides":""}`,children:[t("div",{className:"sf-part-style-caption",children:"Style"}),e.length>1&&r("div",{className:"sf-part-style-count",children:[e.length," selected"]}),e.length===1&&c&&r("div",{className:"sf-part-style-count",children:[c," · every row"]}),y&&u?r(O,{children:[l&&h?t(kt,{face:h,base:u,onPick:l}):t("div",{className:"sf-part-style-name",style:{fontFamily:a?`var(${$(u.fontKey)?.cssVar})`:void 0,fontWeight:u.weight},children:y}),t("div",{className:"sf-part-style-meta",children:[a,`${Math.round(u.sizePx)} px`,yt[u.weight]].filter(Boolean).join(" · ")})]}):t("div",{className:"sf-part-style-name",children:"Mixed styles"}),r("div",{className:"sf-part-style-top",children:[y?N?r(O,{children:[r("span",{className:"sf-part-chip",children:[N," changed"]}),t("button",{type:"button",className:"sf-part-link",onClick:s,children:"Clear"})]}):t("span",{className:"sf-part-style-state",children:"This text follows the site style."}):t("span",{className:"sf-part-style-state",children:"Changes apply to all selected."}),t("span",{className:"sf-part-style-gap"}),y&&n&&t("button",{type:"button",className:"sf-part-link",onClick:n,children:"Edit"})]})]})}function Et({sectionName:e,onBack:n,textEditor:s,styleRow:l,children:c}){return r("div",{className:"sf-part-panel",children:[t(G,{inline:!0,name:e,onSelect:n}),s,l,c]})}function xe({source:e,slug:n}){const s=it(),l=ct[e],c=!l.needsSlug||!!n;return t("div",{className:"sf-note sf-note--source",children:r("p",{className:"es-field-hint sf-hint-keep",children:[at[e],c?r(O,{children:[" ",t("button",{type:"button",className:"sf-source-open",onClick:()=>{l.type==="storefront-editor:open-menus"?s.send(l.type):n&&s.send(l.type,{slug:n})},children:dt})]}):null]})})}const ve=4,Rt=120;function Ct({region:e,sizePx:n,face:s,base:l,onFollowStyle:c,source:i,slug:h,onBack:y}){const u=e.follows;return r("div",{className:"sf-note",children:[t("p",{children:t("strong",{children:e.label})}),r("p",{children:["Its size follows Site styles ▸ ",u,n?` — ${Math.round(n)}px right now`:"","."]}),c?t(O,{children:t(me,{label:"Style",icon:t(Le,{}),hint:r(O,{children:["Every ",e.label.toLowerCase()," in this section follows it. The face and the size change; the letter case is the section’s own and stays as it is."]}),children:t(wt,{face:s,base:l,onPick:c})})}):t("p",{className:"es-field-hint sf-hint-keep",children:"Only the size follows a site style. This section sets its own font, weight and letter case."}),i?t(xe,{source:i,slug:h}):null,t(G,{inline:!0,name:"Page",onSelect:y})]})}function Ft({onBack:e}){return r("div",{className:"sf-note",children:[t("p",{children:t("strong",{children:"This part is no longer here."})}),t("p",{children:"The section it belonged to has been changed or removed."}),t(G,{inline:!0,name:"Page",onSelect:e})]})}function Ot({onBack:e,partId:n,why:s}){return r("div",{className:"sf-note",children:[t("p",{children:t("strong",{children:"This part's settings did not open."})}),t("p",{children:"The words are still on your page and nothing has been lost. The editor could not work out which control belongs to them, which is a fault on our side. Go back and click the section to open its settings instead."}),t("p",{className:"sf-note-detail",children:r("code",{children:[n," — ",s]})}),t(G,{inline:!0,name:"Page",onSelect:e})]})}function It({nodeId:e,propKey:n,onBack:s,onEditStyle:l}){const{getItemById:c,getSelectorForId:i,config:h}=Be(o=>o),y=Ue(),{partStyle:u}=We(),{fonts:N}=Ge(),[a,T]=Q({}),A=J(null),g=c(e),S=i(e),se=g?h.components[g.type]:void 0,{key:E,row:P}=Qe(n),le=se?.fields?.[E],k=P?Je(le,P):le,_=ft(e,n),p=g?.props??{},w=qe(k),Ne=je(k),Pe=pt(k),H=g?ut(g.type):null,U=P?et(p[E],P):p[E],re=!!P&&!(Array.isArray(p[E])&&P.index<p[E].length);M(()=>{A.current?.querySelector(".es-copy [contenteditable='true'], .es-copy textarea, .sf-copybox textarea, .sf-copybox input")?.focus()},[_]);const[K,ie]=Q(0);M(()=>ie(0),[_]);const X=!!g&&!re&&(!S||!k);M(()=>{if(!X||K>=ve)return;const o=setTimeout(()=>ie(d=>d+1),Rt);return()=>clearTimeout(o)},[X,K]);const ke=X&&K<ve;if(!g||re)return t(Ft,{onBack:s});const L=mt(g.type),ae=L?ht(L,n):null;if(L&&ae){const o=u?.partId===_?u:null,d=L[ae],m=d.writable===!0&&!!S,b=f=>{S&&y.replaceComponent(S.zone,S.index,{...g,props:te(p,n,"role",f)})};return t(Ct,{region:d,sizePx:o?.base.sizePx??null,face:ne(p,n,o,N),base:o?.base??null,onFollowStyle:m?b:void 0,source:H,slug:be(p),onBack:s})}if(!S||!k)return ke?t("div",{className:"sf-note","aria-busy":"true"}):t(Ot,{onBack:s,partId:_,why:S?"no field":"no selector"});const z=o=>{y.replaceComponent(S.zone,S.index,{...g,props:o})},Y=o=>{if(W(o).hidden!==W(U).hidden||!w){const m=P?Ze(p,E,P,o):{...p,[E]:o};m&&z(m);return}rt({node:g,propKey:n,kind:w,zone:S.zone,index:S.index},W(o).text,y.replaceComponent)},we=U,B=st(g?.type,n),Te=ge(p,B),D=u?.partId===_?u:null,x=[{partKey:B,face:ne(p,B,D,N),sizeGroup:D?.sizeGroup??null,base:D?.base??_t,style:Te}],Ee=oe(x,o=>o.sizeGroup),ce=ot(D?.inks),V=(o,d)=>{let m=p;for(const b of x)m=te(m,b.partKey,o,d);z(m)},Re=o=>V("role",o),Ce=()=>{let o=p;for(const d of x)o=nt(o,d.partKey);z(o)},Fe=o=>{let d=p;for(const m of x)for(const[b,f]of Object.entries(o))d=te(d,m.partKey,b,f??null);z(d)},Oe=(o,d)=>{const m=x.some(v=>v.style[o]!==void 0),b=oe(x,v=>Pt(v,o)),f=b===null,F=`sf-part-${e}-${n}-${o}`;return o==="color"?t(Ve,{value:f?null:String(b),label:d,colors:ce,onChange:v=>V("color",v)},o):t(me,{label:d,icon:bt[o],className:"sf-part-field",changed:m,htmlFor:F,action:m?t(fe,{label:d,onClear:()=>V(o,null)}):void 0,children:o==="font"?t(pe,{id:F,ariaLabel:d,value:!f&&$(String(b))?String(b):void 0,placeholder:f?"Mixed":"Site style",options:Ke.map(v=>({label:v.label,value:v.key})),onChange:v=>V("font",v||null)}):null},o)},de=w==="text"||w==="textarea",{text:ue,hidden:R}=W(U),C=k?.[He]?.label??n,Ie=r("div",{className:"sf-field-head",children:[t(he,{}),t("span",{className:"sf-field-label sf-field-label--inline",children:C}),de&&t(De,{content:R?`Show ${C} on the slide`:`Hide ${C} on the slide`,children:t("button",{type:"button",className:"sf-field-eye","aria-pressed":R,"aria-label":R?`Show ${C} on the slide`:`Hide ${C} on the slide`,onClick:()=>Y({text:ue,hidden:!R}),children:R?t(ye,{name:"eye-off",size:14}):t(ye,{name:"eye",size:14})})})]}),q=!Ne&&Pe;return r(Et,{sectionName:se?.label??g.type,onBack:s,textEditor:r("div",{ref:A,className:"sf-part-text",children:[w?t(Me,{text:ue,onTextChange:o=>Y(de?{text:o,hidden:R}:o),multiline:Xe(w),markup:w!=="plainText",styled:q,style:x[0].style,base:x[0].base,setValue:Fe,colors:ce.map(o=>({name:o.key,label:o.name,value:o.value})),placeholder:Ye(k),ariaLabel:C,head:Ie}):t($e,{field:k,name:n,value:we,onChange:Y,id:`sf-part-${e}-${n}`}),q&&w&&Ee===null&&t("p",{className:"es-field-hint sf-hint-keep",children:"Fixed size — the site’s Text setting doesn’t resize this."})]}),children:[H&&t(xe,{source:H,slug:be(p)}),q&&gt.map(o=>{const d=Se.filter(f=>f.group===o),m=d.filter(f=>x.some(F=>F.style[f.key]!==void 0)).length,b=`sf-part-g-${e}-${n}-${o.replace(/\W+/g,"-")}`;return r(Ae,{id:b,title:o,open:a[o]!==!0,onToggle:f=>T(F=>({...F,[o]:!f})),count:m,children:[t(Tt,{parts:x,onEditStyle:l,onClearAll:Ce,onFollowStyle:Re,kindLabel:lt[B]}),d.map(f=>Oe(f.key,f.label))]},o)})]})}const _t={fontKey:"",sizePx:16,weight:400,lineRatio:1.4,letterEm:0,align:"left",case:"none",paddingPx:0,widthPx:tt};export{Se as PART_FIELDS,Et as PartPanelBody,fe as PartResetDot,xe as PartSourceNote,Tt as PartStyleCard,wt as PartStyleSelect,It as default,ne as partFace,un as partStyleName};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "efficient-web-builder",
3
- "version": "4.0.0",
3
+ "version": "5.0.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",
@@ -31,7 +31,7 @@
31
31
  "typecheck": "tsc --noEmit",
32
32
  "test:boundary": "node scripts/test-boundary.mjs",
33
33
  "test:screen": "node scripts/test-screen-in-easel.mjs",
34
- "test:own-ui": "node scripts/test-own-ui-ratchet.mjs",
34
+ "test:own-ui": "efficient-shell-ui-check",
35
35
  "build": "node scripts/build.mjs",
36
36
  "verify:dist": "node scripts/verify-dist.mjs",
37
37
  "verify": "pnpm run typecheck && pnpm run test:boundary && pnpm run test:screen && pnpm run test:own-ui && pnpm run test:guards && pnpm run build && pnpm run verify:dist",
@@ -39,8 +39,8 @@
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@tabler/icons-react": "^3.46.0",
42
- "efficient-easel": "^1.0.0",
43
- "efficient-shell": "^4.165.0",
42
+ "efficient-easel": "^2.0.0",
43
+ "efficient-shell": "^4.171.0",
44
44
  "isomorphic-dompurify": "^3.18.0",
45
45
  "next": ">=16",
46
46
  "react": ">=19",
@@ -58,8 +58,8 @@
58
58
  "@types/node": "^20",
59
59
  "@types/react": "^19",
60
60
  "@types/react-dom": "^19",
61
- "efficient-easel": "^1.0.0",
62
- "efficient-shell": "^4.165.0",
61
+ "efficient-easel": "^2.1.0",
62
+ "efficient-shell": "^4.180.0",
63
63
  "esbuild": "^0.28.0",
64
64
  "happy-dom": "^20.14.5",
65
65
  "isomorphic-dompurify": "^3.18.0",