@styleflow.app/flowpress-astro-core 0.2.0-beta.10 → 0.2.0-beta.11

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,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.0-beta.11
6
+
7
+ - Rendered empty Gutenberg and FlowPress text blocks with an internal
8
+ ` `, preserving their typographic line height without visible
9
+ characters or synthetic break elements.
10
+
5
11
  ## 0.2.0-beta.10
6
12
 
7
13
  - Preserved intentionally empty core Gutenberg paragraphs as visible line
package/README.md CHANGED
@@ -9,7 +9,7 @@ The package must not import from an application or project plugin.
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- pnpm add @styleflow.app/flowpress-astro-core@0.2.0-beta.10
12
+ pnpm add @styleflow.app/flowpress-astro-core@0.2.0-beta.11
13
13
  ```
14
14
 
15
15
  Prereleases are pinned exactly. The package expects Astro 7 or newer and the
@@ -37,7 +37,9 @@ to the protected FlowPress REST endpoint, so the same component works in
37
37
  static and server output modes. Gutenberg `anchor` and `className` attributes
38
38
  are applied to the rendered block root as HTML `id` and `class`. Rich Text
39
39
  preserves intentional double, triple and longer line breaks, including content
40
- saved by older editor versions as empty paragraphs. The exported
40
+ saved by older editor versions as empty paragraphs. Empty textual blocks render
41
+ an internal ` `, preserving their typographic line height without adding
42
+ visible characters. The exported
41
43
  `collectFlowpressClassNames()` helper recursively extracts safe class tokens
42
44
  from rendered CMS blocks so consumers can generate Tailwind
43
45
  `@source inline()` directives before compiling their frontend CSS.
@@ -4,6 +4,7 @@ import type { FlowpressBlock } from "../../types.js";
4
4
  import type { StyleflowContract } from "../../styleflow.js";
5
5
  import {
6
6
  blockHtmlAttributes,
7
+ preserveEmptyTextBlock,
7
8
  preserveRichTextSpacing,
8
9
  semanticProps,
9
10
  textValue,
@@ -43,13 +44,16 @@ const coreHtml = String(
43
44
  ?? block.innerHtml
44
45
  ?? ""
45
46
  );
46
- const html = name === "heading"
47
+ const normalizedHtml = name === "heading"
47
48
  ? textValue(block)
48
49
  : name === "rich-text"
49
50
  ? preserveRichTextSpacing(String(attributes.content ?? ""))
50
51
  : name === "quote"
51
52
  ? textValue(block, "quote")
52
53
  : unwrapCoreHtml(preserveRichTextSpacing(coreHtml));
54
+ const html = name === "core/list"
55
+ ? normalizedHtml
56
+ : preserveEmptyTextBlock(normalizedHtml);
53
57
  const listTag = Boolean(attributes.ordered) ? "ol" : "ul";
54
58
  ---
55
59
 
@@ -18,6 +18,8 @@ export declare function textValue(block: FlowpressBlock, key?: string): string;
18
18
  * empty paragraph elements, or literal newlines in an attribute value.
19
19
  */
20
20
  export declare function preserveRichTextSpacing(html: string): string;
21
+ /** Keep an empty text element at its typographic line height without visible text. */
22
+ export declare function preserveEmptyTextBlock(html: string): string;
21
23
  export declare function linkValue(block: FlowpressBlock, key?: string): string;
22
24
  export declare function responsiveValues(attributes: Record<string, unknown>, prefix: "columns" | "span", fallback: number): Record<"base" | "sm" | "md" | "lg" | "xl" | "2xl", number>;
23
25
  export declare function unwrapCoreHtml(html: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,sBAAsB;;;;CAIlC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,SAAS,SAAK,GACb;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBjC;AAqBD,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAA0B,GACzD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAErE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAMrE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,MAAM,CAAC,CAe5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,sBAAsB;;;;CAIlC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,SAAS,SAAK,GACb;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBjC;AAqBD,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAA0B,GACzD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAErE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB5D;AAED,sFAAsF;AACtF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAMrE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,MAAM,CAAC,CAe5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD"}
@@ -63,7 +63,7 @@ export function textValue(block, key = "text") {
63
63
  export function preserveRichTextSpacing(html) {
64
64
  const visibleEmptyBlocks = html
65
65
  .replace(/\r\n?/g, "\n")
66
- .replace(/<(p|div)(\s[^>]*)?>(?:\s|&nbsp;|&#160;)*<\/\1>/gi, (_match, tag, attributes = "") => `<${tag}${attributes}><br aria-hidden="true"></${tag}>`);
66
+ .replace(/<(p|div)(\s[^>]*)?>(?:\s|&nbsp;|&#160;)*<\/\1>/gi, (_match, tag, attributes = "") => `<${tag}${attributes}>&nbsp;</${tag}>`);
67
67
  return visibleEmptyBlocks
68
68
  .split(/(<[^>]+>)/g)
69
69
  .map((part) => {
@@ -74,6 +74,10 @@ export function preserveRichTextSpacing(html) {
74
74
  })
75
75
  .join("");
76
76
  }
77
+ /** Keep an empty text element at its typographic line height without visible text. */
78
+ export function preserveEmptyTextBlock(html) {
79
+ return html.trim() === "" ? "&nbsp;" : html;
80
+ }
77
81
  export function linkValue(block, key = "link") {
78
82
  const value = block.attributes[key];
79
83
  const href = typeof value === "string"
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveDynamicStyleflowProps,\n type StyleflowContract\n} from \"../../styleflow.js\";\nimport type { FlowpressBlock } from \"../../types.js\";\n\nexport const DEFAULT_SEMANTIC_PROPS = {\n surface: \"default\",\n layoutRole: \"stack\",\n density: \"md\"\n};\n\nexport function blockHtmlAttributes(\n attributes: Record<string, unknown>,\n baseClass = \"\"\n): { id?: string; class?: string } {\n const anchor = typeof attributes.anchor === \"string\"\n ? attributes.anchor.trim()\n : \"\";\n const customClass = typeof attributes.className === \"string\"\n ? attributes.className.trim()\n : \"\";\n const className = [baseClass.trim(), customClass]\n .filter(Boolean)\n .join(\" \")\n .replace(/\\s+/g, \" \");\n\n return {\n ...(anchor ? { id: anchor } : {}),\n ...(className ? { class: className } : {})\n };\n}\n\nconst SEMANTIC_KEYS = [\n \"tone\",\n \"intensity\",\n \"surface\",\n \"layoutRole\",\n \"density\",\n \"ty\",\n \"v\",\n \"w\",\n \"foreground\",\n \"interactivePriority\",\n \"noBackground\",\n \"noBorder\",\n \"noPadding\",\n \"noGap\",\n \"noRadius\",\n \"noForeground\"\n] as const;\n\nexport function semanticProps(\n attributes: Record<string, unknown>,\n contract?: StyleflowContract,\n defaults: Record<string, unknown> = DEFAULT_SEMANTIC_PROPS\n): Record<string, unknown> {\n const input = Object.fromEntries(\n SEMANTIC_KEYS\n .filter((key) => attributes[key] !== undefined)\n .map((key) => [key, attributes[key]])\n );\n return contract\n ? resolveDynamicStyleflowProps(input, defaults, contract)\n : { ...defaults, ...input };\n}\n\nexport function objectValue(\n block: FlowpressBlock,\n key: string\n): Record<string, unknown> {\n const value = block.attributes[key];\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function textValue(block: FlowpressBlock, key = \"text\"): string {\n return String(block.attributes[key] ?? \"\");\n}\n\n/**\n * Keep intentional blank lines visible across current and legacy RichText\n * serializations. Gutenberg can represent them either as repeated `<br>` tags,\n * empty paragraph elements, or literal newlines in an attribute value.\n */\nexport function preserveRichTextSpacing(html: string): string {\n const visibleEmptyBlocks = html\n .replace(/\\r\\n?/g, \"\\n\")\n .replace(\n /<(p|div)(\\s[^>]*)?>(?:\\s|&nbsp;|&#160;)*<\\/\\1>/gi,\n (_match, tag: string, attributes = \"\") =>\n `<${tag}${attributes}><br aria-hidden=\"true\"></${tag}>`\n );\n\n return visibleEmptyBlocks\n .split(/(<[^>]+>)/g)\n .map((part) => {\n if (part.startsWith(\"<\") || part.trim() === \"\") {\n return part;\n }\n return part.replace(/\\n(?:[ \\t]*\\n)+/g, (newlines) =>\n \"<br>\".repeat((newlines.match(/\\n/g) || []).length)\n );\n })\n .join(\"\");\n}\n\nexport function linkValue(block: FlowpressBlock, key = \"link\"): string {\n const value = block.attributes[key];\n const href = typeof value === \"string\"\n ? value\n : String(objectValue(block, key).url ?? \"\");\n return /^(?:\\/(?!\\/)|#|https?:\\/\\/|mailto:|tel:)/i.test(href) ? href : \"\";\n}\n\nexport function responsiveValues(\n attributes: Record<string, unknown>,\n prefix: \"columns\" | \"span\",\n fallback: number\n): Record<\"base\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\", number> {\n const keys = [\"base\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n const result = {} as Record<(typeof keys)[number], number>;\n let inherited = fallback;\n for (const key of keys) {\n const suffix = key === \"base\"\n ? \"Base\"\n : key === \"2xl\" ? \"2xl\" : key.charAt(0).toUpperCase() + key.slice(1);\n const candidate = Number(attributes[`${prefix}${suffix}`]);\n if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {\n inherited = candidate;\n }\n result[key] = inherited;\n }\n return result;\n}\n\nexport function unwrapCoreHtml(html: string): string {\n const match = html.trim().match(\n /^<(p|h[1-6]|ul|ol|li|blockquote)(?:\\s[^>]*)?>([\\s\\S]*)<\\/\\1>$/\n );\n return match?.[2] ?? html;\n}\n"]}
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveDynamicStyleflowProps,\n type StyleflowContract\n} from \"../../styleflow.js\";\nimport type { FlowpressBlock } from \"../../types.js\";\n\nexport const DEFAULT_SEMANTIC_PROPS = {\n surface: \"default\",\n layoutRole: \"stack\",\n density: \"md\"\n};\n\nexport function blockHtmlAttributes(\n attributes: Record<string, unknown>,\n baseClass = \"\"\n): { id?: string; class?: string } {\n const anchor = typeof attributes.anchor === \"string\"\n ? attributes.anchor.trim()\n : \"\";\n const customClass = typeof attributes.className === \"string\"\n ? attributes.className.trim()\n : \"\";\n const className = [baseClass.trim(), customClass]\n .filter(Boolean)\n .join(\" \")\n .replace(/\\s+/g, \" \");\n\n return {\n ...(anchor ? { id: anchor } : {}),\n ...(className ? { class: className } : {})\n };\n}\n\nconst SEMANTIC_KEYS = [\n \"tone\",\n \"intensity\",\n \"surface\",\n \"layoutRole\",\n \"density\",\n \"ty\",\n \"v\",\n \"w\",\n \"foreground\",\n \"interactivePriority\",\n \"noBackground\",\n \"noBorder\",\n \"noPadding\",\n \"noGap\",\n \"noRadius\",\n \"noForeground\"\n] as const;\n\nexport function semanticProps(\n attributes: Record<string, unknown>,\n contract?: StyleflowContract,\n defaults: Record<string, unknown> = DEFAULT_SEMANTIC_PROPS\n): Record<string, unknown> {\n const input = Object.fromEntries(\n SEMANTIC_KEYS\n .filter((key) => attributes[key] !== undefined)\n .map((key) => [key, attributes[key]])\n );\n return contract\n ? resolveDynamicStyleflowProps(input, defaults, contract)\n : { ...defaults, ...input };\n}\n\nexport function objectValue(\n block: FlowpressBlock,\n key: string\n): Record<string, unknown> {\n const value = block.attributes[key];\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function textValue(block: FlowpressBlock, key = \"text\"): string {\n return String(block.attributes[key] ?? \"\");\n}\n\n/**\n * Keep intentional blank lines visible across current and legacy RichText\n * serializations. Gutenberg can represent them either as repeated `<br>` tags,\n * empty paragraph elements, or literal newlines in an attribute value.\n */\nexport function preserveRichTextSpacing(html: string): string {\n const visibleEmptyBlocks = html\n .replace(/\\r\\n?/g, \"\\n\")\n .replace(\n /<(p|div)(\\s[^>]*)?>(?:\\s|&nbsp;|&#160;)*<\\/\\1>/gi,\n (_match, tag: string, attributes = \"\") =>\n `<${tag}${attributes}>&nbsp;</${tag}>`\n );\n\n return visibleEmptyBlocks\n .split(/(<[^>]+>)/g)\n .map((part) => {\n if (part.startsWith(\"<\") || part.trim() === \"\") {\n return part;\n }\n return part.replace(/\\n(?:[ \\t]*\\n)+/g, (newlines) =>\n \"<br>\".repeat((newlines.match(/\\n/g) || []).length)\n );\n })\n .join(\"\");\n}\n\n/** Keep an empty text element at its typographic line height without visible text. */\nexport function preserveEmptyTextBlock(html: string): string {\n return html.trim() === \"\" ? \"&nbsp;\" : html;\n}\n\nexport function linkValue(block: FlowpressBlock, key = \"link\"): string {\n const value = block.attributes[key];\n const href = typeof value === \"string\"\n ? value\n : String(objectValue(block, key).url ?? \"\");\n return /^(?:\\/(?!\\/)|#|https?:\\/\\/|mailto:|tel:)/i.test(href) ? href : \"\";\n}\n\nexport function responsiveValues(\n attributes: Record<string, unknown>,\n prefix: \"columns\" | \"span\",\n fallback: number\n): Record<\"base\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\", number> {\n const keys = [\"base\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n const result = {} as Record<(typeof keys)[number], number>;\n let inherited = fallback;\n for (const key of keys) {\n const suffix = key === \"base\"\n ? \"Base\"\n : key === \"2xl\" ? \"2xl\" : key.charAt(0).toUpperCase() + key.slice(1);\n const candidate = Number(attributes[`${prefix}${suffix}`]);\n if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {\n inherited = candidate;\n }\n result[key] = inherited;\n }\n return result;\n}\n\nexport function unwrapCoreHtml(html: string): string {\n const match = html.trim().match(\n /^<(p|h[1-6]|ul|ol|li|blockquote)(?:\\s[^>]*)?>([\\s\\S]*)<\\/\\1>$/\n );\n return match?.[2] ?? html;\n}\n"]}
@@ -90,7 +90,7 @@ export function preserveRichTextSpacing(html: string): string {
90
90
  .replace(
91
91
  /<(p|div)(\s[^>]*)?>(?:\s|&nbsp;|&#160;)*<\/\1>/gi,
92
92
  (_match, tag: string, attributes = "") =>
93
- `<${tag}${attributes}><br aria-hidden="true"></${tag}>`
93
+ `<${tag}${attributes}>&nbsp;</${tag}>`
94
94
  );
95
95
 
96
96
  return visibleEmptyBlocks
@@ -106,6 +106,11 @@ export function preserveRichTextSpacing(html: string): string {
106
106
  .join("");
107
107
  }
108
108
 
109
+ /** Keep an empty text element at its typographic line height without visible text. */
110
+ export function preserveEmptyTextBlock(html: string): string {
111
+ return html.trim() === "" ? "&nbsp;" : html;
112
+ }
113
+
109
114
  export function linkValue(block: FlowpressBlock, key = "link"): string {
110
115
  const value = block.attributes[key];
111
116
  const href = typeof value === "string"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@styleflow.app/flowpress-astro-core",
3
- "version": "0.2.0-beta.10",
3
+ "version": "0.2.0-beta.11",
4
4
  "description": "Project-agnostic WordPress routing, templates, SEO and GraphQL runtime for FlowPress Astro apps.",
5
5
  "type": "module",
6
6
  "private": false,