efficient-web-builder 21.0.0 → 22.0.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/dist/components/AnimatedStat.js +1 -1
  3. package/dist/components/Banner.js +1 -1
  4. package/dist/components/Cards.js +1 -1
  5. package/dist/components/CategoryBar.js +1 -1
  6. package/dist/components/ChartSection.js +1 -1
  7. package/dist/components/Countdown.js +1 -1
  8. package/dist/components/DealerFinder.d.ts +12 -0
  9. package/dist/components/DealerFinder.js +1 -0
  10. package/dist/components/DocMasthead.js +1 -1
  11. package/dist/components/DocMetaCards.js +1 -1
  12. package/dist/components/DocTabs.js +1 -1
  13. package/dist/components/FeaturedCover.js +1 -1
  14. package/dist/components/Footprint.d.ts +8 -1
  15. package/dist/components/Footprint.js +5 -5
  16. package/dist/components/HeroPoster.d.ts +2 -1
  17. package/dist/components/HeroPoster.js +1 -1
  18. package/dist/components/HeroSingle.d.ts +3 -1
  19. package/dist/components/HeroSingle.js +4 -4
  20. package/dist/components/HeroSlide.d.ts +2 -1
  21. package/dist/components/HeroSlide.js +1 -1
  22. package/dist/components/ImageBlock.d.ts +2 -1
  23. package/dist/components/ImageBlock.js +1 -1
  24. package/dist/components/KeyFeatures.js +1 -1
  25. package/dist/components/LocationGroupHeader.js +1 -1
  26. package/dist/components/Logos.js +1 -1
  27. package/dist/components/LongRead.js +1 -1
  28. package/dist/components/Manifesto.js +1 -1
  29. package/dist/components/Masthead.js +1 -1
  30. package/dist/components/MediaUploadField.d.ts +3 -1
  31. package/dist/components/MediaUploadField.js +1 -1
  32. package/dist/components/MockupSection.js +1 -1
  33. package/dist/components/OrderSummary.js +1 -1
  34. package/dist/components/PagesPanel.js +1 -1
  35. package/dist/components/ParagraphsA.js +1 -1
  36. package/dist/components/PostCard.js +1 -1
  37. package/dist/components/ProcessSteps.js +1 -1
  38. package/dist/components/Prose.js +1 -1
  39. package/dist/components/SectionEyebrow.js +1 -1
  40. package/dist/components/SectionHeader.js +1 -1
  41. package/dist/components/Series.js +1 -1
  42. package/dist/components/SiteTextStylesField.js +1 -1
  43. package/dist/components/Split.js +1 -1
  44. package/dist/components/Stats.js +1 -1
  45. package/dist/components/StatsParagraph.js +1 -1
  46. package/dist/components/Testimonials.js +1 -1
  47. package/dist/components/Text.js +1 -1
  48. package/dist/components/ThemeField.js +1 -1
  49. package/dist/components/Timeline.js +1 -1
  50. package/dist/components/TimelineTrack.js +1 -1
  51. package/dist/components/WebsiteScreenParts.js +1 -1
  52. package/dist/components/storefront-editor.css +96 -271
  53. package/dist/components/useLocationsTree.js +1 -1
  54. package/dist/lib/content/phone-focus.d.ts +7 -0
  55. package/dist/lib/content/phone-focus.js +1 -0
  56. package/dist/lib/dealer-source.d.ts +34 -0
  57. package/dist/lib/dealer-source.js +1 -0
  58. package/dist/lib/editor-words-catalogue.d.ts +9 -5
  59. package/dist/lib/editor-words-catalogue.js +4 -4
  60. package/dist/lib/puck/MarkupToolbar.d.ts +1 -1
  61. package/dist/lib/puck/MarkupToolbar.js +1 -1
  62. package/dist/lib/puck/blocks/general/Footprint/fields.js +1 -1
  63. package/dist/lib/puck/blocks/general/Footprint/render.js +2 -2
  64. package/dist/lib/puck/blocks/general/HeroSingle/fields.js +1 -1
  65. package/dist/lib/puck/blocks/general/HeroSingle/render.js +1 -1
  66. package/dist/lib/puck/blocks/general/HeroSlide/fields.js +1 -1
  67. package/dist/lib/puck/blocks/general/HeroSlide/render.js +1 -1
  68. package/dist/lib/puck/blocks/general/ImageBlock/fields.js +1 -1
  69. package/dist/lib/puck/blocks/general/ImageBlock/render.js +1 -1
  70. package/dist/lib/puck/controls.d.ts +5 -2
  71. package/dist/lib/puck/controls.js +1 -1
  72. package/dist/lib/puck/field-groups.js +1 -1
  73. package/dist/lib/sections/collection-card-text-regions.d.ts +9 -1
  74. package/dist/lib/sections/collection-card-text-regions.js +1 -1
  75. package/dist/lib/sections/text-regions.d.ts +1 -0
  76. package/dist/lib/sections/text-regions.js +1 -1
  77. package/dist/lib/site-text-styles.d.ts +1 -0
  78. package/dist/lib/site-text-styles.js +2 -2
  79. package/dist/lib/source-rules/heading-ceiling-sweep.js +3 -3
  80. package/dist/lib/source-rules/part-style-naming.js +2 -2
  81. package/dist/lib/source-rules/rule.d.ts +1 -0
  82. package/dist/lib/source-rules/rule.js +3 -1
  83. package/dist/lib/source-rules/size-token-paints.js +4 -4
  84. package/dist/lib/source-rules/style-face-matches-size.js +3 -3
  85. package/package.json +7 -5
  86. package/dist/components/ImageFocusRow.d.ts +0 -7
  87. package/dist/components/ImageFocusRow.js +0 -1
  88. package/dist/components/TemplateGlyph.d.ts +0 -6
  89. package/dist/components/TemplateGlyph.js +0 -1
  90. package/dist/components/TextStyleRow.d.ts +0 -13
  91. package/dist/components/TextStyleRow.js +0 -1
@@ -1,2 +1,2 @@
1
- import{fontRoleOfClassName as y,FONT_ROLE_LABELS as x}from"../fonts.js";import{isRuleSource as w,ruleChecks as E}from"./rule.js";import{enclosingLiteral as N}from"./heading-ceiling-sweep.js";import{stripComments as b}from"./support/ts-source.js";import{typeScaleGroupOfClassName as u}from"../theme.js";const R=["app/","components/","lib/"],p={"font-display":"heading","font-heading2":"heading2","font-heading3":"heading3","font-body":"body","font-serif":"accent","font-mono":"label"},k={heading:"--font-role-heading",heading2:"--font-role-heading2",heading3:"--font-role-heading3",body:"--font-role-body",accent:"--font-role-accent",label:"--font-role-label"},$=[];function L(m){const n=[],t=new Set;for(const d of m){const c=b(d.text),l=/\bfont-(?:display|body|serif|mono)\b/g;let r;for(;r=l.exec(c);){const i=N(c,r.index);if(!i)continue;const o=c.slice(0,r.index).split(`
2
- `).length,h=`${d.path}:${o}:${i}`;if(t.has(h))continue;t.add(h);const g=i.split(/\s+/).filter(a=>!a.slice(0,a.lastIndexOf(":")+1).includes("&")).map(a=>a.slice(a.lastIndexOf(":")+1)),f=Object.keys(p).filter(a=>g.includes(a));f.length!==0&&n.push({file:d.path,line:o,className:i,utilities:f})}}return n}function I(m,n){const{check:t,report:d}=E("part style naming"),c=m.filter(e=>/\.tsx?$/.test(e.path)&&!w(e.path)&&R.some(s=>e.path.startsWith(s))),l=L(c),r=new Map(m.map(e=>[e.path,e]));t(`read the source (${c.length} .ts/.tsx files)`,c.length>0,"found no source to sweep — this rule is not measuring anything"),t(`the scanner finds face-tagged class lists at all (${l.length}, at least ${n.minSites})`,l.length>=n.minSites);const i=l.filter(e=>u(e.className)!==null),o=l.filter(e=>u(e.className)===null);t(`…both taggings are represented, so neither branch below is untested (${i.length} with a scale token, at least ${n.minWithScale})`,i.length>=n.minWithScale&&i.length>0),t(`…and the face-without-a-size-knob population is real, not an artefact (${o.length}, at least ${n.minWithoutScale})`,o.length>=n.minWithoutScale&&o.length>0,`${o.length} class lists carry a face utility and no matching scale token`);const h=l.filter(e=>y(e.className)!==p[e.utilities[0]]);if(t("every class list in the census reads back as its own utility's role",h.length===0,h.slice(0,5).map(e=>`${e.file}:${e.line} has ${e.utilities[0]} but reads ${y(e.className)}`).join("; ")),n.roleSheet!==void 0){const e=r.get(n.roleSheet),s=Object.values(k).filter(O=>!e||!e.text.includes(O));t(`…and every role named here is a custom property ${n.roleSheet} defines`,!!e&&s.length===0,e?s.join(", "):"the sheet was not read")}const g=Object.values(p).filter(e=>!(e in x));t("every face utility names a role the panel offers",g.length===0,g.join(", "));const f=Object.keys(x).filter(e=>!Object.values(p).includes(e));t("…and the roles no utility can name are exactly the ones decided to have none",f.join()===$.join(),`no utility reaches: [${f}] — expected [${$}]`);const a=o.filter(e=>u(e.className)!==null);t("no class list without a scale token claims a size group",a.length===0,a.slice(0,5).map(e=>`${e.file}:${e.line}`).join("; ")),t("the reported case: a `font-display text-[22px]` heading is Headings, size group none",y("font-display text-[22px] font-medium")==="heading"&&u("font-display text-[22px] font-medium")===null),t("…and genuinely body-tagged copy still reads as the body size group",u("text-[var(--size-body,16px)]")==="body");const S=l.filter(e=>e.utilities.length>1);if(t("every class list names at most one face",S.length===0,S.slice(0,5).map(e=>`${e.file}:${e.line} — ${e.utilities.join(" + ")}`).join("; ")),n.panel!==void 0){const e=r.get(n.panel),s=e?b(e.text):"";t(`read the part panel ${n.panel}`,!!e),t("the box's name comes from the FACE, not from a size group",s.includes("FONT_ROLE_LABELS[face.role]")&&!s.includes("TYPE_GROUP_LABELS[")),t("…a face the page does not draw as its declared role is called Custom",/return\s*"Custom"/.test(s)&&s.includes('kind: "custom"')),t("…and a part no size knob reaches says so under the text box",/sizeGroup === null && \(\s*<p className="es-field-hint sf-hint-keep">(?:\{say\(")?Fixed size/.test(s))}if(n.bridge!==void 0){const e=r.get(n.bridge),s=e?b(e.text):"";t(`read the preview's part bridge ${n.bridge}`,!!e),t("the preview publishes BOTH tags, each walked up to its own nearest declarer",s.includes("fontRoleOfClassName")&&s.includes("typeScaleGroupOfClassName")&&s.includes("el.parentElement")),t("…and the element it reads from is one that declares EITHER tag",/partStyleSource\([\s\S]{0,200}fontRoleOfClassName[\s\S]{0,120}typeScaleGroupOfClassName/.test(s))}return d()}export{I as partStyleNaming};
1
+ import{fontRoleOfClassName as y,FONT_ROLE_LABELS as x}from"../fonts.js";import{inlineTextSizes as w,isRuleSource as E,ruleChecks as N}from"./rule.js";import{enclosingLiteral as R}from"./heading-ceiling-sweep.js";import{stripComments as b}from"./support/ts-source.js";import{typeScaleGroupOfClassName as u}from"../theme.js";const T=["app/","components/","lib/"],p={"font-display":"heading","font-heading2":"heading2","font-heading3":"heading3","font-body":"body","font-serif":"accent","font-mono":"label"},k={heading:"--font-role-heading",heading2:"--font-role-heading2",heading3:"--font-role-heading3",body:"--font-role-body",accent:"--font-role-accent",label:"--font-role-label"},$=[];function L(m){const n=[],t=new Set;for(const d of m){const c=b(w(d.text)),l=/\bfont-(?:display|body|serif|mono)\b/g;let r;for(;r=l.exec(c);){const i=R(c,r.index);if(!i)continue;const o=c.slice(0,r.index).split(`
2
+ `).length,h=`${d.path}:${o}:${i}`;if(t.has(h))continue;t.add(h);const g=i.split(/\s+/).filter(a=>!a.slice(0,a.lastIndexOf(":")+1).includes("&")).map(a=>a.slice(a.lastIndexOf(":")+1)),f=Object.keys(p).filter(a=>g.includes(a));f.length!==0&&n.push({file:d.path,line:o,className:i,utilities:f})}}return n}function I(m,n){const{check:t,report:d}=N("part style naming"),c=m.filter(e=>/\.tsx?$/.test(e.path)&&!E(e.path)&&T.some(s=>e.path.startsWith(s))),l=L(c),r=new Map(m.map(e=>[e.path,e]));t(`read the source (${c.length} .ts/.tsx files)`,c.length>0,"found no source to sweep — this rule is not measuring anything"),t(`the scanner finds face-tagged class lists at all (${l.length}, at least ${n.minSites})`,l.length>=n.minSites);const i=l.filter(e=>u(e.className)!==null),o=l.filter(e=>u(e.className)===null);t(`…both taggings are represented, so neither branch below is untested (${i.length} with a scale token, at least ${n.minWithScale})`,i.length>=n.minWithScale&&i.length>0),t(`…and the face-without-a-size-knob population is real, not an artefact (${o.length}, at least ${n.minWithoutScale})`,o.length>=n.minWithoutScale&&o.length>0,`${o.length} class lists carry a face utility and no matching scale token`);const h=l.filter(e=>y(e.className)!==p[e.utilities[0]]);if(t("every class list in the census reads back as its own utility's role",h.length===0,h.slice(0,5).map(e=>`${e.file}:${e.line} has ${e.utilities[0]} but reads ${y(e.className)}`).join("; ")),n.roleSheet!==void 0){const e=r.get(n.roleSheet),s=Object.values(k).filter(O=>!e||!e.text.includes(O));t(`…and every role named here is a custom property ${n.roleSheet} defines`,!!e&&s.length===0,e?s.join(", "):"the sheet was not read")}const g=Object.values(p).filter(e=>!(e in x));t("every face utility names a role the panel offers",g.length===0,g.join(", "));const f=Object.keys(x).filter(e=>!Object.values(p).includes(e));t("…and the roles no utility can name are exactly the ones decided to have none",f.join()===$.join(),`no utility reaches: [${f}] — expected [${$}]`);const a=o.filter(e=>u(e.className)!==null);t("no class list without a scale token claims a size group",a.length===0,a.slice(0,5).map(e=>`${e.file}:${e.line}`).join("; ")),t("the reported case: a `font-display text-[22px]` heading is Headings, size group none",y("font-display text-[22px] font-medium")==="heading"&&u("font-display text-[22px] font-medium")===null),t("…and genuinely body-tagged copy still reads as the body size group",u("text-[var(--size-body,16px)]")==="body");const S=l.filter(e=>e.utilities.length>1);if(t("every class list names at most one face",S.length===0,S.slice(0,5).map(e=>`${e.file}:${e.line} — ${e.utilities.join(" + ")}`).join("; ")),n.panel!==void 0){const e=r.get(n.panel),s=e?b(e.text):"";t(`read the part panel ${n.panel}`,!!e),t("the box's name comes from the FACE, not from a size group",s.includes("FONT_ROLE_LABELS[face.role]")&&!s.includes("TYPE_GROUP_LABELS[")),t("…a face the page does not draw as its declared role is called Custom",/return\s*"Custom"/.test(s)&&s.includes('kind: "custom"')),t("…and a part no size knob reaches says so under the text box",/sizeGroup === null && \(\s*<p className="es-field-hint sf-hint-keep">(?:\{say\(")?Fixed size/.test(s))}if(n.bridge!==void 0){const e=r.get(n.bridge),s=e?b(e.text):"";t(`read the preview's part bridge ${n.bridge}`,!!e),t("the preview publishes BOTH tags, each walked up to its own nearest declarer",s.includes("fontRoleOfClassName")&&s.includes("typeScaleGroupOfClassName")&&s.includes("el.parentElement")),t("…and the element it reads from is one that declares EITHER tag",/partStyleSource\([\s\S]{0,200}fontRoleOfClassName[\s\S]{0,120}typeScaleGroupOfClassName/.test(s))}return d()}export{I as partStyleNaming};
@@ -2,6 +2,7 @@ export type SourceFile = {
2
2
  readonly path: string;
3
3
  readonly text: string;
4
4
  };
5
+ export declare function inlineTextSizes(text: string): string;
5
6
  export type RuleCheck = {
6
7
  readonly name: string;
7
8
  readonly ok: boolean;
@@ -1 +1,3 @@
1
- const u="lib/source-rules/";function c(r){return r.startsWith(u)}function s(r){const e=[];return{check(n,t,o){return e.push(t||o===void 0?{name:n,ok:t}:{name:n,ok:t,detail:o}),t},report(){return e.length===0&&e.push({name:`${r} checked nothing`,ok:!1}),{rule:r,checks:e}}}}function i(r){return r.checks.filter(e=>!e.ok)}function h(r){return r.checks.map(e=>e.ok?` ok ${e.name}`:` FAIL ${e.name}${e.detail?` — ${e.detail}`:""}`)}export{u as RULES_DIR,i as failedChecks,c as isRuleSource,h as reportLines,s as ruleChecks};
1
+ function u(n){const e=/\nconst TEXT_SIZE = \{\n([\s\S]*?)\} as const;\n/.exec(n);if(!e)return n;const r=new Map;for(const s of e[1].matchAll(/^\s*(\w+): "([^"]+)",\s*$/gm))r.set(s[1],s[2]);const t=`
2
+ `.repeat(e[0].split(`
3
+ `).length-1);return(n.slice(0,e.index)+t+n.slice(e.index+e[0].length)).replace(/\$\{TEXT_SIZE\.(\w+)\}/g,(s,c)=>r.get(c)??s)}const i="lib/source-rules/";function l(n){return n.startsWith(i)}function h(n){const e=[];return{check(r,t,o){return e.push(t||o===void 0?{name:r,ok:t}:{name:r,ok:t,detail:o}),t},report(){return e.length===0&&e.push({name:`${n} checked nothing`,ok:!1}),{rule:n,checks:e}}}}function p(n){return n.checks.filter(e=>!e.ok)}function a(n){return n.checks.map(e=>e.ok?` ok ${e.name}`:` FAIL ${e.name}${e.detail?` — ${e.detail}`:""}`)}export{i as RULES_DIR,p as failedChecks,u as inlineTextSizes,l as isRuleSource,a as reportLines,h as ruleChecks};
@@ -1,11 +1,11 @@
1
- import{ruleChecks as d}from"./rule.js";const b=/text-\[(?!length:)[^\]]*--size-[a-zA-Z]+[^\]]*\]/g;function w(t){let i="",e=0;const l=t.length;for(;e<l;){const a=t[e],h=t[e+1];if(a==="/"&&h==="/"){for(;e<l&&t[e]!==`
1
+ import{inlineTextSizes as d,ruleChecks as b}from"./rule.js";const w=/text-\[(?!length:)[^\]]*--size-[a-zA-Z]+[^\]]*\]/g;function x(t){let i="",e=0;const l=t.length;for(;e<l;){const a=t[e],h=t[e+1];if(a==="/"&&h==="/"){for(;e<l&&t[e]!==`
2
2
  `;)i+=" ",e++;continue}if(a==="/"&&h==="*"){for(;e<l&&!(t[e]==="*"&&t[e+1]==="/");)i+=t[e]===`
3
3
  `?`
4
4
  `:" ",e++;i+=" ",e+=2;continue}if(a==="`"){for(i+=a,e++;e<l;){if(t[e]==="\\"){i+=t[e]+(t[e+1]??""),e+=2;continue}if(t[e]==="`"){i+="`",e++;break}i+=t[e],e++}continue}if(a==='"'||a==="'"){let o=e+1,r=!1;for(;o<l&&t[o]!==`
5
- `;){if(t[o]==="\\"){o+=2;continue}if(t[o]===a){r=!0;break}o++}if(!r){i+=a,e++;continue}i+=t.slice(e,o+1),e=o+1;continue}i+=a,e++}return i}function f(t){return(w(t).match(b)??[]).length}function x(){const t="text-[";return[{name:"empty source",src:"",want:0},{name:"no size token at all",src:`<p className="${t}13px] font-mono">hi</p>`,want:0},{name:"the broken form",src:`<p className="${t}var(--size-label,13px)]">hi</p>`,want:1},{name:"the working form",src:`<p className="${t}length:var(--size-label,13px)]">hi</p>`,want:0},{name:"two on one line, desktop and phone",src:`<h2 className="${t}var(--size-headingMedium,44px)] max-[800px]:${t}var(--size-headingMedium,32px)]">t</h2>`,want:2},{name:"wrapped in min(), still broken",src:`<h1 className="${t}min(var(--size-headingLarge,64px),var(--sf-heading-max,9999px))]">t</h1>`,want:1},{name:"a line comment is not code",src:`// ${t}var(--size-label,13px)] is the broken form`,want:0},{name:"a block comment is not code",src:`/* ${t}var(--size-label,13px)] */`,want:0},{name:"an apostrophe does not stop a later comment being stripped",src:`<p>it doesn't paint</p>
5
+ `;){if(t[o]==="\\"){o+=2;continue}if(t[o]===a){r=!0;break}o++}if(!r){i+=a,e++;continue}i+=t.slice(e,o+1),e=o+1;continue}i+=a,e++}return i}function f(t){return(x(t).match(w)??[]).length}function g(){const t="text-[";return[{name:"empty source",src:"",want:0},{name:"no size token at all",src:`<p className="${t}13px] font-mono">hi</p>`,want:0},{name:"the broken form",src:`<p className="${t}var(--size-label,13px)]">hi</p>`,want:1},{name:"the working form",src:`<p className="${t}length:var(--size-label,13px)]">hi</p>`,want:0},{name:"two on one line, desktop and phone",src:`<h2 className="${t}var(--size-headingMedium,44px)] max-[800px]:${t}var(--size-headingMedium,32px)]">t</h2>`,want:2},{name:"wrapped in min(), still broken",src:`<h1 className="${t}min(var(--size-headingLarge,64px),var(--sf-heading-max,9999px))]">t</h1>`,want:1},{name:"a line comment is not code",src:`// ${t}var(--size-label,13px)] is the broken form`,want:0},{name:"a block comment is not code",src:`/* ${t}var(--size-label,13px)] */`,want:0},{name:"an apostrophe does not stop a later comment being stripped",src:`<p>it doesn't paint</p>
6
6
  // ${t}var(--size-label,13px)]
7
- <p>that's why</p>`,want:0}]}function $(t,i){const{check:e,report:l}=d("size token paints"),a=i.baseline??{};let h=!0;for(const n of x()){const s=f(n.src);h=e(`fixture "${n.name}": ${n.want}`,s===n.want,`got ${s}`)&&h}if(!h)return e("the extractor is right, so its verdict on the tree means something",!1,"a fixture failed above"),l();const o=t.filter(n=>/\.tsx?$/.test(n.path));e(`read the source (${o.length} TS/TSX files, at least ${i.minFiles})`,o.length>=i.minFiles,"fewer means the walk broke, and the check below is vacuous");const r=[],p=[];let m=0;for(const n of o){const s=f(n.text);m+=s;const c=a[n.path]??0;s>c?r.push(`${n.path} — ${s} broken, baseline allows ${c}`):s<c&&p.push(`${n.path} — ${s} broken, baseline says ${c}`)}const u=Object.values(a).reduce((n,s)=>n+s,0);e(`no text size is written in the form that compiles to \`color:\` (${m} broken place(s) found, the baseline allows ${u})`,r.length===0,`${r.length} file(s) over budget:
7
+ <p>that's why</p>`,want:0}]}function k(t,i){const{check:e,report:l}=b("size token paints"),a=i.baseline??{};let h=!0;for(const n of g()){const s=f(n.src);h=e(`fixture "${n.name}": ${n.want}`,s===n.want,`got ${s}`)&&h}if(!h)return e("the extractor is right, so its verdict on the tree means something",!1,"a fixture failed above"),l();const o=t.filter(n=>/\.tsx?$/.test(n.path));e(`read the source (${o.length} TS/TSX files, at least ${i.minFiles})`,o.length>=i.minFiles,"fewer means the walk broke, and the check below is vacuous");const r=[],p=[];let m=0;for(const n of o){const s=f(d(n.text));m+=s;const c=a[n.path]??0;s>c?r.push(`${n.path} — ${s} broken, baseline allows ${c}`):s<c&&p.push(`${n.path} — ${s} broken, baseline says ${c}`)}const u=Object.values(a).reduce((n,s)=>n+s,0);e(`no text size is written in the form that compiles to \`color:\` (${m} broken place(s) found, the baseline allows ${u})`,r.length===0,`${r.length} file(s) over budget:
8
8
  ${r.join(`
9
9
  `)}
10
10
  Write it as \`text-[length:var(--size-<role>,<built-in>)]\`. Without \`length:\` Tailwind reads the var() as a
11
- colour, the declaration is dropped as invalid, and the site's text style setting reaches nothing.`);for(const[n]of Object.entries(a))e(`the baseline entry is still a file: ${n}`,o.some(s=>s.path===n),"delete the stale entry");for(const n of p)e(`improved — lower its baseline entry: ${n}`,!0);return l()}export{f as brokenCount,$ as sizeTokenPaints,w as stripComments};
11
+ colour, the declaration is dropped as invalid, and the site's text style setting reaches nothing.`);for(const[n]of Object.entries(a))e(`the baseline entry is still a file: ${n}`,o.some(s=>s.path===n),"delete the stale entry");for(const n of p)e(`improved — lower its baseline entry: ${n}`,!0);return l()}export{f as brokenCount,k as sizeTokenPaints,x as stripComments};
@@ -1,4 +1,4 @@
1
- import{ruleChecks as m}from"./rule.js";const u={"font-display":"headingLarge","font-heading2":"headingMedium","font-heading3":"headingSmall","font-serif":"accent","font-body":"body","font-mono":"label"};function p(i){const n=/(?<![-\w:])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)text-\[length:[^"'`]*?--size-(headingLarge|headingMedium|headingSmall|accent|body|label)\b/g,t=new Set;for(const o of i.matchAll(n))o[1]===""&&t.add(o[2]);return t.size===1?[...t][0]:null}function $(i){const n=/(?<![-\w])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)(font-display|font-heading2|font-heading3|font-serif|font-body|font-mono)(?![-\w])/g,t=new Set;for(const o of i.matchAll(n))o[1].includes("&")||t.add(o[2]);return[...t]}function f(i){const n=p(i);if(!n)return null;const t=$(i);return t.length===1?{step:n,face:t[0]}:null}const w=i=>i.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");function S(i,n){const{check:t,report:o}=m("style face matches size"),d=e=>i.find(s=>s.path===e)?.text;{const e=r=>`text-[length:var(--size-${r},44px)]`,s=r=>JSON.stringify(f(r));t("a face and a base size are read as a pair",s(`"font-display ${e("headingMedium")}"`)==='{"step":"headingMedium","face":"font-display"}'),t("…a size inside a variant is not the base",f(`"font-heading2 max-[800px]:${e("headingSmall")}"`)===null),t("…the base wins over a variant on the same line",s(`"font-heading3 ${e("headingSmall")} md:${e("headingMedium")}"`)==='{"step":"headingSmall","face":"font-heading3"}'),t("…a face behind a variant naming `&` is a descendant's, not this element's",f(`"group-[&_em]:font-serif ${e("headingLarge")}"`)===null),t("…while a bare `[&_em]:` face is still read as this element's (the reader as it shipped)",s(`"[&_em]:font-serif ${e("accent")}"`)==='{"step":"accent","face":"font-serif"}'),t("…and two faces on one line are left to a reader",f(`"font-display font-mono ${e("label")}"`)===null)}const l=i.filter(e=>/\.tsx?$/.test(e.path)&&n.drawRoots.some(s=>e.path.startsWith(s)));t(`read the drawing roots ${n.drawRoots.join(", ")} (${l.length} TS/TSX files, at least ${n.minFiles})`,l.length>=n.minFiles,"fewer means the walk broke, and the sweep below is vacuous");let h=0;const g=[];for(const e of l)e.text.split(`
2
- `).forEach((s,r)=>{const a=f(s);if(!a||(h+=1,a.step==="body"))return;const c=u[a.face];c!==a.step&&g.push(`${e.path}:${r+1} — size says ${a.step}, face says ${a.face} (${c})`)});if(t(`found >= ${n.pairFloor} lines naming one face and one size (found ${h})`,h>=n.pairFloor,"fewer means the reader broke"),t("§3 no text takes its face from one style row and its size from another",g.length===0,`these lines name one style for their size and another for their face, so the merchant cannot fix the text from one row:
1
+ import{inlineTextSizes as m,ruleChecks as u}from"./rule.js";const p={"font-display":"headingLarge","font-heading2":"headingMedium","font-heading3":"headingSmall","font-serif":"accent","font-body":"body","font-mono":"label"};function $(i){const n=/(?<![-\w:])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)text-\[length:[^"'`]*?--size-(headingLarge|headingMedium|headingSmall|accent|body|label)\b/g,t=new Set;for(const o of i.matchAll(n))o[1]===""&&t.add(o[2]);return t.size===1?[...t][0]:null}function w(i){const n=/(?<![-\w])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)(font-display|font-heading2|font-heading3|font-serif|font-body|font-mono)(?![-\w])/g,t=new Set;for(const o of i.matchAll(n))o[1].includes("&")||t.add(o[2]);return[...t]}function f(i){const n=$(i);if(!n)return null;const t=w(i);return t.length===1?{step:n,face:t[0]}:null}const x=i=>i.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");function z(i,n){const{check:t,report:o}=u("style face matches size"),d=e=>i.find(s=>s.path===e)?.text;{const e=r=>`text-[length:var(--size-${r},44px)]`,s=r=>JSON.stringify(f(r));t("a face and a base size are read as a pair",s(`"font-display ${e("headingMedium")}"`)==='{"step":"headingMedium","face":"font-display"}'),t("…a size inside a variant is not the base",f(`"font-heading2 max-[800px]:${e("headingSmall")}"`)===null),t("…the base wins over a variant on the same line",s(`"font-heading3 ${e("headingSmall")} md:${e("headingMedium")}"`)==='{"step":"headingSmall","face":"font-heading3"}'),t("…a face behind a variant naming `&` is a descendant's, not this element's",f(`"group-[&_em]:font-serif ${e("headingLarge")}"`)===null),t("…while a bare `[&_em]:` face is still read as this element's (the reader as it shipped)",s(`"[&_em]:font-serif ${e("accent")}"`)==='{"step":"accent","face":"font-serif"}'),t("…and two faces on one line are left to a reader",f(`"font-display font-mono ${e("label")}"`)===null)}const l=i.filter(e=>/\.tsx?$/.test(e.path)&&n.drawRoots.some(s=>e.path.startsWith(s)));t(`read the drawing roots ${n.drawRoots.join(", ")} (${l.length} TS/TSX files, at least ${n.minFiles})`,l.length>=n.minFiles,"fewer means the walk broke, and the sweep below is vacuous");let h=0;const g=[];for(const e of l)m(e.text).split(`
2
+ `).forEach((s,r)=>{const a=f(s);if(!a||(h+=1,a.step==="body"))return;const c=p[a.face];c!==a.step&&g.push(`${e.path}:${r+1} — size says ${a.step}, face says ${a.face} (${c})`)});if(t(`found >= ${n.pairFloor} lines naming one face and one size (found ${h})`,h>=n.pairFloor,"fewer means the reader broke"),t("§3 no text takes its face from one style row and its size from another",g.length===0,`these lines name one style for their size and another for their face, so the merchant cannot fix the text from one row:
3
3
  `+g.join(`
4
- `)),n.themeSheet){const e=d(n.themeSheet)??"";if(t(`read the site stylesheet ${n.themeSheet}`,e.length>0)){for(const a of["2","3"])t(`§1 --font-heading${a} exists and resolves --font-role-heading${a}`,new RegExp(`--font-heading${a}:\\s*var\\(--font-role-heading${a}\\)`).test(e),`without it a section cannot DRAW the face Site styles ▸ Heading ${a} sets, and that Font control moves zero pixels again`);const s=e.slice(e.indexOf("@theme inline"));for(const a of["2","3"]){const c=s.match(new RegExp(`--font-heading${a}:[^;]*;`))?.[0]??"";t(`§2 …and in @theme inline --font-heading${a} points at its OWN role, never back at Heading 1`,c.includes(`--font-role-heading${a}`),`\`--font-heading${a}\` resolves ${c.trim()||"nothing"}`)}t("§4 the Accent size is read, in CSS (`em.text-accent` sets var(--size-accent, inherit))",/em\.text-accent\s*\{[^}]*font-size:\s*var\(--size-accent,\s*inherit\)/.test(e.replace(/\/\*[\s\S]*?\*\//g,"")),"the `em.text-accent` rule is gone, so Site styles ▸ Accent ▸ Size is a dead control again"),t("§4 …and it falls back to `inherit`, never a number",!/--size-accent,\s*\d/.test(e));const r=e.slice(e.indexOf(":root"),e.indexOf("@theme inline"));for(const a of["2","3"])t(`§5 :root defaults --font-role-heading${a} to Heading 1's face, so the swap is invisible until a face is chosen`,new RegExp(`--font-role-heading${a}:\\s*var\\(--font-role-heading\\)`).test(r),"every site that has never opened that row would change typeface")}}for(const e of n.accentMarkup??[]){const s=d(e);t(`§4 ${e} does not set the accent size in the MARKUP`,s!==void 0&&!w(s).includes("--size-accent"),s===void 0?"it is not among the files handed in":"that moves 900 published strings for a size `em.text-accent` already delivers")}if(n.fontStore){const e=d(n.fontStore)??"",s=e.slice(e.indexOf("const pick ="),e.indexOf("// DERIVED from FONT_ROLES"));t(`§5 ${n.fontStore}'s normalizeFonts falls Heading 2 and Heading 3 back to Heading 1`,/role === "heading2" \|\| role === "heading3"/.test(s),"a site that chose Oswald for its headings would get two Space Grotesk ones")}return o()}export{S as styleFaceMatchesSize};
4
+ `)),n.themeSheet){const e=d(n.themeSheet)??"";if(t(`read the site stylesheet ${n.themeSheet}`,e.length>0)){for(const a of["2","3"])t(`§1 --font-heading${a} exists and resolves --font-role-heading${a}`,new RegExp(`--font-heading${a}:\\s*var\\(--font-role-heading${a}\\)`).test(e),`without it a section cannot DRAW the face Site styles ▸ Heading ${a} sets, and that Font control moves zero pixels again`);const s=e.slice(e.indexOf("@theme inline"));for(const a of["2","3"]){const c=s.match(new RegExp(`--font-heading${a}:[^;]*;`))?.[0]??"";t(`§2 …and in @theme inline --font-heading${a} points at its OWN role, never back at Heading 1`,c.includes(`--font-role-heading${a}`),`\`--font-heading${a}\` resolves ${c.trim()||"nothing"}`)}t("§4 the Accent size is read, in CSS (`em.text-accent` sets var(--size-accent, inherit))",/em\.text-accent\s*\{[^}]*font-size:\s*var\(--size-accent,\s*inherit\)/.test(e.replace(/\/\*[\s\S]*?\*\//g,"")),"the `em.text-accent` rule is gone, so Site styles ▸ Accent ▸ Size is a dead control again"),t("§4 …and it falls back to `inherit`, never a number",!/--size-accent,\s*\d/.test(e));const r=e.slice(e.indexOf(":root"),e.indexOf("@theme inline"));for(const a of["2","3"])t(`§5 :root defaults --font-role-heading${a} to Heading 1's face, so the swap is invisible until a face is chosen`,new RegExp(`--font-role-heading${a}:\\s*var\\(--font-role-heading\\)`).test(r),"every site that has never opened that row would change typeface")}}for(const e of n.accentMarkup??[]){const s=d(e);t(`§4 ${e} does not set the accent size in the MARKUP`,s!==void 0&&!x(s).includes("--size-accent"),s===void 0?"it is not among the files handed in":"that moves 900 published strings for a size `em.text-accent` already delivers")}if(n.fontStore){const e=d(n.fontStore)??"",s=e.slice(e.indexOf("const pick ="),e.indexOf("// DERIVED from FONT_ROLES"));t(`§5 ${n.fontStore}'s normalizeFonts falls Heading 2 and Heading 3 back to Heading 1`,/role === "heading2" \|\| role === "heading3"/.test(s),"a site that chose Oswald for its headings would get two Space Grotesk ones")}return o()}export{z as styleFaceMatchesSize};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "efficient-web-builder",
3
- "version": "21.0.0",
3
+ "version": "22.0.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",
@@ -40,8 +40,8 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@tabler/icons-react": "^3.46.0",
43
- "efficient-easel": "^11.2.0",
44
- "efficient-shell": "^7.53.0",
43
+ "efficient-easel": "^12.0.0",
44
+ "efficient-shell": "^7.69.1",
45
45
  "isomorphic-dompurify": "^3.18.0",
46
46
  "next": ">=16",
47
47
  "react": ">=19",
@@ -55,15 +55,17 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@tabler/icons-react": "^3.46.0",
58
+ "@tanstack/react-query": "^5.100.9",
58
59
  "@types/google.maps": "^3.64.1",
59
60
  "@types/node": "^20",
60
61
  "@types/react": "^19",
61
62
  "@types/react-dom": "^19",
62
- "efficient-easel": "^11.2.0",
63
- "efficient-shell": "^7.53.0",
63
+ "efficient-easel": "^12.0.0",
64
+ "efficient-shell": "^7.69.1",
64
65
  "esbuild": "^0.28.0",
65
66
  "happy-dom": "^20.14.5",
66
67
  "isomorphic-dompurify": "^3.18.0",
68
+ "maplibre-gl": "6.11.2",
67
69
  "next": "16.2.11",
68
70
  "react": "19.2.4",
69
71
  "react-dom": "19.2.4",
@@ -1,7 +0,0 @@
1
- export interface ImageFocusRowProps {
2
- label: string;
3
- value: string;
4
- onChange: (next: string) => void;
5
- image?: string;
6
- }
7
- export default function ImageFocusRow({ label, value, onChange, image }: ImageFocusRowProps): import("react").JSX.Element;
@@ -1 +0,0 @@
1
- "use client";import{jsx as e}from"react/jsx-runtime";import{useState as l}from"react";import{Button as u,ImageFocusPoint as c}from"efficient-shell/ui";import f from"./TextStyleRow.js";import{say as o}from"../lib/editor-words.js";function p({label:t,value:n,onChange:s,image:i}){const[a,r]=l(!1);return e(f,{styleKey:"imageFocal",label:t,value:n,sampleStyle:{fontSize:11,fontVariantNumeric:"tabular-nums"},open:a,onToggle:()=>r(m=>!m),children:a?e(c,{label:o(t),value:n,onChange:s,image:i,emptyText:o("Choose a background image first — there’s nothing to focus yet."),actions:e(u,{variant:"secondary",size:"sm",onClick:()=>r(!1),children:o("Done")})}):null})}export{p as default};
@@ -1,6 +0,0 @@
1
- export declare const TEMPLATE_GLYPH_PATH = "M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z";
2
- export default function TemplateGlyph({ size, className, strokeWidth, }: {
3
- size?: number;
4
- className?: string;
5
- strokeWidth?: number;
6
- }): import("react").JSX.Element;
@@ -1 +0,0 @@
1
- import{jsx as o}from"react/jsx-runtime";const n="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z";function a({size:e=15,className:r,strokeWidth:t=1.7}){return o("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:t,strokeLinecap:"round",strokeLinejoin:"round",className:r,"aria-hidden":"true",focusable:"false",children:o("path",{d:n})})}export{n as TEMPLATE_GLYPH_PATH,a as default};
@@ -1,13 +0,0 @@
1
- import { type CSSProperties, type ReactNode } from "react";
2
- export interface TextStyleRowProps {
3
- styleKey: string;
4
- label: string;
5
- icon?: ReactNode;
6
- value?: ReactNode;
7
- sampleStyle: CSSProperties;
8
- open: boolean;
9
- onToggle: () => void;
10
- saved?: boolean;
11
- children?: ReactNode;
12
- }
13
- export default function TextStyleRow({ styleKey, label, icon, value, sampleStyle, open, onToggle, saved, children, }: TextStyleRowProps): import("react").JSX.Element;
@@ -1 +0,0 @@
1
- "use client";import{jsx as s,jsxs as i}from"react/jsx-runtime";import{ChevronIcon as f}from"efficient-easel/icons";import{Tooltip as m}from"efficient-shell/ui";import{say as p}from"../lib/editor-words.js";function h({styleKey:a,label:r,icon:t,value:e,sampleStyle:l,open:n,onToggle:o,saved:d=!1,children:c}){return i("div",{"data-sf-style-row":a,className:`sf-style-row${n?" is-open":""}`,children:[i("button",{type:"button",className:"sf-style-head","aria-expanded":n,onClick:o,children:[s("span",{className:"sf-style-caret","aria-hidden":"true",children:s(f,{size:13})}),t?s("span",{className:"es-field-icon",children:t}):null,s("span",{className:"sf-style-name",children:p(r)}),e!=null?typeof e=="string"?s(m,{content:e,className:"sf-style-sample-tip",children:s("span",{className:"sf-style-sample",style:l,children:e})}):s("span",{className:"sf-style-sample",style:l,children:e}):null,s("span",{className:`sf-style-slot${d?" is-saved":""}`,children:s(y,{})})]}),n&&s("div",{className:"sf-style-body",children:c})]})}function y(){return s("svg",{viewBox:"0 0 24 24",width:"14",height:"14",fill:"none",stroke:"currentColor",strokeWidth:3,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:s("polyline",{points:"20 6 9 17 4 12"})})}export{h as default};